209 lines
6.4 KiB
Go
209 lines
6.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"git.k6n.net/go-cart-actor/pkg/cart"
|
|
"git.k6n.net/go-cart-actor/pkg/checkout"
|
|
adyenCheckout "github.com/adyen/adyen-go-api-library/v21/src/checkout"
|
|
"github.com/adyen/adyen-go-api-library/v21/src/common"
|
|
)
|
|
|
|
// CheckoutMeta carries the external / URL metadata required to build a
|
|
// Klarna CheckoutOrder from a CartGrain snapshot. It deliberately excludes
|
|
// any Klarna-specific response fields (HTML snippet, client token, etc.).
|
|
type CheckoutMeta struct {
|
|
SiteUrl string
|
|
// Terms string
|
|
// Checkout string
|
|
// Confirmation string
|
|
ClientIp string
|
|
Country string
|
|
Currency string // optional override (defaults to "SEK" if empty)
|
|
Locale string // optional override (defaults to "sv-se" if empty)
|
|
}
|
|
|
|
// BuildCheckoutOrderPayload converts the current cart grain + meta information
|
|
// into a CheckoutOrder domain struct and returns its JSON-serialized payload
|
|
// (to send to Klarna) alongside the structured CheckoutOrder object.
|
|
//
|
|
// This function is PURE: it does not perform any network I/O or mutate the
|
|
// grain. The caller is responsible for:
|
|
//
|
|
// 1. Choosing whether to create or update the Klarna order.
|
|
// 2. Invoking KlarnaClient.CreateOrder / UpdateOrder with the returned payload.
|
|
// 3. Applying an InitializeCheckout mutation (or equivalent) with the
|
|
// resulting Klarna order id + status.
|
|
//
|
|
// If you later need to support different tax rates per line, you can extend
|
|
// CartItem / Delivery to expose that data and propagate it here.
|
|
func BuildCheckoutOrderPayload(grain *checkout.CheckoutGrain, meta *CheckoutMeta) ([]byte, *CheckoutOrder, error) {
|
|
if grain == nil {
|
|
return nil, nil, fmt.Errorf("nil grain")
|
|
}
|
|
if meta == nil {
|
|
return nil, nil, fmt.Errorf("nil checkout meta")
|
|
}
|
|
|
|
currency := meta.Currency
|
|
if currency == "" {
|
|
currency = "SEK"
|
|
}
|
|
locale := meta.Locale
|
|
if locale == "" {
|
|
locale = "sv-se"
|
|
}
|
|
country := meta.Country
|
|
if country == "" {
|
|
country = "SE" // sensible default; adjust if multi-country support changes
|
|
}
|
|
|
|
lines := make([]*Line, 0, len(grain.CartState.Items)+len(grain.Deliveries))
|
|
|
|
// Item lines
|
|
for _, it := range grain.CartState.Items {
|
|
if it == nil {
|
|
continue
|
|
}
|
|
lines = append(lines, &Line{
|
|
Type: "physical",
|
|
Reference: it.Sku,
|
|
Name: it.Meta.Name,
|
|
Quantity: int(it.Quantity),
|
|
UnitPrice: int(it.Price.IncVat),
|
|
TaxRate: it.Tax, // TODO: derive if variable tax rates are introduced
|
|
QuantityUnit: "st",
|
|
TotalAmount: int(it.TotalPrice.IncVat),
|
|
TotalTaxAmount: int(it.TotalPrice.TotalVat()),
|
|
ImageURL: fmt.Sprintf("https://www.elgiganten.se%s", it.Meta.Image),
|
|
})
|
|
}
|
|
|
|
total := cart.NewPrice()
|
|
total.Add(*grain.CartState.TotalPrice)
|
|
|
|
// Delivery lines
|
|
for _, d := range grain.Deliveries {
|
|
if d == nil || d.Price.IncVat <= 0 {
|
|
continue
|
|
}
|
|
//total.Add(d.Price)
|
|
lines = append(lines, &Line{
|
|
Type: "shipping_fee",
|
|
Reference: d.Provider,
|
|
Name: "Delivery",
|
|
Quantity: 1,
|
|
UnitPrice: int(d.Price.IncVat),
|
|
TaxRate: 2500,
|
|
QuantityUnit: "st",
|
|
TotalAmount: int(d.Price.IncVat),
|
|
TotalTaxAmount: int(d.Price.TotalVat()),
|
|
})
|
|
}
|
|
|
|
order := &CheckoutOrder{
|
|
PurchaseCountry: country,
|
|
PurchaseCurrency: currency,
|
|
Locale: locale,
|
|
OrderAmount: int(total.IncVat),
|
|
OrderTaxAmount: int(total.TotalVat()),
|
|
OrderLines: lines,
|
|
MerchantReference1: grain.Id.String(),
|
|
MerchantURLS: &CheckoutMerchantURLS{
|
|
Terms: fmt.Sprintf("%s/terms", meta.SiteUrl),
|
|
Checkout: fmt.Sprintf("%s/checkout?order_id={checkout.order.id}&provider=klarna", meta.SiteUrl),
|
|
Confirmation: fmt.Sprintf("%s/confirmation?order_id={checkout.order.id}&provider=klarna", meta.SiteUrl),
|
|
Notification: "https://cart.k6n.net/payment/klarna/notification",
|
|
Validation: "https://cart.k6n.net/payment/klarna/validate",
|
|
Push: "https://cart.k6n.net/payment/klarna/push?order_id={checkout.order.id}",
|
|
},
|
|
}
|
|
|
|
payload, err := json.Marshal(order)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("marshal checkout order: %w", err)
|
|
}
|
|
|
|
return payload, order, nil
|
|
}
|
|
|
|
func GetCheckoutMetaFromRequest(r *http.Request) *CheckoutMeta {
|
|
host := getOriginalHost(r)
|
|
country := getCountryFromHost(host)
|
|
return &CheckoutMeta{
|
|
ClientIp: getClientIp(r),
|
|
SiteUrl: fmt.Sprintf("https://%s", host),
|
|
Country: country,
|
|
Currency: getCurrency(country),
|
|
Locale: getLocale(country),
|
|
}
|
|
}
|
|
|
|
func BuildAdyenCheckoutSession(grain *checkout.CheckoutGrain, meta *CheckoutMeta) (*adyenCheckout.CreateCheckoutSessionRequest, error) {
|
|
if grain == nil {
|
|
return nil, fmt.Errorf("nil grain")
|
|
}
|
|
if meta == nil {
|
|
return nil, fmt.Errorf("nil checkout meta")
|
|
}
|
|
|
|
currency := meta.Currency
|
|
if currency == "" {
|
|
currency = "SEK"
|
|
}
|
|
country := meta.Country
|
|
if country == "" {
|
|
country = "SE"
|
|
}
|
|
|
|
lineItems := make([]adyenCheckout.LineItem, 0, len(grain.CartState.Items)+len(grain.Deliveries))
|
|
|
|
// Item lines
|
|
for _, it := range grain.CartState.Items {
|
|
if it == nil {
|
|
continue
|
|
}
|
|
lineItems = append(lineItems, adyenCheckout.LineItem{
|
|
Quantity: common.PtrInt64(int64(it.Quantity)),
|
|
AmountIncludingTax: common.PtrInt64(it.TotalPrice.IncVat),
|
|
Description: common.PtrString(it.Meta.Name),
|
|
AmountExcludingTax: common.PtrInt64(it.TotalPrice.ValueExVat()),
|
|
TaxAmount: common.PtrInt64(it.TotalPrice.TotalVat()),
|
|
TaxPercentage: common.PtrInt64(int64(it.Tax)),
|
|
})
|
|
}
|
|
total := cart.NewPrice()
|
|
total.Add(*grain.CartState.TotalPrice)
|
|
|
|
// Delivery lines
|
|
for _, d := range grain.Deliveries {
|
|
if d == nil || d.Price.IncVat <= 0 {
|
|
continue
|
|
}
|
|
lineItems = append(lineItems, adyenCheckout.LineItem{
|
|
Quantity: common.PtrInt64(1),
|
|
AmountIncludingTax: common.PtrInt64(d.Price.IncVat),
|
|
Description: common.PtrString("Delivery"),
|
|
AmountExcludingTax: common.PtrInt64(d.Price.ValueExVat()),
|
|
TaxPercentage: common.PtrInt64(25),
|
|
})
|
|
}
|
|
|
|
return &adyenCheckout.CreateCheckoutSessionRequest{
|
|
Reference: grain.Id.String(),
|
|
Amount: adyenCheckout.Amount{
|
|
Value: total.IncVat,
|
|
Currency: currency,
|
|
},
|
|
CountryCode: common.PtrString(country),
|
|
MerchantAccount: "ElgigantenECOM",
|
|
Channel: common.PtrString("Web"),
|
|
ShopperIP: common.PtrString(meta.ClientIp),
|
|
ReturnUrl: fmt.Sprintf("%s/payment/adyen/return", meta.SiteUrl),
|
|
LineItems: lineItems,
|
|
}, nil
|
|
|
|
}
|