swell-js 5.9.3 → 5.10.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.
- package/README.md +43 -1
- package/dist/api.mjs +141 -33
- package/dist/payment.mjs +700 -76
- package/dist/swell.cjs +845 -113
- package/dist/swell.cjs.map +1 -1
- package/dist/swell.umd.min.js +845 -113
- package/dist/swell.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/types/consent/camel.ts +17 -0
- package/types/consent/index.ts +21 -0
- package/types/consent/snake.ts +22 -0
- package/types/index.d.cts +10 -0
- package/types/index.d.ts +25 -1
- package/types/payment/index.ts +42 -3
package/README.md
CHANGED
|
@@ -13,7 +13,49 @@ Universal JavaScript client for Swell's Frontend API, providing client-safe acce
|
|
|
13
13
|
|
|
14
14
|
**About Swell**
|
|
15
15
|
|
|
16
|
-
[Swell](https://www.swell.is) is
|
|
16
|
+
[Swell](https://www.swell.is) is an ecommerce platform with subscriptions and B2B pricing built in. Run your store on a Swell theme, or [go headless](https://www.swell.is/headless-commerce) with its APIs.
|
|
17
|
+
|
|
18
|
+
## Checkout payments
|
|
19
|
+
|
|
20
|
+
Besides `createElements`, `tokenize` and `handleRedirect` (see the documentation), `swell.payment` offers helpers a checkout page needs:
|
|
21
|
+
|
|
22
|
+
- `tokenize({ card: { createIntent: false } })` stores a Stripe card without authorizing it now. `tokenize({ card: { paymentMethod: 'pm_…' } })` authorizes a Stripe card the shopper already has (a saved card, a wallet's payment method) with an intent and 3-D Secure, like a new card, before the order is submitted. Both reuse the cart's intent while it still matches the card and amount.
|
|
23
|
+
- Saferpay card and TWINT (`twint`) redirect to the Saferpay payment page from `tokenize` and come back through `handleRedirect`; iDEAL returns through `handleRedirect({ ideal })` too.
|
|
24
|
+
- Stripe iDEAL needs no element: `tokenize({ ideal: {} })` without `createElements` redirects to the iDEAL page, where the shopper picks the bank, and returns through `handleRedirect({ ideal })`.
|
|
25
|
+
- `handleAsyncRedirect(order)` sends the shopper to the gateway when a submitted order waits for an asynchronous payment.
|
|
26
|
+
- Stripe card and iDEAL can render through the Stripe [Payment Element](https://docs.stripe.com/payments/payment-element) instead of the split elements: pass `paymentElement: true` to both `createElements` and `tokenize`. Stripe draws the whole form (fields, labels, errors) and takes its look from `appearance` ([Appearance API](https://docs.stripe.com/elements/appearance-api)). The element mounts into `#payment-element` unless `elementId` is set; `options` go to `elements.create('payment')` over the defaults `layout: 'tabs'`, `fields.billingDetails: 'never'` (billing details come from the cart) and no Apple Pay / Google Pay. `tokenize` re-reads the cart and updates the amount before `elements.submit()`, so a changed cart total needs no extra call.
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
const card = {
|
|
30
|
+
paymentElement: true,
|
|
31
|
+
appearance: {
|
|
32
|
+
theme: 'stripe',
|
|
33
|
+
labels: 'floating',
|
|
34
|
+
variables: { colorPrimary: '#111' },
|
|
35
|
+
},
|
|
36
|
+
elementId: 'card-payment-element',
|
|
37
|
+
onChange: (event) => setComplete(event.complete),
|
|
38
|
+
onSuccess: () => submitOrder(),
|
|
39
|
+
onError: (error) => showError(error.message),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
await swell.payment.createElements({ card });
|
|
43
|
+
// on "Pay": validates, creates the PaymentMethod and the intent, stores them on the cart
|
|
44
|
+
await swell.payment.tokenize({ card });
|
|
45
|
+
|
|
46
|
+
// iDEAL: the same flag; tokenize redirects to the bank and returns through handleRedirect({ ideal })
|
|
47
|
+
await swell.payment.createElements({
|
|
48
|
+
ideal: {
|
|
49
|
+
paymentElement: true,
|
|
50
|
+
appearance,
|
|
51
|
+
elementId: 'ideal-payment-element',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Deprecated
|
|
57
|
+
|
|
58
|
+
- `swell.app` (app components rendered by swell-js) is deprecated and will be removed in the next major version. Apps render their components through [`@swell/apps-sdk/components`](https://www.npmjs.com/package/@swell/apps-sdk) instead; hosted checkout mounts payment components at the place `checkout.payment.methods`.
|
|
17
59
|
|
|
18
60
|
## Documentation
|
|
19
61
|
|
package/dist/api.mjs
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import methods$
|
|
1
|
+
import methods$6 from './card.mjs';
|
|
2
2
|
import { getCookie, setCookie } from './cookie.mjs';
|
|
3
|
-
import methods$
|
|
4
|
-
import methods$
|
|
5
|
-
import methods$
|
|
6
|
-
import { j as isFunction, l as loadScript, e as isObject, v as vaultRequest, k as isEmpty, d as defaultMethods, x as trimEnd, y as utils, z as trimStart, A as trimBoth, b as toSnake, o as base64Encode,
|
|
3
|
+
import methods$5 from './cache.mjs';
|
|
4
|
+
import methods$4 from './cart.mjs';
|
|
5
|
+
import methods$7 from './account.mjs';
|
|
6
|
+
import { j as isFunction, l as loadScript, e as isObject, v as vaultRequest, k as isEmpty, d as defaultMethods, i as isServer, x as trimEnd, y as utils, z as trimStart, A as trimBoth, b as toSnake, o as base64Encode, t as toCamel } from './index.76eb52e5.mjs';
|
|
7
7
|
import { Q as stringifyQuery } from './find.ba551a2e.mjs';
|
|
8
8
|
import 'deepmerge';
|
|
9
9
|
import 'fast-case';
|
|
10
|
-
import methods$
|
|
11
|
-
import methods$
|
|
12
|
-
import methods$
|
|
13
|
-
import methods$
|
|
14
|
-
import methods$
|
|
15
|
-
import methods$
|
|
10
|
+
import methods$8 from './products.mjs';
|
|
11
|
+
import methods$9 from './categories.mjs';
|
|
12
|
+
import methods$a from './attributes.mjs';
|
|
13
|
+
import methods$b from './subscriptions.mjs';
|
|
14
|
+
import methods$c from './content.mjs';
|
|
15
|
+
import methods$d from './settings.mjs';
|
|
16
16
|
import PaymentController from './payment.mjs';
|
|
17
|
-
import methods$
|
|
18
|
-
import methods$
|
|
17
|
+
import methods$e from './locale.mjs';
|
|
18
|
+
import methods$f, { isCurrencyCode } from './currency.mjs';
|
|
19
19
|
import './round.c68b8be5.mjs';
|
|
20
20
|
|
|
21
21
|
class AppComponent {
|
|
@@ -81,7 +81,7 @@ class AppComponent {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
async updateCart(data) {
|
|
84
|
-
const cart = await methods$
|
|
84
|
+
const cart = await methods$4(this.api, this.options).update(data);
|
|
85
85
|
|
|
86
86
|
this.setProps({ cart });
|
|
87
87
|
}
|
|
@@ -122,6 +122,10 @@ class AppComponent {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* @deprecated Payment components are mounted by `@swell/apps-sdk/components`
|
|
127
|
+
* at the host place `checkout.payment.methods`.
|
|
128
|
+
*/
|
|
125
129
|
class AppPaymentComponent extends AppComponent {
|
|
126
130
|
constructor(app, component, api, options) {
|
|
127
131
|
super(app, component, api, options);
|
|
@@ -171,6 +175,7 @@ class AppPaymentComponent extends AppComponent {
|
|
|
171
175
|
}
|
|
172
176
|
}
|
|
173
177
|
|
|
178
|
+
/** @deprecated Use `@swell/apps-sdk/components` instead. */
|
|
174
179
|
class App {
|
|
175
180
|
constructor(app, api, options) {
|
|
176
181
|
this.app = app;
|
|
@@ -279,6 +284,13 @@ class App {
|
|
|
279
284
|
}
|
|
280
285
|
}
|
|
281
286
|
|
|
287
|
+
let deprecationWarned = false;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* @deprecated App components are rendered by `@swell/apps-sdk/components`
|
|
291
|
+
* (`createComponents().mount()`); this module will be removed in the next
|
|
292
|
+
* major version.
|
|
293
|
+
*/
|
|
282
294
|
class AppController {
|
|
283
295
|
#apps = new Map();
|
|
284
296
|
|
|
@@ -287,7 +299,15 @@ class AppController {
|
|
|
287
299
|
this.options = options;
|
|
288
300
|
}
|
|
289
301
|
|
|
302
|
+
/** @deprecated Use `@swell/apps-sdk/components` instead. */
|
|
290
303
|
async load(appId) {
|
|
304
|
+
if (!deprecationWarned) {
|
|
305
|
+
deprecationWarned = true;
|
|
306
|
+
console.warn(
|
|
307
|
+
'swell.app is deprecated: render app components with @swell/apps-sdk/components',
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
291
311
|
const loadedApp = this.#apps.get(appId);
|
|
292
312
|
|
|
293
313
|
if (loadedApp) {
|
|
@@ -317,7 +337,7 @@ class AppController {
|
|
|
317
337
|
}
|
|
318
338
|
}
|
|
319
339
|
|
|
320
|
-
function methods$
|
|
340
|
+
function methods$3(api) {
|
|
321
341
|
const { get, list } = defaultMethods(api, '/invoices', ['list', 'get']);
|
|
322
342
|
return {
|
|
323
343
|
get: (id, ...args) => {
|
|
@@ -328,7 +348,7 @@ function methods$2(api) {
|
|
|
328
348
|
};
|
|
329
349
|
}
|
|
330
350
|
|
|
331
|
-
function methods$
|
|
351
|
+
function methods$2(api, opt) {
|
|
332
352
|
return {
|
|
333
353
|
/**
|
|
334
354
|
* Get the decoded session as an object of session values
|
|
@@ -355,7 +375,7 @@ function methods$1(api, opt) {
|
|
|
355
375
|
};
|
|
356
376
|
}
|
|
357
377
|
|
|
358
|
-
function methods(api, _opt) {
|
|
378
|
+
function methods$1(api, _opt) {
|
|
359
379
|
return {
|
|
360
380
|
/**
|
|
361
381
|
* Make a request to an app function and greceiveet a response
|
|
@@ -424,6 +444,85 @@ function methods(api, _opt) {
|
|
|
424
444
|
};
|
|
425
445
|
}
|
|
426
446
|
|
|
447
|
+
const PURPOSES = ['analytics', 'marketing', 'sale_of_data'];
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* The page the shopper landed on, which the consent call sends along: the
|
|
451
|
+
* first URL of this document and its referrer. Read when needed, so nothing
|
|
452
|
+
* is kept before the shopper answers.
|
|
453
|
+
*
|
|
454
|
+
* @returns {{url: string, referrer: string} | undefined}
|
|
455
|
+
*/
|
|
456
|
+
function getLanding() {
|
|
457
|
+
if (isServer()) {
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
let url = window.location.href;
|
|
462
|
+
|
|
463
|
+
try {
|
|
464
|
+
const [navigation] = window.performance.getEntriesByType('navigation');
|
|
465
|
+
if (navigation?.name) {
|
|
466
|
+
url = navigation.name;
|
|
467
|
+
}
|
|
468
|
+
} catch {
|
|
469
|
+
// Navigation timing is not available
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return { url, referrer: window.document.referrer || '' };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function methods(api) {
|
|
476
|
+
// Calls run one after another. Consent tools fire several events for one
|
|
477
|
+
// answer, and the first call may set the visitor cookie that the next one
|
|
478
|
+
// has to send: in parallel, each would start a visitor of its own.
|
|
479
|
+
let queue = Promise.resolve();
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
/**
|
|
483
|
+
* Records the shopper's consent for attribution, from the storefront's
|
|
484
|
+
* consent banner. Purposes left out stay as they are.
|
|
485
|
+
*
|
|
486
|
+
* Calls the storefront's own origin, so that Swell sees the page's
|
|
487
|
+
* cookies and sets the visitor cookie on the storefront's domain.
|
|
488
|
+
*
|
|
489
|
+
* @param {{analytics?: boolean, marketing?: boolean, sale_of_data?: boolean}} consent
|
|
490
|
+
*/
|
|
491
|
+
set(consent = {}) {
|
|
492
|
+
if (isServer()) {
|
|
493
|
+
return Promise.reject(new Error('consent.set() runs in the browser'));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const data = {};
|
|
497
|
+
|
|
498
|
+
for (const purpose of PURPOSES) {
|
|
499
|
+
// camelCase clients pass saleOfData
|
|
500
|
+
const camel = purpose.replace(/_(\w)/g, (_m, c) => c.toUpperCase());
|
|
501
|
+
const value = consent[purpose] ?? consent[camel];
|
|
502
|
+
|
|
503
|
+
if (typeof value === 'boolean') {
|
|
504
|
+
data[purpose] = value;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const result = queue.then(() =>
|
|
509
|
+
api.request(
|
|
510
|
+
'put',
|
|
511
|
+
'/session/consent',
|
|
512
|
+
{ ...data, landing: getLanding() },
|
|
513
|
+
undefined,
|
|
514
|
+
{ fullUrl: `${window.location.origin}/api/session/consent` },
|
|
515
|
+
),
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
// A failed call doesn't hold back the next one
|
|
519
|
+
queue = result.catch(() => undefined);
|
|
520
|
+
|
|
521
|
+
return result;
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
427
526
|
/**
|
|
428
527
|
* @template {'snake' | 'camel'} C
|
|
429
528
|
* @typedef {import('../types').InitOptions<C>} InitOptions<C>
|
|
@@ -455,7 +554,7 @@ function swell(initStore = undefined, initKey, initOptions = {}) {
|
|
|
455
554
|
const api = {};
|
|
456
555
|
|
|
457
556
|
Object.assign(api, {
|
|
458
|
-
version: '5.
|
|
557
|
+
version: '5.10.0',
|
|
459
558
|
options,
|
|
460
559
|
request,
|
|
461
560
|
|
|
@@ -509,39 +608,41 @@ function swell(initStore = undefined, initKey, initOptions = {}) {
|
|
|
509
608
|
return api.request('delete', url, data);
|
|
510
609
|
},
|
|
511
610
|
|
|
512
|
-
cache: methods$
|
|
611
|
+
cache: methods$5(options),
|
|
513
612
|
|
|
514
|
-
card: methods$
|
|
613
|
+
card: methods$6(options),
|
|
515
614
|
|
|
516
|
-
cart: methods$
|
|
615
|
+
cart: methods$4(api, options),
|
|
517
616
|
|
|
518
|
-
account: methods$
|
|
617
|
+
account: methods$7(api),
|
|
519
618
|
|
|
520
619
|
app: new AppController(api, options),
|
|
521
620
|
|
|
522
|
-
products: methods$
|
|
621
|
+
products: methods$8(api, options),
|
|
523
622
|
|
|
524
|
-
categories: methods$
|
|
623
|
+
categories: methods$9(api),
|
|
525
624
|
|
|
526
|
-
attributes: methods$
|
|
625
|
+
attributes: methods$a(api),
|
|
527
626
|
|
|
528
|
-
subscriptions: methods$
|
|
627
|
+
subscriptions: methods$b(api),
|
|
529
628
|
|
|
530
|
-
invoices: methods$
|
|
629
|
+
invoices: methods$3(api),
|
|
531
630
|
|
|
532
|
-
content: methods$
|
|
631
|
+
content: methods$c(api, options),
|
|
533
632
|
|
|
534
|
-
settings: methods$
|
|
633
|
+
settings: methods$d(api, options),
|
|
535
634
|
|
|
536
635
|
payment: new PaymentController(api, options),
|
|
537
636
|
|
|
538
|
-
locale: methods$
|
|
637
|
+
locale: methods$e(api, options),
|
|
638
|
+
|
|
639
|
+
currency: methods$f(api, options),
|
|
539
640
|
|
|
540
|
-
|
|
641
|
+
session: methods$2(api, options),
|
|
541
642
|
|
|
542
|
-
|
|
643
|
+
functions: methods$1(api),
|
|
543
644
|
|
|
544
|
-
|
|
645
|
+
consent: methods(api),
|
|
545
646
|
|
|
546
647
|
utils,
|
|
547
648
|
|
|
@@ -615,6 +716,13 @@ function swell(initStore = undefined, initKey, initOptions = {}) {
|
|
|
615
716
|
reqHeaders['X-Session'] = session;
|
|
616
717
|
}
|
|
617
718
|
|
|
719
|
+
// The storefront's visitor, for attribution: the cookie is on the
|
|
720
|
+
// storefront's domain and doesn't reach the store's API host
|
|
721
|
+
const visitor = allOptions.getCookie('swell-visitor');
|
|
722
|
+
if (visitor) {
|
|
723
|
+
reqHeaders['X-Visitor'] = visitor;
|
|
724
|
+
}
|
|
725
|
+
|
|
618
726
|
if (locale) {
|
|
619
727
|
reqHeaders['X-Locale'] = locale;
|
|
620
728
|
}
|