update orderflow
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -124,6 +125,14 @@ func (s *CheckoutPoolServer) AdyenHookHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
//}
|
||||
|
||||
// CAPTURE is the moment the Adyen payment is fully settled — the
|
||||
// Adyen analogue of the Klarna push. Create the event-sourced
|
||||
// order now (mirrors KlarnaPushHandler). Idempotent on
|
||||
// "checkout-{checkoutId}", so a retried CAPTURE won't duplicate.
|
||||
if isSuccess && s.orderClient != nil {
|
||||
s.createAdyenOrder(r.Context(), *checkoutId, item)
|
||||
}
|
||||
|
||||
case "AUTHORISATION":
|
||||
|
||||
isSuccess := item.Success == "true"
|
||||
@@ -176,11 +185,11 @@ func (s *CheckoutPoolServer) AdyenHookHandler(w http.ResponseWriter, r *http.Req
|
||||
if err != nil {
|
||||
log.Printf("Error capturing payment: %v", err)
|
||||
} else {
|
||||
log.Printf("Payment captured successfully: %+v", res)
|
||||
s.ApplyAnywhere(r.Context(), *checkoutId, &messages.OrderCreated{
|
||||
OrderId: res.PaymentPspReference,
|
||||
Status: item.EventCode,
|
||||
})
|
||||
// Capture requested. The order is NOT created here — we
|
||||
// only have a PSP reference, not an order. Adyen sends a
|
||||
// CAPTURE notification once the capture settles, and the
|
||||
// CAPTURE branch above creates the event-sourced order.
|
||||
log.Printf("Payment capture requested successfully: %+v", res)
|
||||
}
|
||||
}
|
||||
default:
|
||||
@@ -209,6 +218,45 @@ func (s *CheckoutPoolServer) AdyenHookHandler(w http.ResponseWriter, r *http.Req
|
||||
|
||||
}
|
||||
|
||||
// createAdyenOrder turns a settled (captured) Adyen payment into an
|
||||
// event-sourced order, mirroring KlarnaPushHandler. The Adyen webhook carries
|
||||
// the charged amount + currency but not the shopper's country/locale, so those
|
||||
// are derived from the currency. Failures are logged and left for the next
|
||||
// CAPTURE notification to retry — the from-checkout endpoint is idempotent on
|
||||
// "checkout-{checkoutId}".
|
||||
func (s *CheckoutPoolServer) createAdyenOrder(ctx context.Context, checkoutId cart.CartId, item webhook.NotificationRequestItem) {
|
||||
grain, err := s.GetAnywhere(ctx, checkoutId)
|
||||
if err != nil {
|
||||
log.Printf("from-checkout: could not load checkout grain %s: %v", checkoutId.String(), err)
|
||||
return
|
||||
}
|
||||
|
||||
// Reserve inventory once, guarded by the grain flag so a retried CAPTURE
|
||||
// does not decrement stock twice. (Same as the Klarna path.)
|
||||
if s.inventoryService != nil && grain.CartState != nil && !grain.InventoryReserved {
|
||||
if rerr := s.inventoryService.ReserveInventory(ctx, getInventoryRequests(grain.CartState.Items)...); rerr != nil {
|
||||
log.Printf("from-checkout: inventory reservation failed for %s: %v", checkoutId.String(), rerr)
|
||||
} else {
|
||||
s.Apply(ctx, uint64(grain.Id), &messages.InventoryReserved{
|
||||
Id: grain.Id.String(),
|
||||
Status: "success",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
country := getCountryFromCurrency(item.Amount.Currency)
|
||||
if _, oerr := createOrderFromSettledCheckout(ctx, s, grain, settledPayment{
|
||||
Provider: "adyen",
|
||||
Reference: item.PspReference,
|
||||
Amount: item.Amount.Value,
|
||||
Currency: item.Amount.Currency,
|
||||
Country: country,
|
||||
Locale: getLocale(country),
|
||||
}); oerr != nil {
|
||||
log.Printf("from-checkout failed for checkout %s; will retry on next CAPTURE: %v", checkoutId.String(), oerr)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CheckoutPoolServer) AdyenReturnHandler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("Redirect received")
|
||||
|
||||
|
||||
@@ -205,19 +205,24 @@ func (s *CheckoutPoolServer) KlarnaPushHandler(w http.ResponseWriter, r *http.Re
|
||||
CompletedAt: timestamppb.Now(),
|
||||
})
|
||||
|
||||
// err = confirmOrder(r.Context(), order, orderHandler)
|
||||
// if err != nil {
|
||||
// log.Printf("Error confirming order: %v\n", err)
|
||||
// w.WriteHeader(http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
// ── Create the event-sourced order grain (dual-write with AMQP) ────
|
||||
if s.orderClient != nil {
|
||||
if _, orderErr := createOrderFromCheckout(r.Context(), s, grain, order, "klarna"); orderErr != nil {
|
||||
// Non-fatal: the checkout grain is updated, the order can be
|
||||
// created on retry (idempotency key guards against duplicates).
|
||||
logger.WarnContext(r.Context(), "from-checkout failed; will retry on next push",
|
||||
"err", orderErr, "checkoutId", grain.Id.String())
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dual-write: AMQP order-queue for legacy consumers ───────────────
|
||||
if s.orderHandler != nil {
|
||||
legacyBody, _ := buildLegacyOrderJSON(grain, order, "klarna", orderId)
|
||||
if pubErr := s.orderHandler.OrderCompleted(legacyBody); pubErr != nil {
|
||||
logger.WarnContext(r.Context(), "order-queue publish failed", "err", pubErr)
|
||||
}
|
||||
}
|
||||
|
||||
// err = triggerOrderCompleted(r.Context(), a.server, order)
|
||||
// if err != nil {
|
||||
// log.Printf("Error processing cart message: %v\n", err)
|
||||
// w.WriteHeader(http.StatusInternalServerError)
|
||||
// return
|
||||
// }
|
||||
err = s.klarnaClient.AcknowledgeOrder(r.Context(), orderId)
|
||||
if err != nil {
|
||||
log.Printf("Error acknowledging order: %v\n", err)
|
||||
@@ -240,6 +245,75 @@ var tpl = `<!DOCTYPE html>
|
||||
</html>
|
||||
`
|
||||
|
||||
// createOrderFromCheckout builds and sends the from-checkout request to the
|
||||
// order service, creating an event-sourced order grain from the settled checkout.
|
||||
// The idempotency key is "checkout-{checkoutId}" so that retries (Klarna may
|
||||
// push the same order multiple times) are safe.
|
||||
func createOrderFromCheckout(ctx context.Context, s *CheckoutPoolServer, grain *checkout.CheckoutGrain, klarnaOrder *CheckoutOrder, provider string) (*CreateOrderResult, error) {
|
||||
return createOrderFromSettledCheckout(ctx, s, grain, settledPayment{
|
||||
Provider: provider,
|
||||
Reference: klarnaOrder.ID,
|
||||
Amount: int64(klarnaOrder.OrderAmount),
|
||||
Currency: klarnaOrder.PurchaseCurrency,
|
||||
Country: klarnaOrder.PurchaseCountry,
|
||||
Locale: klarnaOrder.Locale,
|
||||
})
|
||||
}
|
||||
|
||||
// buildLegacyOrderJSON builds the go-order-manager compatible JSON for AMQP
|
||||
// dual-write, so existing downstream consumers on the order-queue still receive
|
||||
// order events during the migration period.
|
||||
func buildLegacyOrderJSON(grain *checkout.CheckoutGrain, klarnaOrder *CheckoutOrder, provider string, orderId string) ([]byte, error) {
|
||||
if grain.CartState == nil {
|
||||
return nil, fmt.Errorf("buildLegacyOrderJSON: checkout %s has no cart state", grain.Id)
|
||||
}
|
||||
type legacyLine struct {
|
||||
Reference string `json:"reference"`
|
||||
Name string `json:"name"`
|
||||
Quantity int `json:"quantity"`
|
||||
UnitPrice int `json:"unit_price"`
|
||||
TaxRate int `json:"tax_rate"`
|
||||
TotalAmount int `json:"total_amount"`
|
||||
TotalTaxAmount int `json:"total_tax_amount"`
|
||||
}
|
||||
type legacyOrder struct {
|
||||
ID string `json:"order_id"`
|
||||
PurchaseCountry string `json:"purchase_country"`
|
||||
PurchaseCurrency string `json:"purchase_currency"`
|
||||
Locale string `json:"locale"`
|
||||
OrderAmount int `json:"order_amount"`
|
||||
OrderTaxAmount int `json:"order_tax_amount"`
|
||||
OrderLines []legacyLine `json:"order_lines"`
|
||||
}
|
||||
|
||||
lines := make([]legacyLine, 0, len(grain.CartState.Items)+len(grain.Deliveries))
|
||||
for _, it := range grain.CartState.Items {
|
||||
if it == nil {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, legacyLine{
|
||||
Reference: it.Sku,
|
||||
Name: it.Meta.Name,
|
||||
Quantity: int(it.Quantity),
|
||||
UnitPrice: int(it.Price.IncVat),
|
||||
TaxRate: it.Tax,
|
||||
TotalAmount: int(it.TotalPrice.IncVat),
|
||||
TotalTaxAmount: int(it.TotalPrice.TotalVat()),
|
||||
})
|
||||
}
|
||||
|
||||
lo := legacyOrder{
|
||||
ID: orderId,
|
||||
PurchaseCountry: klarnaOrder.PurchaseCountry,
|
||||
PurchaseCurrency: klarnaOrder.PurchaseCurrency,
|
||||
Locale: klarnaOrder.Locale,
|
||||
OrderAmount: klarnaOrder.OrderAmount,
|
||||
OrderTaxAmount: klarnaOrder.OrderTaxAmount,
|
||||
OrderLines: lines,
|
||||
}
|
||||
return json.Marshal(lo)
|
||||
}
|
||||
|
||||
func getLocationId(item *cart.CartItem) inventory.LocationID {
|
||||
if item.StoreId == nil || *item.StoreId == "" {
|
||||
return "se"
|
||||
|
||||
+18
-1
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/adyen/adyen-go-api-library/v21/src/adyen"
|
||||
"github.com/adyen/adyen-go-api-library/v21/src/common"
|
||||
"git.k6n.net/mats/go-redis-inventory/pkg/inventory"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/redis/go-redis/v9"
|
||||
@@ -115,7 +116,23 @@ func main() {
|
||||
|
||||
cartClient := NewCartClient(cartInternalUrl)
|
||||
|
||||
syncedServer := NewCheckoutPoolServer(pool, fmt.Sprintf("%s, %s", name, podIp), klarnaClient, cartClient, adyenClient)
|
||||
var orderClient *OrderClient
|
||||
if orderServiceURL := os.Getenv("ORDER_SERVICE_URL"); orderServiceURL != "" {
|
||||
orderClient = NewOrderClient(orderServiceURL)
|
||||
log.Printf("order client enabled: %s", orderServiceURL)
|
||||
}
|
||||
|
||||
var orderHandler *AmqpOrderHandler
|
||||
conn, err := amqp.Dial(amqpUrl)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect to RabbitMQ: %v", err)
|
||||
}
|
||||
orderHandler = NewAmqpOrderHandler(conn)
|
||||
if err := orderHandler.DefineQueue(); err != nil {
|
||||
log.Fatalf("failed to declare order queue: %v", err)
|
||||
}
|
||||
|
||||
syncedServer := NewCheckoutPoolServer(pool, fmt.Sprintf("%s, %s", name, podIp), klarnaClient, cartClient, adyenClient, orderClient, orderHandler)
|
||||
syncedServer.inventoryService = inventoryService
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OrderClient is a minimal HTTP client for the order service. It is the
|
||||
// checkout service's counterpart to the CartClient — the same pattern, but
|
||||
// calling into order-service endpoints instead of cart-service ones.
|
||||
//
|
||||
// Currently only CreateOrder is implemented (the from-checkout handoff).
|
||||
// Add GetOrder/ListOrders methods as the backoffice order UI evolves.
|
||||
type OrderClient struct {
|
||||
httpClient *http.Client
|
||||
baseURL string // e.g. "http://order-service:8092"
|
||||
}
|
||||
|
||||
// NewOrderClient returns a client that talks to the order service at baseURL.
|
||||
func NewOrderClient(baseURL string) *OrderClient {
|
||||
return &OrderClient{
|
||||
httpClient: &http.Client{Timeout: 30 * time.Second},
|
||||
baseURL: baseURL,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateOrderReq is the JSON payload POSTed to the order service's
|
||||
// /api/orders/from-checkout endpoint. Every field is required except where
|
||||
// noted. See cmd/order/handlers_checkout.go for the full definition.
|
||||
type CreateOrderReq struct {
|
||||
CheckoutId string `json:"checkoutId"`
|
||||
IdempotencyKey string `json:"idempotencyKey"`
|
||||
|
||||
CartId string `json:"cartId"`
|
||||
Currency string `json:"currency"`
|
||||
Locale string `json:"locale,omitempty"`
|
||||
Country string `json:"country"`
|
||||
CustomerEmail string `json:"customerEmail,omitempty"`
|
||||
CustomerName string `json:"customerName,omitempty"`
|
||||
Lines []OrderLine `json:"lines"`
|
||||
|
||||
Payment struct {
|
||||
Provider string `json:"provider"`
|
||||
Reference string `json:"reference"`
|
||||
Amount int64 `json:"amount"`
|
||||
} `json:"payment"`
|
||||
}
|
||||
|
||||
// OrderLine is one orderable item in the CreateOrderReq, matching the
|
||||
// lineReq type in the order service.
|
||||
type OrderLine struct {
|
||||
Reference string `json:"reference"`
|
||||
Sku string `json:"sku"`
|
||||
Name string `json:"name"`
|
||||
Quantity int32 `json:"quantity"`
|
||||
UnitPrice int64 `json:"unitPrice"`
|
||||
TaxRate int32 `json:"taxRate"`
|
||||
}
|
||||
|
||||
// CreateOrderResult is the success response from POST /api/orders/from-checkout.
|
||||
type CreateOrderResult struct {
|
||||
OrderId string `json:"orderId"`
|
||||
Flow json.RawMessage `json:"flow,omitempty"`
|
||||
Order json.RawMessage `json:"order,omitempty"`
|
||||
// Existing is true when the response is a 409 Conflict — the idempotency
|
||||
// key was already used and this is the previously-created order.
|
||||
Existing bool `json:"existing,omitempty"`
|
||||
}
|
||||
|
||||
// CreateOrder sends a settled checkout's data to the order service and returns
|
||||
// the created (or previously-created, via idempotency) order. An idempotencyKey
|
||||
// should be generated per checkout (e.g. "checkout-{checkoutId}") to guard
|
||||
// against retries. flowName overrides the default flow; use "" for the default.
|
||||
func (c *OrderClient) CreateOrder(ctx context.Context, req CreateOrderReq, flowName string) (*CreateOrderResult, error) {
|
||||
url := c.baseURL + "/api/orders/from-checkout"
|
||||
if flowName != "" {
|
||||
url += "?flow=" + flowName
|
||||
}
|
||||
|
||||
body, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("order client: marshal: %w", err)
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("order client: new request: %w", err)
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(httpReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("order client: do: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Both 201 (created) and 409 (idempotent hit) are valid responses.
|
||||
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusConflict {
|
||||
return nil, fmt.Errorf("order client: %s", resp.Status)
|
||||
}
|
||||
|
||||
var result CreateOrderResult
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return nil, fmt.Errorf("order client: decode: %w", err)
|
||||
}
|
||||
result.Existing = resp.StatusCode == http.StatusConflict
|
||||
return &result, nil
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
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
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -18,7 +17,6 @@ import (
|
||||
|
||||
adyen "github.com/adyen/adyen-go-api-library/v21/src/adyen"
|
||||
"git.k6n.net/mats/go-redis-inventory/pkg/inventory"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
@@ -51,16 +49,20 @@ type CheckoutPoolServer struct {
|
||||
klarnaClient *KlarnaClient
|
||||
adyenClient *adyen.APIClient
|
||||
cartClient *CartClient
|
||||
orderClient *OrderClient
|
||||
orderHandler *AmqpOrderHandler
|
||||
inventoryService *inventory.RedisInventoryService
|
||||
}
|
||||
|
||||
func NewCheckoutPoolServer(pool actor.GrainPool[checkout.CheckoutGrain], pod_name string, klarnaClient *KlarnaClient, cartClient *CartClient, adyenClient *adyen.APIClient) *CheckoutPoolServer {
|
||||
func NewCheckoutPoolServer(pool actor.GrainPool[checkout.CheckoutGrain], pod_name string, klarnaClient *KlarnaClient, cartClient *CartClient, adyenClient *adyen.APIClient, orderClient *OrderClient, orderHandler *AmqpOrderHandler) *CheckoutPoolServer {
|
||||
srv := &CheckoutPoolServer{
|
||||
GrainPool: pool,
|
||||
pod_name: pod_name,
|
||||
klarnaClient: klarnaClient,
|
||||
cartClient: cartClient,
|
||||
adyenClient: adyenClient,
|
||||
orderClient: orderClient,
|
||||
orderHandler: orderHandler,
|
||||
}
|
||||
|
||||
return srv
|
||||
@@ -527,14 +529,6 @@ func (s *CheckoutPoolServer) Serve(mux *http.ServeMux) {
|
||||
handleFunc("/payment/klarna/push", s.KlarnaPushHandler)
|
||||
handleFunc("/payment/klarna/notification", s.KlarnaNotificationHandler)
|
||||
|
||||
conn, err := amqp.Dial(amqpUrl)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to connect to RabbitMQ: %v", err)
|
||||
}
|
||||
|
||||
orderHandler := NewAmqpOrderHandler(conn)
|
||||
orderHandler.DefineQueue()
|
||||
|
||||
handleFunc("POST /api/checkout/start/{cartid}", s.StartCheckoutHandler)
|
||||
handleFunc("GET /api/checkout", CookieCheckoutIdHandler(s.ProxyHandler(s.GetCheckoutHandler)))
|
||||
handleFunc("POST /api/checkout/delivery", CookieCheckoutIdHandler(s.ProxyHandler(s.SetDeliveryHandler)))
|
||||
|
||||
@@ -43,6 +43,16 @@ func getLocale(country string) string {
|
||||
return "sv-se"
|
||||
}
|
||||
|
||||
// getCountryFromCurrency is the reverse of getCurrency, used by server-to-server
|
||||
// callbacks (e.g. the Adyen webhook) that carry the settled currency but not the
|
||||
// shopper's host/country.
|
||||
func getCountryFromCurrency(currency string) string {
|
||||
if strings.EqualFold(currency, "NOK") {
|
||||
return "no"
|
||||
}
|
||||
return "se"
|
||||
}
|
||||
|
||||
func getCountryFromHost(host string) string {
|
||||
if strings.Contains(strings.ToLower(host), "-no") {
|
||||
return "no"
|
||||
|
||||
Reference in New Issue
Block a user