cart and checkout
This commit is contained in:
@@ -233,6 +233,103 @@ func HandleIssueRefund(o *OrderGrain, m *messages.IssueRefund) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleRequestExchange records an exchange request.
|
||||
func HandleRequestExchange(o *OrderGrain, m *messages.RequestExchange) error {
|
||||
if o.Status != StatusFulfilled && o.Status != StatusCompleted && o.Status != StatusPartiallyFulfilled {
|
||||
return fmt.Errorf("order: cannot request an exchange in status %q", o.Status)
|
||||
}
|
||||
ex := Exchange{
|
||||
ID: m.GetId(),
|
||||
ReturnID: m.GetReturnId(),
|
||||
Reason: m.GetReason(),
|
||||
RequestedAt: msToString(m.GetAtMs()),
|
||||
}
|
||||
|
||||
// 1. Process return lines
|
||||
for _, rl := range m.GetReturnLines() {
|
||||
line := o.findLine(rl.GetReference())
|
||||
if line == nil {
|
||||
return fmt.Errorf("order: exchange return references unknown line %q", rl.GetReference())
|
||||
}
|
||||
ex.ReturnLines = append(ex.ReturnLines, FulfillmentEntry{
|
||||
Reference: rl.GetReference(),
|
||||
Quantity: int(rl.GetQuantity()),
|
||||
})
|
||||
}
|
||||
|
||||
// 2. Process new lines (replacement items)
|
||||
for _, nl := range m.GetNewLines() {
|
||||
line := Line{
|
||||
Reference: nl.GetReference(),
|
||||
Sku: nl.GetSku(),
|
||||
Name: nl.GetName(),
|
||||
Quantity: int(nl.GetQuantity()),
|
||||
UnitPrice: nl.GetUnitPrice(),
|
||||
TaxRate: int(nl.GetTaxRate()),
|
||||
TotalAmount: nl.GetTotalAmount(),
|
||||
TotalTax: nl.GetTotalTax(),
|
||||
}
|
||||
ex.NewLines = append(ex.NewLines, line)
|
||||
|
||||
// Add the replacement line to the order so it can be fulfilled
|
||||
o.Lines = append(o.Lines, line)
|
||||
}
|
||||
|
||||
// 3. Since there are new lines that are not yet fulfilled, transition order status back to partially fulfilled
|
||||
o.Status = StatusPartiallyFulfilled
|
||||
|
||||
o.Exchanges = append(o.Exchanges, ex)
|
||||
o.touch(ex.RequestedAt)
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleEditOrderDetails updates addresses or shipping price.
|
||||
func HandleEditOrderDetails(o *OrderGrain, m *messages.EditOrderDetails) error {
|
||||
if o.Status == StatusCompleted || o.Status == StatusCancelled || o.Status == StatusRefunded {
|
||||
return fmt.Errorf("order: cannot edit details in status %q", o.Status)
|
||||
}
|
||||
|
||||
if s := m.GetShippingAddress(); len(s) > 0 {
|
||||
o.ShippingAddress = append([]byte(nil), s...)
|
||||
}
|
||||
if b := m.GetBillingAddress(); len(b) > 0 {
|
||||
o.BillingAddress = append([]byte(nil), b...)
|
||||
}
|
||||
|
||||
if sp := m.GetShippingPrice(); sp > 0 {
|
||||
found := false
|
||||
var oldTotal int64
|
||||
for i := range o.Lines {
|
||||
if o.Lines[i].Name == "Delivery" {
|
||||
oldTotal = o.Lines[i].TotalAmount
|
||||
o.Lines[i].UnitPrice = sp
|
||||
o.Lines[i].TotalAmount = sp * int64(o.Lines[i].Quantity)
|
||||
o.Lines[i].TotalTax = ComputeTax(o.Lines[i].TotalAmount, o.Lines[i].TaxRate)
|
||||
o.TotalAmount = o.TotalAmount - oldTotal + o.Lines[i].TotalAmount
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
line := Line{
|
||||
Reference: "delivery",
|
||||
Sku: "delivery",
|
||||
Name: "Delivery",
|
||||
Quantity: 1,
|
||||
UnitPrice: sp,
|
||||
TaxRate: 25,
|
||||
TotalAmount: sp,
|
||||
TotalTax: ComputeTax(sp, 25),
|
||||
}
|
||||
o.Lines = append(o.Lines, line)
|
||||
o.TotalAmount += sp
|
||||
}
|
||||
}
|
||||
|
||||
o.touch(msToString(m.GetAtMs()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterMutations registers every order mutation handler with the registry so
|
||||
// the grain pool can apply and replay them.
|
||||
func RegisterMutations(reg actor.MutationRegistry) {
|
||||
@@ -245,5 +342,9 @@ func RegisterMutations(reg actor.MutationRegistry) {
|
||||
actor.NewMutation(HandleCancelOrder),
|
||||
actor.NewMutation(HandleRequestReturn),
|
||||
actor.NewMutation(HandleIssueRefund),
|
||||
actor.NewMutation(HandleRequestExchange),
|
||||
actor.NewMutation(HandleEditOrderDetails),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -101,6 +101,16 @@ type Return struct {
|
||||
RequestedAt string `json:"requestedAt,omitempty"`
|
||||
}
|
||||
|
||||
// Exchange records an exchange request.
|
||||
type Exchange struct {
|
||||
ID string `json:"id"`
|
||||
ReturnID string `json:"returnId"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
ReturnLines []FulfillmentEntry `json:"returnLines,omitempty"`
|
||||
NewLines []Line `json:"newLines,omitempty"`
|
||||
RequestedAt string `json:"requestedAt,omitempty"`
|
||||
}
|
||||
|
||||
// Refund records a refund issued against the order.
|
||||
type Refund struct {
|
||||
Provider string `json:"provider"`
|
||||
@@ -137,8 +147,10 @@ type OrderGrain struct {
|
||||
Payments []*Payment `json:"payments,omitempty"`
|
||||
Fulfillments []Fulfillment `json:"fulfillments,omitempty"`
|
||||
Returns []Return `json:"returns,omitempty"`
|
||||
Exchanges []Exchange `json:"exchanges,omitempty"`
|
||||
Refunds []Refund `json:"refunds,omitempty"`
|
||||
|
||||
|
||||
CapturedAmount int64 `json:"capturedAmount"`
|
||||
RefundedAmount int64 `json:"refundedAmount"`
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package order
|
||||
|
||||
import "git.k6n.net/mats/go-cart-actor/pkg/cart"
|
||||
|
||||
// TaxProvider computes taxes for orders and line items.
|
||||
// The canonical definition is in pkg/cart; this is a re-export for
|
||||
// backward compatibility.
|
||||
type TaxProvider = cart.TaxProvider
|
||||
|
||||
// ComputeTax is the shared tax formula: tax = total x rate / (100 + rate).
|
||||
// Re-exported from pkg/cart for backward compatibility.
|
||||
func ComputeTax(total int64, taxRate int) int64 {
|
||||
return cart.ComputeTax(total, taxRate)
|
||||
}
|
||||
|
||||
// NordicTaxProvider implements TaxProvider for the Nordic countries.
|
||||
// Re-exported from pkg/cart for backward compatibility.
|
||||
type NordicTaxProvider = cart.NordicTaxProvider
|
||||
|
||||
func NewNordicTaxProvider(defaultCountry string) *NordicTaxProvider {
|
||||
return cart.NewNordicTaxProvider(defaultCountry)
|
||||
}
|
||||
|
||||
// StaticTaxProvider is a stateless TaxProvider with no country awareness.
|
||||
// Re-exported from pkg/cart for backward compatibility.
|
||||
type StaticTaxProvider = cart.StaticTaxProvider
|
||||
|
||||
func NewStaticTaxProvider() *StaticTaxProvider {
|
||||
return cart.NewStaticTaxProvider()
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestReExports verifies that the order package's type aliases and
|
||||
// forwarders to pkg/cart compile and work correctly.
|
||||
func TestReExports(t *testing.T) {
|
||||
// Type alias: TaxProvider = cart.TaxProvider
|
||||
var _ TaxProvider = NewNordicTaxProvider("SE")
|
||||
var _ TaxProvider = NewStaticTaxProvider()
|
||||
|
||||
// NordicTaxProvider
|
||||
p := NewNordicTaxProvider("SE")
|
||||
if p.Name() != "nordic" {
|
||||
t.Errorf("Name = %q", p.Name())
|
||||
}
|
||||
if got := p.DefaultTaxRate("SE"); got != 25 {
|
||||
t.Errorf("DefaultTaxRate(SE) = %d, want 25", got)
|
||||
}
|
||||
if got := ComputeTax(1250, 25); got != 250 {
|
||||
t.Errorf("ComputeTax(1250, 25) = %d, want 250", got)
|
||||
}
|
||||
|
||||
// StaticTaxProvider
|
||||
s := NewStaticTaxProvider()
|
||||
if s.Name() != "static" {
|
||||
t.Errorf("Name = %q", s.Name())
|
||||
}
|
||||
if got := s.DefaultTaxRate("SE"); got != 0 {
|
||||
t.Errorf("DefaultTaxRate(SE) = %d, want 0", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user