all the refactor
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/cart"
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/actor"
|
||||
messages "git.k6n.net/mats/go-cart-actor/proto/cart"
|
||||
"git.k6n.net/mats/platform/money"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -230,7 +231,7 @@ func TestCartResponse_Conversion(t *testing.T) {
|
||||
|
||||
func mustPrice(incVat int64, totalVat int64) *cart.Price {
|
||||
return &cart.Price{
|
||||
IncVat: incVat,
|
||||
VatRates: map[float32]int64{25: totalVat},
|
||||
IncVat: money.Cents(incVat),
|
||||
VatRates: map[int]money.Cents{2500: money.Cents(totalVat)}, // 25% in basis points
|
||||
}
|
||||
}
|
||||
|
||||
+14
-12
@@ -15,6 +15,7 @@ import (
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/cart"
|
||||
profilePkg "git.k6n.net/mats/go-cart-actor/pkg/profile"
|
||||
checkoutMessages "git.k6n.net/mats/go-cart-actor/proto/checkout"
|
||||
"git.k6n.net/mats/platform/money"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
@@ -447,15 +448,15 @@ 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"`
|
||||
Quantity int32 `json:"quantity"`
|
||||
UnitPrice money.Cents `json:"unitPrice"`
|
||||
TaxRate int32 `json:"taxRate"`
|
||||
}
|
||||
|
||||
type orderPayment struct {
|
||||
Provider string `json:"provider"`
|
||||
Reference string `json:"reference"`
|
||||
Amount int64 `json:"amount"`
|
||||
Provider string `json:"provider"`
|
||||
Reference string `json:"reference"`
|
||||
Amount money.Cents `json:"amount"`
|
||||
}
|
||||
|
||||
// CreateOrder implements OrderApplier. It builds a from-checkout request from
|
||||
@@ -476,10 +477,10 @@ func (c *OrderHTTPClient) CreateOrder(ctx context.Context, checkoutID uint64, g
|
||||
}
|
||||
|
||||
// Compute total from cart items + deliveries.
|
||||
var totalAmount int64
|
||||
var totalAmount money.Cents
|
||||
lines := buildOrderLines(g)
|
||||
for _, l := range lines {
|
||||
totalAmount += l.UnitPrice * int64(l.Quantity)
|
||||
totalAmount += l.UnitPrice.Mul(int64(l.Quantity))
|
||||
}
|
||||
|
||||
req := fromCheckoutReq{
|
||||
@@ -554,8 +555,9 @@ func buildOrderLines(g *checkout.CheckoutGrain) []orderLine {
|
||||
Name: name,
|
||||
Quantity: int32(it.Quantity),
|
||||
UnitPrice: it.Price.IncVat,
|
||||
// CartItem.Tax is percentage x 100 (e.g. 2500 = 25.00%). Convert to raw percent (25).
|
||||
TaxRate: int32(it.Tax / 100),
|
||||
// CartItem.Tax and OrderLine.TaxRate share the platform basis-point scale
|
||||
// (2500 = 25%), so the rate passes through unchanged.
|
||||
TaxRate: int32(it.Tax),
|
||||
})
|
||||
}
|
||||
for _, d := range g.Deliveries {
|
||||
@@ -568,7 +570,7 @@ func buildOrderLines(g *checkout.CheckoutGrain) []orderLine {
|
||||
Name: "Delivery",
|
||||
Quantity: 1,
|
||||
UnitPrice: d.Price.IncVat,
|
||||
TaxRate: 25,
|
||||
TaxRate: 2500, // 25% in basis points
|
||||
})
|
||||
}
|
||||
return lines
|
||||
@@ -672,7 +674,7 @@ func checkoutGrainToResponse(id string, g *checkout.CheckoutGrain) CheckoutRespo
|
||||
resp.Payments = append(resp.Payments, PaymentResponse{
|
||||
Id: p.PaymentId,
|
||||
Provider: p.Provider,
|
||||
Amount: p.Amount,
|
||||
Amount: money.Cents(p.Amount),
|
||||
Currency: p.Currency,
|
||||
Status: string(p.Status),
|
||||
})
|
||||
|
||||
@@ -175,7 +175,7 @@ func (s *OrderServer) handleIssueRefund(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
amount := req.Amount
|
||||
if amount <= 0 {
|
||||
amount = g.CapturedAmount - g.RefundedAmount
|
||||
amount = (g.CapturedAmount - g.RefundedAmount).Int64()
|
||||
}
|
||||
if amount <= 0 {
|
||||
writeError(w, http.StatusBadRequest, "no captured amount to refund")
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/actor"
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/order"
|
||||
messages "git.k6n.net/mats/go-cart-actor/proto/order"
|
||||
"git.k6n.net/mats/platform/money"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -86,10 +87,10 @@ func (a *testOrderApplier) Apply(_ context.Context, id uint64, msgs ...proto.Mes
|
||||
case *messages.IssueRefund:
|
||||
g.Refunds = append(g.Refunds, order.Refund{
|
||||
Provider: m.Provider,
|
||||
Amount: m.Amount,
|
||||
Amount: money.Cents(m.Amount),
|
||||
Reference: m.Reference,
|
||||
})
|
||||
g.RefundedAmount += m.Amount
|
||||
g.RefundedAmount += money.Cents(m.Amount)
|
||||
if g.RefundedAmount >= g.CapturedAmount {
|
||||
g.Status = order.StatusRefunded
|
||||
}
|
||||
|
||||
+61
-24
@@ -1,6 +1,7 @@
|
||||
package ucp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
@@ -31,8 +32,8 @@ type SigningConfig struct {
|
||||
// NewSigningConfigFromPEM parses a PEM-encoded ECDSA P-256 private key and
|
||||
// returns a SigningConfig ready for use.
|
||||
//
|
||||
// data — PEM block containing an EC PRIVATE KEY (PKCS#8 or SEC1)
|
||||
// keyID — the kid matched by the UCP profile signing_keys entry
|
||||
// data — PEM block containing an EC PRIVATE KEY (PKCS#8 or SEC1)
|
||||
// keyID — the kid matched by the UCP profile signing_keys entry
|
||||
func NewSigningConfigFromPEM(pemData []byte, keyID string) (*SigningConfig, error) {
|
||||
block, _ := pem.Decode(pemData)
|
||||
if block == nil {
|
||||
@@ -72,8 +73,8 @@ func NewSigningConfigFromPEM(pemData []byte, keyID string) (*SigningConfig, erro
|
||||
|
||||
// WithSigning wraps h with an HTTP middleware that adds RFC 9421 HTTP Message
|
||||
// Signatures to every response. The Signature-Input and Signature headers are
|
||||
// computed over @status, content-type, and x-ucp-timestamp, signed with the
|
||||
// configured ECDSA P-256 key.
|
||||
// computed over @status, content-type, x-ucp-timestamp, and content-digest,
|
||||
// signed with the configured ECDSA P-256 key.
|
||||
//
|
||||
// Mount it on any existing UCP handler:
|
||||
//
|
||||
@@ -83,11 +84,9 @@ func WithSigning(h http.Handler, cfg *SigningConfig) http.Handler {
|
||||
return h // pass through without signing
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
sw := &signedWriter{
|
||||
ResponseWriter: w,
|
||||
cfg: cfg,
|
||||
}
|
||||
sw := newSignedWriter(w, cfg)
|
||||
h.ServeHTTP(sw, r)
|
||||
sw.flush()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -96,10 +95,24 @@ func WithSigning(h http.Handler, cfg *SigningConfig) http.Handler {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type signedWriter struct {
|
||||
http.ResponseWriter
|
||||
cfg *SigningConfig
|
||||
statusCode int
|
||||
wroteHeader bool
|
||||
responseWriter http.ResponseWriter
|
||||
header http.Header
|
||||
body bytes.Buffer
|
||||
cfg *SigningConfig
|
||||
statusCode int
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
func newSignedWriter(w http.ResponseWriter, cfg *SigningConfig) *signedWriter {
|
||||
return &signedWriter{
|
||||
responseWriter: w,
|
||||
header: make(http.Header),
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *signedWriter) Header() http.Header {
|
||||
return w.header
|
||||
}
|
||||
|
||||
func (w *signedWriter) WriteHeader(statusCode int) {
|
||||
@@ -108,28 +121,44 @@ func (w *signedWriter) WriteHeader(statusCode int) {
|
||||
}
|
||||
w.wroteHeader = true
|
||||
w.statusCode = statusCode
|
||||
|
||||
created := time.Now().Unix()
|
||||
w.ResponseWriter.Header().Set("x-ucp-timestamp", strconv.FormatInt(created, 10))
|
||||
|
||||
// Add signature headers before flushing.
|
||||
w.addSignatureHeaders(created)
|
||||
|
||||
w.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (w *signedWriter) Write(p []byte) (int, error) {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
return w.ResponseWriter.Write(p)
|
||||
return w.body.Write(p)
|
||||
}
|
||||
|
||||
func (w *signedWriter) flush() {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
if w.header.Get("Content-Type") == "" && w.body.Len() > 0 {
|
||||
w.header.Set("Content-Type", http.DetectContentType(w.body.Bytes()))
|
||||
}
|
||||
|
||||
created := time.Now().Unix()
|
||||
w.header.Set("x-ucp-timestamp", strconv.FormatInt(created, 10))
|
||||
w.header.Set("Content-Digest", buildContentDigest(w.body.Bytes()))
|
||||
|
||||
// Add signature headers before flushing.
|
||||
w.addSignatureHeaders(created)
|
||||
|
||||
dst := w.responseWriter.Header()
|
||||
for k, vals := range w.header {
|
||||
dst[k] = append([]string(nil), vals...)
|
||||
}
|
||||
w.responseWriter.WriteHeader(w.statusCode)
|
||||
_, _ = w.responseWriter.Write(w.body.Bytes())
|
||||
}
|
||||
|
||||
// addSignatureHeaders computes and writes the Signature-Input and Signature
|
||||
// headers per RFC 9421 §3.2 / §3.3.
|
||||
func (w *signedWriter) addSignatureHeaders(created int64) {
|
||||
contentType := w.ResponseWriter.Header().Get("Content-Type")
|
||||
contentType := w.header.Get("Content-Type")
|
||||
sigTimestamp := strconv.FormatInt(created, 10)
|
||||
contentDigest := w.header.Get("Content-Digest")
|
||||
|
||||
// Covered components (in order). RFC 9421 §3.2: derived component names
|
||||
// (@status) are bare identifiers; HTTP header field names are sf-strings
|
||||
@@ -138,6 +167,7 @@ func (w *signedWriter) addSignatureHeaders(created int64) {
|
||||
"@status",
|
||||
`"content-type"`,
|
||||
`"x-ucp-timestamp"`,
|
||||
`"content-digest"`,
|
||||
}
|
||||
|
||||
// Build the signature base string (RFC 9421 §2.2).
|
||||
@@ -148,6 +178,8 @@ func (w *signedWriter) addSignatureHeaders(created int64) {
|
||||
base.WriteByte('\n')
|
||||
base.WriteString(fmt.Sprintf(`"x-ucp-timestamp": %s`, sigTimestamp))
|
||||
base.WriteByte('\n')
|
||||
base.WriteString(fmt.Sprintf(`"content-digest": %s`, contentDigest))
|
||||
base.WriteByte('\n')
|
||||
|
||||
// Append the signature-params pseudo-line (RFC 9421 §2.2).
|
||||
compList := strings.Join(components, " ")
|
||||
@@ -178,6 +210,11 @@ func (w *signedWriter) addSignatureHeaders(created int64) {
|
||||
// Signature: sig1=:base64url:
|
||||
sigValue := fmt.Sprintf("sig1=:%s:", sigB64)
|
||||
|
||||
w.ResponseWriter.Header().Set("Signature-Input", sigInput)
|
||||
w.ResponseWriter.Header().Set("Signature", sigValue)
|
||||
w.header.Set("Signature-Input", sigInput)
|
||||
w.header.Set("Signature", sigValue)
|
||||
}
|
||||
|
||||
func buildContentDigest(body []byte) string {
|
||||
sum := sha256.Sum256(body)
|
||||
return "sha-256=:" + base64.StdEncoding.EncodeToString(sum[:]) + ":"
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ func TestWithSigning_AddsHeaders(t *testing.T) {
|
||||
sigInput := rec.Header().Get("Signature-Input")
|
||||
sig := rec.Header().Get("Signature")
|
||||
ts := rec.Header().Get("x-ucp-timestamp")
|
||||
digest := rec.Header().Get("Content-Digest")
|
||||
|
||||
if sigInput == "" {
|
||||
t.Fatal("expected Signature-Input header")
|
||||
@@ -73,9 +74,12 @@ func TestWithSigning_AddsHeaders(t *testing.T) {
|
||||
if ts == "" {
|
||||
t.Fatal("expected x-ucp-timestamp header")
|
||||
}
|
||||
if digest == "" {
|
||||
t.Fatal("expected Content-Digest header")
|
||||
}
|
||||
|
||||
// Verify the signature input format.
|
||||
if !strings.HasPrefix(sigInput, `sig1=(@status "content-type" "x-ucp-timestamp");`) {
|
||||
if !strings.HasPrefix(sigInput, `sig1=(@status "content-type" "x-ucp-timestamp" "content-digest");`) {
|
||||
t.Fatalf("unexpected Signature-Input format: %q", sigInput)
|
||||
}
|
||||
if !strings.Contains(sigInput, `keyid="k6n-ecdsa-2026"`) {
|
||||
@@ -155,6 +159,9 @@ func TestWithSigning_WithUCPCartHandler(t *testing.T) {
|
||||
if rec.Header().Get("x-ucp-timestamp") == "" {
|
||||
t.Fatal("expected x-ucp-timestamp on UCP cart response")
|
||||
}
|
||||
if rec.Header().Get("Content-Digest") == "" {
|
||||
t.Fatal("expected Content-Digest on UCP cart response")
|
||||
}
|
||||
}
|
||||
|
||||
// mustLoadTestKey loads the test PEM for signing tests.
|
||||
|
||||
+90
-78
@@ -19,11 +19,12 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/actor"
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/cart"
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/checkout"
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/order"
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/profile"
|
||||
"git.k6n.net/mats/go-cart-actor/pkg/actor"
|
||||
"git.k6n.net/mats/platform/money"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -105,8 +106,8 @@ type CartItemInput struct {
|
||||
Sku string `json:"sku"`
|
||||
Quantity int `json:"quantity,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Price int64 `json:"price,omitempty"` // inc-vat in minor units (öre)
|
||||
TaxRate float64 `json:"taxRate,omitempty"` // e.g. 25 or 12.5
|
||||
Price money.Cents `json:"price,omitempty"` // inc-vat in minor units (öre / money.Cents on the wire)
|
||||
TaxRate int `json:"taxRate,omitempty"` // basis points: 2500 = 25%, 1250 = 12.5% (matches OrderLine.TaxRate / CartItem.Tax)
|
||||
Image string `json:"image,omitempty"`
|
||||
StoreId *string `json:"storeId,omitempty"`
|
||||
Children []CartItemInput `json:"children,omitempty"`
|
||||
@@ -115,16 +116,16 @@ type CartItemInput struct {
|
||||
|
||||
// VoucherInput is the UCP wire format for a voucher code to apply.
|
||||
type VoucherInput struct {
|
||||
Code string `json:"code"`
|
||||
Value int64 `json:"value,omitempty"`
|
||||
Code string `json:"code"`
|
||||
Value money.Cents `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// CartResponse is the UCP cart response body.
|
||||
type CartResponse struct {
|
||||
Id string `json:"id"`
|
||||
Items []CartItem `json:"items,omitempty"`
|
||||
Totals Totals `json:"totals"`
|
||||
Status string `json:"status"`
|
||||
Id string `json:"id"`
|
||||
Items []CartItem `json:"items,omitempty"`
|
||||
Totals Totals `json:"totals"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// CartItem is the UCP wire format for a cart line item in responses.
|
||||
@@ -132,8 +133,8 @@ type CartItem struct {
|
||||
Sku string `json:"sku"`
|
||||
Quantity int `json:"quantity"`
|
||||
Name string `json:"name,omitempty"`
|
||||
UnitPrice int64 `json:"unitPrice"` // inc-vat minor units
|
||||
TotalPrice int64 `json:"totalPrice"` // inc-vat minor units
|
||||
UnitPrice money.Cents `json:"unitPrice"` // inc-vat minor units
|
||||
TotalPrice money.Cents `json:"totalPrice"` // inc-vat minor units
|
||||
TaxRate int `json:"taxRate"`
|
||||
Image string `json:"image,omitempty"`
|
||||
ItemId uint32 `json:"itemId,omitempty"`
|
||||
@@ -142,11 +143,11 @@ type CartItem struct {
|
||||
|
||||
// Totals is the UCP wire format for price breakdown.
|
||||
type Totals struct {
|
||||
TotalIncVat int64 `json:"totalIncVat"`
|
||||
TotalExVat int64 `json:"totalExVat"`
|
||||
TotalVat int64 `json:"totalVat"`
|
||||
Currency string `json:"currency"`
|
||||
Discount int64 `json:"discount,omitempty"`
|
||||
TotalIncVat money.Cents `json:"totalIncVat"`
|
||||
TotalExVat money.Cents `json:"totalExVat"`
|
||||
TotalVat money.Cents `json:"totalVat"`
|
||||
Currency string `json:"currency"`
|
||||
Discount money.Cents `json:"discount,omitempty"`
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -205,24 +206,24 @@ type CompleteCheckoutRequest struct {
|
||||
|
||||
// CheckoutResponse is the UCP checkout session response.
|
||||
type CheckoutResponse struct {
|
||||
Id string `json:"id"`
|
||||
CartId string `json:"cartId"`
|
||||
Status string `json:"status"`
|
||||
Items []CartItem `json:"items,omitempty"`
|
||||
Totals Totals `json:"totals"`
|
||||
Deliveries []DeliveryResponse `json:"deliveries,omitempty"`
|
||||
Contact *ContactResponse `json:"contact,omitempty"`
|
||||
OrderId *string `json:"orderId,omitempty"`
|
||||
Payments []PaymentResponse `json:"payments,omitempty"`
|
||||
Id string `json:"id"`
|
||||
CartId string `json:"cartId"`
|
||||
Status string `json:"status"`
|
||||
Items []CartItem `json:"items,omitempty"`
|
||||
Totals Totals `json:"totals"`
|
||||
Deliveries []DeliveryResponse `json:"deliveries,omitempty"`
|
||||
Contact *ContactResponse `json:"contact,omitempty"`
|
||||
OrderId *string `json:"orderId,omitempty"`
|
||||
Payments []PaymentResponse `json:"payments,omitempty"`
|
||||
}
|
||||
|
||||
// DeliveryResponse is the UCP wire format for a delivery option.
|
||||
type DeliveryResponse struct {
|
||||
Id uint32 `json:"id"`
|
||||
Provider string `json:"provider"`
|
||||
Price int64 `json:"price"`
|
||||
Items []uint32 `json:"items"`
|
||||
PickupPoint *PickupPointResp `json:"pickupPoint,omitempty"`
|
||||
Id uint32 `json:"id"`
|
||||
Provider string `json:"provider"`
|
||||
Price money.Cents `json:"price"`
|
||||
Items []uint32 `json:"items"`
|
||||
PickupPoint *PickupPointResp `json:"pickupPoint,omitempty"`
|
||||
}
|
||||
|
||||
// PickupPointResp is the UCP wire format for a pickup point.
|
||||
@@ -244,11 +245,11 @@ type ContactResponse struct {
|
||||
|
||||
// PaymentResponse is the UCP wire format for a payment record.
|
||||
type PaymentResponse struct {
|
||||
Id string `json:"id"`
|
||||
Provider string `json:"provider"`
|
||||
Amount int64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Status string `json:"status"`
|
||||
Id string `json:"id"`
|
||||
Provider string `json:"provider"`
|
||||
Amount money.Cents `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -257,24 +258,24 @@ type PaymentResponse struct {
|
||||
|
||||
// OrderResponse is the UCP order response body.
|
||||
type OrderResponse struct {
|
||||
OrderId string `json:"orderId"`
|
||||
Reference string `json:"reference,omitempty"`
|
||||
CartId string `json:"cartId,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Currency string `json:"currency,omitempty"`
|
||||
TotalAmount int64 `json:"totalAmount"`
|
||||
TotalTax int64 `json:"totalTax"`
|
||||
Lines []OrderLineResp `json:"lines,omitempty"`
|
||||
Payments []OrderPaymentResp `json:"payments,omitempty"`
|
||||
Fulfillments []OrderFulfillmentResp `json:"fulfillments,omitempty"`
|
||||
Returns []OrderReturnResp `json:"returns,omitempty"`
|
||||
Refunds []OrderRefundResp `json:"refunds,omitempty"`
|
||||
CapturedAmount int64 `json:"capturedAmount"`
|
||||
RefundedAmount int64 `json:"refundedAmount"`
|
||||
CustomerEmail string `json:"customerEmail,omitempty"`
|
||||
CustomerName string `json:"customerName,omitempty"`
|
||||
PlacedAt string `json:"placedAt,omitempty"`
|
||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||
OrderId string `json:"orderId"`
|
||||
Reference string `json:"reference,omitempty"`
|
||||
CartId string `json:"cartId,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Currency string `json:"currency,omitempty"`
|
||||
TotalAmount money.Cents `json:"totalAmount"`
|
||||
TotalTax money.Cents `json:"totalTax"`
|
||||
Lines []OrderLineResp `json:"lines,omitempty"`
|
||||
Payments []OrderPaymentResp `json:"payments,omitempty"`
|
||||
Fulfillments []OrderFulfillmentResp `json:"fulfillments,omitempty"`
|
||||
Returns []OrderReturnResp `json:"returns,omitempty"`
|
||||
Refunds []OrderRefundResp `json:"refunds,omitempty"`
|
||||
CapturedAmount money.Cents `json:"capturedAmount"`
|
||||
RefundedAmount money.Cents `json:"refundedAmount"`
|
||||
CustomerEmail string `json:"customerEmail,omitempty"`
|
||||
CustomerName string `json:"customerName,omitempty"`
|
||||
PlacedAt string `json:"placedAt,omitempty"`
|
||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||
}
|
||||
|
||||
// OrderLineResp represents a single order line item in responses.
|
||||
@@ -282,21 +283,21 @@ type OrderLineResp struct {
|
||||
Reference string `json:"reference"`
|
||||
Sku string `json:"sku"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Quantity int `json:"quantity"`
|
||||
UnitPrice int64 `json:"unitPrice"`
|
||||
TaxRate int `json:"taxRate"`
|
||||
TotalAmount int64 `json:"totalAmount"`
|
||||
TotalTax int64 `json:"totalTax"`
|
||||
Fulfilled int `json:"fulfilled,omitempty"`
|
||||
Quantity int `json:"quantity"`
|
||||
UnitPrice money.Cents `json:"unitPrice"`
|
||||
TaxRate int `json:"taxRate"`
|
||||
TotalAmount money.Cents `json:"totalAmount"`
|
||||
TotalTax money.Cents `json:"totalTax"`
|
||||
Fulfilled int `json:"fulfilled,omitempty"`
|
||||
}
|
||||
|
||||
// OrderPaymentResp represents a payment record on an order.
|
||||
type OrderPaymentResp struct {
|
||||
Provider string `json:"provider"`
|
||||
Authorized int64 `json:"authorized"`
|
||||
Captured int64 `json:"captured"`
|
||||
Refunded int64 `json:"refunded"`
|
||||
AuthRef string `json:"authRef,omitempty"`
|
||||
Provider string `json:"provider"`
|
||||
Authorized money.Cents `json:"authorized"`
|
||||
Captured money.Cents `json:"captured"`
|
||||
Refunded money.Cents `json:"refunded"`
|
||||
AuthRef string `json:"authRef,omitempty"`
|
||||
CaptureRef string `json:"captureRef,omitempty"`
|
||||
}
|
||||
|
||||
@@ -318,9 +319,9 @@ type OrderReturnResp struct {
|
||||
|
||||
// OrderRefundResp represents a refund record.
|
||||
type OrderRefundResp struct {
|
||||
Provider string `json:"provider"`
|
||||
Amount int64 `json:"amount"`
|
||||
Reference string `json:"reference,omitempty"`
|
||||
Provider string `json:"provider"`
|
||||
Amount money.Cents `json:"amount"`
|
||||
Reference string `json:"reference,omitempty"`
|
||||
}
|
||||
|
||||
// OrderLineEntry is a lightweight reference+quantity pair used in fulfillment
|
||||
@@ -408,17 +409,18 @@ type UpdateAddressRequest struct {
|
||||
|
||||
// ProfileData is the UCP business profile served at /.well-known/ucp.
|
||||
type ProfileData struct {
|
||||
UCP Profile `json:"ucp"`
|
||||
UCP Profile `json:"ucp"`
|
||||
SigningKeys []SigningKey `json:"signing_keys,omitempty"`
|
||||
}
|
||||
|
||||
// Profile is the core UCP profile object.
|
||||
type Profile struct {
|
||||
Version string `json:"version"`
|
||||
Business BusinessInfo `json:"business,omitempty"`
|
||||
Services map[string][]ServiceBinding `json:"services"`
|
||||
Capabilities map[string][]CapabilityDecl `json:"capabilities"`
|
||||
PaymentHandlers map[string][]PaymentHandlerDecl `json:"payment_handlers,omitempty"`
|
||||
SupportedVersions []string `json:"supported_versions,omitempty"`
|
||||
Version string `json:"version"`
|
||||
Business BusinessInfo `json:"business,omitempty"`
|
||||
Services map[string][]ServiceBinding `json:"services"`
|
||||
Capabilities map[string][]CapabilityDecl `json:"capabilities"`
|
||||
PaymentHandlers map[string][]PaymentHandlerDecl `json:"payment_handlers,omitempty"`
|
||||
SupportedVersions []string `json:"supported_versions,omitempty"`
|
||||
}
|
||||
|
||||
// BusinessInfo describes the business behind a UCP profile.
|
||||
@@ -455,13 +457,23 @@ type OpDef struct {
|
||||
|
||||
// PaymentHandlerDecl describes a payment handler specification.
|
||||
type PaymentHandlerDecl struct {
|
||||
ID string `json:"id"`
|
||||
Version string `json:"version"`
|
||||
Spec string `json:"spec"`
|
||||
Schema string `json:"schema"`
|
||||
ID string `json:"id"`
|
||||
Version string `json:"version"`
|
||||
Spec string `json:"spec"`
|
||||
Schema string `json:"schema"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// SigningKey describes a published verification key for signed UCP responses.
|
||||
type SigningKey struct {
|
||||
Kid string `json:"kid"`
|
||||
Kty string `json:"kty"`
|
||||
Crv string `json:"crv,omitempty"`
|
||||
X string `json:"x,omitempty"`
|
||||
Y string `json:"y,omitempty"`
|
||||
Alg string `json:"alg,omitempty"`
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HTTP helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user