111 lines
3.5 KiB
Go
111 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"git.k6n.net/mats/go-cart-actor/pkg/checkout"
|
|
messages "git.k6n.net/mats/go-cart-actor/proto/checkout"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
)
|
|
|
|
// settledPayment carries the provider-agnostic facts about an already-settled
|
|
// payment. Each payment integration (Klarna push, Adyen CAPTURE notification,
|
|
// …) fills this in from its own callback and hands it to
|
|
// createOrderFromSettledCheckout, so the from-checkout request is identical
|
|
// regardless of which provider settled the payment.
|
|
type settledPayment struct {
|
|
Provider string // "klarna" | "adyen"
|
|
Reference string // processor / PSP reference
|
|
Amount int64 // minor units, as charged
|
|
Currency string
|
|
Country string
|
|
Locale string
|
|
}
|
|
|
|
// buildOrderLinesFromGrain converts the checkout grain's cart items and delivery
|
|
// selections into from-checkout order lines. Shared by every provider so the
|
|
// order payload is built the same way no matter who settled the payment.
|
|
func buildOrderLinesFromGrain(grain *checkout.CheckoutGrain) []OrderLine {
|
|
lines := make([]OrderLine, 0, len(grain.CartState.Items)+len(grain.Deliveries))
|
|
for _, it := range grain.CartState.Items {
|
|
if it == nil {
|
|
continue
|
|
}
|
|
lines = append(lines, OrderLine{
|
|
Reference: it.Sku,
|
|
Sku: it.Sku,
|
|
Name: it.Meta.Name,
|
|
Quantity: int32(it.Quantity),
|
|
UnitPrice: it.Price.IncVat,
|
|
TaxRate: int32(it.Tax),
|
|
})
|
|
}
|
|
for _, d := range grain.Deliveries {
|
|
if d == nil || d.Price.IncVat <= 0 {
|
|
continue
|
|
}
|
|
lines = append(lines, OrderLine{
|
|
Reference: d.Provider,
|
|
Sku: d.Provider,
|
|
Name: "Delivery",
|
|
Quantity: 1,
|
|
UnitPrice: d.Price.IncVat,
|
|
TaxRate: 2500,
|
|
})
|
|
}
|
|
return lines
|
|
}
|
|
|
|
// createOrderFromSettledCheckout sends the from-checkout request for a settled
|
|
// checkout (any provider) and, on success, records the returned order id on the
|
|
// checkout grain via the OrderCreated mutation so the backoffice can link
|
|
// checkout → order.
|
|
//
|
|
// It is idempotent: the order service dedupes on "checkout-{checkoutId}", so
|
|
// repeated provider callbacks (Klarna re-push, retried Adyen CAPTURE) return the
|
|
// existing order rather than creating a duplicate. Callers should treat a
|
|
// non-nil error as non-fatal and retry on the next callback.
|
|
func createOrderFromSettledCheckout(ctx context.Context, s *CheckoutPoolServer, grain *checkout.CheckoutGrain, pay settledPayment) (*CreateOrderResult, error) {
|
|
if grain.CartState == nil {
|
|
return nil, fmt.Errorf("from-checkout: checkout %s has no cart state", grain.Id)
|
|
}
|
|
|
|
var customerEmail, customerName string
|
|
if grain.ContactDetails != nil {
|
|
if grain.ContactDetails.Email != nil {
|
|
customerEmail = *grain.ContactDetails.Email
|
|
}
|
|
if grain.ContactDetails.Name != nil {
|
|
customerName = *grain.ContactDetails.Name
|
|
}
|
|
}
|
|
|
|
req := CreateOrderReq{
|
|
CheckoutId: grain.Id.String(),
|
|
IdempotencyKey: "checkout-" + grain.Id.String(),
|
|
CartId: grain.CartId.String(),
|
|
Currency: pay.Currency,
|
|
Locale: pay.Locale,
|
|
Country: pay.Country,
|
|
CustomerEmail: customerEmail,
|
|
CustomerName: customerName,
|
|
Lines: buildOrderLinesFromGrain(grain),
|
|
}
|
|
req.Payment.Provider = pay.Provider
|
|
req.Payment.Reference = pay.Reference
|
|
req.Payment.Amount = pay.Amount
|
|
|
|
result, err := s.orderClient.CreateOrder(ctx, req, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_ = s.ApplyAnywhere(ctx, grain.Id, &messages.OrderCreated{
|
|
OrderId: result.OrderId,
|
|
Status: "completed",
|
|
CreatedAt: timestamppb.Now(),
|
|
})
|
|
return result, nil
|
|
}
|