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/dist/swell.umd.min.js
CHANGED
|
@@ -5111,7 +5111,7 @@
|
|
|
5111
5111
|
vaultRequest: vaultRequest
|
|
5112
5112
|
});
|
|
5113
5113
|
|
|
5114
|
-
function methods$
|
|
5114
|
+
function methods$f(options) {
|
|
5115
5115
|
return {
|
|
5116
5116
|
async createToken(data) {
|
|
5117
5117
|
let error = null;
|
|
@@ -5328,7 +5328,7 @@
|
|
|
5328
5328
|
|
|
5329
5329
|
const RECORD_TIMEOUT = 5000;
|
|
5330
5330
|
|
|
5331
|
-
function methods$
|
|
5331
|
+
function methods$e(opt) {
|
|
5332
5332
|
let VALUES = {
|
|
5333
5333
|
/*
|
|
5334
5334
|
[model]: {
|
|
@@ -5490,7 +5490,7 @@
|
|
|
5490
5490
|
};
|
|
5491
5491
|
}
|
|
5492
5492
|
|
|
5493
|
-
function methods$
|
|
5493
|
+
function methods$d(api) {
|
|
5494
5494
|
const { get, list } = defaultMethods(api, '/attributes', ['list', 'get']);
|
|
5495
5495
|
|
|
5496
5496
|
return {
|
|
@@ -5502,7 +5502,7 @@
|
|
|
5502
5502
|
};
|
|
5503
5503
|
}
|
|
5504
5504
|
|
|
5505
|
-
function methods$
|
|
5505
|
+
function methods$c(api, opt) {
|
|
5506
5506
|
const { get, list } = defaultMethods(api, '/products', ['list', 'get']);
|
|
5507
5507
|
return {
|
|
5508
5508
|
get: (id, ...args) => {
|
|
@@ -5711,7 +5711,7 @@
|
|
|
5711
5711
|
}
|
|
5712
5712
|
|
|
5713
5713
|
async function getFilterableAttributeFilters(api, products, options) {
|
|
5714
|
-
const { results: filterableAttributes } = await methods$
|
|
5714
|
+
const { results: filterableAttributes } = await methods$d(api).list({
|
|
5715
5715
|
filterable: true,
|
|
5716
5716
|
});
|
|
5717
5717
|
|
|
@@ -5895,7 +5895,7 @@
|
|
|
5895
5895
|
};
|
|
5896
5896
|
}
|
|
5897
5897
|
|
|
5898
|
-
function methods$
|
|
5898
|
+
function methods$b(api, options) {
|
|
5899
5899
|
return {
|
|
5900
5900
|
state: null,
|
|
5901
5901
|
order: null,
|
|
@@ -6080,7 +6080,7 @@
|
|
|
6080
6080
|
};
|
|
6081
6081
|
}
|
|
6082
6082
|
|
|
6083
|
-
function methods$
|
|
6083
|
+
function methods$a(api) {
|
|
6084
6084
|
return {
|
|
6085
6085
|
state: null,
|
|
6086
6086
|
|
|
@@ -6254,7 +6254,7 @@
|
|
|
6254
6254
|
}
|
|
6255
6255
|
|
|
6256
6256
|
async updateCart(data) {
|
|
6257
|
-
const cart = await methods$
|
|
6257
|
+
const cart = await methods$b(this.api, this.options).update(data);
|
|
6258
6258
|
|
|
6259
6259
|
this.setProps({ cart });
|
|
6260
6260
|
}
|
|
@@ -6295,6 +6295,10 @@
|
|
|
6295
6295
|
}
|
|
6296
6296
|
}
|
|
6297
6297
|
|
|
6298
|
+
/**
|
|
6299
|
+
* @deprecated Payment components are mounted by `@swell/apps-sdk/components`
|
|
6300
|
+
* at the host place `checkout.payment.methods`.
|
|
6301
|
+
*/
|
|
6298
6302
|
class AppPaymentComponent extends AppComponent {
|
|
6299
6303
|
constructor(app, component, api, options) {
|
|
6300
6304
|
super(app, component, api, options);
|
|
@@ -6344,6 +6348,7 @@
|
|
|
6344
6348
|
}
|
|
6345
6349
|
}
|
|
6346
6350
|
|
|
6351
|
+
/** @deprecated Use `@swell/apps-sdk/components` instead. */
|
|
6347
6352
|
class App {
|
|
6348
6353
|
constructor(app, api, options) {
|
|
6349
6354
|
this.app = app;
|
|
@@ -6452,6 +6457,13 @@
|
|
|
6452
6457
|
}
|
|
6453
6458
|
}
|
|
6454
6459
|
|
|
6460
|
+
let deprecationWarned = false;
|
|
6461
|
+
|
|
6462
|
+
/**
|
|
6463
|
+
* @deprecated App components are rendered by `@swell/apps-sdk/components`
|
|
6464
|
+
* (`createComponents().mount()`); this module will be removed in the next
|
|
6465
|
+
* major version.
|
|
6466
|
+
*/
|
|
6455
6467
|
class AppController {
|
|
6456
6468
|
#apps = new Map();
|
|
6457
6469
|
|
|
@@ -6460,7 +6472,15 @@
|
|
|
6460
6472
|
this.options = options;
|
|
6461
6473
|
}
|
|
6462
6474
|
|
|
6475
|
+
/** @deprecated Use `@swell/apps-sdk/components` instead. */
|
|
6463
6476
|
async load(appId) {
|
|
6477
|
+
if (!deprecationWarned) {
|
|
6478
|
+
deprecationWarned = true;
|
|
6479
|
+
console.warn(
|
|
6480
|
+
'swell.app is deprecated: render app components with @swell/apps-sdk/components',
|
|
6481
|
+
);
|
|
6482
|
+
}
|
|
6483
|
+
|
|
6464
6484
|
const loadedApp = this.#apps.get(appId);
|
|
6465
6485
|
|
|
6466
6486
|
if (loadedApp) {
|
|
@@ -6490,7 +6510,7 @@
|
|
|
6490
6510
|
}
|
|
6491
6511
|
}
|
|
6492
6512
|
|
|
6493
|
-
function methods$
|
|
6513
|
+
function methods$9(api) {
|
|
6494
6514
|
const { get, list } = defaultMethods(api, '/categories', ['list', 'get']);
|
|
6495
6515
|
|
|
6496
6516
|
return {
|
|
@@ -6502,7 +6522,7 @@
|
|
|
6502
6522
|
};
|
|
6503
6523
|
}
|
|
6504
6524
|
|
|
6505
|
-
function methods$
|
|
6525
|
+
function methods$8(api) {
|
|
6506
6526
|
const { get, list } = defaultMethods(api, '/subscriptions', ['list', 'get']);
|
|
6507
6527
|
return {
|
|
6508
6528
|
get: (id, ...args) => {
|
|
@@ -6567,7 +6587,7 @@
|
|
|
6567
6587
|
};
|
|
6568
6588
|
}
|
|
6569
6589
|
|
|
6570
|
-
function methods$
|
|
6590
|
+
function methods$7(api) {
|
|
6571
6591
|
const { get, list } = defaultMethods(api, '/invoices', ['list', 'get']);
|
|
6572
6592
|
return {
|
|
6573
6593
|
get: (id, ...args) => {
|
|
@@ -6578,7 +6598,7 @@
|
|
|
6578
6598
|
};
|
|
6579
6599
|
}
|
|
6580
6600
|
|
|
6581
|
-
function methods$
|
|
6601
|
+
function methods$6(api, opt) {
|
|
6582
6602
|
return {
|
|
6583
6603
|
get: (type, id, query) => {
|
|
6584
6604
|
return api.cache.getFetch(`content_${type}`, id, () =>
|
|
@@ -6594,7 +6614,7 @@
|
|
|
6594
6614
|
};
|
|
6595
6615
|
}
|
|
6596
6616
|
|
|
6597
|
-
function methods$
|
|
6617
|
+
function methods$5(api, opt) {
|
|
6598
6618
|
return {
|
|
6599
6619
|
state: null,
|
|
6600
6620
|
menuState: null,
|
|
@@ -6889,7 +6909,7 @@
|
|
|
6889
6909
|
}
|
|
6890
6910
|
}
|
|
6891
6911
|
|
|
6892
|
-
function methods$
|
|
6912
|
+
function methods$4(api, opt) {
|
|
6893
6913
|
return {
|
|
6894
6914
|
/**
|
|
6895
6915
|
* Get the decoded session as an object of session values
|
|
@@ -7277,7 +7297,7 @@
|
|
|
7277
7297
|
* @returns {Promise<Cart>}
|
|
7278
7298
|
*/
|
|
7279
7299
|
async getCart() {
|
|
7280
|
-
const cart = await methods$
|
|
7300
|
+
const cart = await methods$b(this.api, this.options).get();
|
|
7281
7301
|
|
|
7282
7302
|
if (!cart) {
|
|
7283
7303
|
throw new Error('Cart not found');
|
|
@@ -7305,7 +7325,7 @@
|
|
|
7305
7325
|
}
|
|
7306
7326
|
}
|
|
7307
7327
|
|
|
7308
|
-
const updatedCart = await methods$
|
|
7328
|
+
const updatedCart = await methods$b(this.api, this.options).update(
|
|
7309
7329
|
updateData,
|
|
7310
7330
|
);
|
|
7311
7331
|
|
|
@@ -7318,7 +7338,7 @@
|
|
|
7318
7338
|
* @returns {Promise<object>}
|
|
7319
7339
|
*/
|
|
7320
7340
|
async getSettings() {
|
|
7321
|
-
return methods$
|
|
7341
|
+
return methods$5(this.api, this.options).get();
|
|
7322
7342
|
}
|
|
7323
7343
|
|
|
7324
7344
|
/**
|
|
@@ -7585,25 +7605,184 @@
|
|
|
7585
7605
|
billing_details: billingDetails,
|
|
7586
7606
|
});
|
|
7587
7607
|
|
|
7588
|
-
return error
|
|
7589
|
-
? { error }
|
|
7590
|
-
: {
|
|
7591
|
-
gateway: 'stripe',
|
|
7592
|
-
token: paymentMethod.id,
|
|
7593
|
-
last4: paymentMethod.card.last4,
|
|
7594
|
-
exp_month: paymentMethod.card.exp_month,
|
|
7595
|
-
exp_year: paymentMethod.card.exp_year,
|
|
7596
|
-
brand: paymentMethod.card.brand,
|
|
7597
|
-
display_brand: paymentMethod.card.display_brand,
|
|
7598
|
-
address_check: paymentMethod.card.checks.address_line1_check,
|
|
7599
|
-
cvc_check: paymentMethod.card.checks.cvc_check,
|
|
7600
|
-
zip_check: paymentMethod.card.checks.address_postal_code_check,
|
|
7601
|
-
};
|
|
7608
|
+
return error ? { error } : mapCardPaymentMethod(paymentMethod);
|
|
7602
7609
|
}
|
|
7603
7610
|
|
|
7604
7611
|
/**
|
|
7612
|
+
* Elements group options of the card Payment Element for the cart amount,
|
|
7613
|
+
* matching the intent StripeCardPayment#_createIntent creates.
|
|
7614
|
+
*
|
|
7615
|
+
* @param {Cart} cart
|
|
7616
|
+
* @returns {import('@stripe/stripe-js').StripeElementsOptionsMode}
|
|
7617
|
+
*/
|
|
7618
|
+
function getCardElementsOptions(cart) {
|
|
7619
|
+
const { capture_total, auth_total } = cart;
|
|
7620
|
+
const currency = cart.currency || 'USD';
|
|
7621
|
+
const stripeCurrency = currency.toLowerCase();
|
|
7622
|
+
|
|
7623
|
+
// the card is only stored when the amount is not chargeable, no intent is created
|
|
7624
|
+
if (!isStripeChargeableAmount(capture_total, cart.currency)) {
|
|
7625
|
+
return {
|
|
7626
|
+
mode: 'setup',
|
|
7627
|
+
currency: stripeCurrency,
|
|
7628
|
+
paymentMethodTypes: ['card'],
|
|
7629
|
+
};
|
|
7630
|
+
}
|
|
7631
|
+
|
|
7632
|
+
return {
|
|
7633
|
+
mode: 'payment',
|
|
7634
|
+
amount: stripeAmountByCurrency(currency, capture_total + (auth_total || 0)),
|
|
7635
|
+
currency: stripeCurrency,
|
|
7636
|
+
captureMethod: 'manual',
|
|
7637
|
+
setupFutureUsage: 'off_session',
|
|
7638
|
+
paymentMethodTypes: ['card'],
|
|
7639
|
+
};
|
|
7640
|
+
}
|
|
7641
|
+
|
|
7642
|
+
/**
|
|
7643
|
+
* Elements group options of the iDEAL Payment Element,
|
|
7644
|
+
* matching the intent StripeIDealPayment#_createIntent creates.
|
|
7645
|
+
*
|
|
7646
|
+
* @param {Cart} cart
|
|
7647
|
+
* @returns {import('@stripe/stripe-js').StripeElementsOptionsMode}
|
|
7648
|
+
*/
|
|
7649
|
+
function getIDealElementsOptions(cart) {
|
|
7650
|
+
const { capture_total } = cart;
|
|
7651
|
+
const currency = cart.currency || 'EUR';
|
|
7652
|
+
|
|
7653
|
+
return {
|
|
7654
|
+
mode: 'payment',
|
|
7655
|
+
amount: stripeAmountByCurrency(currency, capture_total),
|
|
7656
|
+
currency: currency.toLowerCase(),
|
|
7657
|
+
paymentMethodTypes: ['ideal'],
|
|
7658
|
+
};
|
|
7659
|
+
}
|
|
7660
|
+
|
|
7661
|
+
/**
|
|
7662
|
+
* Creates an Elements group without an intent and mounts the Payment Element
|
|
7663
|
+
* into `#${params.elementId || 'payment-element'}`.
|
|
7664
|
+
*
|
|
7605
7665
|
* @param {Stripe} stripe
|
|
7606
|
-
* @param {import('@stripe/stripe-js').
|
|
7666
|
+
* @param {import('@stripe/stripe-js').StripeElementsOptionsMode} elementsOptions
|
|
7667
|
+
* @param {object} params
|
|
7668
|
+
* @returns {import('@stripe/stripe-js').StripeElements}
|
|
7669
|
+
*/
|
|
7670
|
+
function createPaymentElements(stripe, elementsOptions, params) {
|
|
7671
|
+
const elements = stripe.elements({
|
|
7672
|
+
...params.config,
|
|
7673
|
+
...elementsOptions,
|
|
7674
|
+
// lets stripe.createPaymentMethod read the Payment Element
|
|
7675
|
+
paymentMethodCreation: 'manual',
|
|
7676
|
+
...(params.appearance && { appearance: params.appearance }),
|
|
7677
|
+
});
|
|
7678
|
+
|
|
7679
|
+
createElement('payment', elements, {
|
|
7680
|
+
...params,
|
|
7681
|
+
options: {
|
|
7682
|
+
layout: 'tabs',
|
|
7683
|
+
fields: { billingDetails: 'never' },
|
|
7684
|
+
// wallets are separate express buttons
|
|
7685
|
+
wallets: { applePay: 'never', googlePay: 'never' },
|
|
7686
|
+
...params.options,
|
|
7687
|
+
},
|
|
7688
|
+
});
|
|
7689
|
+
|
|
7690
|
+
return elements;
|
|
7691
|
+
}
|
|
7692
|
+
|
|
7693
|
+
/**
|
|
7694
|
+
* Creates a PaymentMethod from the Payment Element.
|
|
7695
|
+
* Brings the Elements amount up to date with the cart first.
|
|
7696
|
+
*
|
|
7697
|
+
* @param {Stripe} stripe
|
|
7698
|
+
* @param {import('@stripe/stripe-js').StripeElements} elements
|
|
7699
|
+
* @param {import('@stripe/stripe-js').StripeElementsOptionsMode} elementsOptions
|
|
7700
|
+
* @param {Cart} cart
|
|
7701
|
+
* @returns {Promise<import('@stripe/stripe-js').PaymentMethodResult>}
|
|
7702
|
+
*/
|
|
7703
|
+
async function createPaymentMethodFromElements(
|
|
7704
|
+
stripe,
|
|
7705
|
+
elements,
|
|
7706
|
+
elementsOptions,
|
|
7707
|
+
cart,
|
|
7708
|
+
) {
|
|
7709
|
+
// setup mode stores the card without an intent, there is no amount to update
|
|
7710
|
+
if (elementsOptions.mode === 'payment') {
|
|
7711
|
+
await elements.update(elementsOptions);
|
|
7712
|
+
}
|
|
7713
|
+
|
|
7714
|
+
const { error } = await elements.submit();
|
|
7715
|
+
|
|
7716
|
+
if (error) {
|
|
7717
|
+
return { error };
|
|
7718
|
+
}
|
|
7719
|
+
|
|
7720
|
+
const { address, ...details } = getBillingDetails(cart);
|
|
7721
|
+
|
|
7722
|
+
// fields the Payment Element does not collect must all be passed, null when unknown
|
|
7723
|
+
return stripe.createPaymentMethod({
|
|
7724
|
+
elements,
|
|
7725
|
+
params: {
|
|
7726
|
+
billing_details: {
|
|
7727
|
+
name: null,
|
|
7728
|
+
email: null,
|
|
7729
|
+
phone: null,
|
|
7730
|
+
...details,
|
|
7731
|
+
address: {
|
|
7732
|
+
line1: null,
|
|
7733
|
+
line2: null,
|
|
7734
|
+
city: null,
|
|
7735
|
+
state: null,
|
|
7736
|
+
postal_code: null,
|
|
7737
|
+
country: null,
|
|
7738
|
+
...address,
|
|
7739
|
+
},
|
|
7740
|
+
},
|
|
7741
|
+
},
|
|
7742
|
+
});
|
|
7743
|
+
}
|
|
7744
|
+
|
|
7745
|
+
/**
|
|
7746
|
+
* @param {Stripe} stripe
|
|
7747
|
+
* @param {import('@stripe/stripe-js').StripeElements} elements
|
|
7748
|
+
* @param {Cart} cart
|
|
7749
|
+
*/
|
|
7750
|
+
async function createCardPaymentMethodFromElements(
|
|
7751
|
+
stripe,
|
|
7752
|
+
elements,
|
|
7753
|
+
cart,
|
|
7754
|
+
) {
|
|
7755
|
+
const { paymentMethod, error } = await createPaymentMethodFromElements(
|
|
7756
|
+
stripe,
|
|
7757
|
+
elements,
|
|
7758
|
+
getCardElementsOptions(cart),
|
|
7759
|
+
cart,
|
|
7760
|
+
);
|
|
7761
|
+
|
|
7762
|
+
return error ? { error } : mapCardPaymentMethod(paymentMethod);
|
|
7763
|
+
}
|
|
7764
|
+
|
|
7765
|
+
/**
|
|
7766
|
+
* @param {import('@stripe/stripe-js').PaymentMethod} paymentMethod
|
|
7767
|
+
*/
|
|
7768
|
+
function mapCardPaymentMethod(paymentMethod) {
|
|
7769
|
+
return {
|
|
7770
|
+
gateway: 'stripe',
|
|
7771
|
+
token: paymentMethod.id,
|
|
7772
|
+
last4: paymentMethod.card.last4,
|
|
7773
|
+
exp_month: paymentMethod.card.exp_month,
|
|
7774
|
+
exp_year: paymentMethod.card.exp_year,
|
|
7775
|
+
brand: paymentMethod.card.brand,
|
|
7776
|
+
display_brand: paymentMethod.card.display_brand,
|
|
7777
|
+
address_check: paymentMethod.card.checks.address_line1_check,
|
|
7778
|
+
cvc_check: paymentMethod.card.checks.cvc_check,
|
|
7779
|
+
zip_check: paymentMethod.card.checks.address_postal_code_check,
|
|
7780
|
+
};
|
|
7781
|
+
}
|
|
7782
|
+
|
|
7783
|
+
/**
|
|
7784
|
+
* @param {Stripe} stripe
|
|
7785
|
+
* @param {import('@stripe/stripe-js').StripeIdealBankElement | {}} element `{}` when there is no idealBank element
|
|
7607
7786
|
* @param {Cart} cart
|
|
7608
7787
|
*/
|
|
7609
7788
|
async function createIDealPaymentMethod(stripe, element, cart) {
|
|
@@ -7791,6 +7970,29 @@
|
|
|
7791
7970
|
return !minAmount || amount >= minAmount;
|
|
7792
7971
|
}
|
|
7793
7972
|
|
|
7973
|
+
// The cart keeps what checkout v1 keeps, so the next tokenize can retrieve and reuse
|
|
7974
|
+
// the intent; the vault cancels the replaced ones when the order is paid
|
|
7975
|
+
const cartIntent = (cart, intent) => {
|
|
7976
|
+
const exIntent = cart.billing?.intent?.stripe;
|
|
7977
|
+
|
|
7978
|
+
return {
|
|
7979
|
+
id: intent.id,
|
|
7980
|
+
client_secret: intent.client_secret,
|
|
7981
|
+
amount: intent.amount,
|
|
7982
|
+
currency: intent.currency,
|
|
7983
|
+
...(Boolean(cart.auth_total) && { auth_amount: cart.auth_total }),
|
|
7984
|
+
...(exIntent?.id &&
|
|
7985
|
+
exIntent.id !== intent.id && {
|
|
7986
|
+
canceled: [...(exIntent.canceled || []), exIntent.id],
|
|
7987
|
+
}),
|
|
7988
|
+
};
|
|
7989
|
+
};
|
|
7990
|
+
|
|
7991
|
+
const intentAmount = ({ currency, capture_total, auth_total }) =>
|
|
7992
|
+
stripeAmountByCurrency(currency, capture_total + auth_total);
|
|
7993
|
+
|
|
7994
|
+
const intentCurrency = ({ currency }) => (currency || 'USD').toLowerCase();
|
|
7995
|
+
|
|
7794
7996
|
/** @typedef {import('@stripe/stripe-js').Stripe} Stripe */
|
|
7795
7997
|
/** @typedef {import('@stripe/stripe-js').StripeCardElement} StripeCardElement */
|
|
7796
7998
|
/** @typedef {import('@stripe/stripe-js').StripeCardNumberElement} StripeCardNumberElement */
|
|
@@ -7834,9 +8036,30 @@
|
|
|
7834
8036
|
StripeCardPayment.stripeElement = stripeElement;
|
|
7835
8037
|
}
|
|
7836
8038
|
|
|
7837
|
-
|
|
8039
|
+
/** @returns {import('@stripe/stripe-js').StripeElements} */
|
|
8040
|
+
get stripeElements() {
|
|
8041
|
+
return StripeCardPayment.stripeElements;
|
|
8042
|
+
}
|
|
8043
|
+
|
|
8044
|
+
/** @param {import('@stripe/stripe-js').StripeElements} stripeElements */
|
|
8045
|
+
set stripeElements(stripeElements) {
|
|
8046
|
+
StripeCardPayment.stripeElements = stripeElements;
|
|
8047
|
+
}
|
|
8048
|
+
|
|
8049
|
+
/** @param {import('../../../types').Cart} cart */
|
|
8050
|
+
async createElements(cart) {
|
|
7838
8051
|
await this.loadScripts(this.scripts);
|
|
7839
8052
|
|
|
8053
|
+
if (this.params.paymentElement) {
|
|
8054
|
+
this.stripeElements = createPaymentElements(
|
|
8055
|
+
this.stripe,
|
|
8056
|
+
getCardElementsOptions(cart),
|
|
8057
|
+
this.params,
|
|
8058
|
+
);
|
|
8059
|
+
|
|
8060
|
+
return;
|
|
8061
|
+
}
|
|
8062
|
+
|
|
7840
8063
|
const elements = this.stripe.elements(this.params.config);
|
|
7841
8064
|
|
|
7842
8065
|
if (this.params.separateElements) {
|
|
@@ -7849,18 +8072,26 @@
|
|
|
7849
8072
|
}
|
|
7850
8073
|
|
|
7851
8074
|
async tokenize() {
|
|
7852
|
-
|
|
8075
|
+
const { paymentElement, paymentMethod: token } = this.params;
|
|
8076
|
+
|
|
8077
|
+
if (token) {
|
|
8078
|
+
return this._authorizePaymentMethod(token);
|
|
8079
|
+
}
|
|
8080
|
+
|
|
8081
|
+
if (!(paymentElement ? this.stripeElements : this.stripeElement)) {
|
|
7853
8082
|
throw new Error('Stripe payment element is not defined');
|
|
7854
8083
|
}
|
|
7855
8084
|
|
|
7856
8085
|
await this.loadScripts(this.scripts);
|
|
7857
8086
|
|
|
7858
8087
|
const cart = await this.getCart();
|
|
7859
|
-
const paymentMethod =
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
8088
|
+
const paymentMethod = paymentElement
|
|
8089
|
+
? await createCardPaymentMethodFromElements(
|
|
8090
|
+
this.stripe,
|
|
8091
|
+
this.stripeElements,
|
|
8092
|
+
cart,
|
|
8093
|
+
)
|
|
8094
|
+
: await createPaymentMethod(this.stripe, this.stripeElement, cart);
|
|
7864
8095
|
|
|
7865
8096
|
if (paymentMethod.error) {
|
|
7866
8097
|
throw new Error(paymentMethod.error.message);
|
|
@@ -7870,8 +8101,12 @@
|
|
|
7870
8101
|
paymentMethod.test = true;
|
|
7871
8102
|
}
|
|
7872
8103
|
|
|
7873
|
-
// should save payment method data when payment amount is not chargeable
|
|
7874
|
-
|
|
8104
|
+
// should save payment method data when payment amount is not chargeable,
|
|
8105
|
+
// or when the host collects the card without authorizing it now
|
|
8106
|
+
if (
|
|
8107
|
+
!isStripeChargeableAmount(cart.capture_total, cart.currency) ||
|
|
8108
|
+
this.params.createIntent === false
|
|
8109
|
+
) {
|
|
7875
8110
|
await this.updateCart({
|
|
7876
8111
|
billing: {
|
|
7877
8112
|
method: 'card',
|
|
@@ -7882,60 +8117,129 @@
|
|
|
7882
8117
|
return this.onSuccess();
|
|
7883
8118
|
}
|
|
7884
8119
|
|
|
7885
|
-
const intent = await this.
|
|
8120
|
+
const intent = await this._authorizeIntent(cart, paymentMethod.token);
|
|
7886
8121
|
|
|
7887
8122
|
await this.updateCart({
|
|
7888
8123
|
billing: {
|
|
7889
8124
|
method: 'card',
|
|
7890
8125
|
card: paymentMethod,
|
|
7891
|
-
intent: {
|
|
7892
|
-
stripe: {
|
|
7893
|
-
id: intent.id,
|
|
7894
|
-
...(Boolean(cart.auth_total) && {
|
|
7895
|
-
auth_amount: cart.auth_total,
|
|
7896
|
-
}),
|
|
7897
|
-
},
|
|
7898
|
-
},
|
|
8126
|
+
intent: { stripe: cartIntent(cart, intent) },
|
|
7899
8127
|
},
|
|
7900
8128
|
});
|
|
7901
8129
|
|
|
7902
8130
|
this.onSuccess();
|
|
7903
8131
|
}
|
|
7904
8132
|
|
|
7905
|
-
|
|
7906
|
-
|
|
8133
|
+
// A card the shopper already has (a saved card, a wallet's payment method) gets
|
|
8134
|
+
// the intent and 3-D Secure of a new one; other cards are authorized with the order
|
|
8135
|
+
async _authorizePaymentMethod(token) {
|
|
8136
|
+
const cart = await this.getCart();
|
|
7907
8137
|
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
8138
|
+
if (
|
|
8139
|
+
token.startsWith('pm_') &&
|
|
8140
|
+
isStripeChargeableAmount(cart.capture_total, cart.currency) &&
|
|
8141
|
+
this.params.createIntent !== false
|
|
8142
|
+
) {
|
|
8143
|
+
await this.loadScripts(this.scripts);
|
|
8144
|
+
|
|
8145
|
+
const intent = await this._authorizeIntent(cart, token);
|
|
7912
8146
|
|
|
7913
|
-
|
|
7914
|
-
|
|
8147
|
+
if (intent.id !== cart.billing?.intent?.stripe?.id) {
|
|
8148
|
+
await this.updateCart({
|
|
8149
|
+
billing: {
|
|
8150
|
+
method: 'card',
|
|
8151
|
+
intent: { stripe: cartIntent(cart, intent) },
|
|
8152
|
+
},
|
|
8153
|
+
});
|
|
8154
|
+
}
|
|
7915
8155
|
}
|
|
7916
8156
|
|
|
7917
|
-
|
|
8157
|
+
this.onSuccess();
|
|
8158
|
+
}
|
|
8159
|
+
|
|
8160
|
+
// Reuses the cart's intent while Stripe still has it for this card and amount
|
|
8161
|
+
async _authorizeIntent(cart, token) {
|
|
8162
|
+
const exIntent = cart.billing?.intent?.stripe;
|
|
8163
|
+
const intent =
|
|
8164
|
+
(exIntent?.client_secret &&
|
|
8165
|
+
(await this._retrieveIntent(cart, exIntent, token))) ||
|
|
8166
|
+
(await this._createIntent(cart, token));
|
|
8167
|
+
|
|
8168
|
+
switch (intent.status) {
|
|
8169
|
+
case 'requires_capture':
|
|
8170
|
+
break;
|
|
8171
|
+
|
|
8172
|
+
case 'requires_confirmation':
|
|
8173
|
+
await this._confirmCardPayment(intent);
|
|
8174
|
+
break;
|
|
8175
|
+
|
|
8176
|
+
case 'requires_action':
|
|
8177
|
+
case 'requires_source_action': {
|
|
8178
|
+
const actionResult = await this.stripe.handleCardAction(
|
|
8179
|
+
intent.client_secret,
|
|
8180
|
+
);
|
|
8181
|
+
|
|
8182
|
+
if (actionResult.error) {
|
|
8183
|
+
throw new Error(actionResult.error.message);
|
|
8184
|
+
}
|
|
8185
|
+
|
|
8186
|
+
break;
|
|
8187
|
+
}
|
|
7918
8188
|
|
|
8189
|
+
default:
|
|
8190
|
+
throw new Error(`Unsupported intent status: ${intent.status}`);
|
|
8191
|
+
}
|
|
8192
|
+
|
|
8193
|
+
return intent;
|
|
8194
|
+
}
|
|
8195
|
+
|
|
8196
|
+
async _retrieveIntent(cart, exIntent, token) {
|
|
8197
|
+
const { paymentIntent: intent, error } =
|
|
8198
|
+
await this.stripe.retrievePaymentIntent(exIntent.client_secret);
|
|
8199
|
+
|
|
8200
|
+
const matches =
|
|
8201
|
+
!error &&
|
|
8202
|
+
intent.amount === intentAmount(cart) &&
|
|
8203
|
+
intent.currency === intentCurrency(cart) &&
|
|
8204
|
+
intent.payment_method === token &&
|
|
8205
|
+
!['canceled', 'requires_source', 'requires_payment_method'].includes(
|
|
8206
|
+
intent.status,
|
|
8207
|
+
);
|
|
8208
|
+
|
|
8209
|
+
return matches ? intent : null;
|
|
8210
|
+
}
|
|
8211
|
+
|
|
8212
|
+
async authenticate(payment) {
|
|
8213
|
+
const { transaction_id: id, card: { token } = {} } = payment;
|
|
8214
|
+
|
|
8215
|
+
// The payment is re-queried whatever happens here: an intent the shopper
|
|
8216
|
+
// already confirmed can't be updated, and the payment must still learn it
|
|
7919
8217
|
try {
|
|
7920
|
-
const
|
|
8218
|
+
const intent = await this.updateIntent({
|
|
8219
|
+
gateway: 'stripe',
|
|
8220
|
+
intent: { id, payment_method: token },
|
|
8221
|
+
});
|
|
7921
8222
|
|
|
7922
|
-
|
|
8223
|
+
if (intent.error) {
|
|
8224
|
+
throw new Error(intent.error.message);
|
|
8225
|
+
}
|
|
8226
|
+
|
|
8227
|
+
await this.loadScripts(this.scripts);
|
|
8228
|
+
|
|
8229
|
+
return await this._confirmCardPayment(intent);
|
|
7923
8230
|
} finally {
|
|
7924
8231
|
await this.resetAsyncPayment(payment.id);
|
|
7925
8232
|
}
|
|
7926
8233
|
}
|
|
7927
8234
|
|
|
7928
|
-
async _createIntent(cart,
|
|
7929
|
-
const
|
|
7930
|
-
const stripeCustomer = account && account.stripe_customer;
|
|
7931
|
-
const stripeCurrency = (currency || 'USD').toLowerCase();
|
|
7932
|
-
const amount = stripeAmountByCurrency(currency, capture_total + auth_total);
|
|
8235
|
+
async _createIntent(cart, token) {
|
|
8236
|
+
const stripeCustomer = cart.account && cart.account.stripe_customer;
|
|
7933
8237
|
const intent = await this.createIntent({
|
|
7934
8238
|
gateway: 'stripe',
|
|
7935
8239
|
intent: {
|
|
7936
|
-
amount,
|
|
7937
|
-
currency:
|
|
7938
|
-
payment_method:
|
|
8240
|
+
amount: intentAmount(cart),
|
|
8241
|
+
currency: intentCurrency(cart),
|
|
8242
|
+
payment_method: token,
|
|
7939
8243
|
capture_method: 'manual',
|
|
7940
8244
|
setup_future_usage: 'off_session',
|
|
7941
8245
|
...(stripeCustomer ? { customer: stripeCustomer } : {}),
|
|
@@ -7946,20 +8250,6 @@
|
|
|
7946
8250
|
throw new Error('Stripe payment intent is not defined');
|
|
7947
8251
|
}
|
|
7948
8252
|
|
|
7949
|
-
switch (intent.status) {
|
|
7950
|
-
case 'requires_capture':
|
|
7951
|
-
case 'requires_confirmation':
|
|
7952
|
-
break;
|
|
7953
|
-
|
|
7954
|
-
default:
|
|
7955
|
-
throw new Error(`Unsupported intent status: ${intent.status}`);
|
|
7956
|
-
}
|
|
7957
|
-
|
|
7958
|
-
// Confirm the payment intent
|
|
7959
|
-
if (intent.status === 'requires_confirmation') {
|
|
7960
|
-
await this._confirmCardPayment(intent);
|
|
7961
|
-
}
|
|
7962
|
-
|
|
7963
8253
|
return intent;
|
|
7964
8254
|
}
|
|
7965
8255
|
|
|
@@ -8027,24 +8317,58 @@
|
|
|
8027
8317
|
StripeIDealPayment.stripeElement = stripeElement;
|
|
8028
8318
|
}
|
|
8029
8319
|
|
|
8030
|
-
|
|
8320
|
+
/** @returns {import('@stripe/stripe-js').StripeElements} */
|
|
8321
|
+
get stripeElements() {
|
|
8322
|
+
return StripeIDealPayment.stripeElements;
|
|
8323
|
+
}
|
|
8324
|
+
|
|
8325
|
+
/** @param {import('@stripe/stripe-js').StripeElements} stripeElements */
|
|
8326
|
+
set stripeElements(stripeElements) {
|
|
8327
|
+
StripeIDealPayment.stripeElements = stripeElements;
|
|
8328
|
+
}
|
|
8329
|
+
|
|
8330
|
+
/** @param {import('../../../types').Cart} cart */
|
|
8331
|
+
async createElements(cart) {
|
|
8031
8332
|
await this.loadScripts(this.scripts);
|
|
8032
8333
|
|
|
8334
|
+
if (this.params.paymentElement) {
|
|
8335
|
+
this.stripeElements = createPaymentElements(
|
|
8336
|
+
this.stripe,
|
|
8337
|
+
getIDealElementsOptions(cart),
|
|
8338
|
+
this.params,
|
|
8339
|
+
);
|
|
8340
|
+
|
|
8341
|
+
return;
|
|
8342
|
+
}
|
|
8343
|
+
|
|
8033
8344
|
const elements = this.stripe.elements(this.params.config);
|
|
8034
8345
|
|
|
8035
8346
|
this.stripeElement = createElement('idealBank', elements, this.params);
|
|
8036
8347
|
}
|
|
8037
8348
|
|
|
8038
8349
|
async tokenize() {
|
|
8039
|
-
|
|
8350
|
+
const { paymentElement } = this.params;
|
|
8351
|
+
|
|
8352
|
+
if (paymentElement && !this.stripeElements) {
|
|
8040
8353
|
throw new Error('Stripe payment element is not defined');
|
|
8041
8354
|
}
|
|
8042
8355
|
|
|
8043
8356
|
await this.loadScripts(this.scripts);
|
|
8044
8357
|
|
|
8045
8358
|
const cart = await this.getCart();
|
|
8046
|
-
const { paymentMethod, error: paymentMethodError } =
|
|
8047
|
-
await
|
|
8359
|
+
const { paymentMethod, error: paymentMethodError } = paymentElement
|
|
8360
|
+
? await createPaymentMethodFromElements(
|
|
8361
|
+
this.stripe,
|
|
8362
|
+
this.stripeElements,
|
|
8363
|
+
getIDealElementsOptions(cart),
|
|
8364
|
+
cart,
|
|
8365
|
+
)
|
|
8366
|
+
: // without an idealBank element the shopper picks the bank on the iDEAL page
|
|
8367
|
+
await createIDealPaymentMethod(
|
|
8368
|
+
this.stripe,
|
|
8369
|
+
this.stripeElement || {},
|
|
8370
|
+
cart,
|
|
8371
|
+
);
|
|
8048
8372
|
|
|
8049
8373
|
if (paymentMethodError) {
|
|
8050
8374
|
throw new Error(paymentMethodError.message);
|
|
@@ -8055,6 +8379,38 @@
|
|
|
8055
8379
|
await this.stripe.handleCardAction(intent.client_secret);
|
|
8056
8380
|
}
|
|
8057
8381
|
|
|
8382
|
+
async handleRedirect(queryParams) {
|
|
8383
|
+
const { redirect_status, payment_intent_client_secret } = queryParams;
|
|
8384
|
+
|
|
8385
|
+
if (redirect_status !== 'succeeded') {
|
|
8386
|
+
throw new UnableAuthenticatePaymentMethodError();
|
|
8387
|
+
}
|
|
8388
|
+
|
|
8389
|
+
await this.loadScripts(this.scripts);
|
|
8390
|
+
|
|
8391
|
+
const { paymentIntent, error } = await this.stripe.retrievePaymentIntent(
|
|
8392
|
+
payment_intent_client_secret,
|
|
8393
|
+
);
|
|
8394
|
+
|
|
8395
|
+
if (error) {
|
|
8396
|
+
throw new Error(error.message);
|
|
8397
|
+
}
|
|
8398
|
+
|
|
8399
|
+
if (
|
|
8400
|
+
!['succeeded', 'requires_capture', 'processing'].includes(
|
|
8401
|
+
paymentIntent.status,
|
|
8402
|
+
)
|
|
8403
|
+
) {
|
|
8404
|
+
throw new Error(
|
|
8405
|
+
paymentIntent.last_payment_error?.message ||
|
|
8406
|
+
`Unsupported intent status (${paymentIntent.status})`,
|
|
8407
|
+
);
|
|
8408
|
+
}
|
|
8409
|
+
|
|
8410
|
+
// the cart's billing was written in `tokenize`
|
|
8411
|
+
this.onSuccess();
|
|
8412
|
+
}
|
|
8413
|
+
|
|
8058
8414
|
/**
|
|
8059
8415
|
* @param {object} cart
|
|
8060
8416
|
* @param {import('@stripe/stripe-js').PaymentMethod} paymentMethod
|
|
@@ -8072,7 +8428,8 @@
|
|
|
8072
8428
|
payment_method_types: 'ideal',
|
|
8073
8429
|
confirmation_method: 'manual',
|
|
8074
8430
|
confirm: true,
|
|
8075
|
-
|
|
8431
|
+
// `gateway` lets `handleRedirect` run on return; Stripe appends the intent and `redirect_status`
|
|
8432
|
+
return_url: getReturnUrl(),
|
|
8076
8433
|
},
|
|
8077
8434
|
});
|
|
8078
8435
|
|
|
@@ -8104,6 +8461,14 @@
|
|
|
8104
8461
|
}
|
|
8105
8462
|
}
|
|
8106
8463
|
|
|
8464
|
+
function getReturnUrl() {
|
|
8465
|
+
const url = new URL(window.location.href);
|
|
8466
|
+
|
|
8467
|
+
url.searchParams.set('gateway', 'stripe');
|
|
8468
|
+
|
|
8469
|
+
return url.toString();
|
|
8470
|
+
}
|
|
8471
|
+
|
|
8107
8472
|
/** @typedef {import('@stripe/stripe-js').Stripe} Stripe */
|
|
8108
8473
|
|
|
8109
8474
|
class StripeBancontactPayment extends Payment {
|
|
@@ -8423,6 +8788,7 @@
|
|
|
8423
8788
|
async _onPaymentMethod(event) {
|
|
8424
8789
|
const {
|
|
8425
8790
|
payerEmail,
|
|
8791
|
+
payerName,
|
|
8426
8792
|
paymentMethod: { id: paymentMethod, card, billing_details },
|
|
8427
8793
|
shippingAddress,
|
|
8428
8794
|
shippingOption,
|
|
@@ -8441,6 +8807,8 @@
|
|
|
8441
8807
|
}),
|
|
8442
8808
|
billing: {
|
|
8443
8809
|
...this._mapBillingAddress(billing_details),
|
|
8810
|
+
// a wallet may leave the card's address without a name; the payer's, when asked for, fills it
|
|
8811
|
+
name: billing_details?.name || payerName,
|
|
8444
8812
|
method: 'card',
|
|
8445
8813
|
card: {
|
|
8446
8814
|
gateway: 'stripe',
|
|
@@ -8679,6 +9047,7 @@
|
|
|
8679
9047
|
async _onPaymentMethod(event) {
|
|
8680
9048
|
const {
|
|
8681
9049
|
payerEmail,
|
|
9050
|
+
payerName,
|
|
8682
9051
|
paymentMethod: { id: paymentMethod, card, billing_details },
|
|
8683
9052
|
shippingAddress,
|
|
8684
9053
|
shippingOption,
|
|
@@ -8697,6 +9066,8 @@
|
|
|
8697
9066
|
}),
|
|
8698
9067
|
billing: {
|
|
8699
9068
|
...this._mapBillingAddress(billing_details),
|
|
9069
|
+
// a wallet may leave the card's address without a name; the payer's, when asked for, fills it
|
|
9070
|
+
name: billing_details?.name || payerName,
|
|
8700
9071
|
method: 'card',
|
|
8701
9072
|
card: {
|
|
8702
9073
|
gateway: 'stripe',
|
|
@@ -11127,6 +11498,238 @@
|
|
|
11127
11498
|
}
|
|
11128
11499
|
}
|
|
11129
11500
|
|
|
11501
|
+
// Saferpay payment-page brands per Swell method id; a list is joined because the
|
|
11502
|
+
// vault rejects arrays in `PaymentMethods`
|
|
11503
|
+
const PAYMENT_METHODS = {
|
|
11504
|
+
card: [
|
|
11505
|
+
'AMEX',
|
|
11506
|
+
'DINERS',
|
|
11507
|
+
'MAESTRO',
|
|
11508
|
+
'JCB',
|
|
11509
|
+
'VISA',
|
|
11510
|
+
'BANCONTACT',
|
|
11511
|
+
'MASTERCARD',
|
|
11512
|
+
'SAFERPAYTEST',
|
|
11513
|
+
'UNIONPAY',
|
|
11514
|
+
].join(','),
|
|
11515
|
+
twint: 'TWINT',
|
|
11516
|
+
};
|
|
11517
|
+
|
|
11518
|
+
/**
|
|
11519
|
+
* @param {string} method 'card' or 'twint'
|
|
11520
|
+
* @returns {string}
|
|
11521
|
+
*/
|
|
11522
|
+
function getSaferpayPaymentMethods(method) {
|
|
11523
|
+
return PAYMENT_METHODS[method];
|
|
11524
|
+
}
|
|
11525
|
+
|
|
11526
|
+
/**
|
|
11527
|
+
* The Saferpay payment page request for a cart.
|
|
11528
|
+
*
|
|
11529
|
+
* @param {object} cart
|
|
11530
|
+
* @param {string} method 'card' or 'twint'
|
|
11531
|
+
* @returns {object}
|
|
11532
|
+
*/
|
|
11533
|
+
function getSaferpayPaymentPageData(cart, method) {
|
|
11534
|
+
const currency = (cart.currency || 'USD').toUpperCase();
|
|
11535
|
+
const returnUrl = window.location.origin + window.location.pathname;
|
|
11536
|
+
|
|
11537
|
+
return {
|
|
11538
|
+
Payment: {
|
|
11539
|
+
Amount: {
|
|
11540
|
+
Value: amountInCents(currency, cart.grand_total || 0),
|
|
11541
|
+
CurrencyCode: currency,
|
|
11542
|
+
},
|
|
11543
|
+
Description: 'Saferpay payment powered by Swell',
|
|
11544
|
+
},
|
|
11545
|
+
PaymentMethods: getSaferpayPaymentMethods(method),
|
|
11546
|
+
ReturnUrls: {
|
|
11547
|
+
Success: `${returnUrl}?gateway=saferpay&redirect_status=succeeded`,
|
|
11548
|
+
Fail: `${returnUrl}?gateway=saferpay&redirect_status=failed`,
|
|
11549
|
+
Abort: `${returnUrl}?gateway=saferpay&redirect_status=canceled`,
|
|
11550
|
+
},
|
|
11551
|
+
};
|
|
11552
|
+
}
|
|
11553
|
+
|
|
11554
|
+
/**
|
|
11555
|
+
* @param {object} state the Saferpay transaction state
|
|
11556
|
+
* @returns {boolean}
|
|
11557
|
+
*/
|
|
11558
|
+
function isSaferpayCardPayment(state) {
|
|
11559
|
+
const brand = state?.PaymentMeans?.Brand;
|
|
11560
|
+
|
|
11561
|
+
return Boolean(
|
|
11562
|
+
state?.RegistrationResult?.Success &&
|
|
11563
|
+
brand &&
|
|
11564
|
+
PAYMENT_METHODS.card.split(',').includes(brand.PaymentMethod),
|
|
11565
|
+
);
|
|
11566
|
+
}
|
|
11567
|
+
|
|
11568
|
+
/**
|
|
11569
|
+
* @param {object} state the Saferpay transaction state
|
|
11570
|
+
* @returns {boolean}
|
|
11571
|
+
*/
|
|
11572
|
+
function isSaferpayTwintPayment(state) {
|
|
11573
|
+
return state?.PaymentMeans?.Brand?.PaymentMethod === 'TWINT';
|
|
11574
|
+
}
|
|
11575
|
+
|
|
11576
|
+
/**
|
|
11577
|
+
* The card stored on the cart's billing from a Saferpay card state.
|
|
11578
|
+
*
|
|
11579
|
+
* @param {object} state
|
|
11580
|
+
* @returns {object}
|
|
11581
|
+
*/
|
|
11582
|
+
function getSaferpayCard(state) {
|
|
11583
|
+
const {
|
|
11584
|
+
RegistrationResult: {
|
|
11585
|
+
Alias: { Id: token },
|
|
11586
|
+
},
|
|
11587
|
+
PaymentMeans: {
|
|
11588
|
+
Card: { ExpMonth: exp_month, ExpYear: exp_year, MaskedNumber },
|
|
11589
|
+
Brand: { Name: brand },
|
|
11590
|
+
},
|
|
11591
|
+
} = state;
|
|
11592
|
+
|
|
11593
|
+
return {
|
|
11594
|
+
token,
|
|
11595
|
+
exp_month,
|
|
11596
|
+
exp_year,
|
|
11597
|
+
brand,
|
|
11598
|
+
last4: MaskedNumber.slice(-4),
|
|
11599
|
+
gateway: 'saferpay',
|
|
11600
|
+
};
|
|
11601
|
+
}
|
|
11602
|
+
|
|
11603
|
+
/**
|
|
11604
|
+
* @param {object} state
|
|
11605
|
+
* @returns {string | undefined}
|
|
11606
|
+
*/
|
|
11607
|
+
function getSaferpayTransactionId(state) {
|
|
11608
|
+
return state?.Transaction?.Id;
|
|
11609
|
+
}
|
|
11610
|
+
|
|
11611
|
+
/**
|
|
11612
|
+
* Saferpay payment page: `tokenize` sends the shopper to Saferpay, and
|
|
11613
|
+
* `handleRedirect` turns the returned transaction into the cart's billing.
|
|
11614
|
+
* The card and TWINT classes share it; each one handles only the redirect of
|
|
11615
|
+
* the method the cart was tokenized with, so a host can pass both.
|
|
11616
|
+
*/
|
|
11617
|
+
class SaferpayPayment extends Payment {
|
|
11618
|
+
constructor(api, options, params, methods, methodId) {
|
|
11619
|
+
super(api, options, params, methods[methodId]);
|
|
11620
|
+
this.methodId = methodId;
|
|
11621
|
+
this.cardMethod = methods.card;
|
|
11622
|
+
}
|
|
11623
|
+
|
|
11624
|
+
async tokenize() {
|
|
11625
|
+
const cart = await this.getCart();
|
|
11626
|
+
|
|
11627
|
+
// a card Saferpay already registered on this cart pays again without a new page visit
|
|
11628
|
+
if (
|
|
11629
|
+
this.methodId === 'card' &&
|
|
11630
|
+
cart.billing?.card?.gateway === 'saferpay' &&
|
|
11631
|
+
!this.params?.replace
|
|
11632
|
+
) {
|
|
11633
|
+
return this.onSuccess();
|
|
11634
|
+
}
|
|
11635
|
+
|
|
11636
|
+
const intent = await this.createIntent({
|
|
11637
|
+
gateway: 'saferpay',
|
|
11638
|
+
intent: getSaferpayPaymentPageData(cart, this.methodId),
|
|
11639
|
+
});
|
|
11640
|
+
|
|
11641
|
+
if (!intent) {
|
|
11642
|
+
throw new Error('Saferpay payment page is not defined');
|
|
11643
|
+
}
|
|
11644
|
+
|
|
11645
|
+
await this.updateCart({
|
|
11646
|
+
billing: {
|
|
11647
|
+
method: this.methodId,
|
|
11648
|
+
intent: {
|
|
11649
|
+
saferpay: {
|
|
11650
|
+
token: intent.token,
|
|
11651
|
+
},
|
|
11652
|
+
},
|
|
11653
|
+
},
|
|
11654
|
+
});
|
|
11655
|
+
|
|
11656
|
+
window.location.replace(intent.redirect_url);
|
|
11657
|
+
}
|
|
11658
|
+
|
|
11659
|
+
async handleRedirect(queryParams) {
|
|
11660
|
+
const { redirect_status: status } = queryParams;
|
|
11661
|
+
const cart = await this.getCart();
|
|
11662
|
+
|
|
11663
|
+
// another Saferpay method of this host was tokenized: its instance answers
|
|
11664
|
+
if (cart.billing?.method !== this.methodId) {
|
|
11665
|
+
return;
|
|
11666
|
+
}
|
|
11667
|
+
|
|
11668
|
+
switch (status) {
|
|
11669
|
+
case 'succeeded':
|
|
11670
|
+
return this._handleSuccessfulRedirect(cart);
|
|
11671
|
+
case 'canceled':
|
|
11672
|
+
return this.onCancel();
|
|
11673
|
+
case 'failed':
|
|
11674
|
+
throw new UnableAuthenticatePaymentMethodError();
|
|
11675
|
+
default:
|
|
11676
|
+
throw new Error(`Unknown redirect status: ${status}.`);
|
|
11677
|
+
}
|
|
11678
|
+
}
|
|
11679
|
+
|
|
11680
|
+
async _handleSuccessfulRedirect(cart) {
|
|
11681
|
+
const token = cart.billing?.intent?.saferpay?.token;
|
|
11682
|
+
|
|
11683
|
+
if (!token) {
|
|
11684
|
+
throw new Error('Saferpay transaction token is not defined');
|
|
11685
|
+
}
|
|
11686
|
+
|
|
11687
|
+
const intent = await this.updateIntent({
|
|
11688
|
+
gateway: 'saferpay',
|
|
11689
|
+
intent: { token },
|
|
11690
|
+
});
|
|
11691
|
+
|
|
11692
|
+
if (!intent) {
|
|
11693
|
+
throw new Error('Saferpay transaction is not defined');
|
|
11694
|
+
}
|
|
11695
|
+
|
|
11696
|
+
const { state } = intent;
|
|
11697
|
+
const transaction = { transaction_id: getSaferpayTransactionId(state) };
|
|
11698
|
+
|
|
11699
|
+
if (isSaferpayCardPayment(state)) {
|
|
11700
|
+
const card = getSaferpayCard(state);
|
|
11701
|
+
|
|
11702
|
+
if (!isLiveMode(this.cardMethod?.mode)) {
|
|
11703
|
+
card.test = true;
|
|
11704
|
+
}
|
|
11705
|
+
|
|
11706
|
+
await this.updateCart({
|
|
11707
|
+
billing: { method: 'card', card, intent: { saferpay: transaction } },
|
|
11708
|
+
});
|
|
11709
|
+
} else if (isSaferpayTwintPayment(state)) {
|
|
11710
|
+
await this.updateCart({
|
|
11711
|
+
billing: { method: 'twint', intent: { saferpay: transaction } },
|
|
11712
|
+
});
|
|
11713
|
+
} else {
|
|
11714
|
+
throw new Error('Payment method not supported by this gateway.');
|
|
11715
|
+
}
|
|
11716
|
+
|
|
11717
|
+
this.onSuccess();
|
|
11718
|
+
}
|
|
11719
|
+
}
|
|
11720
|
+
|
|
11721
|
+
class SaferpayCardPayment extends SaferpayPayment {
|
|
11722
|
+
constructor(api, options, params, methods) {
|
|
11723
|
+
super(api, options, params, methods, 'card');
|
|
11724
|
+
}
|
|
11725
|
+
}
|
|
11726
|
+
|
|
11727
|
+
class SaferpayTwintPayment extends SaferpayPayment {
|
|
11728
|
+
constructor(api, options, params, methods) {
|
|
11729
|
+
super(api, options, params, methods, 'twint');
|
|
11730
|
+
}
|
|
11731
|
+
}
|
|
11732
|
+
|
|
11130
11733
|
let convesioSettings = null;
|
|
11131
11734
|
|
|
11132
11735
|
function getBaseConvesioApiUrl(mode) {
|
|
@@ -12807,6 +13410,33 @@
|
|
|
12807
13410
|
return this.api.request('get', '/payments', id);
|
|
12808
13411
|
}
|
|
12809
13412
|
|
|
13413
|
+
/**
|
|
13414
|
+
* Sends the shopper to the gateway when a submitted order waits for an
|
|
13415
|
+
* asynchronous payment; resolves `true` after starting the redirect.
|
|
13416
|
+
*
|
|
13417
|
+
* @param {object} order the result of `cart.submitOrder()`
|
|
13418
|
+
* @returns {Promise<boolean>}
|
|
13419
|
+
*/
|
|
13420
|
+
async handleAsyncRedirect(order) {
|
|
13421
|
+
const paymentId =
|
|
13422
|
+
order?.authorized_payment_id ?? order?.authorizedPaymentId;
|
|
13423
|
+
|
|
13424
|
+
if (order?.status !== 'payment_pending' || !paymentId) {
|
|
13425
|
+
return false;
|
|
13426
|
+
}
|
|
13427
|
+
|
|
13428
|
+
const payment = await this.get(paymentId);
|
|
13429
|
+
const url = payment?.async_data?.url ?? payment?.asyncData?.url;
|
|
13430
|
+
|
|
13431
|
+
if (!payment || payment.captured || !url) {
|
|
13432
|
+
return false;
|
|
13433
|
+
}
|
|
13434
|
+
|
|
13435
|
+
window.location.replace(url);
|
|
13436
|
+
|
|
13437
|
+
return true;
|
|
13438
|
+
}
|
|
13439
|
+
|
|
12810
13440
|
async methods() {
|
|
12811
13441
|
if (this.methodSettings) {
|
|
12812
13442
|
return this.methodSettings;
|
|
@@ -12824,7 +13454,7 @@
|
|
|
12824
13454
|
throw new Error('Payment element parameters are not provided');
|
|
12825
13455
|
}
|
|
12826
13456
|
|
|
12827
|
-
const paymentInstances = await this._createPaymentInstances();
|
|
13457
|
+
const paymentInstances = await this._createPaymentInstances(params);
|
|
12828
13458
|
const cart = await this.payment.getCart();
|
|
12829
13459
|
|
|
12830
13460
|
await this._performPaymentAction(
|
|
@@ -12843,7 +13473,7 @@
|
|
|
12843
13473
|
throw new Error('Tokenization parameters are not provided');
|
|
12844
13474
|
}
|
|
12845
13475
|
|
|
12846
|
-
const paymentInstances = await this._createPaymentInstances();
|
|
13476
|
+
const paymentInstances = await this._createPaymentInstances(params);
|
|
12847
13477
|
|
|
12848
13478
|
await this._performPaymentAction(paymentInstances, 'tokenize');
|
|
12849
13479
|
}
|
|
@@ -12863,7 +13493,7 @@
|
|
|
12863
13493
|
|
|
12864
13494
|
removeUrlParams();
|
|
12865
13495
|
|
|
12866
|
-
const paymentInstances = await this._createPaymentInstances();
|
|
13496
|
+
const paymentInstances = await this._createPaymentInstances(params);
|
|
12867
13497
|
|
|
12868
13498
|
await this._performPaymentAction(
|
|
12869
13499
|
paymentInstances,
|
|
@@ -12934,7 +13564,7 @@
|
|
|
12934
13564
|
}
|
|
12935
13565
|
|
|
12936
13566
|
async _getPaymentMethods() {
|
|
12937
|
-
const paymentMethods = await methods$
|
|
13567
|
+
const paymentMethods = await methods$5(this.api, this.options).payments();
|
|
12938
13568
|
|
|
12939
13569
|
if (paymentMethods.error) {
|
|
12940
13570
|
throw new Error(paymentMethods.error);
|
|
@@ -12956,9 +13586,10 @@
|
|
|
12956
13586
|
/**
|
|
12957
13587
|
* @returns {Promise<Payment[]>}
|
|
12958
13588
|
*/
|
|
12959
|
-
|
|
13589
|
+
// Takes the caller's params: this.params can change while the methods load (two createElements at once)
|
|
13590
|
+
async _createPaymentInstances(callParams) {
|
|
12960
13591
|
const paymentMethods = await this._getPaymentMethods();
|
|
12961
|
-
const params = adjustParams(
|
|
13592
|
+
const params = adjustParams(callParams);
|
|
12962
13593
|
|
|
12963
13594
|
return Object.entries(params).reduce((acc, [method, params]) => {
|
|
12964
13595
|
const methodSettings = paymentMethods[method];
|
|
@@ -13056,6 +13687,8 @@
|
|
|
13056
13687
|
return this._getAmazonPaymentClass(gateway);
|
|
13057
13688
|
case 'sezzle':
|
|
13058
13689
|
return this._getSezzlePaymentClass(gateway);
|
|
13690
|
+
case 'twint':
|
|
13691
|
+
return this._getTwintPaymentClass(gateway);
|
|
13059
13692
|
default:
|
|
13060
13693
|
return null;
|
|
13061
13694
|
}
|
|
@@ -13069,6 +13702,17 @@
|
|
|
13069
13702
|
return QuickpayCardPayment;
|
|
13070
13703
|
case 'convesiopay':
|
|
13071
13704
|
return ConvesioCardPayment;
|
|
13705
|
+
case 'saferpay':
|
|
13706
|
+
return SaferpayCardPayment;
|
|
13707
|
+
default:
|
|
13708
|
+
return null;
|
|
13709
|
+
}
|
|
13710
|
+
}
|
|
13711
|
+
|
|
13712
|
+
_getTwintPaymentClass(gateway) {
|
|
13713
|
+
switch (gateway) {
|
|
13714
|
+
case 'saferpay':
|
|
13715
|
+
return SaferpayTwintPayment;
|
|
13072
13716
|
default:
|
|
13073
13717
|
return null;
|
|
13074
13718
|
}
|
|
@@ -13164,7 +13808,7 @@
|
|
|
13164
13808
|
}
|
|
13165
13809
|
}
|
|
13166
13810
|
|
|
13167
|
-
function methods$
|
|
13811
|
+
function methods$3(api, opt) {
|
|
13168
13812
|
return {
|
|
13169
13813
|
code: null,
|
|
13170
13814
|
state: null,
|
|
@@ -13220,7 +13864,7 @@
|
|
|
13220
13864
|
return typeof navigator === 'object' ? navigator.language : 'en-US';
|
|
13221
13865
|
}
|
|
13222
13866
|
|
|
13223
|
-
function methods$
|
|
13867
|
+
function methods$2(api, opt) {
|
|
13224
13868
|
// Reads a store setting without ever returning a Promise. Until settings
|
|
13225
13869
|
// load, this starts the request and reports `loaded: false`.
|
|
13226
13870
|
function readSetting(path, def) {
|
|
@@ -13441,7 +14085,7 @@
|
|
|
13441
14085
|
};
|
|
13442
14086
|
}
|
|
13443
14087
|
|
|
13444
|
-
function methods(api, _opt) {
|
|
14088
|
+
function methods$1(api, _opt) {
|
|
13445
14089
|
return {
|
|
13446
14090
|
/**
|
|
13447
14091
|
* Make a request to an app function and greceiveet a response
|
|
@@ -13510,6 +14154,85 @@
|
|
|
13510
14154
|
};
|
|
13511
14155
|
}
|
|
13512
14156
|
|
|
14157
|
+
const PURPOSES = ['analytics', 'marketing', 'sale_of_data'];
|
|
14158
|
+
|
|
14159
|
+
/**
|
|
14160
|
+
* The page the shopper landed on, which the consent call sends along: the
|
|
14161
|
+
* first URL of this document and its referrer. Read when needed, so nothing
|
|
14162
|
+
* is kept before the shopper answers.
|
|
14163
|
+
*
|
|
14164
|
+
* @returns {{url: string, referrer: string} | undefined}
|
|
14165
|
+
*/
|
|
14166
|
+
function getLanding() {
|
|
14167
|
+
if (isServer()) {
|
|
14168
|
+
return undefined;
|
|
14169
|
+
}
|
|
14170
|
+
|
|
14171
|
+
let url = window.location.href;
|
|
14172
|
+
|
|
14173
|
+
try {
|
|
14174
|
+
const [navigation] = window.performance.getEntriesByType('navigation');
|
|
14175
|
+
if (navigation?.name) {
|
|
14176
|
+
url = navigation.name;
|
|
14177
|
+
}
|
|
14178
|
+
} catch {
|
|
14179
|
+
// Navigation timing is not available
|
|
14180
|
+
}
|
|
14181
|
+
|
|
14182
|
+
return { url, referrer: window.document.referrer || '' };
|
|
14183
|
+
}
|
|
14184
|
+
|
|
14185
|
+
function methods(api) {
|
|
14186
|
+
// Calls run one after another. Consent tools fire several events for one
|
|
14187
|
+
// answer, and the first call may set the visitor cookie that the next one
|
|
14188
|
+
// has to send: in parallel, each would start a visitor of its own.
|
|
14189
|
+
let queue = Promise.resolve();
|
|
14190
|
+
|
|
14191
|
+
return {
|
|
14192
|
+
/**
|
|
14193
|
+
* Records the shopper's consent for attribution, from the storefront's
|
|
14194
|
+
* consent banner. Purposes left out stay as they are.
|
|
14195
|
+
*
|
|
14196
|
+
* Calls the storefront's own origin, so that Swell sees the page's
|
|
14197
|
+
* cookies and sets the visitor cookie on the storefront's domain.
|
|
14198
|
+
*
|
|
14199
|
+
* @param {{analytics?: boolean, marketing?: boolean, sale_of_data?: boolean}} consent
|
|
14200
|
+
*/
|
|
14201
|
+
set(consent = {}) {
|
|
14202
|
+
if (isServer()) {
|
|
14203
|
+
return Promise.reject(new Error('consent.set() runs in the browser'));
|
|
14204
|
+
}
|
|
14205
|
+
|
|
14206
|
+
const data = {};
|
|
14207
|
+
|
|
14208
|
+
for (const purpose of PURPOSES) {
|
|
14209
|
+
// camelCase clients pass saleOfData
|
|
14210
|
+
const camel = purpose.replace(/_(\w)/g, (_m, c) => c.toUpperCase());
|
|
14211
|
+
const value = consent[purpose] ?? consent[camel];
|
|
14212
|
+
|
|
14213
|
+
if (typeof value === 'boolean') {
|
|
14214
|
+
data[purpose] = value;
|
|
14215
|
+
}
|
|
14216
|
+
}
|
|
14217
|
+
|
|
14218
|
+
const result = queue.then(() =>
|
|
14219
|
+
api.request(
|
|
14220
|
+
'put',
|
|
14221
|
+
'/session/consent',
|
|
14222
|
+
{ ...data, landing: getLanding() },
|
|
14223
|
+
undefined,
|
|
14224
|
+
{ fullUrl: `${window.location.origin}/api/session/consent` },
|
|
14225
|
+
),
|
|
14226
|
+
);
|
|
14227
|
+
|
|
14228
|
+
// A failed call doesn't hold back the next one
|
|
14229
|
+
queue = result.catch(() => undefined);
|
|
14230
|
+
|
|
14231
|
+
return result;
|
|
14232
|
+
},
|
|
14233
|
+
};
|
|
14234
|
+
}
|
|
14235
|
+
|
|
13513
14236
|
/**
|
|
13514
14237
|
* @template {'snake' | 'camel'} C
|
|
13515
14238
|
* @typedef {import('../types').InitOptions<C>} InitOptions<C>
|
|
@@ -13541,7 +14264,7 @@
|
|
|
13541
14264
|
const api = {};
|
|
13542
14265
|
|
|
13543
14266
|
Object.assign(api, {
|
|
13544
|
-
version: '5.
|
|
14267
|
+
version: '5.10.0',
|
|
13545
14268
|
options,
|
|
13546
14269
|
request,
|
|
13547
14270
|
|
|
@@ -13595,39 +14318,41 @@
|
|
|
13595
14318
|
return api.request('delete', url, data);
|
|
13596
14319
|
},
|
|
13597
14320
|
|
|
13598
|
-
cache: methods$
|
|
14321
|
+
cache: methods$e(options),
|
|
13599
14322
|
|
|
13600
|
-
card: methods$
|
|
14323
|
+
card: methods$f(options),
|
|
13601
14324
|
|
|
13602
|
-
cart: methods$
|
|
14325
|
+
cart: methods$b(api, options),
|
|
13603
14326
|
|
|
13604
|
-
account: methods$
|
|
14327
|
+
account: methods$a(api),
|
|
13605
14328
|
|
|
13606
14329
|
app: new AppController(api, options),
|
|
13607
14330
|
|
|
13608
|
-
products: methods$
|
|
14331
|
+
products: methods$c(api, options),
|
|
13609
14332
|
|
|
13610
|
-
categories: methods$
|
|
14333
|
+
categories: methods$9(api),
|
|
13611
14334
|
|
|
13612
|
-
attributes: methods$
|
|
14335
|
+
attributes: methods$d(api),
|
|
13613
14336
|
|
|
13614
|
-
subscriptions: methods$
|
|
14337
|
+
subscriptions: methods$8(api),
|
|
13615
14338
|
|
|
13616
|
-
invoices: methods$
|
|
14339
|
+
invoices: methods$7(api),
|
|
13617
14340
|
|
|
13618
|
-
content: methods$
|
|
14341
|
+
content: methods$6(api, options),
|
|
13619
14342
|
|
|
13620
|
-
settings: methods$
|
|
14343
|
+
settings: methods$5(api, options),
|
|
13621
14344
|
|
|
13622
14345
|
payment: new PaymentController(api, options),
|
|
13623
14346
|
|
|
13624
|
-
locale: methods$
|
|
14347
|
+
locale: methods$3(api, options),
|
|
13625
14348
|
|
|
13626
|
-
currency: methods$
|
|
14349
|
+
currency: methods$2(api, options),
|
|
13627
14350
|
|
|
13628
|
-
session: methods$
|
|
14351
|
+
session: methods$4(api, options),
|
|
13629
14352
|
|
|
13630
|
-
functions: methods(api),
|
|
14353
|
+
functions: methods$1(api),
|
|
14354
|
+
|
|
14355
|
+
consent: methods(api),
|
|
13631
14356
|
|
|
13632
14357
|
utils,
|
|
13633
14358
|
|
|
@@ -13701,6 +14426,13 @@
|
|
|
13701
14426
|
reqHeaders['X-Session'] = session;
|
|
13702
14427
|
}
|
|
13703
14428
|
|
|
14429
|
+
// The storefront's visitor, for attribution: the cookie is on the
|
|
14430
|
+
// storefront's domain and doesn't reach the store's API host
|
|
14431
|
+
const visitor = allOptions.getCookie('swell-visitor');
|
|
14432
|
+
if (visitor) {
|
|
14433
|
+
reqHeaders['X-Visitor'] = visitor;
|
|
14434
|
+
}
|
|
14435
|
+
|
|
13704
14436
|
if (locale) {
|
|
13705
14437
|
reqHeaders['X-Locale'] = locale;
|
|
13706
14438
|
}
|