@cartbase/storefront 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +3 -3
  2. package/package.json +231 -226
  3. package/src/api/auth.ts +1 -1
  4. package/src/api/carts.ts +5 -3
  5. package/src/api/checkout.ts +91 -40
  6. package/src/api/consent.ts +2 -2
  7. package/src/api/integrations.ts +4 -17
  8. package/src/api/regions.ts +1 -1
  9. package/src/cart-drawer/context.tsx +4 -4
  10. package/src/cart-drawer/cross-sell-carousel.tsx +1 -1
  11. package/src/cart-drawer/cross-sell-sidebar.tsx +1 -1
  12. package/src/cart-drawer/header.tsx +1 -1
  13. package/src/cart-drawer/item/index.tsx +2 -2
  14. package/src/cart-drawer/item/quantity.tsx +2 -2
  15. package/src/cart-drawer/item/upsell.tsx +3 -3
  16. package/src/cart-drawer/item/variant.tsx +2 -2
  17. package/src/cart-drawer/labels-bg.ts +2 -2
  18. package/src/cart-drawer/labels.ts +119 -119
  19. package/src/cart-drawer/summary-breakdown.tsx +197 -196
  20. package/src/cart-drawer/template.tsx +2 -2
  21. package/src/cart-drawer/tiered-progress.tsx +1 -1
  22. package/src/checkout/address-error-copy.ts +3 -3
  23. package/src/checkout/address-form.tsx +2 -2
  24. package/src/checkout/address-select.tsx +1 -1
  25. package/src/checkout/boxnow-locker-selector.tsx +3 -3
  26. package/src/checkout/checkout-client.tsx +15 -12
  27. package/src/checkout/company-details.tsx +1 -1
  28. package/src/checkout/compare-addresses.ts +1 -1
  29. package/src/checkout/discount-section.tsx +5 -5
  30. package/src/checkout/econt-office-selector.tsx +2 -2
  31. package/src/checkout/gift-card-section.tsx +2 -2
  32. package/src/checkout/index.ts +0 -1
  33. package/src/checkout/labels-bg.ts +2 -2
  34. package/src/checkout/labels.ts +263 -263
  35. package/src/checkout/line-item-card.tsx +2 -2
  36. package/src/checkout/order-summary.tsx +48 -48
  37. package/src/checkout/payment-button.tsx +4 -4
  38. package/src/checkout/payment-error-copy.ts +6 -6
  39. package/src/checkout/payment-method-list.tsx +93 -33
  40. package/src/checkout/payment-wrapper.tsx +8 -11
  41. package/src/checkout/promotion-error-copy.ts +2 -2
  42. package/src/checkout/shipping-method-list.tsx +2 -2
  43. package/src/checkout/stripe-wrapper.tsx +1 -1
  44. package/src/checkout/use-checkout-orchestration.ts +108 -98
  45. package/src/common/cart-button-client.tsx +1 -1
  46. package/src/common/cart-button.tsx +1 -1
  47. package/src/common/country-select.tsx +2 -2
  48. package/src/common/delete-button.tsx +1 -1
  49. package/src/common/index.ts +1 -1
  50. package/src/common/language-select.tsx +2 -2
  51. package/src/common/localized-link.tsx +1 -1
  52. package/src/lib/cart-helpers.ts +2 -2
  53. package/src/lib/cookie-names.ts +41 -0
  54. package/src/lib/get-product-price.ts +3 -3
  55. package/src/lib/payment-constants.ts +53 -66
  56. package/src/lib/store-api-error.ts +1 -1
  57. package/src/order/index.ts +1 -1
  58. package/src/order/labels-bg.ts +39 -39
  59. package/src/order/labels.ts +79 -79
  60. package/src/order/order-address-card.tsx +1 -1
  61. package/src/order/order-completed-template.tsx +20 -12
  62. package/src/order/order-confirmation-header.tsx +1 -1
  63. package/src/order/order-delivery-card.tsx +3 -3
  64. package/src/order/order-item.tsx +2 -2
  65. package/src/order/order-items-list.tsx +3 -3
  66. package/src/order/order-payment-card.tsx +27 -36
  67. package/src/order/order-timeline.tsx +2 -2
  68. package/src/order/order-totals.tsx +250 -245
  69. package/src/platform/identity.ts +18 -0
  70. package/src/platform/index.ts +8 -0
  71. package/src/platform/metadata.ts +26 -0
  72. package/src/platform/platform-init.tsx +46 -0
  73. package/src/products/image-gallery.tsx +1 -1
  74. package/src/products/mobile-actions.tsx +1 -1
  75. package/src/products/option-select.tsx +1 -1
  76. package/src/products/preview-price.tsx +1 -1
  77. package/src/products/product-actions.tsx +6 -6
  78. package/src/products/product-info.tsx +1 -1
  79. package/src/products/product-preview.tsx +1 -1
  80. package/src/products/product-price.tsx +2 -2
  81. package/src/products/product-tabs.tsx +1 -1
  82. package/src/products/purchase-options.tsx +1 -1
  83. package/src/products/related-products.tsx +3 -3
  84. package/src/products/variant-matching.ts +4 -4
  85. package/src/reviews-ui/labels.ts +1 -1
  86. package/src/reviews-ui/photo-upload.tsx +1 -1
  87. package/src/reviews-ui/review-list.tsx +1 -1
  88. package/src/reviews-ui/review-widget.tsx +1 -1
  89. package/src/reviews-ui/review-wizard.tsx +1 -1
  90. package/src/reviews-ui/wizard-state.ts +1 -1
  91. package/src/store/category-template.tsx +2 -2
  92. package/src/store/collection-template.tsx +2 -2
  93. package/src/store/labels.ts +2 -2
  94. package/src/store/paginated-products.tsx +1 -1
  95. package/src/store/search-params.ts +1 -1
  96. package/src/store/search-template.tsx +1 -1
  97. package/src/tracking/consent-banner.tsx +1 -1
  98. package/src/tracking/consent-init.tsx +1 -1
  99. package/src/tracking/consent.ts +6 -6
  100. package/src/tracking/fbq.ts +2 -2
  101. package/src/tracking/get-tracking-attribution.ts +1 -1
  102. package/src/tracking/get-tracking-config.ts +2 -2
  103. package/src/tracking/index.ts +2 -2
  104. package/src/tracking/meta-pixel.tsx +1 -1
  105. package/src/tracking/rybbit.tsx +1 -1
  106. package/src/tracking/types.ts +6 -6
@@ -23,9 +23,11 @@ import {
23
23
  prepareCheckout,
24
24
  refreshPaymentIfTerminal as refreshPaymentIfTerminalApi,
25
25
  syncPaymentAmount as syncPaymentAmountApi,
26
+ isMethodEntry,
26
27
  type PrepareCheckoutInput,
27
28
  type RefreshPaymentResult,
28
- type StorePaymentProvider,
29
+ type StorePaymentEntry,
30
+ type StorePaymentMethodEntry,
29
31
  type StoreShippingOption,
30
32
  type SyncPaymentAmountResult,
31
33
  } from "../api/checkout"
@@ -35,8 +37,7 @@ import {
35
37
  type StoreCustomer,
36
38
  type UpdateCustomerInput,
37
39
  } from "../api/customers"
38
- import type { PublicCodConfig } from "../api/integrations"
39
- import { isManual, isStripeLike } from "../lib/payment-constants"
40
+ import { isStripeLike } from "../lib/payment-constants"
40
41
  import compareAddresses from "./compare-addresses"
41
42
  import { translateAddressError } from "./address-error-copy"
42
43
  import { translatePaymentError } from "./payment-error-copy"
@@ -54,7 +55,7 @@ import type { BoxNowLocker } from "../api/integrations"
54
55
  * persistence, custom summary).
55
56
  *
56
57
  * Ported from `@1click/ui/src/checkout/use-checkout-orchestration.ts`
57
- * (v2.3.1, the deferred-intent architecture) with the barter data seam:
58
+ * (v2.3.1, the deferred-intent architecture) with the Cartbase data seam:
58
59
  *
59
60
  * - Server actions → `@cartbase/storefront/api` calls on a caller-supplied
60
61
  * `StorefrontClient` (`updateCart`, `prepareCheckout`,
@@ -63,24 +64,25 @@ import type { BoxNowLocker } from "../api/integrations"
63
64
  * - `placeOrder` (server action with baked-in redirect) →
64
65
  * `completeCart()` + template navigation (`orderConfirmedPath` with
65
66
  * `{id}`/`{country}` substitution) or the `onOrderPlaced` callback.
66
- * - **Zero-remainder gift path** (barter gift-card tender,
67
+ * - **Zero-remainder gift path** (Cartbase gift-card tender,
67
68
  * docs/storefront/checkout.md): when applied gift cards cover the whole
68
69
  * total, `prepareCheckout` returns `client_secret: null` +
69
70
  * `provider_id: null` and the card path SKIPS `stripe.confirmPayment`
70
71
  * entirely — the cart completes on the gift session alone. The @1click
71
- * original threw on a missing client_secret; barter treats
72
+ * original threw on a missing client_secret; Cartbase treats
72
73
  * null-secret + null-provider as the documented gift path.
73
- * - COD fee: barter's fee is CART-LEVEL decoration (`cod_fee_total` /
74
- * `cod_fee_label`, folded into `cart.total` while a live pp_cod session
75
- * exists) not a metadata-flagged line item. The optimistic-fee math
76
- * reads `cart.cod_fee_total`; the prediction comes from the
77
- * integrations config `cod` block (never hardcoded).
78
- * - Provider ids: `pp_stripe` / `pp_cod` / `pp_manual` exactly
79
- * (lib/payment-constants + the pp_cod resolution below).
74
+ * - The payment method fee: CART-LEVEL decoration
75
+ * (`payment_method_fee_total` / `payment_method_fee_label`, folded into
76
+ * `cart.total` while a live method session exists) not a
77
+ * metadata-flagged line item. ANY method may carry a fee; the optimistic
78
+ * prediction reads `fee_amount` off the selected LISTING entry (the
79
+ * integrations `cod` block died 2026-08-11).
80
+ * - Processor ids: `pp_stripe` exactly (lib/payment-constants); merchant
81
+ * methods have no provider id — sessions ride `payment_method_id`.
80
82
  * - `logCheckoutError`/`logEvent` (Supabase-side sinks in @1click) have no
81
- * barter store endpoint — the hook takes an optional `logError` callback
83
+ * Cartbase store endpoint — the hook takes an optional `logError` callback
82
84
  * so stores wire their own sink; all production log points are kept.
83
- * - Barter regions carry NO embedded countries array (api/regions.ts
85
+ * - Cartbase regions carry NO embedded countries array (api/regions.ts
84
86
  * divergence note) — the country list is a `countries` option with a
85
87
  * `countryCode` single-entry fallback.
86
88
  *
@@ -93,11 +95,10 @@ import type { BoxNowLocker } from "../api/integrations"
93
95
  * class of fork-rot bug structurally impossible.
94
96
  */
95
97
 
96
- /** COD checkout config the integrations `cod` block (api/integrations). */
97
- export type CheckoutCodConfig = PublicCodConfig | null
98
-
99
- /** Minimal provider row the hook accepts (SDK DTO or bare `{id}`). */
100
- export type PaymentProviderLike = StorePaymentProvider | { id: string }
98
+ /** A payable entry the hook accepts: a processor row (`{id}`) or a
99
+ * merchant-method row (`{payment_method_id, name, kind}`) — the two wire
100
+ * shapes of GET /api/store/payment-providers since the pp_* kill. */
101
+ export type PaymentProviderLike = StorePaymentEntry | { id: string }
101
102
 
102
103
  export type CheckoutLogError = (
103
104
  errorType: string,
@@ -115,7 +116,7 @@ export type UseCheckoutOrchestrationOptions = {
115
116
  /** Default country code when the cart has no shipping address yet. */
116
117
  countryCode?: string
117
118
  /**
118
- * Countries offered in the address form. Barter regions do NOT embed a
119
+ * Countries offered in the address form. Cartbase regions do NOT embed a
119
120
  * countries array (store-API divergence), so the host app supplies the
120
121
  * list; omitted → a single entry derived from `countryCode`.
121
122
  */
@@ -128,13 +129,6 @@ export type UseCheckoutOrchestrationOptions = {
128
129
  methods: PaymentProviderLike[] | null,
129
130
  selectedShippingOption: StoreShippingOption | null
130
131
  ) => PaymentProviderLike[] | null
131
- /**
132
- * Optional cash-on-delivery configuration — pass the integrations config
133
- * `cod` block (`getIntegrationsConfig(client).cod`). When present, the
134
- * hook paints `optimisticCodFee` on tab-toggle so the totals row updates
135
- * before the server-side fee (applied at prepare) arrives.
136
- */
137
- codConfig?: CheckoutCodConfig
138
132
  /**
139
133
  * Order-confirmed redirect path template (`{id}` → order.id,
140
134
  * `{country}` → shipping country). Falls back to the CheckoutProvider
@@ -159,7 +153,7 @@ export type UseCheckoutOrchestrationOptions = {
159
153
  resolveTrackingMetadata?: () => Record<string, unknown> | undefined
160
154
  /**
161
155
  * Operational-visibility sink (successor of @1click's logCheckoutError /
162
- * logEvent Supabase writers — barter has no store-side log endpoint).
156
+ * logEvent Supabase writers — Cartbase has no store-side log endpoint).
163
157
  * Called with (errorType, message, context). Optional; defaults to no-op.
164
158
  */
165
159
  logError?: CheckoutLogError
@@ -171,7 +165,7 @@ export type UseCheckoutOrchestrationOptions = {
171
165
  *
172
166
  * `shipping_address.phone` is required — Bulgarian carriers (Econt,
173
167
  * BoxNow) need it to contact the customer; it's the courier's primary
174
- * recovery channel when the address is ambiguous. (Barter's
168
+ * recovery channel when the address is ambiguous. (Cartbase's
175
169
  * prepare-checkout schema requires it too — docs/storefront/checkout.md.)
176
170
  */
177
171
  const REQUIRED_ADDRESS_FIELDS = [
@@ -194,12 +188,18 @@ const REQUIRED_ADDRESS_FIELDS = [
194
188
  */
195
189
  const ADDRESS_AUTO_SAVE_DEBOUNCE_MS = 600
196
190
 
197
- /** Barter COD provider resolution: `pp_cod` exactly, with the Medusa-era
198
- * `pp_system_default*` prefix kept as fallback (lib/payment-constants),
199
- * and `pp_manual` accepted as the offline tab when no true COD provider
200
- * is enabled. Fee prediction only ever applies via `codConfig` (pp_cod). */
201
- const isCodLikeId = (id?: string): boolean =>
202
- !!id && (id === "pp_cod" || isManual(id))
191
+ /** The offline tab's tender: a merchant METHOD entry (pp_* kill). The COD
192
+ * method (kind 'cod') wins; any manual method serves as the offline tab
193
+ * when no COD switch is on. The entry carries its own fee for prediction. */
194
+ const findOfflineMethod = (
195
+ entries: PaymentProviderLike[] | null | undefined
196
+ ): StorePaymentMethodEntry | null => {
197
+ const methods = (entries ?? []).filter(
198
+ (m): m is Extract<PaymentProviderLike, { payment_method_id: string }> =>
199
+ "payment_method_id" in m && isMethodEntry(m as StorePaymentEntry)
200
+ )
201
+ return methods.find((m) => m.kind === "cod") ?? methods[0] ?? null
202
+ }
203
203
 
204
204
  /**
205
205
  * Snapshot the address-relevant subset of formData. Used to skip
@@ -250,7 +250,6 @@ export function useCheckoutOrchestration({
250
250
  countryCode = "",
251
251
  countries,
252
252
  paymentMethodFilter,
253
- codConfig,
254
253
  orderConfirmedPath: orderConfirmedPathProp,
255
254
  onOrderPlaced,
256
255
  resolveTrackingMetadata,
@@ -322,7 +321,7 @@ export function useCheckoutOrchestration({
322
321
  : true
323
322
  )
324
323
 
325
- // Barter regions have no embedded countries — the caller-supplied list
324
+ // Cartbase regions have no embedded countries — the caller-supplied list
326
325
  // (or the countryCode fallback) is the authority for both the select
327
326
  // options and the saved-address region filter.
328
327
  const regionCountries = useMemo(
@@ -500,7 +499,7 @@ export function useCheckoutOrchestration({
500
499
  lastSavedSnapshotRef.current = snapshot
501
500
 
502
501
  if (customer) {
503
- // Sync the profile best-effort. Barter has first-class company
502
+ // Sync the profile best-effort. Cartbase has first-class company
504
503
  // fields on the customer (company_name / company_eik —
505
504
  // api/customers.ts), so the invoice data also lands on the
506
505
  // profile — an upgrade over the @1click cart-metadata-only
@@ -702,7 +701,7 @@ export function useCheckoutOrchestration({
702
701
  [paymentMethodFilter, availablePaymentMethods, selectedShippingOption]
703
702
  )
704
703
 
705
- // Calculated-rate price resolution. Barter serves flat prices today
704
+ // Calculated-rate price resolution. Cartbase serves flat prices today
706
705
  // (`price_type: "flat"` always — api/checkout.ts), so this effect
707
706
  // normally resolves immediately; the calculated branch is kept for
708
707
  // forward-compat with calculated-rate carriers (the /calculate route
@@ -794,20 +793,20 @@ export function useCheckoutOrchestration({
794
793
  // ── Payment ─────────────────────────────────────────────────────────
795
794
  const [paymentError, setPaymentError] = useState<string | null>(null)
796
795
 
797
- const hasCard = !!effectiveAvailablePaymentMethods?.some((m) =>
798
- isStripeLike(m.id)
799
- )
800
- const hasCod = !!effectiveAvailablePaymentMethods?.some(
801
- (m) => isCodLikeId(m.id) || m.id === "pp_manual"
796
+ const hasCard = !!effectiveAvailablePaymentMethods?.some(
797
+ (m) => "id" in m && isStripeLike(m.id)
802
798
  )
803
- const cardId = effectiveAvailablePaymentMethods?.find((m) =>
804
- isStripeLike(m.id)
799
+ const cardId = (
800
+ effectiveAvailablePaymentMethods?.find(
801
+ (m) => "id" in m && isStripeLike(m.id)
802
+ ) as { id: string } | undefined
805
803
  )?.id
806
- // Prefer the true COD provider; fall back to pp_manual so single-
807
- // provider stores still get an offline tab.
808
- const codId =
809
- effectiveAvailablePaymentMethods?.find((m) => isCodLikeId(m.id))?.id ??
810
- effectiveAvailablePaymentMethods?.find((m) => m.id === "pp_manual")?.id
804
+ // The offline tab = a merchant METHOD (the COD switch, else the first
805
+ // manual method) sessions initiate by payment_method_id, provider NULL.
806
+ const offlineMethod = findOfflineMethod(effectiveAvailablePaymentMethods)
807
+ const hasCod = !!offlineMethod
808
+ const codMethodId = offlineMethod?.payment_method_id
809
+ const offlineIsCodKind = offlineMethod?.kind === "cod"
811
810
 
812
811
  // Default tab: card when available, else COD. The eager-session model
813
812
  // used to seed from the cart's pending session provider; in the
@@ -816,14 +815,15 @@ export function useCheckoutOrchestration({
816
815
  hasCard ? "card" : "cod"
817
816
  )
818
817
 
819
- // Optimistic COD-fee state. Painted instantly on tab toggle so the
820
- // totals row shows the predicted fee BEFORE the server applies the
821
- // native fee (barter: at prepare, when the pp_cod session is minted).
818
+ // Optimistic method-fee state. Painted instantly on tab toggle so the
819
+ // totals row shows the predicted fee BEFORE the server applies it (at
820
+ // prepare, when the method session is minted). Any method may carry a
821
+ // fee — the prediction reads the selected entry's own fee_amount.
822
822
  // Three values:
823
823
  // - null → no prediction; render whatever the cart says
824
- // - 0 → predict no fee (toggling away from COD)
825
- // - positive → predict the fee at this amount (toggling to COD)
826
- const [optimisticCodFee, setOptimisticCodFee] = useState<number | null>(
824
+ // - 0 → predict no fee (toggling to a fee-less tender)
825
+ // - positive → predict the fee at this amount
826
+ const [optimisticMethodFee, setOptimisticMethodFee] = useState<number | null>(
827
827
  null
828
828
  )
829
829
 
@@ -840,30 +840,28 @@ export function useCheckoutOrchestration({
840
840
  setPaymentTab(tab)
841
841
  setPaymentError(null)
842
842
 
843
- // Optimistic COD-fee prediction so the totals row updates instantly.
844
- // Currency must match the configured fee currency or the backend
845
- // skips the fee at apply time, so we mirror that gate here. The
846
- // prediction ONLY applies when the offline tab is the true pp_cod
847
- // provider — pp_manual carries no fee.
848
- if (codConfig) {
849
- const codCurrencyMatches =
850
- codConfig.fee_currency.toLowerCase() ===
851
- (cart.currency_code || "").toLowerCase()
852
- if (tab === "cod" && codCurrencyMatches && codId === "pp_cod") {
853
- setOptimisticCodFee(codConfig.fee_amount)
854
- } else {
855
- setOptimisticCodFee(0)
856
- }
843
+ // Optimistic fee prediction so the totals row updates instantly
844
+ // the selected METHOD entry carries its own fee; processors charge
845
+ // none. Mirrors the server exactly (resolveMethodFee reads the same
846
+ // row the listing serialized).
847
+ if (offlineMethod) {
848
+ const fee = Number(offlineMethod.fee_amount ?? 0)
849
+ setOptimisticMethodFee(tab === "cod" && fee > 0 ? fee : 0)
857
850
  }
858
851
 
859
- const provider = tab === "card" ? cardId : codId
860
- if (provider) {
861
- void syncPaymentAmountApi(client, cart.id, {
862
- provider_id: provider,
863
- }).catch(() => {})
852
+ const tender =
853
+ tab === "card"
854
+ ? cardId
855
+ ? { provider_id: cardId }
856
+ : null
857
+ : codMethodId
858
+ ? { payment_method_id: codMethodId }
859
+ : null
860
+ if (tender) {
861
+ void syncPaymentAmountApi(client, cart.id, tender).catch(() => {})
864
862
  }
865
863
  },
866
- [cart.currency_code, cart.id, client, codConfig, cardId, codId]
864
+ [cart.id, client, cardId, codMethodId, offlineMethod]
867
865
  )
868
866
 
869
867
  // Shipping selection is client state only. No addShippingMethod call,
@@ -1088,26 +1086,26 @@ export function useCheckoutOrchestration({
1088
1086
  // start = cart.total (covers subtotal + tax + any already-written
1089
1087
  // shipping/fee)
1090
1088
  // if optimisticShippingCost set: replace cart.shipping_total with it
1091
- // if optimisticCodFee set: replace cart.cod_fee_total with it
1092
- // (barter: the COD fee is CART-LEVEL decoration folded into total,
1093
- // not a fee line item)
1089
+ // if optimisticMethodFee set: replace cart.payment_method_fee_total
1090
+ // with it (the fee is CART-LEVEL decoration folded into total,
1091
+ // not a fee line item)
1094
1092
  // When nothing is optimistic, this collapses to cart.total.
1095
1093
  const optimisticTotal = useMemo(() => {
1096
- const realCodFeeAmount = cart?.cod_fee_total ?? 0
1094
+ const realMethodFeeAmount = cart?.payment_method_fee_total ?? 0
1097
1095
  let total = cart?.total ?? 0
1098
1096
  if (optimisticShippingCost !== null) {
1099
1097
  total = total - (cart?.shipping_total ?? 0) + optimisticShippingCost
1100
1098
  }
1101
- if (optimisticCodFee !== null && optimisticCodFee !== undefined) {
1102
- total = total - realCodFeeAmount + optimisticCodFee
1099
+ if (optimisticMethodFee !== null && optimisticMethodFee !== undefined) {
1100
+ total = total - realMethodFeeAmount + optimisticMethodFee
1103
1101
  }
1104
1102
  return total
1105
1103
  }, [
1106
1104
  cart?.total,
1107
1105
  cart?.shipping_total,
1108
- cart?.cod_fee_total,
1106
+ cart?.payment_method_fee_total,
1109
1107
  optimisticShippingCost,
1110
- optimisticCodFee,
1108
+ optimisticMethodFee,
1111
1109
  ])
1112
1110
 
1113
1111
  // ── Optimistic total in cents (for Stripe Elements deferred-intent) ─
@@ -1157,9 +1155,18 @@ export function useCheckoutOrchestration({
1157
1155
  if (!shippingMethodId) {
1158
1156
  throw new Error("No shipping method selected")
1159
1157
  }
1160
- const paymentProvider = paymentTab === "card" ? cardId : codId
1161
- if (!paymentProvider) {
1162
- throw new Error("No payment provider available")
1158
+ // The tender: card tab the processor; offline tab → the merchant
1159
+ // method (provider-less session, pp_* kill).
1160
+ const tender =
1161
+ paymentTab === "card"
1162
+ ? cardId
1163
+ ? { payment_provider: cardId }
1164
+ : null
1165
+ : codMethodId
1166
+ ? { payment_method_id: codMethodId }
1167
+ : null
1168
+ if (!tender) {
1169
+ throw new Error("No payment method available")
1163
1170
  }
1164
1171
 
1165
1172
  return {
@@ -1175,7 +1182,7 @@ export function useCheckoutOrchestration({
1175
1182
  },
1176
1183
  shipping_method_id: shippingMethodId,
1177
1184
  carrier_metadata: carrierMetadata,
1178
- payment_provider: paymentProvider,
1185
+ ...tender,
1179
1186
  }
1180
1187
  }, [
1181
1188
  formData,
@@ -1184,7 +1191,7 @@ export function useCheckoutOrchestration({
1184
1191
  selectedBoxnowLocker,
1185
1192
  paymentTab,
1186
1193
  cardId,
1187
- codId,
1194
+ codMethodId,
1188
1195
  ])
1189
1196
 
1190
1197
  // ── Buy click ───────────────────────────────────────────────────────
@@ -1229,7 +1236,7 @@ export function useCheckoutOrchestration({
1229
1236
  cart_id: cart.id,
1230
1237
  paymentTab,
1231
1238
  cardId,
1232
- codId,
1239
+ codMethodId,
1233
1240
  selectedShippingMethod,
1234
1241
  selectedEcontOffice: selectedEcontOffice
1235
1242
  ? { code: selectedEcontOffice.code, name: selectedEcontOffice.name }
@@ -1268,7 +1275,8 @@ export function useCheckoutOrchestration({
1268
1275
  // eslint-disable-next-line no-console
1269
1276
  console.log("[buy-click] PAYLOAD →", payload)
1270
1277
  logError?.("other", "buy_click_payload", {
1271
- provider_id: payload.payment_provider,
1278
+ provider_id: payload.payment_provider ?? null,
1279
+ payment_method_id: payload.payment_method_id ?? null,
1272
1280
  shipping_method_id: payload.shipping_method_id,
1273
1281
  carrier_metadata_keys: Object.keys(payload.carrier_metadata ?? {}),
1274
1282
  country_code: payload.shipping_address.country_code,
@@ -1287,7 +1295,7 @@ export function useCheckoutOrchestration({
1287
1295
  provider_id: prep.provider_id,
1288
1296
  })
1289
1297
 
1290
- // ── Zero-remainder gift path (barter gift-card tender) ──────────
1298
+ // ── Zero-remainder gift path (Cartbase gift-card tender) ──────────
1291
1299
  // When applied gift cards cover the whole total, prepare skips the
1292
1300
  // provider session: client_secret AND provider_id come back null
1293
1301
  // and the cart completes on the internal pp_giftcard session alone
@@ -1420,7 +1428,7 @@ export function useCheckoutOrchestration({
1420
1428
  cart.id,
1421
1429
  paymentTab,
1422
1430
  cardId,
1423
- codId,
1431
+ codMethodId,
1424
1432
  selectedShippingMethod,
1425
1433
  selectedEcontOffice,
1426
1434
  selectedBoxnowLocker,
@@ -1478,12 +1486,14 @@ export function useCheckoutOrchestration({
1478
1486
  hasCard,
1479
1487
  hasCod,
1480
1488
  cardId,
1481
- codId,
1489
+ /** The offline tab's method (the COD switch or a manual method). */
1490
+ codMethodId,
1491
+ offlineMethod,
1482
1492
  paymentError,
1483
1493
  setPaymentError,
1484
1494
  deliveryReady,
1485
- optimisticCodFee,
1486
- setOptimisticCodFee,
1495
+ optimisticMethodFee,
1496
+ setOptimisticMethodFee,
1487
1497
  handlePaymentTab,
1488
1498
  handlePaymentElementChange,
1489
1499
 
@@ -9,7 +9,7 @@ import { useCartDrawer } from "../cart-drawer/context"
9
9
  * Header cart button (badge + opens the cart drawer). Ported from
10
10
  * `@1click/ui/src/common/cart-button-client.tsx` (v2.3.1); seams:
11
11
  *
12
- * - `cart` is the barter decorated cart (`api/carts` `Cart`).
12
+ * - `cart` is the Cartbase decorated cart (`api/carts` `Cart`).
13
13
  * - Badge count uses `productItemCount` (fee-line-aware) instead of the
14
14
  * source's raw `reduce` — keeps the badge consistent with the cart
15
15
  * drawer's own count, which already excludes backend-injected fee
@@ -5,7 +5,7 @@ import { CartButtonClient } from "./cart-button-client"
5
5
  /**
6
6
  * Server wrapper for the header cart button. Ported from
7
7
  * `@1click/ui/src/common/cart-button.tsx` (v2.3.1); data seam: the source
8
- * called the cookie-reading `retrieveCart()` server action — barter takes
8
+ * called the cookie-reading `retrieveCart()` server action — Cartbase takes
9
9
  * the `StorefrontClient` + the app-persisted cart id explicitly (the app
10
10
  * owns the cart-id cookie, see docs/storefront/carts.md) and calls
11
11
  * `api/carts.retrieveCart`. Any fetch failure (stale id, 404) degrades to
@@ -9,12 +9,12 @@ import type { StoreRegion } from "../api/regions"
9
9
  * change forced by the SDK (SDK wins over the source):
10
10
  *
11
11
  * - Medusa regions embedded a `countries[]` array and the select listed
12
- * countries; barter regions carry NO countries on the store surface
12
+ * countries; Cartbase regions carry NO countries on the store surface
13
13
  * (divergence documented in `api/regions` — tax scoping is by
14
14
  * `tax_regions` server-side). The select therefore lists the REGIONS
15
15
  * themselves (`regions.listRegions`), valued by region id.
16
16
  * - The source called the `updateRegion(countryCode, path)` server
17
- * action bound to URL `[countryCode]` routing; barter region
17
+ * action bound to URL `[countryCode]` routing; Cartbase region
18
18
  * persistence is app-owned (usually `carts.updateCart(client, cartId,
19
19
  * {region_id})` + a cookie) — so the change surfaces via `onChange`.
20
20
  */
@@ -10,7 +10,7 @@ import { cn } from "../lib/utils"
10
10
  * Cart-line delete button with spinner. Ported from
11
11
  * `@1click/ui/src/common/delete-button.tsx` (v2.3.1); data seam: the
12
12
  * source called the cookie-scoped `deleteLineItem(id)` server action —
13
- * barter calls `api/carts.deleteLineItem(client, cartId, lineItemId)`
13
+ * Cartbase calls `api/carts.deleteLineItem(client, cartId, lineItemId)`
14
14
  * (idempotent server-side) and hands the refreshed cart to `onDeleted`
15
15
  * so the app can update its state / `router.refresh()`. The spinner
16
16
  * resets on failure exactly like the source's `.catch`.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @cartbase/storefront/common — shared storefront chrome. Ported from
3
3
  * `@1click/ui/src/common` (v2.3.1) + `LocalizedLink` (from the
4
- * mindpages-storefront production component); barter data seams per
4
+ * mindpages-storefront production component); Cartbase data seams per
5
5
  * module JSDoc.
6
6
  */
7
7
  export { LocalizedLink, type LocalizedLinkProps } from "./localized-link"
@@ -7,13 +7,13 @@ import { useTransition } from "react"
7
7
  * `@1click/ui/src/common/language-select.tsx` (v2.3.1); data seam:
8
8
  *
9
9
  * - The locale list feeds from `api/regions.listLocales(client)` — an
10
- * array of bare codes, store default first (barter has no `{code,
10
+ * array of bare codes, store default first (Cartbase has no `{code,
11
11
  * name}` locale objects on the store surface; SDK wins). Display
12
12
  * names resolve via `localeDisplayName()` below (native-language
13
13
  * autonym through `Intl.DisplayNames`), overridable per store with
14
14
  * the `labels` map.
15
15
  * - The source awaited an `updateLocale(code)` server action then
16
- * `router.refresh()`; barter locale persistence is app-owned (the
16
+ * `router.refresh()`; Cartbase locale persistence is app-owned (the
17
17
  * cookie read by `StorefrontClient.getLocale`) — the async `onChange`
18
18
  * callback carries it, and the `useTransition` pending-disable UX is
19
19
  * preserved around it.
@@ -10,7 +10,7 @@ import type { ReactNode } from "react"
10
10
  * `LocalizedClientLink` (src/modules/common/components/localized-client-link)
11
11
  * — the component `@1click/ui` consumers used for the same job.
12
12
  *
13
- * barter seam: barter storefronts default to COOKIE-based locale
13
+ * Cartbase seam: Cartbase storefronts default to COOKIE-based locale
14
14
  * (`StorefrontClient.getLocale`, see docs/storefront/BUILD-A-STOREFRONT.md)
15
15
  * with no mandatory URL segment — so the prefix is applied ONLY when the
16
16
  * route actually has a `[countryCode]` (or custom `paramName`) dynamic
@@ -4,7 +4,7 @@
4
4
  * Ported from `@1click/ui/src/lib/cart-helpers.ts` (v2.3.1) with its
5
5
  * production reasoning intact; the `@medusajs/types` generic constraint is
6
6
  * replaced by the structural `LineLike` shape so this module stays decoupled
7
- * from any SDK layer (barter cart/order line items are structurally
7
+ * from any SDK layer (Cartbase cart/order line items are structurally
8
8
  * compatible: they carry `metadata`, `total`, `quantity`).
9
9
  *
10
10
  * Why this exists. There is no native "fee" / "surcharge" primitive in the
@@ -29,7 +29,7 @@
29
29
  * `findFeeLine`. Customer SHOULD see the fee in checkout / on the
30
30
  * order; they should NOT see it in the cart drawer pre-checkout.
31
31
  *
32
- * Money note (barter): amounts are decimal EUR major units everywhere in
32
+ * Money note (Cartbase): amounts are decimal EUR major units everywhere in
33
33
  * the store API — the arithmetic below is unit-agnostic and unchanged.
34
34
  */
35
35
 
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Cookie names the store API + @cartbase/storefront agree on
3
+ * (platform-fingerprints card, fingerprint #4 — cookie names are a
4
+ * primary Wappalyzer/BuiltWith detection signal).
5
+ *
6
+ * The app still OWNS *setting* the cart cookie — carts.md is explicit that
7
+ * "the SDK never persists the cart" — this module is the single source of
8
+ * the NAME so every consumer emits the same wire fingerprint instead of
9
+ * inventing its own prefix (the reference app previously hardcoded
10
+ * `_barter_cart_id` locally in `examples/storefront/src/lib/config.ts`).
11
+ *
12
+ * Renamed from the `_barter_*` prefix (2026-08-04, docs/features/
13
+ * cartbase-rename.md). `readCartCookie` accepts the legacy name for the
14
+ * transition window; every write uses the new name only.
15
+ */
16
+
17
+ /** Current cart-id cookie name. */
18
+ export const CART_COOKIE = "_cartbase_cart"
19
+
20
+ /** @deprecated Legacy name — read-only compat, never written. */
21
+ export const LEGACY_CART_COOKIE = "_barter_cart_id"
22
+
23
+ /**
24
+ * Reserved for a future cookie-backed customer session. Auth today is pure
25
+ * Bearer-JWT, persisted by the consuming app (auth.md — "the Cartbase API
26
+ * does not proxy token refresh"); nothing sets this cookie yet. Defined now
27
+ * so a future cookie-based session launches with the fingerprint-correct
28
+ * name from day one rather than inventing a new prefix later.
29
+ */
30
+ export const SESSION_COOKIE = "_cartbase_session"
31
+
32
+ /**
33
+ * Read a cart id given a cookie-name → value lookup, preferring the
34
+ * current name and falling back to the legacy `_barter_cart_id` name so an
35
+ * existing visitor's cart survives the rename.
36
+ */
37
+ export function readCartCookie(
38
+ get: (name: string) => string | undefined
39
+ ): string | undefined {
40
+ return get(CART_COOKIE) ?? get(LEGACY_CART_COOKIE)
41
+ }
@@ -6,11 +6,11 @@ import { convertToLocale } from "./money"
6
6
  * `@1click/ui/src/lib/get-product-price.ts` (v2.3.1). The
7
7
  * `@medusajs/types` `HttpTypes.StoreProduct` parameter is replaced by the
8
8
  * structural shapes below so this module stays decoupled from the SDK
9
- * domain modules (barter's `variant.calculated_price` is the b2b-v1
9
+ * domain modules (Cartbase's `variant.calculated_price` is the b2b-v1
10
10
  * pricing context documented in `@cartbase/storefront/api/types`
11
11
  * `CalculatedPrice`, decimal EUR major units).
12
12
  *
13
- * Wire-shape note (code truth, `src/lib/products/pricing.ts`): barter serves
13
+ * Wire-shape note (code truth, `src/lib/products/pricing.ts`): Cartbase serves
14
14
  * `calculated_price.price_list_type` FLAT on the calculated_price object.
15
15
  * The Medusa wire shape ALSO nests a `calculated_price.calculated_price`
16
16
  * detail object carrying `price_list_type` — the original library read the
@@ -71,7 +71,7 @@ export const getPricesForVariant = (
71
71
  currency_code: cp.currency_code,
72
72
  }),
73
73
  currency_code: cp.currency_code,
74
- // Flat key (barter wire shape) first — an explicit null stays null;
74
+ // Flat key (Cartbase wire shape) first — an explicit null stays null;
75
75
  // the nested Medusa detail is the fallback only when the flat key is
76
76
  // absent entirely (the original @1click read path).
77
77
  price_type: