@webround/commerce 0.0.0-stage → 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 (138) hide show
  1. package/README.md +192 -2
  2. package/dist/account/account.d.ts +54 -0
  3. package/dist/account/account.js +40 -0
  4. package/dist/account/index.d.ts +3 -0
  5. package/dist/account/index.js +2 -0
  6. package/dist/addresses/api.d.ts +16 -0
  7. package/dist/addresses/api.js +13 -0
  8. package/dist/addresses/book.d.ts +40 -0
  9. package/dist/addresses/book.js +144 -0
  10. package/dist/addresses/index.d.ts +8 -0
  11. package/dist/addresses/index.js +5 -0
  12. package/dist/addresses/rules.d.ts +4 -0
  13. package/dist/addresses/rules.js +9 -0
  14. package/dist/addresses/types.d.ts +48 -0
  15. package/dist/addresses/types.js +6 -0
  16. package/dist/auth/api.d.ts +43 -0
  17. package/dist/auth/api.js +22 -0
  18. package/dist/auth/auth.d.ts +32 -0
  19. package/dist/auth/auth.js +23 -0
  20. package/dist/auth/cookies.d.ts +46 -0
  21. package/dist/auth/cookies.js +156 -0
  22. package/dist/auth/index.d.ts +14 -0
  23. package/dist/auth/index.js +8 -0
  24. package/dist/auth/renewal.d.ts +4 -0
  25. package/dist/auth/renewal.js +37 -0
  26. package/dist/auth/store.d.ts +70 -0
  27. package/dist/auth/store.js +284 -0
  28. package/dist/auth/types.d.ts +105 -0
  29. package/dist/auth/types.js +5 -0
  30. package/dist/auth/wake.d.ts +18 -0
  31. package/dist/auth/wake.js +23 -0
  32. package/dist/cache.d.ts +49 -0
  33. package/dist/cache.js +144 -0
  34. package/dist/canonical.d.ts +7 -0
  35. package/dist/canonical.js +32 -0
  36. package/dist/cart/api.d.ts +25 -0
  37. package/dist/cart/api.js +16 -0
  38. package/dist/cart/index.d.ts +10 -0
  39. package/dist/cart/index.js +6 -0
  40. package/dist/cart/lines.d.ts +15 -0
  41. package/dist/cart/lines.js +23 -0
  42. package/dist/cart/storage.d.ts +30 -0
  43. package/dist/cart/storage.js +82 -0
  44. package/dist/cart/store.d.ts +58 -0
  45. package/dist/cart/store.js +218 -0
  46. package/dist/cart/types.d.ts +194 -0
  47. package/dist/cart/types.js +11 -0
  48. package/dist/catalog/cached.d.ts +47 -0
  49. package/dist/catalog/cached.js +52 -0
  50. package/dist/catalog/catalog.d.ts +10 -0
  51. package/dist/catalog/catalog.js +7 -0
  52. package/dist/catalog/named.d.ts +3 -0
  53. package/dist/catalog/named.js +7 -0
  54. package/dist/catalog/products.d.ts +28 -0
  55. package/dist/catalog/products.js +12 -0
  56. package/dist/catalog/store-data.d.ts +13 -0
  57. package/dist/catalog/store-data.js +6 -0
  58. package/dist/catalog/types.d.ts +430 -0
  59. package/dist/catalog/types.js +4 -0
  60. package/dist/checkout/api.d.ts +14 -0
  61. package/dist/checkout/api.js +7 -0
  62. package/dist/checkout/errors.d.ts +23 -0
  63. package/dist/checkout/errors.js +18 -0
  64. package/dist/checkout/index.d.ts +10 -0
  65. package/dist/checkout/index.js +6 -0
  66. package/dist/checkout/return-urls.d.ts +7 -0
  67. package/dist/checkout/return-urls.js +11 -0
  68. package/dist/checkout/store.d.ts +98 -0
  69. package/dist/checkout/store.js +179 -0
  70. package/dist/checkout/types.d.ts +225 -0
  71. package/dist/checkout/types.js +28 -0
  72. package/dist/commerce.d.ts +55 -0
  73. package/dist/commerce.js +55 -0
  74. package/dist/customer-resources.d.ts +18 -0
  75. package/dist/customer-resources.js +40 -0
  76. package/dist/errors.d.ts +65 -0
  77. package/dist/errors.js +96 -0
  78. package/dist/http.d.ts +49 -0
  79. package/dist/http.js +94 -0
  80. package/dist/index.d.ts +32 -0
  81. package/dist/index.js +22 -0
  82. package/dist/orders/api.d.ts +16 -0
  83. package/dist/orders/api.js +13 -0
  84. package/dist/orders/index.d.ts +6 -0
  85. package/dist/orders/index.js +3 -0
  86. package/dist/orders/orders.d.ts +19 -0
  87. package/dist/orders/orders.js +18 -0
  88. package/dist/orders/types.d.ts +88 -0
  89. package/dist/orders/types.js +4 -0
  90. package/dist/resource.d.ts +19 -0
  91. package/dist/resource.js +15 -0
  92. package/dist/returns/api.d.ts +27 -0
  93. package/dist/returns/api.js +19 -0
  94. package/dist/returns/index.d.ts +6 -0
  95. package/dist/returns/index.js +3 -0
  96. package/dist/returns/returns.d.ts +30 -0
  97. package/dist/returns/returns.js +31 -0
  98. package/dist/returns/types.d.ts +99 -0
  99. package/dist/returns/types.js +2 -0
  100. package/dist/reviews/api.d.ts +19 -0
  101. package/dist/reviews/api.js +15 -0
  102. package/dist/reviews/index.d.ts +9 -0
  103. package/dist/reviews/index.js +5 -0
  104. package/dist/reviews/reviews.d.ts +23 -0
  105. package/dist/reviews/reviews.js +37 -0
  106. package/dist/reviews/rules.d.ts +8 -0
  107. package/dist/reviews/rules.js +7 -0
  108. package/dist/reviews/types.d.ts +54 -0
  109. package/dist/reviews/types.js +10 -0
  110. package/dist/shop/address-store.d.ts +14 -0
  111. package/dist/shop/address-store.js +28 -0
  112. package/dist/shop/address.d.ts +12 -0
  113. package/dist/shop/address.js +95 -0
  114. package/dist/shop/index.d.ts +16 -0
  115. package/dist/shop/index.js +9 -0
  116. package/dist/shop/question.d.ts +15 -0
  117. package/dist/shop/question.js +12 -0
  118. package/dist/shop/range.d.ts +7 -0
  119. package/dist/shop/range.js +38 -0
  120. package/dist/shop/sort.d.ts +19 -0
  121. package/dist/shop/sort.js +18 -0
  122. package/dist/shop/state.d.ts +47 -0
  123. package/dist/shop/state.js +51 -0
  124. package/dist/shop/store.d.ts +26 -0
  125. package/dist/shop/store.js +47 -0
  126. package/dist/shop/to-search.d.ts +14 -0
  127. package/dist/shop/to-search.js +53 -0
  128. package/dist/store-kit.d.ts +39 -0
  129. package/dist/store-kit.js +81 -0
  130. package/dist/wishlist/api.d.ts +12 -0
  131. package/dist/wishlist/api.js +10 -0
  132. package/dist/wishlist/index.d.ts +7 -0
  133. package/dist/wishlist/index.js +4 -0
  134. package/dist/wishlist/types.d.ts +30 -0
  135. package/dist/wishlist/types.js +4 -0
  136. package/dist/wishlist/wishlist.d.ts +45 -0
  137. package/dist/wishlist/wishlist.js +84 -0
  138. package/package.json +39 -5
@@ -0,0 +1,179 @@
1
+ import { canonicalJson } from '../canonical.js';
2
+ import { CommerceError, toCommerceError } from '../errors.js';
3
+ import { createStateHolder } from '../store-kit.js';
4
+ import { CHECKOUT_ERROR_CODES } from './types.js';
5
+ /** The checkout before anything is chosen (new objects each time: a state is never changed in place). */
6
+ const noChoices = () => ({
7
+ deliveryMethods: {},
8
+ shippingAddress: null,
9
+ billingAddress: null,
10
+ shippingMethodId: null,
11
+ promotionCodes: [],
12
+ additionalData: null,
13
+ email: '',
14
+ requestsInvoice: false,
15
+ invoiceVatCode: null,
16
+ invoiceRecipientCode: null,
17
+ });
18
+ export function createCheckoutStore(options) {
19
+ const { api, cart } = options;
20
+ /** What the preview asks for these choices and these lines: only what the store reads, and only what there is. */
21
+ function quoteBodyOf(choices, lines) {
22
+ const deliveryMethods = {};
23
+ for (const line of lines) {
24
+ const method = choices.deliveryMethods[line.variantId];
25
+ if (method !== undefined)
26
+ deliveryMethods[line.variantId] = method;
27
+ }
28
+ return {
29
+ langCode: options.langCode?.() ?? 'default',
30
+ cartItems: lines.map((l) => ({ variantId: l.variantId, priceId: l.priceId, quantity: l.quantity })),
31
+ deliveryMethods,
32
+ ...(choices.promotionCodes.length > 0 ? { promotionCodes: choices.promotionCodes } : {}),
33
+ ...(choices.shippingMethodId !== null ? { shippingMethodId: choices.shippingMethodId } : {}),
34
+ ...(choices.shippingAddress !== null ? { shippingAddress: choices.shippingAddress } : {}),
35
+ ...(choices.billingAddress !== null ? { billingAddress: choices.billingAddress } : {}),
36
+ ...(choices.additionalData !== null ? { additionalData: choices.additionalData } : {}),
37
+ };
38
+ }
39
+ // Who is asking is part of the question: the store prices a logged-in customer by what it knows of them (a VAT exemption, for one)
40
+ const keyOf = (body) => canonicalJson({ owner: cart.getState().owner, body });
41
+ const currentLines = () => cart.getState().lines;
42
+ const holder = createStateHolder({
43
+ ...noChoices(),
44
+ quote: null,
45
+ requestKey: keyOf(quoteBodyOf(noChoices(), currentLines())),
46
+ quotedKey: null,
47
+ isStale: false,
48
+ lineCount: currentLines().length,
49
+ quoting: false,
50
+ paying: false,
51
+ error: null,
52
+ });
53
+ /** Changes the state and, in the same change, what the preview would ask now and whether the one on show still answers it. */
54
+ function change(patch) {
55
+ const next = { ...holder.get(), ...patch };
56
+ const lines = currentLines();
57
+ const requestKey = keyOf(quoteBodyOf(next, lines));
58
+ holder.set({ ...patch, requestKey, lineCount: lines.length, isStale: next.quote !== null && next.quotedKey !== requestKey });
59
+ }
60
+ /** Everything chosen and the preview, forgotten; a preview on its way is cancelled. */
61
+ function reset() {
62
+ attempt++;
63
+ inflight?.abort();
64
+ inflight = null;
65
+ attemptedKey = null;
66
+ change({ ...noChoices(), quote: null, quotedKey: null, error: null, quoting: false });
67
+ }
68
+ // Which preview is the last one asked: an answer that is not the last is thrown away
69
+ let attempt = 0;
70
+ let inflight = null;
71
+ let stopFollowing = null;
72
+ // What the preview was last asked, by whoever asked: `ensureQuote` does not ask the same question twice, so any number of components can use it, and a refusal is not asked again by itself
73
+ let attemptedKey = null;
74
+ const superseded = () => new CommerceError({ kind: 'aborted', message: 'A newer preview was asked for.' });
75
+ const store = {
76
+ getState: holder.get,
77
+ subscribe: holder.subscribe,
78
+ start() {
79
+ if (stopFollowing)
80
+ return;
81
+ let known = canonicalJson({ owner: cart.getState().owner, lines: currentLines() });
82
+ stopFollowing = cart.subscribe(() => {
83
+ const now = canonicalJson({ owner: cart.getState().owner, lines: currentLines() });
84
+ if (now === known)
85
+ return;
86
+ known = now;
87
+ change({});
88
+ });
89
+ change({});
90
+ },
91
+ setDeliveryMethod: (variantId, method) => change({ deliveryMethods: { ...holder.get().deliveryMethods, [variantId]: method } }),
92
+ setShippingAddress: (address) => change({ shippingAddress: address }),
93
+ setBillingAddress: (address) => change({ billingAddress: address }),
94
+ selectShippingMethod: (id) => change({ shippingMethodId: id }),
95
+ setPromotionCodes: (codes) => change({ promotionCodes: [...codes] }),
96
+ setAdditionalData: (data) => change({ additionalData: data }),
97
+ setEmail: (email) => change({ email }),
98
+ setInvoice: (invoice) => change({ requestsInvoice: invoice.requested, invoiceVatCode: invoice.vatCode ?? null, invoiceRecipientCode: invoice.recipientCode ?? null }),
99
+ async quote(callOptions) {
100
+ const body = quoteBodyOf(holder.get(), currentLines());
101
+ const key = keyOf(body);
102
+ attemptedKey = key;
103
+ const mine = ++attempt;
104
+ inflight?.abort();
105
+ const controller = new AbortController();
106
+ inflight = controller;
107
+ // The caller's own way of giving up cancels this one too
108
+ const onCallerAbort = () => controller.abort();
109
+ callOptions?.signal?.addEventListener('abort', onCallerAbort, { once: true });
110
+ holder.set({ quoting: true });
111
+ try {
112
+ const answer = await api.quote({ ...body, cartTrackingId: options.trackingId() }, { signal: controller.signal });
113
+ if (mine !== attempt)
114
+ throw superseded();
115
+ change({ quote: answer, quotedKey: key, error: null });
116
+ return answer;
117
+ }
118
+ catch (error) {
119
+ const failure = toCommerceError(error);
120
+ // What a newer question made useless is not a failure of the checkout
121
+ if (mine === attempt && failure.kind !== 'aborted')
122
+ holder.set({ error: failure });
123
+ throw failure;
124
+ }
125
+ finally {
126
+ callOptions?.signal?.removeEventListener('abort', onCallerAbort);
127
+ if (mine === attempt) {
128
+ inflight = null;
129
+ holder.set({ quoting: false });
130
+ }
131
+ }
132
+ },
133
+ async ensureQuote() {
134
+ const { lineCount, requestKey } = holder.get();
135
+ if (lineCount === 0 || requestKey === attemptedKey)
136
+ return null;
137
+ return store.quote();
138
+ },
139
+ async pay(returnUrls) {
140
+ const state = holder.get();
141
+ // A line the last preview said is unavailable is never sent to the payment (the store would refuse it): what is paid is what can be bought. A page pays with a fresh preview; the store's own refusal
142
+ // (the last unit sold between the preview and the payment) is what is left, and the page asks for the preview again.
143
+ const unavailable = new Set(state.quote?.unavailable.map((l) => l.variantId) ?? []);
144
+ const all = currentLines();
145
+ const lines = all.filter((l) => !unavailable.has(l.variantId));
146
+ if (all.length > 0 && lines.length === 0) {
147
+ throw new CommerceError({ kind: 'http', status: 400, code: CHECKOUT_ERROR_CODES.nothingToPay, message: 'Nothing in the cart can be bought: every line is unavailable.' });
148
+ }
149
+ const invoice = state.requestsInvoice
150
+ ? { requestsInvoice: true, invoiceVatCode: state.invoiceVatCode ?? '', invoiceRecipientCode: state.invoiceRecipientCode ?? '' }
151
+ : { invoiceVatCode: state.invoiceVatCode, invoiceRecipientCode: state.invoiceRecipientCode };
152
+ const urls = returnUrls ?? options.returnUrls?.() ?? {};
153
+ // The payment needs a delivery method for every line: the customer's choice, and for a line they did not choose for, the one the preview gave it (its default)
154
+ const given = Object.fromEntries((state.quote?.items ?? []).flatMap((i) => (lines.some((l) => l.variantId === i.variantId) ? [[i.variantId, i.deliveryMethod]] : [])));
155
+ const input = { ...quoteBodyOf({ ...state, deliveryMethods: { ...given, ...state.deliveryMethods } }, lines), cartTrackingId: options.trackingId(), ...urls, ...invoice, email: state.email };
156
+ holder.set({ paying: true });
157
+ try {
158
+ return await api.pay(input);
159
+ }
160
+ catch (error) {
161
+ throw toCommerceError(error);
162
+ }
163
+ finally {
164
+ holder.set({ paying: false });
165
+ }
166
+ },
167
+ reset,
168
+ async complete() {
169
+ await options.clearCart();
170
+ reset();
171
+ },
172
+ dispose() {
173
+ stopFollowing?.();
174
+ stopFollowing = null;
175
+ },
176
+ };
177
+ return store;
178
+ }
179
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1,225 @@
1
+ import type { CartLineInput } from '../cart/types.js';
2
+ import type { AddressInput } from '../addresses/types.js';
3
+ /** How a line reaches the customer: they pick it up (`pickup`), it is shipped (`shipping`), or there is nothing to deliver (`none`, a digital product). Chosen for each VARIANT in the cart. */
4
+ export declare const DELIVERY_METHODS: readonly ["pickup", "shipping", "none"];
5
+ export type DeliveryMethod = (typeof DELIVERY_METHODS)[number];
6
+ /**
7
+ * An address for a checkout: what the checkout takes for `shippingAddress` and `billingAddress`, in the same shape everywhere (here, in the checkout state, in the store). It is an address of the address book without its
8
+ * id and without the "default" flags. `fullName` is at least 2 characters, `addressLine1` 3, `postalCode` 3, `countryCode` the two letters of the country (`IT`): the store checks it, not this package.
9
+ */
10
+ export type CheckoutAddress = Omit<AddressInput, 'isDefaultShipping' | 'isDefaultBilling'>;
11
+ /** What the preview (`quote`) asks. Nothing in it is a total: the store works out every figure. */
12
+ export interface QuoteInput {
13
+ /** The language the store answers in (`default` for the store's own). */
14
+ langCode: string;
15
+ /** The lines of the cart: ONE for each variant (a variant twice is refused), 1 to 10000 each. */
16
+ cartItems: CartLineInput[];
17
+ /** How each variant of the cart reaches the customer, by the variant's id. One for each variant. */
18
+ deliveryMethods: Record<string, DeliveryMethod>;
19
+ /** The codes the customer typed (up to 10). */
20
+ promotionCodes?: string[];
21
+ /** The shipping method the customer chose, from `Quote.shipping.options`; absent = not chosen yet. */
22
+ shippingMethodId?: string;
23
+ shippingAddress?: CheckoutAddress;
24
+ billingAddress?: CheckoutAddress;
25
+ requestsInvoice?: boolean;
26
+ /** Webround Commerce's own id of this cart (the same for a guest and for the customer they become). */
27
+ cartTrackingId?: string;
28
+ /** What the site wants kept with the order: free-form, the store's hooks may add to it. */
29
+ additionalData?: Record<string, unknown> | null;
30
+ }
31
+ /** Where the customer comes back to after paying or giving up. Both or neither: the store refuses one alone, and their origins must be the store's, an allowed one or localhost. */
32
+ export type ReturnUrls = {
33
+ successUrl: string;
34
+ cancelUrl: string;
35
+ } | {
36
+ successUrl?: undefined;
37
+ cancelUrl?: undefined;
38
+ };
39
+ /** An invoice: when one is asked for, the store needs the VAT code (the recipient code is optional); without an invoice both are optional. */
40
+ export type InvoiceInput = {
41
+ requestsInvoice: true;
42
+ invoiceVatCode: string;
43
+ invoiceRecipientCode?: string;
44
+ } | {
45
+ requestsInvoice?: false;
46
+ invoiceVatCode?: string | null;
47
+ invoiceRecipientCode?: string | null;
48
+ };
49
+ /** What starting a payment asks: what the preview asks, who pays (`email`: for a guest it is who the order is for), where they come back to, and the invoice. */
50
+ export type PayInput = Omit<QuoteInput, 'requestsInvoice'> & ReturnUrls & InvoiceInput & {
51
+ email: string;
52
+ };
53
+ /** What starting a payment gives: the page of the payment provider, to send the customer to, and the order that was made (it is paid when the provider says so, a moment later). */
54
+ export interface PayAnswer {
55
+ url: string;
56
+ /** The order the payment is for. */
57
+ orderId: string;
58
+ /** The order's number, the one the customer sees (`null` if the store does not number its orders). */
59
+ reference: number | null;
60
+ }
61
+ /**
62
+ * What describes a line (the cart page, the checkout, the confirmation show it) and is not part of the price: the preview is the ONE call that prices a cart AND describes it, so a site asks `/cart/total` or the catalog for
63
+ * nothing else. The names are in the language asked; `options` are the variant's, as the cart has always given them.
64
+ */
65
+ export interface QuoteLinePresentation {
66
+ variantName: string;
67
+ productName: string | null;
68
+ /** The page of the variant is `/product/<variantSlug>`. */
69
+ variantSlug: string;
70
+ skuCode: string | null;
71
+ coverUrl: string | null;
72
+ options: {
73
+ optionName: string;
74
+ tagValue: string;
75
+ tagValueAlias: string | null;
76
+ }[];
77
+ /** How the product can reach the customer: what the line can choose from (`deliveryMethods` of the request). */
78
+ allowedDeliveryMethods: DeliveryMethod[];
79
+ /** Units there are now (not what the line asks). A line that asks for more is in `Quote.unavailable`, unless `allowBackorder`. */
80
+ availableStock: number;
81
+ allowBackorder: boolean;
82
+ /** The most the variant sells in one order. */
83
+ maxOrderQuantity: number;
84
+ }
85
+ /** A line of the preview, priced: unit and line amounts (each unit rounded first), net, VAT and gross. A line a hook added has `origin: { hookId }`. */
86
+ export interface QuoteLine extends QuoteLinePresentation {
87
+ variantId: string;
88
+ productId: string;
89
+ priceId: string;
90
+ quantity: number;
91
+ deliveryMethod: DeliveryMethod;
92
+ vatRate: number;
93
+ unitNet: number;
94
+ unitVat: number;
95
+ unitGross: number;
96
+ totalNet: number;
97
+ totalVat: number;
98
+ totalGross: number;
99
+ origin: 'cart' | {
100
+ hookId: string;
101
+ };
102
+ }
103
+ export type QuoteDiscountSource = {
104
+ type: 'promotion';
105
+ id: string;
106
+ code?: string;
107
+ } | {
108
+ type: 'hook';
109
+ hookId: string;
110
+ };
111
+ export type QuoteDiscountTarget = 'order' | {
112
+ variantIds: string[];
113
+ } | {
114
+ shippingMethodId: string;
115
+ };
116
+ /**
117
+ * A line of the preview that cannot be bought because there is not enough of it (no backorder): it is NOT an error of the preview. It is priced like the others but without discounts, and it is left out of `items`, `totals`,
118
+ * the promotions, the shipping and the hooks. The whole line is unavailable even if some units exist (5 asked, 3 there): `availableStock` says how many there are, `quantity` is what was asked. The payment never takes it
119
+ * (`CheckoutStore.pay` does not send it, and the store answers 400 if it ever gets one).
120
+ */
121
+ export interface QuoteUnavailableLine extends QuoteLinePresentation {
122
+ variantId: string;
123
+ productId: string;
124
+ priceId: string;
125
+ /** What was asked (`availableStock` is what there is now: 0 for none). */
126
+ quantity: number;
127
+ deliveryMethod: DeliveryMethod;
128
+ vatRate: number;
129
+ unitNet: number;
130
+ unitVat: number;
131
+ unitGross: number;
132
+ totalNet: number;
133
+ totalVat: number;
134
+ totalGross: number;
135
+ }
136
+ /** A discount that was applied, on the net, already worked out (never below zero). */
137
+ export interface QuoteDiscount {
138
+ source: QuoteDiscountSource;
139
+ label?: string;
140
+ class: 'product' | 'order' | 'shipping';
141
+ target: QuoteDiscountTarget;
142
+ amount: number;
143
+ }
144
+ /** A shipping option the customer can choose, priced; `grandGrossIfSelected` is what the whole order would cost with it. */
145
+ export interface QuoteShippingOption {
146
+ id: string;
147
+ label: string;
148
+ net: number;
149
+ vatRate: number;
150
+ vat: number;
151
+ gross: number;
152
+ etaMin?: number | null;
153
+ etaMax?: number | null;
154
+ grandGrossIfSelected: number;
155
+ }
156
+ export interface QuoteTotals {
157
+ net: number;
158
+ vat: number;
159
+ gross: number;
160
+ /** What the discounts took off: the lines' and the chosen shipping's (without a chosen one, only the lines'). */
161
+ discountTotal: number;
162
+ shippingNet: number | null;
163
+ shippingVat: number | null;
164
+ shippingGross: number | null;
165
+ grandNet: number;
166
+ grandVat: number;
167
+ /** What the customer pays: the lines and the chosen shipping. This is the figure to show, and the one the payment is for. */
168
+ grandGross: number;
169
+ /** There is something to ship and no shipping method is chosen yet: the total is without shipping. */
170
+ shippingPending: boolean;
171
+ }
172
+ /** Why a code that is valid did nothing: no line could take the discount, or it does not apply to this cart (a fixed amount without the cart's currency). Not an error. */
173
+ export declare const PROMOTION_REJECTION_REASONS: readonly ["NO_ELIGIBLE_ITEMS", "NOT_APPLICABLE"];
174
+ export type PromotionRejectionReason = (typeof PROMOTION_REJECTION_REASONS)[number];
175
+ export interface QuotePromotions {
176
+ /** The codes that discounted something. */
177
+ applied: string[];
178
+ /** The valid codes that discounted nothing, and why. */
179
+ rejected: {
180
+ code: string;
181
+ reason: PromotionRejectionReason;
182
+ }[];
183
+ }
184
+ /** What the preview answers: what the customer would pay for this cart, with the store's rules and hooks applied. The preview changes nothing in the store. */
185
+ export interface Quote {
186
+ currencyCode: string;
187
+ /** How the cart is paid: `once`, or the cadence of a subscription (`monthly`, `quarterly`…). A cart has only one: a line with another is refused. */
188
+ cadence: string;
189
+ /** The lines that can be bought: the ones `totals` add up. */
190
+ items: QuoteLine[];
191
+ /** The lines with not enough stock: priced, but out of `items` and `totals`. */
192
+ unavailable: QuoteUnavailableLine[];
193
+ discounts: QuoteDiscount[];
194
+ shipping: {
195
+ options: QuoteShippingOption[];
196
+ selectedId: string | null;
197
+ };
198
+ totals: QuoteTotals;
199
+ promotions: QuotePromotions;
200
+ additionalData: Record<string, unknown>;
201
+ }
202
+ /** The `code` the checkout gives to what it refuses on purpose (`CommerceError.code`), for the interface to translate. */
203
+ export declare const CHECKOUT_ERROR_CODES: {
204
+ /** A hook of the store stopped the checkout (`meta`: `{ hookId, code, target }`: the hook's own reason and what it is about). */
205
+ readonly blocked: "CHECKOUT_BLOCKED";
206
+ /** A hook of the store that must not fail did (`meta`: `{ hookId, reason }`). */
207
+ readonly hookFailed: "CHECKOUT_HOOK_FAILED";
208
+ /** The chosen shipping method is not available for this order (`meta`: `{ shippingMethodId }`). */
209
+ readonly shippingMethodUnavailable: "SHIPPING_METHOD_UNAVAILABLE";
210
+ /** Raised here, before asking the store: every line of the cart is one the last preview said is unavailable, so there is nothing to pay for. */
211
+ readonly nothingToPay: "NOTHING_TO_PAY";
212
+ /** There is not enough of a variant (`meta`: `{ variantId, requested, available }`). The payment is the one that refuses it: the preview sets such a line aside. */
213
+ readonly insufficientStock: "INSUFFICIENT_STOCK";
214
+ /** A subscription (a recurring price) is for a customer who is signed in: a guest cannot buy it. Both the preview and the payment refuse it. */
215
+ readonly loginRequiredForSubscription: "LOGIN_REQUIRED_FOR_SUBSCRIPTION";
216
+ /** A subscription is for a customer whose email is verified. */
217
+ readonly emailNotVerifiedForSubscription: "EMAIL_NOT_VERIFIED_FOR_SUBSCRIPTION";
218
+ /** More of a variant than one order can have (`meta`: `{ variantId, requested, max }`). */
219
+ readonly maxQuantityExceeded: "MAX_QUANTITY_EXCEEDED";
220
+ };
221
+ /** What a hook that stopped the checkout points at: the cart as a whole, the address, or a variant. */
222
+ export type CheckoutBlockTarget = 'cart' | 'address' | {
223
+ variantId: string;
224
+ };
225
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,28 @@
1
+ // What the checkout's calls (`/v2/checkout/quote` and `/v2/checkout/stripe`) take and give, as the checkout API says it: the shapes are its own, nothing is renamed, reshaped or completed. Typed as strictly as the API is
2
+ // (a delivery method is one of three words, an invoice's codes come with the invoice), because a mistake that the compiler can see is one the store never has to refuse. The store's `quote` and `pay` are
3
+ // defined by the checkout service of the Commerce Stripe Processor; if it changes, this changes with it.
4
+ /** How a line reaches the customer: they pick it up (`pickup`), it is shipped (`shipping`), or there is nothing to deliver (`none`, a digital product). Chosen for each VARIANT in the cart. */
5
+ export const DELIVERY_METHODS = ['pickup', 'shipping', 'none'];
6
+ /** Why a code that is valid did nothing: no line could take the discount, or it does not apply to this cart (a fixed amount without the cart's currency). Not an error. */
7
+ export const PROMOTION_REJECTION_REASONS = ['NO_ELIGIBLE_ITEMS', 'NOT_APPLICABLE'];
8
+ // ── what the checkout refuses with a code of its own ─────────
9
+ /** The `code` the checkout gives to what it refuses on purpose (`CommerceError.code`), for the interface to translate. */
10
+ export const CHECKOUT_ERROR_CODES = {
11
+ /** A hook of the store stopped the checkout (`meta`: `{ hookId, code, target }`: the hook's own reason and what it is about). */
12
+ blocked: 'CHECKOUT_BLOCKED',
13
+ /** A hook of the store that must not fail did (`meta`: `{ hookId, reason }`). */
14
+ hookFailed: 'CHECKOUT_HOOK_FAILED',
15
+ /** The chosen shipping method is not available for this order (`meta`: `{ shippingMethodId }`). */
16
+ shippingMethodUnavailable: 'SHIPPING_METHOD_UNAVAILABLE',
17
+ /** Raised here, before asking the store: every line of the cart is one the last preview said is unavailable, so there is nothing to pay for. */
18
+ nothingToPay: 'NOTHING_TO_PAY',
19
+ /** There is not enough of a variant (`meta`: `{ variantId, requested, available }`). The payment is the one that refuses it: the preview sets such a line aside. */
20
+ insufficientStock: 'INSUFFICIENT_STOCK',
21
+ /** A subscription (a recurring price) is for a customer who is signed in: a guest cannot buy it. Both the preview and the payment refuse it. */
22
+ loginRequiredForSubscription: 'LOGIN_REQUIRED_FOR_SUBSCRIPTION',
23
+ /** A subscription is for a customer whose email is verified. */
24
+ emailNotVerifiedForSubscription: 'EMAIL_NOT_VERIFIED_FOR_SUBSCRIPTION',
25
+ /** More of a variant than one order can have (`meta`: `{ variantId, requested, max }`). */
26
+ maxQuantityExceeded: 'MAX_QUANTITY_EXCEEDED',
27
+ };
28
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,55 @@
1
+ import { type Cache } from './cache.js';
2
+ import { type Auth, type AuthConfig } from './auth/auth.js';
3
+ import type { CachedCatalog } from './catalog/cached.js';
4
+ import { type Account } from './account/account.js';
5
+ import { type AddressBook } from './addresses/book.js';
6
+ import { type Reviews } from './reviews/reviews.js';
7
+ import { type Wishlist } from './wishlist/wishlist.js';
8
+ import { type Returns } from './returns/returns.js';
9
+ import { type Orders } from './orders/orders.js';
10
+ import { type KeyValuePort } from './cart/storage.js';
11
+ import { type CartStore } from './cart/store.js';
12
+ import { type CheckoutStore } from './checkout/store.js';
13
+ import type { ReturnUrls } from './checkout/types.js';
14
+ export interface CommerceConfig extends Omit<AuthConfig, 'baseUrl'> {
15
+ /** The commerce API (customers, cart, checkout), without a trailing slash. */
16
+ commerceApiUrl: string;
17
+ /** The catalog API (products, collections, filters), without a trailing slash. */
18
+ catalogApiUrl: string;
19
+ /** The checkout API (it prices a cart and starts its payment), without a trailing slash. Without it the store works, and `checkout` fails where it is used, saying so. */
20
+ checkoutApiUrl?: string;
21
+ /** Where the customer comes back to after paying (success) or giving up (cancel); the origins must be the store's, an allowed one or localhost. This page's own success and home pages by default. */
22
+ returnUrls?: () => ReturnUrls | undefined;
23
+ /** Where a guest's cart is kept between page loads: the browser's `localStorage` by default. */
24
+ cartStorage?: KeyValuePort;
25
+ /** The language the store answers the cart in (names, options): read at each call. */
26
+ langCode?: () => string | undefined;
27
+ /** Whether a guest's cart is priced with `/cart/total` (default: yes). `false` for a site that prices and describes its cart with the checkout's preview: see `CartStoreOptions.pricing`. */
28
+ cartPricing?: boolean;
29
+ }
30
+ export interface Commerce {
31
+ storeId: string;
32
+ /** The customer's session: its client carries the customer's JWT. */
33
+ auth: Auth;
34
+ /** The catalog's calls, through the cache. */
35
+ catalog: CachedCatalog;
36
+ cache: Cache;
37
+ /** The cart: a guest's in the browser, a customer's in the store, following `auth`. */
38
+ cart: CartStore;
39
+ /** The checkout: what the customer chose, what it costs (`quote`) and the start of the payment (`pay`). Needs `checkoutApiUrl`: without it this throws where it is read. */
40
+ checkout: CheckoutStore;
41
+ /** The requests about the customer's own account: password reset, verification, deletion, two-factor, newsletter. */
42
+ account: Account;
43
+ /** The addresses: a customer's in the store, a guest's in memory, following `auth`. */
44
+ addresses: AddressBook;
45
+ /** What the customer bought: orders and subscriptions, as resources of the cache. */
46
+ orders: Orders;
47
+ /** Returns: a customer's requests and the guest's link flow. */
48
+ returns: Returns;
49
+ /** The customer's wishlist (a guest has none). */
50
+ wishlist: Wishlist;
51
+ /** What the customer writes: their reviews of what they bought. */
52
+ reviews: Reviews;
53
+ }
54
+ export declare function createCommerce(config: CommerceConfig): Commerce;
55
+ //# sourceMappingURL=commerce.d.ts.map
@@ -0,0 +1,55 @@
1
+ import { createCache } from './cache.js';
2
+ import { createAuth } from './auth/auth.js';
3
+ import { createCachedCatalog } from './catalog/cached.js';
4
+ import { createAccount } from './account/account.js';
5
+ import { createAddressApi } from './addresses/api.js';
6
+ import { createAddressBook } from './addresses/book.js';
7
+ import { createAuthApi } from './auth/api.js';
8
+ import { createCartApi } from './cart/api.js';
9
+ import { createCustomerResources } from './customer-resources.js';
10
+ import { createOrderApi } from './orders/api.js';
11
+ import { createReturnApi } from './returns/api.js';
12
+ import { createReviewApi } from './reviews/api.js';
13
+ import { createReviews } from './reviews/reviews.js';
14
+ import { createWishlistApi } from './wishlist/api.js';
15
+ import { createWishlist } from './wishlist/wishlist.js';
16
+ import { createReturns } from './returns/returns.js';
17
+ import { createOrders } from './orders/orders.js';
18
+ import { browserKeyValuePort, createCartStorage } from './cart/storage.js';
19
+ import { createCartStore } from './cart/store.js';
20
+ import { createCheckoutApi } from './checkout/api.js';
21
+ import { browserReturnUrls } from './checkout/return-urls.js';
22
+ import { createCheckoutStore } from './checkout/store.js';
23
+ import { createHttpClient } from './http.js';
24
+ export function createCommerce(config) {
25
+ const { commerceApiUrl, catalogApiUrl, checkoutApiUrl, returnUrls, cartStorage, langCode, cartPricing, ...rest } = config;
26
+ const cache = createCache();
27
+ const catalog = createCachedCatalog(createHttpClient({ baseUrl: catalogApiUrl, storeId: config.storeId, timeoutMs: config.timeoutMs, fetch: config.fetch }), cache);
28
+ const auth = createAuth({ ...rest, baseUrl: commerceApiUrl });
29
+ const cartKept = createCartStorage(cartStorage ?? browserKeyValuePort(), config.storeId);
30
+ const cart = createCartStore({ api: createCartApi(auth.http), auth: auth.store, storage: cartKept, langCode, ...(cartPricing !== undefined ? { pricing: cartPricing } : {}) });
31
+ // Made when it is first used: a store without the checkout's address is fine until something asks for the checkout
32
+ let checkout;
33
+ const checkoutOf = () => {
34
+ if (checkoutApiUrl === undefined)
35
+ throw new Error('@webround/commerce: this store was made without the checkout API\'s address (`checkoutApiUrl`), so it does not know where to price a cart or start a payment.');
36
+ return (checkout ?? (checkout = createCheckoutStore({
37
+ api: createCheckoutApi(auth.httpFor(checkoutApiUrl)),
38
+ cart,
39
+ trackingId: () => cartKept.trackingId(),
40
+ langCode,
41
+ returnUrls: returnUrls ?? browserReturnUrls,
42
+ clearCart: () => cart.clear(),
43
+ })));
44
+ };
45
+ const account = createAccount({ api: createAuthApi(auth.http), auth: auth.store, storage: auth.storage, language: langCode });
46
+ const addresses = createAddressBook({ api: createAddressApi(auth.http), auth: auth.store });
47
+ const identity = { baseUrl: commerceApiUrl, storeId: config.storeId };
48
+ const customer = createCustomerResources({ cache, identity, auth: auth.store });
49
+ const orders = createOrders({ api: createOrderApi(auth.http), customer });
50
+ const returns = createReturns({ api: createReturnApi(auth.http), customer, cache, identity });
51
+ const wishlist = createWishlist({ api: createWishlistApi(auth.http), auth: auth.store });
52
+ const reviews = createReviews({ api: createReviewApi(auth.http), customer, catalog, auth: auth.store });
53
+ return { storeId: config.storeId, auth, catalog, cache, cart, get checkout() { return checkoutOf(); }, account, addresses, orders, returns, wishlist, reviews };
54
+ }
55
+ //# sourceMappingURL=commerce.js.map
@@ -0,0 +1,18 @@
1
+ import { type Cache } from './cache.js';
2
+ import type { CallOptions } from './catalog/types.js';
3
+ import type { AuthStore } from './auth/store.js';
4
+ import { type Resource, type ResourceIdentity } from './resource.js';
5
+ export interface CustomerResources {
6
+ /** A question about the logged-in customer's data, or null when nobody is. `subject` is what the call is about (an order's id). */
7
+ resource<T>(call: string, subject: string | undefined, params: unknown, fetch: (options: CallOptions) => Promise<T>): Resource<T> | null;
8
+ /** Forgets what was kept for the customer: of one call (all of it, or about one subject), or of all of them. The next question is asked again. */
9
+ forget(call?: string, subject?: string): void;
10
+ /** Stops following the auth store. */
11
+ dispose(): void;
12
+ }
13
+ export declare function createCustomerResources(options: {
14
+ cache: Cache;
15
+ identity: ResourceIdentity;
16
+ auth: Pick<AuthStore, 'getState' | 'subscribe'>;
17
+ }): CustomerResources;
18
+ //# sourceMappingURL=customer-resources.d.ts.map
@@ -0,0 +1,40 @@
1
+ import { cachePrefix } from './cache.js';
2
+ import { createResourceFactory } from './resource.js';
3
+ export function createCustomerResources(options) {
4
+ const { cache, identity, auth } = options;
5
+ const make = createResourceFactory(cache, identity);
6
+ // The calls that were asked: what is forgotten when the customer changes
7
+ const calls = new Set();
8
+ const customerId = () => {
9
+ const { status, customer } = auth.getState();
10
+ return status === 'signedIn' && customer ? customer.customerId : null;
11
+ };
12
+ const subjectOf = (id, subject) => (subject === undefined ? id : `${id}/${subject}`);
13
+ let last = customerId();
14
+ function forget(call, subject) {
15
+ const id = customerId();
16
+ for (const c of call === undefined ? [...calls] : [call]) {
17
+ // With a subject, only what was asked about it, for this customer; else everything of the call, whoever it was for
18
+ cache.invalidate(cachePrefix({ base: identity.baseUrl, storeId: identity.storeId, call: c, subject: subject !== undefined && id !== null ? subjectOf(id, subject) : undefined }));
19
+ }
20
+ }
21
+ const stop = auth.subscribe(() => {
22
+ const now = customerId();
23
+ if (now === last)
24
+ return;
25
+ last = now;
26
+ forget();
27
+ });
28
+ return {
29
+ resource(call, subject, params, fetch) {
30
+ const id = customerId();
31
+ if (id === null)
32
+ return null;
33
+ calls.add(call);
34
+ return make(call, subjectOf(id, subject), params, fetch);
35
+ },
36
+ forget,
37
+ dispose: stop,
38
+ };
39
+ }
40
+ //# sourceMappingURL=customer-resources.js.map