swell-js 3.22.2 → 3.22.3
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/dist/api-73c0aac4.js +207 -0
- package/dist/api.js +2 -2
- package/dist/index-ffb531dc.js +3446 -0
- package/dist/index.js +2 -2
- package/dist/payment.js +1 -1
- package/dist/swell.cjs.js +3 -1
- package/dist/swell.cjs.js.map +1 -1
- package/dist/swell.umd.min.js +2 -1
- package/dist/swell.umd.min.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,3446 @@
|
|
|
1
|
+
import { m as methods } from './cart-cec81203.js';
|
|
2
|
+
import { m as methods$1 } from './settings-34395c72.js';
|
|
3
|
+
import { v as isFunction, w as isObject, I as loadScript, b as cloneDeep, g as get, j as toSnake, F as vaultRequest, p as pick, y as toLower, A as isEmpty, D as reduce, J as isLiveMode, C as map, x as toNumber, G as getLocationParams, H as removeUrlParams, t as toCamel } from './index-ca9cb73c.js';
|
|
4
|
+
import 'qs';
|
|
5
|
+
import 'deepmerge';
|
|
6
|
+
import 'fast-case';
|
|
7
|
+
|
|
8
|
+
const SCRIPT_HANDLERS = {
|
|
9
|
+
'stripe-js': loadStripe,
|
|
10
|
+
'paypal-sdk': loadPaypal,
|
|
11
|
+
'google-pay': loadGoogle,
|
|
12
|
+
'braintree-web': loadBraintree,
|
|
13
|
+
'braintree-paypal-sdk': loadBraintreePaypal,
|
|
14
|
+
'braintree-web-paypal-checkout': loadBraintreePaypalCheckout,
|
|
15
|
+
'braintree-google-payment': loadBraintreeGoogle,
|
|
16
|
+
'braintree-apple-payment': loadBraintreeApple,
|
|
17
|
+
'amazon-checkout': loadAmazonCheckout,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const BRAINTREE_VERSION = '3.91.0';
|
|
21
|
+
|
|
22
|
+
async function loadStripe() {
|
|
23
|
+
if (!window.Stripe || window.Stripe.version !== 3) {
|
|
24
|
+
await loadScript('stripe-js', 'https://js.stripe.com/v3/');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!window.Stripe) {
|
|
28
|
+
console.error('Warning: Stripe was not loaded');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (window.Stripe.StripeV3) {
|
|
32
|
+
window.Stripe = window.Stripe.StripeV3;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (window.Stripe.version !== 3) {
|
|
36
|
+
console.error('Warning: Stripe V3 was not loaded');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function loadPaypal(params) {
|
|
41
|
+
if (!window.paypal) {
|
|
42
|
+
const { currency, client_id, merchant_id } = params;
|
|
43
|
+
const paypalParams = {
|
|
44
|
+
currency,
|
|
45
|
+
'client-id': client_id,
|
|
46
|
+
commit: false,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
if (merchant_id) {
|
|
50
|
+
// paypal express and ppcp onboarded
|
|
51
|
+
paypalParams['merchant-id'] = merchant_id;
|
|
52
|
+
paypalParams.intent = 'authorize';
|
|
53
|
+
} else {
|
|
54
|
+
// ppcp progressive
|
|
55
|
+
paypalParams.intent = 'capture';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const urlSearchParams = new URLSearchParams(paypalParams).toString();
|
|
59
|
+
|
|
60
|
+
await loadScript(
|
|
61
|
+
'paypal-sdk',
|
|
62
|
+
`https://www.paypal.com/sdk/js?${urlSearchParams}`,
|
|
63
|
+
{
|
|
64
|
+
'data-partner-attribution-id': 'SwellCommerce_SP',
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!window.paypal) {
|
|
70
|
+
console.error('Warning: PayPal was not loaded');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function loadGoogle() {
|
|
75
|
+
if (!window.google) {
|
|
76
|
+
await loadScript('google-pay', 'https://pay.google.com/gp/p/js/pay.js');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!window.google) {
|
|
80
|
+
console.error('Warning: Google was not loaded');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function loadBraintree() {
|
|
85
|
+
if (!window.braintree) {
|
|
86
|
+
await loadScript(
|
|
87
|
+
'braintree-web',
|
|
88
|
+
`https://js.braintreegateway.com/web/${BRAINTREE_VERSION}/js/client.min.js`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!window.braintree) {
|
|
93
|
+
console.error('Warning: Braintree was not loaded');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function loadBraintreePaypal(params) {
|
|
98
|
+
if (!window.paypal) {
|
|
99
|
+
const { currency, client_id, merchant_id } = params;
|
|
100
|
+
const paypalParams = {
|
|
101
|
+
currency,
|
|
102
|
+
'client-id': client_id,
|
|
103
|
+
'merchant-id': merchant_id,
|
|
104
|
+
commit: false,
|
|
105
|
+
vault: true,
|
|
106
|
+
};
|
|
107
|
+
const urlSearchParams = new URLSearchParams(paypalParams).toString();
|
|
108
|
+
|
|
109
|
+
await loadScript(
|
|
110
|
+
'braintree-paypal-sdk',
|
|
111
|
+
`https://www.paypal.com/sdk/js?${urlSearchParams}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!window.paypal) {
|
|
116
|
+
console.error('Warning: Braintree PayPal was not loaded');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function loadBraintreePaypalCheckout() {
|
|
121
|
+
if (window.braintree && !window.braintree.paypalCheckout) {
|
|
122
|
+
await loadScript(
|
|
123
|
+
'braintree-web-paypal-checkout',
|
|
124
|
+
`https://js.braintreegateway.com/web/${BRAINTREE_VERSION}/js/paypal-checkout.min.js`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (window.braintree && !window.braintree.paypalCheckout) {
|
|
129
|
+
console.error('Warning: Braintree PayPal Checkout was not loaded');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function loadBraintreeGoogle() {
|
|
134
|
+
if (window.braintree && !window.braintree.googlePayment) {
|
|
135
|
+
await loadScript(
|
|
136
|
+
'braintree-google-payment',
|
|
137
|
+
`https://js.braintreegateway.com/web/${BRAINTREE_VERSION}/js/google-payment.min.js`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (window.braintree && !window.braintree.googlePayment) {
|
|
142
|
+
console.error('Warning: Braintree Google Payment was not loaded');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function loadBraintreeApple() {
|
|
147
|
+
if (window.braintree && !window.braintree.applePay) {
|
|
148
|
+
await loadScript(
|
|
149
|
+
'braintree-apple-payment',
|
|
150
|
+
`https://js.braintreegateway.com/web/${BRAINTREE_VERSION}/js/apple-pay.min.js`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (window.braintree && !window.braintree.applePay) {
|
|
155
|
+
console.error('Warning: Braintree Apple Payment was not loaded');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function loadAmazonCheckout() {
|
|
160
|
+
if (!window.amazon) {
|
|
161
|
+
await loadScript(
|
|
162
|
+
'amazon-checkout',
|
|
163
|
+
'https://static-na.payments-amazon.com/checkout.js',
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!window.amazon) {
|
|
168
|
+
console.error('Warning: Amazon Checkout was not loaded');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function loadScripts(scripts) {
|
|
173
|
+
if (!scripts) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const script of scripts) {
|
|
178
|
+
let scriptId = script;
|
|
179
|
+
let scriptParams;
|
|
180
|
+
|
|
181
|
+
if (isObject(script)) {
|
|
182
|
+
scriptId = script.id;
|
|
183
|
+
scriptParams = script.params;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const scriptHandler = SCRIPT_HANDLERS[scriptId];
|
|
187
|
+
|
|
188
|
+
if (!isFunction(scriptHandler)) {
|
|
189
|
+
console.error(`Unknown script ID: ${scriptId}`);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
await scriptHandler(scriptParams);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Wait until the scripts are fully loaded.
|
|
197
|
+
// Some scripts don't work correctly in Safari without this.
|
|
198
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
class PaymentMethodDisabledError extends Error {
|
|
202
|
+
constructor(method) {
|
|
203
|
+
const message = `${method} payments are disabled. See Payment settings in the Swell dashboard for details`;
|
|
204
|
+
super(message);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
class UnsupportedPaymentMethodError extends Error {
|
|
209
|
+
constructor(method, gateway) {
|
|
210
|
+
let message = `Unsupported payment method: ${method}`;
|
|
211
|
+
|
|
212
|
+
if (gateway) {
|
|
213
|
+
message += ` (${gateway})`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
super(message);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
class UnableAuthenticatePaymentMethodError extends Error {
|
|
221
|
+
constructor() {
|
|
222
|
+
const message =
|
|
223
|
+
'We are unable to authenticate your payment method. Please choose a different payment method and try again';
|
|
224
|
+
super(message);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
class LibraryNotLoadedError extends Error {
|
|
229
|
+
constructor(library) {
|
|
230
|
+
const message = `${library} was not loaded`;
|
|
231
|
+
super(message);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
class MethodPropertyMissingError extends Error {
|
|
236
|
+
constructor(method, property) {
|
|
237
|
+
const message = `${method} ${property} is missing`;
|
|
238
|
+
super(message);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
class DomElementNotFoundError extends Error {
|
|
243
|
+
constructor(elementId) {
|
|
244
|
+
const message = `DOM element with '${elementId}' ID not found`;
|
|
245
|
+
super(message);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
class PaymentElementNotCreatedError extends Error {
|
|
250
|
+
constructor(methodName) {
|
|
251
|
+
const message = `The ${methodName} payment element was not created`;
|
|
252
|
+
super(message);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
class Payment {
|
|
257
|
+
_element = null;
|
|
258
|
+
_elementContainer = null;
|
|
259
|
+
|
|
260
|
+
constructor(request, options, params, method) {
|
|
261
|
+
this.request = request;
|
|
262
|
+
this.options = options;
|
|
263
|
+
this.params = params;
|
|
264
|
+
this.method = method;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Returns a payment element.
|
|
269
|
+
*
|
|
270
|
+
* @returns {any}
|
|
271
|
+
*/
|
|
272
|
+
get element() {
|
|
273
|
+
if (!this._element) {
|
|
274
|
+
throw new PaymentElementNotCreatedError(this.method.name);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return this._element;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Sets a payment element.
|
|
282
|
+
*
|
|
283
|
+
* @param {any} element
|
|
284
|
+
*/
|
|
285
|
+
set element(element) {
|
|
286
|
+
this._element = element;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Returns a HTMLElement container of the payment element.
|
|
291
|
+
*
|
|
292
|
+
* @returns {HTMLElement}
|
|
293
|
+
*/
|
|
294
|
+
get elementContainer() {
|
|
295
|
+
return this._elementContainer;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Sets a HTMLElement container of the payment element.
|
|
300
|
+
*
|
|
301
|
+
* @param {string} elementId
|
|
302
|
+
*/
|
|
303
|
+
setElementContainer(elementId) {
|
|
304
|
+
this._elementContainer = document.getElementById(elementId);
|
|
305
|
+
|
|
306
|
+
if (!this.elementContainer) {
|
|
307
|
+
throw new DomElementNotFoundError(elementId);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Loads payment scripts.
|
|
313
|
+
*
|
|
314
|
+
* @param {Array<string | object>} scripts
|
|
315
|
+
*/
|
|
316
|
+
async loadScripts(scripts) {
|
|
317
|
+
await this._populateScriptsParams(scripts);
|
|
318
|
+
await loadScripts(scripts);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Returns a cart.
|
|
323
|
+
*
|
|
324
|
+
* @returns {object}
|
|
325
|
+
*/
|
|
326
|
+
async getCart() {
|
|
327
|
+
const cart = await methods(this.request, this.options).get();
|
|
328
|
+
|
|
329
|
+
if (!cart) {
|
|
330
|
+
throw new Error('Cart not found');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return this._adjustCart(cart);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Updates a cart.
|
|
338
|
+
*
|
|
339
|
+
* @param {object} data
|
|
340
|
+
* @returns {object}
|
|
341
|
+
*/
|
|
342
|
+
async updateCart(data) {
|
|
343
|
+
const updateData = cloneDeep(data);
|
|
344
|
+
|
|
345
|
+
// account data should only be updated when the user is a guest and no email is present
|
|
346
|
+
if (data.account) {
|
|
347
|
+
const cart = await this.getCart();
|
|
348
|
+
const shouldUpdateAccount = cart.guest && !get(cart, 'account.email');
|
|
349
|
+
|
|
350
|
+
if (!shouldUpdateAccount) {
|
|
351
|
+
delete updateData.account;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const updatedCart = await methods(this.request, this.options).update(
|
|
356
|
+
updateData,
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
return this._adjustCart(updatedCart);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Returns the store settings.
|
|
364
|
+
*
|
|
365
|
+
* @returns {object}
|
|
366
|
+
*/
|
|
367
|
+
async getSettings() {
|
|
368
|
+
return methods$1(this.request, this.options).get();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Creates a payment intent.
|
|
373
|
+
*
|
|
374
|
+
* @param {object} data
|
|
375
|
+
* @returns {object}
|
|
376
|
+
*/
|
|
377
|
+
async createIntent(data) {
|
|
378
|
+
return this._vaultRequest('post', '/intent', data);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Updates a payment intent.
|
|
383
|
+
*
|
|
384
|
+
* @param {object} data
|
|
385
|
+
* @returns {object}
|
|
386
|
+
*/
|
|
387
|
+
async updateIntent(data) {
|
|
388
|
+
return this._vaultRequest('put', '/intent', data);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Authorizes a payment gateway.
|
|
393
|
+
*
|
|
394
|
+
* @param {object} data
|
|
395
|
+
* @returns {object}
|
|
396
|
+
*/
|
|
397
|
+
async authorizeGateway(data) {
|
|
398
|
+
return this._vaultRequest('post', '/authorization', data);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Calls the onSuccess handler.
|
|
403
|
+
*
|
|
404
|
+
* @param {object | undefined} data
|
|
405
|
+
* @returns {any}
|
|
406
|
+
*/
|
|
407
|
+
onSuccess(data) {
|
|
408
|
+
const successHandler = get(this.params, 'onSuccess');
|
|
409
|
+
|
|
410
|
+
if (isFunction(successHandler)) {
|
|
411
|
+
return successHandler(data);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Calls the onCancel handler.
|
|
417
|
+
*
|
|
418
|
+
* @returns {any}
|
|
419
|
+
*/
|
|
420
|
+
onCancel() {
|
|
421
|
+
const cancelHandler = get(this.params, 'onCancel');
|
|
422
|
+
|
|
423
|
+
if (isFunction(cancelHandler)) {
|
|
424
|
+
return cancelHandler();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Calls the onError handler.
|
|
430
|
+
*
|
|
431
|
+
* @param {Error} error
|
|
432
|
+
* @returns {any}
|
|
433
|
+
*/
|
|
434
|
+
onError(error) {
|
|
435
|
+
const errorHandler = get(this.params, 'onError');
|
|
436
|
+
|
|
437
|
+
if (isFunction(errorHandler)) {
|
|
438
|
+
return errorHandler(error);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
console.error(error.message);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Adjusts cart data.
|
|
446
|
+
*
|
|
447
|
+
* @param {object} cart
|
|
448
|
+
* @returns {object}
|
|
449
|
+
*/
|
|
450
|
+
async _adjustCart(cart) {
|
|
451
|
+
return this._ensureCartSettings(cart).then(toSnake);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Sets the store settings to cart.
|
|
456
|
+
*
|
|
457
|
+
* @param {object} cart
|
|
458
|
+
* @returns {object}
|
|
459
|
+
*/
|
|
460
|
+
async _ensureCartSettings(cart) {
|
|
461
|
+
if (cart.settings) {
|
|
462
|
+
return cart;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const settings = await this.getSettings();
|
|
466
|
+
|
|
467
|
+
return { ...cart, settings: { ...settings.store } };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Sends a Vault request.
|
|
472
|
+
*
|
|
473
|
+
* @param {string} method
|
|
474
|
+
* @param {string} url
|
|
475
|
+
* @param {object} data
|
|
476
|
+
* @returns {object}
|
|
477
|
+
*/
|
|
478
|
+
async _vaultRequest(method, url, data) {
|
|
479
|
+
const response = await vaultRequest(method, url, data);
|
|
480
|
+
|
|
481
|
+
if (response.errors) {
|
|
482
|
+
const param = Object.keys(response.errors)[0];
|
|
483
|
+
const err = new Error(response.errors[param].message || 'Unknown error');
|
|
484
|
+
err.code = 'vault_error';
|
|
485
|
+
err.status = 402;
|
|
486
|
+
err.param = param;
|
|
487
|
+
throw err;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return response;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Sets values for payment scripts.
|
|
495
|
+
*
|
|
496
|
+
* @param {Array<string | object>} scripts
|
|
497
|
+
*/
|
|
498
|
+
async _populateScriptsParams(scripts = []) {
|
|
499
|
+
for (const script of scripts) {
|
|
500
|
+
await this._populateScriptWithCartParams(script);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Sets the cart values to the payment script params.
|
|
506
|
+
*
|
|
507
|
+
* @param {string | object} script
|
|
508
|
+
*/
|
|
509
|
+
async _populateScriptWithCartParams(script) {
|
|
510
|
+
const cartParams = get(script, 'params.cart');
|
|
511
|
+
|
|
512
|
+
if (!cartParams) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const cart = await this.getCart();
|
|
517
|
+
|
|
518
|
+
script.params = {
|
|
519
|
+
...script.params,
|
|
520
|
+
...pick(cart, cartParams),
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
delete script.params.cart;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// https://stripe.com/docs/currencies#minimum-and-maximum-charge-amounts
|
|
528
|
+
const MINIMUM_CHARGE_AMOUNT = {
|
|
529
|
+
USD: 0.5,
|
|
530
|
+
AED: 2,
|
|
531
|
+
AUD: 0.5,
|
|
532
|
+
BGN: 1,
|
|
533
|
+
BRL: 0.5,
|
|
534
|
+
CAD: 0.5,
|
|
535
|
+
CHF: 0.5,
|
|
536
|
+
CZK: 15,
|
|
537
|
+
DKK: 2.5,
|
|
538
|
+
EUR: 0.5,
|
|
539
|
+
GBP: 0.3,
|
|
540
|
+
HKD: 4,
|
|
541
|
+
HRK: 0.5,
|
|
542
|
+
HUF: 175,
|
|
543
|
+
INR: 0.5,
|
|
544
|
+
JPY: 50,
|
|
545
|
+
MXN: 10,
|
|
546
|
+
MYR: 2,
|
|
547
|
+
NOK: 3,
|
|
548
|
+
NZD: 0.5,
|
|
549
|
+
PLN: 2,
|
|
550
|
+
RON: 2,
|
|
551
|
+
SEK: 3,
|
|
552
|
+
SGD: 0.5,
|
|
553
|
+
THB: 10,
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const addressFieldsMap$1 = {
|
|
557
|
+
city: 'city',
|
|
558
|
+
country: 'country',
|
|
559
|
+
line1: 'address1',
|
|
560
|
+
line2: 'address2',
|
|
561
|
+
postal_code: 'zip',
|
|
562
|
+
state: 'state',
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
const billingFieldsMap = {
|
|
566
|
+
name: 'name',
|
|
567
|
+
phone: 'phone',
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
function mapValues(fieldsMap, data) {
|
|
571
|
+
const result = {};
|
|
572
|
+
for (const [destinationKey, sourceKey] of Object.entries(fieldsMap)) {
|
|
573
|
+
const value = data[sourceKey];
|
|
574
|
+
if (value) {
|
|
575
|
+
result[destinationKey] = value;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return result;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function getBillingDetails(cart) {
|
|
582
|
+
const details = {
|
|
583
|
+
...mapValues(billingFieldsMap, cart.billing),
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
if (cart.account && cart.account.email) {
|
|
587
|
+
details.email = cart.account.email;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const address = mapValues(addressFieldsMap$1, cart.billing);
|
|
591
|
+
if (!isEmpty(address)) {
|
|
592
|
+
details.address = address;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return details;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function setBancontactOwner(source, data) {
|
|
599
|
+
const fillValues = (fieldsMap, data) =>
|
|
600
|
+
reduce(
|
|
601
|
+
fieldsMap,
|
|
602
|
+
(acc, srcKey, destKey) => {
|
|
603
|
+
const value = data[srcKey];
|
|
604
|
+
if (value) {
|
|
605
|
+
acc[destKey] = value;
|
|
606
|
+
}
|
|
607
|
+
return acc;
|
|
608
|
+
},
|
|
609
|
+
{},
|
|
610
|
+
);
|
|
611
|
+
const { account = {}, billing, shipping } = data;
|
|
612
|
+
const billingData = {
|
|
613
|
+
...account.shipping,
|
|
614
|
+
...account.billing,
|
|
615
|
+
...shipping,
|
|
616
|
+
...billing,
|
|
617
|
+
};
|
|
618
|
+
const billingAddress = fillValues(addressFieldsMap$1, billingData);
|
|
619
|
+
|
|
620
|
+
source.owner = {
|
|
621
|
+
email: account.email,
|
|
622
|
+
name: billingData.name || account.name,
|
|
623
|
+
...(billingData.phone
|
|
624
|
+
? { phone: billingData.phone }
|
|
625
|
+
: account.phone
|
|
626
|
+
? { phone: account.phone }
|
|
627
|
+
: {}),
|
|
628
|
+
...(!isEmpty(billingAddress) ? { address: billingAddress } : {}),
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function createElement(type, elements, params) {
|
|
633
|
+
const elementParams = params[type] || params;
|
|
634
|
+
const elementOptions = elementParams.options || {};
|
|
635
|
+
const elementId = elementParams.elementId || `${type}-element`;
|
|
636
|
+
const element = elements.create(type, elementOptions);
|
|
637
|
+
|
|
638
|
+
elementParams.onChange && element.on('change', elementParams.onChange);
|
|
639
|
+
elementParams.onReady && element.on('ready', elementParams.onReady);
|
|
640
|
+
elementParams.onFocus && element.on('focus', elementParams.onFocus);
|
|
641
|
+
elementParams.onBlur && element.on('blur', elementParams.onBlur);
|
|
642
|
+
elementParams.onEscape && element.on('escape', elementParams.onEscape);
|
|
643
|
+
elementParams.onClick && element.on('click', elementParams.onClick);
|
|
644
|
+
|
|
645
|
+
element.mount(`#${elementId}`);
|
|
646
|
+
|
|
647
|
+
return element;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
async function createPaymentMethod(stripe, cardElement, cart) {
|
|
651
|
+
const billingDetails = getBillingDetails(cart);
|
|
652
|
+
const { paymentMethod, error } = await stripe.createPaymentMethod({
|
|
653
|
+
type: 'card',
|
|
654
|
+
card: cardElement,
|
|
655
|
+
billing_details: billingDetails,
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
return error
|
|
659
|
+
? { error }
|
|
660
|
+
: {
|
|
661
|
+
token: paymentMethod.id,
|
|
662
|
+
last4: paymentMethod.card.last4,
|
|
663
|
+
exp_month: paymentMethod.card.exp_month,
|
|
664
|
+
exp_year: paymentMethod.card.exp_year,
|
|
665
|
+
brand: paymentMethod.card.brand,
|
|
666
|
+
address_check: paymentMethod.card.checks.address_line1_check,
|
|
667
|
+
cvc_check: paymentMethod.card.checks.cvc_check,
|
|
668
|
+
zip_check: paymentMethod.card.checks.address_zip_check,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async function createIDealPaymentMethod(stripe, element, cart) {
|
|
673
|
+
const billingDetails = getBillingDetails(cart);
|
|
674
|
+
return await stripe.createPaymentMethod({
|
|
675
|
+
type: 'ideal',
|
|
676
|
+
ideal: element,
|
|
677
|
+
...(billingDetails ? { billing_details: billingDetails } : {}),
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function getKlarnaIntentDetails(cart) {
|
|
682
|
+
const { account, currency, capture_total } = cart;
|
|
683
|
+
const stripeCustomer = account && account.stripe_customer;
|
|
684
|
+
const stripeCurrency = (currency || 'USD').toLowerCase();
|
|
685
|
+
const stripeAmount = stripeAmountByCurrency(currency, capture_total);
|
|
686
|
+
const details = {
|
|
687
|
+
payment_method_types: 'klarna',
|
|
688
|
+
amount: stripeAmount,
|
|
689
|
+
currency: stripeCurrency,
|
|
690
|
+
capture_method: 'manual',
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
if (stripeCustomer) {
|
|
694
|
+
details.customer = stripeCustomer;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return details;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function getKlarnaConfirmationDetails(cart) {
|
|
701
|
+
const billingDetails = getBillingDetails(cart);
|
|
702
|
+
const returnUrl = `${
|
|
703
|
+
window.location.origin + window.location.pathname
|
|
704
|
+
}?gateway=stripe`;
|
|
705
|
+
|
|
706
|
+
return {
|
|
707
|
+
payment_method: {
|
|
708
|
+
billing_details: billingDetails,
|
|
709
|
+
},
|
|
710
|
+
return_url: returnUrl,
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
async function createBancontactSource(stripe, cart) {
|
|
715
|
+
const sourceObject = {
|
|
716
|
+
type: 'bancontact',
|
|
717
|
+
amount: Math.round(get(cart, 'grand_total', 0) * 100),
|
|
718
|
+
currency: toLower(get(cart, 'currency', 'eur')),
|
|
719
|
+
redirect: {
|
|
720
|
+
return_url: window.location.href,
|
|
721
|
+
},
|
|
722
|
+
};
|
|
723
|
+
setBancontactOwner(sourceObject, cart);
|
|
724
|
+
|
|
725
|
+
return await stripe.createSource(sourceObject);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function stripeAmountByCurrency(currency, amount) {
|
|
729
|
+
const zeroDecimalCurrencies = [
|
|
730
|
+
'BIF', // Burundian Franc
|
|
731
|
+
'DJF', // Djiboutian Franc,
|
|
732
|
+
'JPY', // Japanese Yen
|
|
733
|
+
'KRW', // South Korean Won
|
|
734
|
+
'PYG', // Paraguayan Guaraní
|
|
735
|
+
'VND', // Vietnamese Đồng
|
|
736
|
+
'XAF', // Central African Cfa Franc
|
|
737
|
+
'XPF', // Cfp Franc
|
|
738
|
+
'CLP', // Chilean Peso
|
|
739
|
+
'GNF', // Guinean Franc
|
|
740
|
+
'KMF', // Comorian Franc
|
|
741
|
+
'MGA', // Malagasy Ariary
|
|
742
|
+
'RWF', // Rwandan Franc
|
|
743
|
+
'VUV', // Vanuatu Vatu
|
|
744
|
+
'XOF', // West African Cfa Franc
|
|
745
|
+
];
|
|
746
|
+
if (zeroDecimalCurrencies.includes(currency.toUpperCase())) {
|
|
747
|
+
return amount;
|
|
748
|
+
} else {
|
|
749
|
+
return Math.round(amount * 100);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function isStripeChargeableAmount(amount, currency) {
|
|
754
|
+
const minAmount = MINIMUM_CHARGE_AMOUNT[currency];
|
|
755
|
+
return !minAmount || amount >= minAmount;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
class StripeCardPayment extends Payment {
|
|
759
|
+
constructor(request, options, params, methods) {
|
|
760
|
+
super(request, options, params, methods.card);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
get scripts() {
|
|
764
|
+
return ['stripe-js'];
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
get stripe() {
|
|
768
|
+
if (!StripeCardPayment.stripe) {
|
|
769
|
+
if (window.Stripe) {
|
|
770
|
+
this.stripe = window.Stripe(this.method.publishable_key);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (!StripeCardPayment.stripe) {
|
|
774
|
+
throw new LibraryNotLoadedError('Stripe');
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return StripeCardPayment.stripe;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
set stripe(stripe) {
|
|
782
|
+
StripeCardPayment.stripe = stripe;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
get stripeElement() {
|
|
786
|
+
return StripeCardPayment.stripeElement;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
set stripeElement(stripeElement) {
|
|
790
|
+
StripeCardPayment.stripeElement = stripeElement;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
async createElements() {
|
|
794
|
+
await this.loadScripts(this.scripts);
|
|
795
|
+
|
|
796
|
+
const elements = this.stripe.elements(this.params.config);
|
|
797
|
+
|
|
798
|
+
if (this.params.separateElements) {
|
|
799
|
+
this.stripeElement = createElement('cardNumber', elements, this.params);
|
|
800
|
+
createElement('cardExpiry', elements, this.params);
|
|
801
|
+
createElement('cardCvc', elements, this.params);
|
|
802
|
+
} else {
|
|
803
|
+
this.stripeElement = createElement('card', elements, this.params);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
async tokenize() {
|
|
808
|
+
if (!this.stripeElement) {
|
|
809
|
+
throw new Error('Stripe payment element is not defined');
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
await this.loadScripts(this.scripts);
|
|
813
|
+
|
|
814
|
+
const cart = await this.getCart();
|
|
815
|
+
const paymentMethod = await createPaymentMethod(
|
|
816
|
+
this.stripe,
|
|
817
|
+
this.stripeElement,
|
|
818
|
+
cart,
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
if (paymentMethod.error) {
|
|
822
|
+
throw new Error(paymentMethod.error.message);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// should save payment method data when payment amount is not chargeable
|
|
826
|
+
if (!isStripeChargeableAmount(cart.capture_total, cart.currency)) {
|
|
827
|
+
await this.updateCart({
|
|
828
|
+
billing: {
|
|
829
|
+
method: 'card',
|
|
830
|
+
card: paymentMethod,
|
|
831
|
+
},
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
return this.onSuccess();
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const intent = await this._createIntent(cart, paymentMethod);
|
|
838
|
+
|
|
839
|
+
await this.updateCart({
|
|
840
|
+
billing: {
|
|
841
|
+
method: 'card',
|
|
842
|
+
card: paymentMethod,
|
|
843
|
+
intent: {
|
|
844
|
+
stripe: {
|
|
845
|
+
id: intent.id,
|
|
846
|
+
...(Boolean(cart.auth_total) && {
|
|
847
|
+
auth_amount: cart.auth_total,
|
|
848
|
+
}),
|
|
849
|
+
},
|
|
850
|
+
},
|
|
851
|
+
},
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
this.onSuccess();
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
async authenticate(payment) {
|
|
858
|
+
const { transaction_id: id, card: { token } = {} } = payment;
|
|
859
|
+
const intent = await this.updateIntent({
|
|
860
|
+
gateway: 'stripe',
|
|
861
|
+
intent: { id, payment_method: token },
|
|
862
|
+
});
|
|
863
|
+
|
|
864
|
+
if (intent.error) {
|
|
865
|
+
throw new Error(intent.error.message);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
await this.loadScripts(this.scripts);
|
|
869
|
+
|
|
870
|
+
return this._confirmCardPayment(intent);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
async _createIntent(cart, paymentMethod) {
|
|
874
|
+
const { account, currency, capture_total, auth_total } = cart;
|
|
875
|
+
const stripeCustomer = account && account.stripe_customer;
|
|
876
|
+
const stripeCurrency = (currency || 'USD').toLowerCase();
|
|
877
|
+
const amount = stripeAmountByCurrency(currency, capture_total + auth_total);
|
|
878
|
+
const intent = await this.createIntent({
|
|
879
|
+
gateway: 'stripe',
|
|
880
|
+
intent: {
|
|
881
|
+
amount,
|
|
882
|
+
currency: stripeCurrency,
|
|
883
|
+
payment_method: paymentMethod.token,
|
|
884
|
+
capture_method: 'manual',
|
|
885
|
+
setup_future_usage: 'off_session',
|
|
886
|
+
...(stripeCustomer ? { customer: stripeCustomer } : {}),
|
|
887
|
+
},
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
if (!intent) {
|
|
891
|
+
throw new Error('Stripe payment intent is not defined');
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (
|
|
895
|
+
!['requires_capture', 'requires_confirmation'].includes(intent.status)
|
|
896
|
+
) {
|
|
897
|
+
throw new Error(`Unsupported intent status: ${intent.status}`);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Confirm the payment intent
|
|
901
|
+
if (intent.status === 'requires_confirmation') {
|
|
902
|
+
await this._confirmCardPayment(intent);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
return intent;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
async _confirmCardPayment(intent) {
|
|
909
|
+
const actionResult = await this.stripe.confirmCardPayment(
|
|
910
|
+
intent.client_secret,
|
|
911
|
+
);
|
|
912
|
+
|
|
913
|
+
if (actionResult.error) {
|
|
914
|
+
throw new Error(actionResult.error.message);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
return { status: actionResult.status };
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
class StripeIDealPayment extends Payment {
|
|
922
|
+
constructor(request, options, params, methods) {
|
|
923
|
+
if (!methods.card) {
|
|
924
|
+
throw new PaymentMethodDisabledError('Credit cards');
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const method = {
|
|
928
|
+
...methods.ideal,
|
|
929
|
+
publishable_key: methods.card.publishable_key,
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
super(request, options, params, method);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
get scripts() {
|
|
936
|
+
return ['stripe-js'];
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
get stripe() {
|
|
940
|
+
if (!StripeIDealPayment.stripe) {
|
|
941
|
+
if (window.Stripe) {
|
|
942
|
+
this.stripe = window.Stripe(this.method.publishable_key);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if (!StripeIDealPayment.stripe) {
|
|
946
|
+
throw new LibraryNotLoadedError('Stripe');
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return StripeIDealPayment.stripe;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
set stripe(stripe) {
|
|
954
|
+
StripeIDealPayment.stripe = stripe;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
get stripeElement() {
|
|
958
|
+
return StripeIDealPayment.stripeElement;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
set stripeElement(stripeElement) {
|
|
962
|
+
StripeIDealPayment.stripeElement = stripeElement;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
async createElements() {
|
|
966
|
+
await this.loadScripts(this.scripts);
|
|
967
|
+
|
|
968
|
+
const elements = this.stripe.elements(this.params.config);
|
|
969
|
+
|
|
970
|
+
this.stripeElement = createElement('idealBank', elements, this.params);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
async tokenize() {
|
|
974
|
+
if (!this.stripeElement) {
|
|
975
|
+
throw new Error('Stripe payment element is not defined');
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
await this.loadScripts(this.scripts);
|
|
979
|
+
|
|
980
|
+
const cart = await this.getCart();
|
|
981
|
+
const { paymentMethod, error: paymentMethodError } =
|
|
982
|
+
await createIDealPaymentMethod(this.stripe, this.stripeElement, cart);
|
|
983
|
+
|
|
984
|
+
if (paymentMethodError) {
|
|
985
|
+
throw new Error(paymentMethodError.message);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const intent = await this._createIntent(cart, paymentMethod);
|
|
989
|
+
|
|
990
|
+
await this.stripe.handleCardAction(intent.client_secret);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
async _createIntent(cart, paymentMethod) {
|
|
994
|
+
const { currency, capture_total } = cart;
|
|
995
|
+
const stripeCurrency = (currency || 'EUR').toLowerCase();
|
|
996
|
+
const amount = stripeAmountByCurrency(currency, capture_total);
|
|
997
|
+
const intent = await this.createIntent({
|
|
998
|
+
gateway: 'stripe',
|
|
999
|
+
intent: {
|
|
1000
|
+
amount,
|
|
1001
|
+
currency: stripeCurrency,
|
|
1002
|
+
payment_method: paymentMethod.id,
|
|
1003
|
+
payment_method_types: 'ideal',
|
|
1004
|
+
confirmation_method: 'manual',
|
|
1005
|
+
confirm: true,
|
|
1006
|
+
return_url: window.location.href,
|
|
1007
|
+
},
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
if (!intent) {
|
|
1011
|
+
throw new Error('Stripe payment intent is not defined');
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (
|
|
1015
|
+
!['requires_action', 'requires_source_action'].includes(intent.status)
|
|
1016
|
+
) {
|
|
1017
|
+
throw new Error(`Unsupported intent status (${intent.status})`);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
await this.updateCart({
|
|
1021
|
+
billing: {
|
|
1022
|
+
method: 'ideal',
|
|
1023
|
+
ideal: {
|
|
1024
|
+
token: paymentMethod.id,
|
|
1025
|
+
},
|
|
1026
|
+
intent: {
|
|
1027
|
+
stripe: {
|
|
1028
|
+
id: intent.id,
|
|
1029
|
+
},
|
|
1030
|
+
},
|
|
1031
|
+
},
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
return intent;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
class StripeBancontactPayment extends Payment {
|
|
1039
|
+
constructor(request, options, params, methods) {
|
|
1040
|
+
if (!methods.card) {
|
|
1041
|
+
throw new PaymentMethodDisabledError('Credit cards');
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
const method = {
|
|
1045
|
+
...methods.bancontact,
|
|
1046
|
+
publishable_key: methods.card.publishable_key,
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
super(request, options, params, method);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
get scripts() {
|
|
1053
|
+
return ['stripe-js'];
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
get stripe() {
|
|
1057
|
+
if (!StripeBancontactPayment.stripe) {
|
|
1058
|
+
if (window.Stripe) {
|
|
1059
|
+
this.stripe = window.Stripe(this.method.publishable_key);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (!StripeBancontactPayment.stripe) {
|
|
1063
|
+
throw new LibraryNotLoadedError('Stripe');
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
return StripeBancontactPayment.stripe;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
set stripe(stripe) {
|
|
1071
|
+
StripeBancontactPayment.stripe = stripe;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
async tokenize() {
|
|
1075
|
+
await this.loadScripts(this.scripts);
|
|
1076
|
+
|
|
1077
|
+
const cart = await this.getCart();
|
|
1078
|
+
const { source, error: sourceError } = await createBancontactSource(
|
|
1079
|
+
this.stripe,
|
|
1080
|
+
cart,
|
|
1081
|
+
);
|
|
1082
|
+
|
|
1083
|
+
if (sourceError) {
|
|
1084
|
+
throw new Error(sourceError.message);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
await this.updateCart({
|
|
1088
|
+
billing: {
|
|
1089
|
+
method: 'bancontact',
|
|
1090
|
+
},
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
window.location.replace(source.redirect.url);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
class StripeKlarnaPayment extends Payment {
|
|
1098
|
+
constructor(request, options, params, methods) {
|
|
1099
|
+
if (!methods.card) {
|
|
1100
|
+
throw new PaymentMethodDisabledError('Credit cards');
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
const method = {
|
|
1104
|
+
...methods.klarna,
|
|
1105
|
+
publishable_key: methods.card.publishable_key,
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
super(request, options, params, method);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
get scripts() {
|
|
1112
|
+
return ['stripe-js'];
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
get stripe() {
|
|
1116
|
+
if (!StripeKlarnaPayment.stripe) {
|
|
1117
|
+
if (window.Stripe) {
|
|
1118
|
+
this.stripe = window.Stripe(this.method.publishable_key);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (!StripeKlarnaPayment.stripe) {
|
|
1122
|
+
throw new LibraryNotLoadedError('Stripe');
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
return StripeKlarnaPayment.stripe;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
set stripe(stripe) {
|
|
1130
|
+
StripeKlarnaPayment.stripe = stripe;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
async tokenize() {
|
|
1134
|
+
const cart = await this.getCart();
|
|
1135
|
+
const intent = await this.createIntent({
|
|
1136
|
+
gateway: 'stripe',
|
|
1137
|
+
intent: getKlarnaIntentDetails(cart),
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
await this.loadScripts(this.scripts);
|
|
1141
|
+
|
|
1142
|
+
const { error } = await this.stripe.confirmKlarnaPayment(
|
|
1143
|
+
intent.client_secret,
|
|
1144
|
+
getKlarnaConfirmationDetails(cart),
|
|
1145
|
+
);
|
|
1146
|
+
|
|
1147
|
+
if (error) {
|
|
1148
|
+
throw new Error(error.message);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
async handleRedirect(queryParams) {
|
|
1153
|
+
const { redirect_status, payment_intent_client_secret } = queryParams;
|
|
1154
|
+
|
|
1155
|
+
if (redirect_status !== 'succeeded') {
|
|
1156
|
+
throw new UnableAuthenticatePaymentMethodError();
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
await this.loadScripts(this.scripts);
|
|
1160
|
+
|
|
1161
|
+
const { paymentIntent, error } = await this.stripe.retrievePaymentIntent(
|
|
1162
|
+
payment_intent_client_secret,
|
|
1163
|
+
);
|
|
1164
|
+
|
|
1165
|
+
if (error) {
|
|
1166
|
+
throw new Error(error.message);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
await this.updateCart({
|
|
1170
|
+
billing: {
|
|
1171
|
+
method: 'klarna',
|
|
1172
|
+
klarna: {
|
|
1173
|
+
token: paymentIntent.payment_method,
|
|
1174
|
+
},
|
|
1175
|
+
intent: {
|
|
1176
|
+
stripe: {
|
|
1177
|
+
id: paymentIntent.id,
|
|
1178
|
+
},
|
|
1179
|
+
},
|
|
1180
|
+
},
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
this.onSuccess();
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
const VERSION$1 = '2018-10-31';
|
|
1188
|
+
const API_VERSION$1 = 2;
|
|
1189
|
+
const API_MINOR_VERSION$1 = 0;
|
|
1190
|
+
const ALLOWED_CARD_AUTH_METHODS$1 = ['PAN_ONLY', 'CRYPTOGRAM_3DS'];
|
|
1191
|
+
const ALLOWED_CARD_NETWORKS$1 = [
|
|
1192
|
+
'AMEX',
|
|
1193
|
+
'DISCOVER',
|
|
1194
|
+
'INTERAC',
|
|
1195
|
+
'JCB',
|
|
1196
|
+
'MASTERCARD',
|
|
1197
|
+
'VISA',
|
|
1198
|
+
];
|
|
1199
|
+
|
|
1200
|
+
class StripeGooglePayment extends Payment {
|
|
1201
|
+
constructor(request, options, params, methods) {
|
|
1202
|
+
if (!methods.card) {
|
|
1203
|
+
throw new PaymentMethodDisabledError('Credit cards');
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
const method = {
|
|
1207
|
+
...methods.google,
|
|
1208
|
+
publishable_key: methods.card.publishable_key,
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
super(request, options, params, method);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
get scripts() {
|
|
1215
|
+
return ['google-pay'];
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
get google() {
|
|
1219
|
+
if (!window.google) {
|
|
1220
|
+
throw new LibraryNotLoadedError('Google');
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
return window.google;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
get googleClient() {
|
|
1227
|
+
if (!StripeGooglePayment.googleClient) {
|
|
1228
|
+
if (this.google) {
|
|
1229
|
+
this.googleClient = new this.google.payments.api.PaymentsClient({
|
|
1230
|
+
environment: isLiveMode(this.method.mode) ? 'PRODUCTION' : 'TEST',
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
if (!StripeGooglePayment.googleClient) {
|
|
1235
|
+
throw new LibraryNotLoadedError('Google client');
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
return StripeGooglePayment.googleClient;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
set googleClient(googleClient) {
|
|
1243
|
+
StripeGooglePayment.googleClient = googleClient;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
get tokenizationSpecification() {
|
|
1247
|
+
const publishableKey = this.method.publishable_key;
|
|
1248
|
+
|
|
1249
|
+
if (!publishableKey) {
|
|
1250
|
+
throw new Error('Stripe publishable key is not defined');
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
return {
|
|
1254
|
+
type: 'PAYMENT_GATEWAY',
|
|
1255
|
+
parameters: {
|
|
1256
|
+
gateway: 'stripe',
|
|
1257
|
+
'stripe:version': VERSION$1,
|
|
1258
|
+
'stripe:publishableKey': publishableKey,
|
|
1259
|
+
},
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
get cardPaymentMethod() {
|
|
1264
|
+
return {
|
|
1265
|
+
type: 'CARD',
|
|
1266
|
+
tokenizationSpecification: this.tokenizationSpecification,
|
|
1267
|
+
parameters: {
|
|
1268
|
+
allowedAuthMethods: ALLOWED_CARD_AUTH_METHODS$1,
|
|
1269
|
+
allowedCardNetworks: ALLOWED_CARD_NETWORKS$1,
|
|
1270
|
+
billingAddressRequired: true,
|
|
1271
|
+
billingAddressParameters: {
|
|
1272
|
+
format: 'FULL',
|
|
1273
|
+
phoneNumberRequired: true,
|
|
1274
|
+
},
|
|
1275
|
+
},
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
get allowedPaymentMethods() {
|
|
1280
|
+
return [this.cardPaymentMethod];
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
async createElements() {
|
|
1284
|
+
const {
|
|
1285
|
+
elementId = 'googlepay-button',
|
|
1286
|
+
locale = 'en',
|
|
1287
|
+
style: { color = 'black', type = 'buy', sizeMode = 'fill' } = {},
|
|
1288
|
+
} = this.params;
|
|
1289
|
+
|
|
1290
|
+
if (!this.method.merchant_id) {
|
|
1291
|
+
throw new Error('Google merchant ID is not defined');
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
this.setElementContainer(elementId);
|
|
1295
|
+
await this.loadScripts(this.scripts);
|
|
1296
|
+
|
|
1297
|
+
const isReadyToPay = await this.googleClient.isReadyToPay({
|
|
1298
|
+
apiVersion: API_VERSION$1,
|
|
1299
|
+
apiVersionMinor: API_MINOR_VERSION$1,
|
|
1300
|
+
allowedPaymentMethods: this.allowedPaymentMethods,
|
|
1301
|
+
existingPaymentMethodRequired: true,
|
|
1302
|
+
});
|
|
1303
|
+
|
|
1304
|
+
if (!isReadyToPay.result) {
|
|
1305
|
+
throw new Error(
|
|
1306
|
+
'This device is not capable of making Google Pay payments',
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const cart = await this.getCart();
|
|
1311
|
+
const paymentRequestData = this._createPaymentRequestData(cart);
|
|
1312
|
+
|
|
1313
|
+
this.element = this.googleClient.createButton({
|
|
1314
|
+
buttonColor: color,
|
|
1315
|
+
buttonType: type,
|
|
1316
|
+
buttonSizeMode: sizeMode,
|
|
1317
|
+
buttonLocale: locale,
|
|
1318
|
+
onClick: this._onClick.bind(this, paymentRequestData),
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
mountElements() {
|
|
1323
|
+
const { classes = {} } = this.params;
|
|
1324
|
+
const container = this.elementContainer;
|
|
1325
|
+
|
|
1326
|
+
container.appendChild(this.element);
|
|
1327
|
+
|
|
1328
|
+
if (classes.base) {
|
|
1329
|
+
container.classList.add(classes.base);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
_createPaymentRequestData(cart) {
|
|
1334
|
+
const {
|
|
1335
|
+
settings: { name },
|
|
1336
|
+
capture_total,
|
|
1337
|
+
currency,
|
|
1338
|
+
} = cart;
|
|
1339
|
+
const { require: { email, shipping, phone } = {} } = this.params;
|
|
1340
|
+
|
|
1341
|
+
return {
|
|
1342
|
+
apiVersion: API_VERSION$1,
|
|
1343
|
+
apiVersionMinor: API_MINOR_VERSION$1,
|
|
1344
|
+
transactionInfo: {
|
|
1345
|
+
currencyCode: currency,
|
|
1346
|
+
totalPrice: capture_total.toString(),
|
|
1347
|
+
totalPriceStatus: 'ESTIMATED',
|
|
1348
|
+
},
|
|
1349
|
+
allowedPaymentMethods: this.allowedPaymentMethods,
|
|
1350
|
+
emailRequired: Boolean(email),
|
|
1351
|
+
shippingAddressRequired: Boolean(shipping),
|
|
1352
|
+
shippingAddressParameters: {
|
|
1353
|
+
phoneNumberRequired: Boolean(phone),
|
|
1354
|
+
},
|
|
1355
|
+
merchantInfo: {
|
|
1356
|
+
merchantName: name,
|
|
1357
|
+
merchantId: this.method.merchant_id,
|
|
1358
|
+
},
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
async _onClick(paymentRequestData) {
|
|
1363
|
+
try {
|
|
1364
|
+
const paymentData = await this.googleClient.loadPaymentData(
|
|
1365
|
+
paymentRequestData,
|
|
1366
|
+
);
|
|
1367
|
+
|
|
1368
|
+
if (paymentData) {
|
|
1369
|
+
await this._submitPayment(paymentData);
|
|
1370
|
+
}
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
this.onError(error);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
async _submitPayment(paymentData) {
|
|
1377
|
+
const { require: { shipping: requireShipping } = {} } = this.params;
|
|
1378
|
+
const { email, shippingAddress, paymentMethodData } = paymentData;
|
|
1379
|
+
const {
|
|
1380
|
+
info: { billingAddress },
|
|
1381
|
+
tokenizationData,
|
|
1382
|
+
} = paymentMethodData;
|
|
1383
|
+
const token = JSON.parse(tokenizationData.token);
|
|
1384
|
+
const { card } = token;
|
|
1385
|
+
|
|
1386
|
+
await this.updateCart({
|
|
1387
|
+
account: {
|
|
1388
|
+
email,
|
|
1389
|
+
},
|
|
1390
|
+
billing: {
|
|
1391
|
+
method: 'card',
|
|
1392
|
+
card: {
|
|
1393
|
+
token: token.id,
|
|
1394
|
+
brand: card.brand,
|
|
1395
|
+
last4: card.last4,
|
|
1396
|
+
exp_month: card.exp_month,
|
|
1397
|
+
exp_year: card.exp_year,
|
|
1398
|
+
gateway: 'stripe',
|
|
1399
|
+
},
|
|
1400
|
+
...this._mapAddress(billingAddress),
|
|
1401
|
+
},
|
|
1402
|
+
...(requireShipping && {
|
|
1403
|
+
shipping: this._mapAddress(shippingAddress),
|
|
1404
|
+
}),
|
|
1405
|
+
});
|
|
1406
|
+
|
|
1407
|
+
this.onSuccess();
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
_mapAddress(address) {
|
|
1411
|
+
return {
|
|
1412
|
+
name: address.name,
|
|
1413
|
+
address1: address.address1,
|
|
1414
|
+
address2: address.address2,
|
|
1415
|
+
city: address.locality,
|
|
1416
|
+
state: address.administrativeArea,
|
|
1417
|
+
zip: address.postalCode,
|
|
1418
|
+
country: address.countryCode,
|
|
1419
|
+
phone: address.phoneNumber,
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
class StripeApplePayment extends Payment {
|
|
1425
|
+
constructor(request, options, params, methods) {
|
|
1426
|
+
if (!methods.card) {
|
|
1427
|
+
throw new PaymentMethodDisabledError('Credit cards');
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
const method = {
|
|
1431
|
+
...methods.apple,
|
|
1432
|
+
publishable_key: methods.card.publishable_key,
|
|
1433
|
+
};
|
|
1434
|
+
|
|
1435
|
+
super(request, options, params, method);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
get scripts() {
|
|
1439
|
+
return ['stripe-js'];
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
get stripe() {
|
|
1443
|
+
if (!StripeApplePayment.stripe) {
|
|
1444
|
+
if (window.Stripe) {
|
|
1445
|
+
this.stripe = window.Stripe(this.method.publishable_key);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
if (!StripeApplePayment.stripe) {
|
|
1449
|
+
throw new LibraryNotLoadedError('Stripe');
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
return StripeApplePayment.stripe;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
set stripe(stripe) {
|
|
1457
|
+
StripeApplePayment.stripe = stripe;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
async createElements() {
|
|
1461
|
+
const {
|
|
1462
|
+
elementId = 'applepay-button',
|
|
1463
|
+
style: { type = 'default', theme = 'dark', height = '40px' } = {},
|
|
1464
|
+
classes = {},
|
|
1465
|
+
} = this.params;
|
|
1466
|
+
|
|
1467
|
+
this.setElementContainer(elementId);
|
|
1468
|
+
await this.loadScripts(this.scripts);
|
|
1469
|
+
await this._authorizeDomain();
|
|
1470
|
+
|
|
1471
|
+
const cart = await this.getCart();
|
|
1472
|
+
const paymentRequest = this._createPaymentRequest(cart);
|
|
1473
|
+
const canMakePayment = await paymentRequest.canMakePayment();
|
|
1474
|
+
|
|
1475
|
+
if (!canMakePayment || !canMakePayment.applePay) {
|
|
1476
|
+
throw new Error(
|
|
1477
|
+
'This device is not capable of making Apple Pay payments',
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
this.element = this.stripe.elements().create('paymentRequestButton', {
|
|
1482
|
+
paymentRequest,
|
|
1483
|
+
style: {
|
|
1484
|
+
paymentRequestButton: {
|
|
1485
|
+
type,
|
|
1486
|
+
theme,
|
|
1487
|
+
height,
|
|
1488
|
+
},
|
|
1489
|
+
},
|
|
1490
|
+
classes,
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
mountElements() {
|
|
1495
|
+
this.element.mount(`#${this.elementContainer.id}`);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
async _authorizeDomain() {
|
|
1499
|
+
const domain = window.location.hostname;
|
|
1500
|
+
const authorization = await this.authorizeGateway({
|
|
1501
|
+
gateway: 'stripe',
|
|
1502
|
+
params: {
|
|
1503
|
+
applepay_domain: domain,
|
|
1504
|
+
},
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
if (!authorization) {
|
|
1508
|
+
throw new Error(`${domain} domain is not verified`);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
_createPaymentRequest(cart) {
|
|
1513
|
+
const { require: { name, email, shipping, phone } = {} } = this.params;
|
|
1514
|
+
|
|
1515
|
+
const paymentRequest = this.stripe.paymentRequest({
|
|
1516
|
+
requestPayerName: Boolean(name),
|
|
1517
|
+
requestPayerEmail: Boolean(email),
|
|
1518
|
+
requestPayerPhone: Boolean(phone),
|
|
1519
|
+
requestShipping: Boolean(shipping),
|
|
1520
|
+
disableWallets: ['googlePay', 'browserCard', 'link'],
|
|
1521
|
+
...this._getPaymentRequestData(cart),
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
paymentRequest.on(
|
|
1525
|
+
'shippingaddresschange',
|
|
1526
|
+
this._onShippingAddressChange.bind(this),
|
|
1527
|
+
);
|
|
1528
|
+
paymentRequest.on(
|
|
1529
|
+
'shippingoptionchange',
|
|
1530
|
+
this._onShippingOptionChange.bind(this),
|
|
1531
|
+
);
|
|
1532
|
+
paymentRequest.on('paymentmethod', this._onPaymentMethod.bind(this));
|
|
1533
|
+
|
|
1534
|
+
return paymentRequest;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
_getPaymentRequestData(cart) {
|
|
1538
|
+
const {
|
|
1539
|
+
currency,
|
|
1540
|
+
shipping,
|
|
1541
|
+
items,
|
|
1542
|
+
capture_total,
|
|
1543
|
+
shipment_rating,
|
|
1544
|
+
shipment_total,
|
|
1545
|
+
tax_included_total,
|
|
1546
|
+
settings,
|
|
1547
|
+
} = cart;
|
|
1548
|
+
|
|
1549
|
+
const stripeCurrency = currency.toLowerCase();
|
|
1550
|
+
const displayItems = items.map((item) => ({
|
|
1551
|
+
label: item.product.name,
|
|
1552
|
+
amount: stripeAmountByCurrency(
|
|
1553
|
+
currency,
|
|
1554
|
+
item.price_total - item.discount_total,
|
|
1555
|
+
),
|
|
1556
|
+
}));
|
|
1557
|
+
|
|
1558
|
+
if (tax_included_total) {
|
|
1559
|
+
displayItems.push({
|
|
1560
|
+
label: 'Taxes',
|
|
1561
|
+
amount: stripeAmountByCurrency(currency, tax_included_total),
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
if (shipping.price && shipment_total) {
|
|
1566
|
+
displayItems.push({
|
|
1567
|
+
label: shipping.service_name,
|
|
1568
|
+
amount: stripeAmountByCurrency(currency, shipment_total),
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
const services = shipment_rating && shipment_rating.services;
|
|
1573
|
+
let shippingOptions;
|
|
1574
|
+
if (services) {
|
|
1575
|
+
shippingOptions = services.map((service) => ({
|
|
1576
|
+
id: service.id,
|
|
1577
|
+
label: service.name,
|
|
1578
|
+
detail: service.description,
|
|
1579
|
+
amount: stripeAmountByCurrency(currency, service.price),
|
|
1580
|
+
}));
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
return {
|
|
1584
|
+
country: settings.country,
|
|
1585
|
+
currency: stripeCurrency,
|
|
1586
|
+
total: {
|
|
1587
|
+
label: settings.name,
|
|
1588
|
+
amount: stripeAmountByCurrency(currency, capture_total),
|
|
1589
|
+
pending: true,
|
|
1590
|
+
},
|
|
1591
|
+
displayItems,
|
|
1592
|
+
shippingOptions,
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
async _onShippingAddressChange(event) {
|
|
1597
|
+
const { shippingAddress, updateWith } = event;
|
|
1598
|
+
const shipping = this._mapShippingAddress(shippingAddress);
|
|
1599
|
+
const cart = await this.updateCart({
|
|
1600
|
+
shipping: { ...shipping, service: null },
|
|
1601
|
+
shipment_rating: null,
|
|
1602
|
+
});
|
|
1603
|
+
|
|
1604
|
+
if (cart) {
|
|
1605
|
+
updateWith({ status: 'success', ...this._getPaymentRequestData(cart) });
|
|
1606
|
+
} else {
|
|
1607
|
+
updateWith({ status: 'invalid_shipping_address' });
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
async _onShippingOptionChange(event) {
|
|
1612
|
+
const { shippingOption, updateWith } = event;
|
|
1613
|
+
const cart = await this.updateCart({
|
|
1614
|
+
shipping: { service: shippingOption.id },
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
if (cart) {
|
|
1618
|
+
updateWith({ status: 'success', ...this._getPaymentRequestData(cart) });
|
|
1619
|
+
} else {
|
|
1620
|
+
updateWith({ status: 'fail' });
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
async _onPaymentMethod(event) {
|
|
1625
|
+
const {
|
|
1626
|
+
payerEmail,
|
|
1627
|
+
paymentMethod: { id: paymentMethod, card, billing_details },
|
|
1628
|
+
shippingAddress,
|
|
1629
|
+
shippingOption,
|
|
1630
|
+
complete,
|
|
1631
|
+
} = event;
|
|
1632
|
+
const { require: { shipping: requireShipping } = {} } = this.params;
|
|
1633
|
+
|
|
1634
|
+
await this.updateCart({
|
|
1635
|
+
account: {
|
|
1636
|
+
email: payerEmail,
|
|
1637
|
+
},
|
|
1638
|
+
...(requireShipping && {
|
|
1639
|
+
shipping: {
|
|
1640
|
+
...this._mapShippingAddress(shippingAddress),
|
|
1641
|
+
service: shippingOption.id,
|
|
1642
|
+
},
|
|
1643
|
+
}),
|
|
1644
|
+
billing: {
|
|
1645
|
+
...this._mapBillingAddress(billing_details),
|
|
1646
|
+
method: 'card',
|
|
1647
|
+
card: {
|
|
1648
|
+
gateway: 'stripe',
|
|
1649
|
+
token: paymentMethod,
|
|
1650
|
+
brand: card.brand,
|
|
1651
|
+
exp_month: card.exp_month,
|
|
1652
|
+
exp_year: card.exp_year,
|
|
1653
|
+
last4: card.last4,
|
|
1654
|
+
address_check: card.checks.address_line1_check,
|
|
1655
|
+
zip_check: card.checks.address_postal_code_check,
|
|
1656
|
+
cvc_check: card.checks.cvc_check,
|
|
1657
|
+
},
|
|
1658
|
+
},
|
|
1659
|
+
});
|
|
1660
|
+
|
|
1661
|
+
complete('success');
|
|
1662
|
+
|
|
1663
|
+
this.onSuccess();
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
_mapShippingAddress(address = {}) {
|
|
1667
|
+
return {
|
|
1668
|
+
name: address.recipient,
|
|
1669
|
+
address1: address.addressLine[0],
|
|
1670
|
+
address2: address.addressLine[1],
|
|
1671
|
+
city: address.city,
|
|
1672
|
+
state: address.region,
|
|
1673
|
+
zip: address.postalCode,
|
|
1674
|
+
country: address.country,
|
|
1675
|
+
phone: address.phone,
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
_mapBillingAddress(address = {}) {
|
|
1680
|
+
return {
|
|
1681
|
+
name: address.name,
|
|
1682
|
+
phone: address.phone,
|
|
1683
|
+
address1: address.address.line1,
|
|
1684
|
+
address2: address.address.line2,
|
|
1685
|
+
city: address.address.city,
|
|
1686
|
+
state: address.address.state,
|
|
1687
|
+
zip: address.address.postal_code,
|
|
1688
|
+
country: address.address.country,
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
class BraintreePaypalPayment extends Payment {
|
|
1694
|
+
constructor(request, options, params, methods) {
|
|
1695
|
+
super(request, options, params, methods.paypal);
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
get scripts() {
|
|
1699
|
+
const { client_id, merchant_id } = this.method;
|
|
1700
|
+
|
|
1701
|
+
return [
|
|
1702
|
+
{
|
|
1703
|
+
id: 'braintree-paypal-sdk',
|
|
1704
|
+
params: { client_id, merchant_id, cart: ['currency'] },
|
|
1705
|
+
},
|
|
1706
|
+
'braintree-web',
|
|
1707
|
+
'braintree-web-paypal-checkout',
|
|
1708
|
+
];
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
get paypal() {
|
|
1712
|
+
if (!window.paypal) {
|
|
1713
|
+
throw new LibraryNotLoadedError('PayPal');
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
return window.paypal;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
get braintree() {
|
|
1720
|
+
if (!window.braintree) {
|
|
1721
|
+
throw new LibraryNotLoadedError('Braintree');
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
return window.braintree;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
get braintreePaypalCheckout() {
|
|
1728
|
+
if (!this.braintree.paypalCheckout) {
|
|
1729
|
+
throw new LibraryNotLoadedError('Braintree PayPal Checkout');
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
return this.braintree.paypalCheckout;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
async createElements() {
|
|
1736
|
+
const {
|
|
1737
|
+
elementId = 'paypal-button',
|
|
1738
|
+
locale = 'en_US',
|
|
1739
|
+
style: {
|
|
1740
|
+
layout = 'horizontal',
|
|
1741
|
+
height = 45,
|
|
1742
|
+
color = 'gold',
|
|
1743
|
+
shape = 'rect',
|
|
1744
|
+
label = 'paypal',
|
|
1745
|
+
tagline = false,
|
|
1746
|
+
} = {},
|
|
1747
|
+
} = this.params;
|
|
1748
|
+
|
|
1749
|
+
this.setElementContainer(elementId);
|
|
1750
|
+
|
|
1751
|
+
const authorization = await this.authorizeGateway({
|
|
1752
|
+
gateway: 'braintree',
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
if (authorization.error) {
|
|
1756
|
+
throw new Error(authorization.error.message);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
await this.loadScripts(this.scripts);
|
|
1760
|
+
|
|
1761
|
+
const braintreeClient = await this.braintree.client.create({
|
|
1762
|
+
authorization,
|
|
1763
|
+
});
|
|
1764
|
+
const paypalCheckout = await this.braintreePaypalCheckout.create({
|
|
1765
|
+
client: braintreeClient,
|
|
1766
|
+
});
|
|
1767
|
+
const cart = await this.getCart();
|
|
1768
|
+
|
|
1769
|
+
this.element = this.paypal.Buttons({
|
|
1770
|
+
locale,
|
|
1771
|
+
style: {
|
|
1772
|
+
layout,
|
|
1773
|
+
height,
|
|
1774
|
+
color,
|
|
1775
|
+
shape,
|
|
1776
|
+
label,
|
|
1777
|
+
tagline,
|
|
1778
|
+
},
|
|
1779
|
+
fundingSource: this.paypal.FUNDING.PAYPAL,
|
|
1780
|
+
createBillingAgreement: this._createBillingAgreement.bind(
|
|
1781
|
+
this,
|
|
1782
|
+
paypalCheckout,
|
|
1783
|
+
cart,
|
|
1784
|
+
),
|
|
1785
|
+
onApprove: this._onApprove.bind(this, paypalCheckout),
|
|
1786
|
+
onCancel: this.onCancel.bind(this),
|
|
1787
|
+
onError: this.onError.bind(this),
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
mountElements() {
|
|
1792
|
+
const { classes = {} } = this.params;
|
|
1793
|
+
const container = this.elementContainer;
|
|
1794
|
+
|
|
1795
|
+
this.element.render(`#${container.id}`);
|
|
1796
|
+
|
|
1797
|
+
if (classes.base) {
|
|
1798
|
+
container.classList.add(classes.base);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
_createBillingAgreement(paypalCheckout, cart) {
|
|
1803
|
+
const { require: { shipping: requireShipping = true } = {} } = this.params;
|
|
1804
|
+
|
|
1805
|
+
return paypalCheckout.createPayment({
|
|
1806
|
+
flow: 'vault',
|
|
1807
|
+
amount: cart.capture_total,
|
|
1808
|
+
currency: cart.currency,
|
|
1809
|
+
requestBillingAgreement: true,
|
|
1810
|
+
enableShippingAddress: Boolean(requireShipping),
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
async _onApprove(paypalCheckout, data, actions) {
|
|
1815
|
+
const { require: { shipping: requireShipping = true } = {} } = this.params;
|
|
1816
|
+
const { details, nonce } = await paypalCheckout.tokenizePayment(data);
|
|
1817
|
+
const { email, countryCode, firstName, lastName } = details;
|
|
1818
|
+
|
|
1819
|
+
await this.updateCart({
|
|
1820
|
+
account: {
|
|
1821
|
+
email: email,
|
|
1822
|
+
},
|
|
1823
|
+
billing: {
|
|
1824
|
+
name: `${firstName} ${lastName}`,
|
|
1825
|
+
first_name: firstName,
|
|
1826
|
+
last_name: lastName,
|
|
1827
|
+
country: countryCode,
|
|
1828
|
+
method: 'paypal',
|
|
1829
|
+
paypal: {
|
|
1830
|
+
nonce,
|
|
1831
|
+
},
|
|
1832
|
+
},
|
|
1833
|
+
...(requireShipping && {
|
|
1834
|
+
shipping: {
|
|
1835
|
+
name: details.shippingAddress.recipientName,
|
|
1836
|
+
...this._mapAddress(details.shippingAddress),
|
|
1837
|
+
},
|
|
1838
|
+
}),
|
|
1839
|
+
});
|
|
1840
|
+
|
|
1841
|
+
this.onSuccess();
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
_mapAddress(address) {
|
|
1845
|
+
return {
|
|
1846
|
+
address1: address.line1,
|
|
1847
|
+
address2: address.line2,
|
|
1848
|
+
state: address.state,
|
|
1849
|
+
city: address.city,
|
|
1850
|
+
zip: address.postalCode,
|
|
1851
|
+
country: address.countryCode,
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
const API_VERSION = 2;
|
|
1857
|
+
const API_MINOR_VERSION = 0;
|
|
1858
|
+
const ALLOWED_CARD_AUTH_METHODS = ['PAN_ONLY', 'CRYPTOGRAM_3DS'];
|
|
1859
|
+
const ALLOWED_CARD_NETWORKS = [
|
|
1860
|
+
'AMEX',
|
|
1861
|
+
'DISCOVER',
|
|
1862
|
+
'INTERAC',
|
|
1863
|
+
'JCB',
|
|
1864
|
+
'MASTERCARD',
|
|
1865
|
+
'VISA',
|
|
1866
|
+
];
|
|
1867
|
+
|
|
1868
|
+
class BraintreeGooglePayment extends Payment {
|
|
1869
|
+
constructor(request, options, params, methods) {
|
|
1870
|
+
if (!methods.card) {
|
|
1871
|
+
throw new PaymentMethodDisabledError('Credit cards');
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
super(request, options, params, methods.google);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
get scripts() {
|
|
1878
|
+
return ['google-pay', 'braintree-web', 'braintree-google-payment'];
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
get braintree() {
|
|
1882
|
+
if (!window.braintree) {
|
|
1883
|
+
throw new LibraryNotLoadedError('Braintree');
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
return window.braintree;
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
get google() {
|
|
1890
|
+
if (!window.google) {
|
|
1891
|
+
throw new LibraryNotLoadedError('Google');
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
return window.google;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
get googleClient() {
|
|
1898
|
+
if (!BraintreeGooglePayment.googleClient) {
|
|
1899
|
+
if (this.google) {
|
|
1900
|
+
this.googleClient = new this.google.payments.api.PaymentsClient({
|
|
1901
|
+
environment: isLiveMode(this.method.mode) ? 'PRODUCTION' : 'TEST',
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
if (!BraintreeGooglePayment.googleClient) {
|
|
1906
|
+
throw new LibraryNotLoadedError('Google client');
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
return BraintreeGooglePayment.googleClient;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
set googleClient(googleClient) {
|
|
1914
|
+
BraintreeGooglePayment.googleClient = googleClient;
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
get cardPaymentMethod() {
|
|
1918
|
+
return {
|
|
1919
|
+
type: 'CARD',
|
|
1920
|
+
parameters: {
|
|
1921
|
+
allowedAuthMethods: ALLOWED_CARD_AUTH_METHODS,
|
|
1922
|
+
allowedCardNetworks: ALLOWED_CARD_NETWORKS,
|
|
1923
|
+
billingAddressRequired: true,
|
|
1924
|
+
billingAddressParameters: {
|
|
1925
|
+
format: 'FULL',
|
|
1926
|
+
phoneNumberRequired: true,
|
|
1927
|
+
},
|
|
1928
|
+
},
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
get allowedPaymentMethods() {
|
|
1933
|
+
return [this.cardPaymentMethod];
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
async createElements() {
|
|
1937
|
+
const {
|
|
1938
|
+
elementId = 'googlepay-button',
|
|
1939
|
+
locale = 'en',
|
|
1940
|
+
style: { color = 'black', type = 'buy', sizeMode = 'fill' } = {},
|
|
1941
|
+
} = this.params;
|
|
1942
|
+
|
|
1943
|
+
if (!this.method.merchant_id) {
|
|
1944
|
+
throw new Error('Google merchant ID is not defined');
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
this.setElementContainer(elementId);
|
|
1948
|
+
await this.loadScripts(this.scripts);
|
|
1949
|
+
|
|
1950
|
+
const isReadyToPay = await this.googleClient.isReadyToPay({
|
|
1951
|
+
apiVersion: API_VERSION,
|
|
1952
|
+
apiVersionMinor: API_MINOR_VERSION,
|
|
1953
|
+
allowedPaymentMethods: this.allowedPaymentMethods,
|
|
1954
|
+
existingPaymentMethodRequired: true,
|
|
1955
|
+
});
|
|
1956
|
+
|
|
1957
|
+
if (!isReadyToPay.result) {
|
|
1958
|
+
throw new Error(
|
|
1959
|
+
'This device is not capable of making Google Pay payments',
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
const braintreeClient = await this._createBraintreeClient();
|
|
1964
|
+
const googlePayment = await this.braintree.googlePayment.create({
|
|
1965
|
+
client: braintreeClient,
|
|
1966
|
+
googleMerchantId: this.method.merchant_id,
|
|
1967
|
+
googlePayVersion: API_VERSION,
|
|
1968
|
+
});
|
|
1969
|
+
const cart = await this.getCart();
|
|
1970
|
+
const paymentRequestData = this._createPaymentRequestData(cart);
|
|
1971
|
+
const paymentDataRequest =
|
|
1972
|
+
googlePayment.createPaymentDataRequest(paymentRequestData);
|
|
1973
|
+
|
|
1974
|
+
this.element = this.googleClient.createButton({
|
|
1975
|
+
buttonColor: color,
|
|
1976
|
+
buttonType: type,
|
|
1977
|
+
buttonSizeMode: sizeMode,
|
|
1978
|
+
buttonLocale: locale,
|
|
1979
|
+
onClick: this._onClick.bind(this, googlePayment, paymentDataRequest),
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
mountElements() {
|
|
1984
|
+
const { classes = {} } = this.params;
|
|
1985
|
+
const container = this.elementContainer;
|
|
1986
|
+
|
|
1987
|
+
container.appendChild(this.element);
|
|
1988
|
+
|
|
1989
|
+
if (classes.base) {
|
|
1990
|
+
container.classList.add(classes.base);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
async _createBraintreeClient() {
|
|
1995
|
+
const authorization = await this.authorizeGateway({
|
|
1996
|
+
gateway: 'braintree',
|
|
1997
|
+
});
|
|
1998
|
+
|
|
1999
|
+
if (authorization.error) {
|
|
2000
|
+
throw new Error(authorization.error.message);
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
return this.braintree.client.create({
|
|
2004
|
+
authorization,
|
|
2005
|
+
});
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
_createPaymentRequestData(cart) {
|
|
2009
|
+
const {
|
|
2010
|
+
settings: { name },
|
|
2011
|
+
capture_total,
|
|
2012
|
+
currency,
|
|
2013
|
+
} = cart;
|
|
2014
|
+
const { require: { email, shipping, phone } = {} } = this.params;
|
|
2015
|
+
|
|
2016
|
+
return {
|
|
2017
|
+
apiVersion: API_VERSION,
|
|
2018
|
+
apiVersionMinor: API_MINOR_VERSION,
|
|
2019
|
+
transactionInfo: {
|
|
2020
|
+
currencyCode: currency,
|
|
2021
|
+
totalPrice: capture_total.toString(),
|
|
2022
|
+
totalPriceStatus: 'ESTIMATED',
|
|
2023
|
+
},
|
|
2024
|
+
allowedPaymentMethods: this.allowedPaymentMethods,
|
|
2025
|
+
emailRequired: Boolean(email),
|
|
2026
|
+
shippingAddressRequired: Boolean(shipping),
|
|
2027
|
+
shippingAddressParameters: {
|
|
2028
|
+
phoneNumberRequired: Boolean(phone),
|
|
2029
|
+
},
|
|
2030
|
+
merchantInfo: {
|
|
2031
|
+
merchantName: name,
|
|
2032
|
+
merchantId: this.method.merchant_id,
|
|
2033
|
+
},
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
async _onClick(googlePayment, paymentDataRequest) {
|
|
2038
|
+
try {
|
|
2039
|
+
const paymentData = await this.googleClient.loadPaymentData(
|
|
2040
|
+
paymentDataRequest,
|
|
2041
|
+
);
|
|
2042
|
+
|
|
2043
|
+
if (paymentData) {
|
|
2044
|
+
await this._submitPayment(googlePayment, paymentData);
|
|
2045
|
+
}
|
|
2046
|
+
} catch (error) {
|
|
2047
|
+
this.onError(error);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
async _submitPayment(googlePayment, paymentData) {
|
|
2052
|
+
const { require: { shipping: requireShipping } = {} } = this.params;
|
|
2053
|
+
const { nonce } = await googlePayment.parseResponse(paymentData);
|
|
2054
|
+
const { email, shippingAddress, paymentMethodData } = paymentData;
|
|
2055
|
+
const {
|
|
2056
|
+
info: { billingAddress },
|
|
2057
|
+
} = paymentMethodData;
|
|
2058
|
+
|
|
2059
|
+
await this.updateCart({
|
|
2060
|
+
account: {
|
|
2061
|
+
email,
|
|
2062
|
+
},
|
|
2063
|
+
billing: {
|
|
2064
|
+
method: 'google',
|
|
2065
|
+
google: {
|
|
2066
|
+
nonce,
|
|
2067
|
+
gateway: 'braintree',
|
|
2068
|
+
},
|
|
2069
|
+
...this._mapAddress(billingAddress),
|
|
2070
|
+
},
|
|
2071
|
+
...(requireShipping && {
|
|
2072
|
+
shipping: this._mapAddress(shippingAddress),
|
|
2073
|
+
}),
|
|
2074
|
+
});
|
|
2075
|
+
|
|
2076
|
+
this.onSuccess();
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
_mapAddress(address) {
|
|
2080
|
+
return {
|
|
2081
|
+
name: address.name,
|
|
2082
|
+
address1: address.address1,
|
|
2083
|
+
address2: address.address2,
|
|
2084
|
+
city: address.locality,
|
|
2085
|
+
state: address.administrativeArea,
|
|
2086
|
+
zip: address.postalCode,
|
|
2087
|
+
country: address.countryCode,
|
|
2088
|
+
phone: address.phoneNumber,
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
const VERSION = 3;
|
|
2094
|
+
const MERCHANT_CAPABILITIES = [
|
|
2095
|
+
'supports3DS',
|
|
2096
|
+
'supportsDebit',
|
|
2097
|
+
'supportsCredit',
|
|
2098
|
+
];
|
|
2099
|
+
|
|
2100
|
+
class BraintreeApplePayment extends Payment {
|
|
2101
|
+
constructor(request, options, params, methods) {
|
|
2102
|
+
if (!methods.card) {
|
|
2103
|
+
throw new PaymentMethodDisabledError('Credit cards');
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
super(request, options, params, methods.apple);
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
get scripts() {
|
|
2110
|
+
return ['braintree-web', 'braintree-apple-payment'];
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
get braintree() {
|
|
2114
|
+
if (!window.braintree) {
|
|
2115
|
+
throw new LibraryNotLoadedError('Braintree');
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
return window.braintree;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
get ApplePaySession() {
|
|
2122
|
+
if (!window.ApplePaySession) {
|
|
2123
|
+
throw new LibraryNotLoadedError('Apple');
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
return window.ApplePaySession;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
async createElements() {
|
|
2130
|
+
const { elementId = 'applepay-button' } = this.params;
|
|
2131
|
+
|
|
2132
|
+
this.setElementContainer(elementId);
|
|
2133
|
+
await this.loadScripts(this.scripts);
|
|
2134
|
+
|
|
2135
|
+
if (!this.ApplePaySession.canMakePayments()) {
|
|
2136
|
+
throw new Error(
|
|
2137
|
+
'This device is not capable of making Apple Pay payments',
|
|
2138
|
+
);
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
const cart = await this.getCart();
|
|
2142
|
+
const braintreeClient = await this._createBraintreeClient();
|
|
2143
|
+
const applePayment = await this.braintree.applePay.create({
|
|
2144
|
+
client: braintreeClient,
|
|
2145
|
+
});
|
|
2146
|
+
const paymentRequest = await this._createPaymentRequest(cart, applePayment);
|
|
2147
|
+
|
|
2148
|
+
this.element = this._createButton(applePayment, paymentRequest);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
mountElements() {
|
|
2152
|
+
const { classes = {} } = this.params;
|
|
2153
|
+
const container = this.elementContainer;
|
|
2154
|
+
|
|
2155
|
+
container.appendChild(this.element);
|
|
2156
|
+
|
|
2157
|
+
if (classes.base) {
|
|
2158
|
+
container.classList.add(classes.base);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
_createButton(applePayment, paymentRequest) {
|
|
2163
|
+
const { style: { type = 'plain', theme = 'black', height = '40px' } = {} } =
|
|
2164
|
+
this.params;
|
|
2165
|
+
|
|
2166
|
+
const button = document.createElement('div');
|
|
2167
|
+
|
|
2168
|
+
button.style.appearance = '-apple-pay-button';
|
|
2169
|
+
button.style['-apple-pay-button-type'] = type;
|
|
2170
|
+
button.style['-apple-pay-button-style'] = theme;
|
|
2171
|
+
button.style.height = height;
|
|
2172
|
+
|
|
2173
|
+
button.addEventListener(
|
|
2174
|
+
'click',
|
|
2175
|
+
this._createPaymentSession.bind(this, applePayment, paymentRequest),
|
|
2176
|
+
);
|
|
2177
|
+
|
|
2178
|
+
return button;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
async _createBraintreeClient() {
|
|
2182
|
+
const authorization = await this.authorizeGateway({
|
|
2183
|
+
gateway: 'braintree',
|
|
2184
|
+
});
|
|
2185
|
+
|
|
2186
|
+
if (authorization.error) {
|
|
2187
|
+
throw new Error(authorization.error.message);
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
return this.braintree.client.create({
|
|
2191
|
+
authorization,
|
|
2192
|
+
});
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
_createPaymentRequest(cart, applePayment) {
|
|
2196
|
+
const { require = {} } = this.params;
|
|
2197
|
+
const {
|
|
2198
|
+
settings: { name },
|
|
2199
|
+
capture_total,
|
|
2200
|
+
currency,
|
|
2201
|
+
} = cart;
|
|
2202
|
+
|
|
2203
|
+
const requiredShippingContactFields = [];
|
|
2204
|
+
const requiredBillingContactFields = ['postalAddress'];
|
|
2205
|
+
|
|
2206
|
+
if (require.name) {
|
|
2207
|
+
requiredShippingContactFields.push('name');
|
|
2208
|
+
}
|
|
2209
|
+
if (require.email) {
|
|
2210
|
+
requiredShippingContactFields.push('email');
|
|
2211
|
+
}
|
|
2212
|
+
if (require.phone) {
|
|
2213
|
+
requiredShippingContactFields.push('phone');
|
|
2214
|
+
}
|
|
2215
|
+
if (require.shipping) {
|
|
2216
|
+
requiredShippingContactFields.push('postalAddress');
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
return applePayment.createPaymentRequest({
|
|
2220
|
+
total: {
|
|
2221
|
+
label: name,
|
|
2222
|
+
type: 'pending',
|
|
2223
|
+
amount: capture_total.toString(),
|
|
2224
|
+
},
|
|
2225
|
+
currencyCode: currency,
|
|
2226
|
+
merchantCapabilities: MERCHANT_CAPABILITIES,
|
|
2227
|
+
requiredShippingContactFields,
|
|
2228
|
+
requiredBillingContactFields,
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
_createPaymentSession(applePayment, paymentRequest) {
|
|
2233
|
+
const session = new this.ApplePaySession(VERSION, paymentRequest);
|
|
2234
|
+
|
|
2235
|
+
session.onvalidatemerchant = async (event) => {
|
|
2236
|
+
const merchantSession = await applePayment
|
|
2237
|
+
.performValidation({
|
|
2238
|
+
validationURL: event.validationURL,
|
|
2239
|
+
displayName: paymentRequest.total.label,
|
|
2240
|
+
})
|
|
2241
|
+
.catch(this.onError.bind(this));
|
|
2242
|
+
|
|
2243
|
+
if (merchantSession) {
|
|
2244
|
+
session.completeMerchantValidation(merchantSession);
|
|
2245
|
+
} else {
|
|
2246
|
+
session.abort();
|
|
2247
|
+
}
|
|
2248
|
+
};
|
|
2249
|
+
|
|
2250
|
+
session.onpaymentauthorized = async (event) => {
|
|
2251
|
+
const {
|
|
2252
|
+
payment: { token, shippingContact, billingContact },
|
|
2253
|
+
} = event;
|
|
2254
|
+
const { require: { shipping: requireShipping } = {} } = this.params;
|
|
2255
|
+
const payload = await applePayment
|
|
2256
|
+
.tokenize({ token })
|
|
2257
|
+
.catch(this.onError.bind(this));
|
|
2258
|
+
|
|
2259
|
+
if (!payload) {
|
|
2260
|
+
return session.completePayment(this.ApplePaySession.STATUS_FAILURE);
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
await this.updateCart({
|
|
2264
|
+
account: {
|
|
2265
|
+
email: shippingContact.emailAddress,
|
|
2266
|
+
},
|
|
2267
|
+
billing: {
|
|
2268
|
+
method: 'apple',
|
|
2269
|
+
apple: {
|
|
2270
|
+
nonce: payload.nonce,
|
|
2271
|
+
gateway: 'braintree',
|
|
2272
|
+
},
|
|
2273
|
+
...this._mapAddress(billingContact),
|
|
2274
|
+
},
|
|
2275
|
+
...(requireShipping && {
|
|
2276
|
+
shipping: this._mapAddress(shippingContact),
|
|
2277
|
+
}),
|
|
2278
|
+
});
|
|
2279
|
+
|
|
2280
|
+
this.onSuccess();
|
|
2281
|
+
|
|
2282
|
+
return session.completePayment(this.ApplePaySession.STATUS_SUCCESS);
|
|
2283
|
+
};
|
|
2284
|
+
|
|
2285
|
+
session.begin();
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
_mapAddress(address = {}) {
|
|
2289
|
+
return {
|
|
2290
|
+
first_name: address.givenName,
|
|
2291
|
+
last_name: address.familyName,
|
|
2292
|
+
address1: address.addressLines[0],
|
|
2293
|
+
address2: address.addressLines[1],
|
|
2294
|
+
city: address.locality,
|
|
2295
|
+
state: address.administrativeArea,
|
|
2296
|
+
zip: address.postalCode,
|
|
2297
|
+
country: address.countryCode,
|
|
2298
|
+
phone: address.phoneNumber,
|
|
2299
|
+
};
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
class QuickpayCardPayment extends Payment {
|
|
2304
|
+
constructor(request, options, params, methods) {
|
|
2305
|
+
super(request, options, params, methods.card);
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
get orderId() {
|
|
2309
|
+
return Math.random().toString(36).substr(2, 9);
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
async tokenize() {
|
|
2313
|
+
const cart = await this.getCart();
|
|
2314
|
+
const intent = await this.createIntent({
|
|
2315
|
+
gateway: 'quickpay',
|
|
2316
|
+
intent: {
|
|
2317
|
+
order_id: this.orderId,
|
|
2318
|
+
currency: cart.currency || 'USD',
|
|
2319
|
+
},
|
|
2320
|
+
});
|
|
2321
|
+
|
|
2322
|
+
await this.updateCart({
|
|
2323
|
+
billing: {
|
|
2324
|
+
method: 'card',
|
|
2325
|
+
intent: {
|
|
2326
|
+
quickpay: {
|
|
2327
|
+
id: intent,
|
|
2328
|
+
},
|
|
2329
|
+
},
|
|
2330
|
+
},
|
|
2331
|
+
});
|
|
2332
|
+
|
|
2333
|
+
const returnUrl = window.location.origin + window.location.pathname;
|
|
2334
|
+
const authorization = await this.authorizeGateway({
|
|
2335
|
+
gateway: 'quickpay',
|
|
2336
|
+
params: {
|
|
2337
|
+
action: 'create',
|
|
2338
|
+
continueurl: `${returnUrl}?gateway=quickpay&redirect_status=succeeded`,
|
|
2339
|
+
cancelurl: `${returnUrl}?gateway=quickpay&redirect_status=canceled`,
|
|
2340
|
+
},
|
|
2341
|
+
});
|
|
2342
|
+
|
|
2343
|
+
if (authorization && authorization.url) {
|
|
2344
|
+
window.location.replace(authorization.url);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
async handleRedirect(queryParams) {
|
|
2349
|
+
const { redirect_status: status, card_id: id } = queryParams;
|
|
2350
|
+
|
|
2351
|
+
switch (status) {
|
|
2352
|
+
case 'succeeded':
|
|
2353
|
+
return this._handleSuccessfulRedirect(id);
|
|
2354
|
+
case 'canceled':
|
|
2355
|
+
throw new UnableAuthenticatePaymentMethodError();
|
|
2356
|
+
default:
|
|
2357
|
+
throw new Error(`Unknown redirect status: ${status}`);
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
async _handleSuccessfulRedirect(cardId) {
|
|
2362
|
+
const card = await this.authorizeGateway({
|
|
2363
|
+
gateway: 'quickpay',
|
|
2364
|
+
params: { action: 'get', id: cardId },
|
|
2365
|
+
});
|
|
2366
|
+
|
|
2367
|
+
if (card.error) {
|
|
2368
|
+
throw new Error(card.error.message);
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
await this.updateCart({
|
|
2372
|
+
billing: {
|
|
2373
|
+
method: 'card',
|
|
2374
|
+
card,
|
|
2375
|
+
},
|
|
2376
|
+
});
|
|
2377
|
+
|
|
2378
|
+
this.onSuccess();
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
function createPaysafecardPaymentData(cart) {
|
|
2383
|
+
const returnUrl = window.location.origin + window.location.pathname;
|
|
2384
|
+
const url = `${returnUrl}?gateway=paysafecard`;
|
|
2385
|
+
|
|
2386
|
+
return {
|
|
2387
|
+
type: 'PAYSAFECARD',
|
|
2388
|
+
amount: cart.capture_total,
|
|
2389
|
+
redirect: {
|
|
2390
|
+
success_url: url,
|
|
2391
|
+
failure_url: url,
|
|
2392
|
+
},
|
|
2393
|
+
notification_url: url,
|
|
2394
|
+
customer: {
|
|
2395
|
+
id: get(cart, 'account.id'),
|
|
2396
|
+
},
|
|
2397
|
+
currency: get(cart, 'currency', 'USD'),
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
class PaysafecardDirectPayment extends Payment {
|
|
2402
|
+
constructor(request, options, params, methods) {
|
|
2403
|
+
super(request, options, params, methods.paysafecard);
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
async tokenize() {
|
|
2407
|
+
const cart = await this.getCart();
|
|
2408
|
+
const intentData = createPaysafecardPaymentData(cart);
|
|
2409
|
+
const intent = await this.createIntent({
|
|
2410
|
+
gateway: 'paysafecard',
|
|
2411
|
+
intent: intentData,
|
|
2412
|
+
});
|
|
2413
|
+
|
|
2414
|
+
if (!intent) {
|
|
2415
|
+
throw new Error('Paysafecard payment is not defined');
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
await this.updateCart({
|
|
2419
|
+
billing: {
|
|
2420
|
+
method: 'paysafecard',
|
|
2421
|
+
intent: {
|
|
2422
|
+
paysafecard: {
|
|
2423
|
+
id: intent.id,
|
|
2424
|
+
},
|
|
2425
|
+
},
|
|
2426
|
+
},
|
|
2427
|
+
});
|
|
2428
|
+
|
|
2429
|
+
window.location.replace(intent.redirect.auth_url);
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
async handleRedirect() {
|
|
2433
|
+
const cart = await this.getCart();
|
|
2434
|
+
const paymentId = get(cart, 'billing.intent.paysafecard.id');
|
|
2435
|
+
|
|
2436
|
+
if (!paymentId) {
|
|
2437
|
+
throw new Error('Paysafecard payment ID is not defined');
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
const intent = await this.updateIntent({
|
|
2441
|
+
gateway: 'paysafecard',
|
|
2442
|
+
intent: { payment_id: paymentId },
|
|
2443
|
+
});
|
|
2444
|
+
|
|
2445
|
+
if (!intent) {
|
|
2446
|
+
throw new Error('Paysafecard payment is not defined');
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
switch (intent.status) {
|
|
2450
|
+
case 'SUCCESS':
|
|
2451
|
+
case 'AUTHORIZED':
|
|
2452
|
+
return this.onSuccess();
|
|
2453
|
+
case 'CANCELED_CUSTOMER':
|
|
2454
|
+
throw new UnableAuthenticatePaymentMethodError();
|
|
2455
|
+
default:
|
|
2456
|
+
throw new Error(`Unknown redirect status: ${intent.status}.`);
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
const addressFieldsMap = {
|
|
2462
|
+
given_name: 'first_name',
|
|
2463
|
+
family_name: 'last_name',
|
|
2464
|
+
city: 'city',
|
|
2465
|
+
country: 'country',
|
|
2466
|
+
phone: 'phone',
|
|
2467
|
+
postal_code: 'zip',
|
|
2468
|
+
street_address: 'address1',
|
|
2469
|
+
street_address2: 'address2',
|
|
2470
|
+
region: 'state',
|
|
2471
|
+
};
|
|
2472
|
+
|
|
2473
|
+
const mapFields = (fieldsMap, data) =>
|
|
2474
|
+
reduce(
|
|
2475
|
+
fieldsMap,
|
|
2476
|
+
(acc, srcKey, destKey) => {
|
|
2477
|
+
const value = data[srcKey];
|
|
2478
|
+
if (value) {
|
|
2479
|
+
acc[destKey] = value;
|
|
2480
|
+
}
|
|
2481
|
+
return acc;
|
|
2482
|
+
},
|
|
2483
|
+
{},
|
|
2484
|
+
);
|
|
2485
|
+
|
|
2486
|
+
const mapAddressFields = (cart, addressField) => ({
|
|
2487
|
+
...mapFields(addressFieldsMap, cart[addressField]),
|
|
2488
|
+
email: get(cart, 'account.email'),
|
|
2489
|
+
});
|
|
2490
|
+
|
|
2491
|
+
function getOrderLines(cart) {
|
|
2492
|
+
const items = map(cart.items, (item) => ({
|
|
2493
|
+
type: 'physical',
|
|
2494
|
+
name: get(item, 'product.name'),
|
|
2495
|
+
reference: get(item, 'product.sku') || get(item, 'product.slug'),
|
|
2496
|
+
quantity: item.quantity,
|
|
2497
|
+
unit_price: Math.round(toNumber(item.price - item.discount_each) * 100),
|
|
2498
|
+
total_amount: Math.round(
|
|
2499
|
+
toNumber(item.price_total - item.discount_total) * 100,
|
|
2500
|
+
),
|
|
2501
|
+
tax_rate: 0,
|
|
2502
|
+
total_tax_amount: 0,
|
|
2503
|
+
}));
|
|
2504
|
+
|
|
2505
|
+
const tax = get(cart, 'tax_included_total');
|
|
2506
|
+
const taxAmount = toNumber(tax) * 100;
|
|
2507
|
+
if (tax) {
|
|
2508
|
+
items.push({
|
|
2509
|
+
type: 'sales_tax',
|
|
2510
|
+
name: 'Taxes',
|
|
2511
|
+
quantity: 1,
|
|
2512
|
+
unit_price: taxAmount,
|
|
2513
|
+
total_amount: taxAmount,
|
|
2514
|
+
tax_rate: 0,
|
|
2515
|
+
total_tax_amount: 0,
|
|
2516
|
+
});
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
const shipping = get(cart, 'shipping', {});
|
|
2520
|
+
const shippingTotal = get(cart, 'shipment_total', {});
|
|
2521
|
+
const shippingAmount = toNumber(shippingTotal) * 100;
|
|
2522
|
+
if (shipping.price) {
|
|
2523
|
+
items.push({
|
|
2524
|
+
type: 'shipping_fee',
|
|
2525
|
+
name: shipping.service_name,
|
|
2526
|
+
quantity: 1,
|
|
2527
|
+
unit_price: shippingAmount,
|
|
2528
|
+
total_amount: shippingAmount,
|
|
2529
|
+
tax_rate: 0,
|
|
2530
|
+
total_tax_amount: 0,
|
|
2531
|
+
});
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
return items;
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
function getKlarnaSessionData(cart) {
|
|
2538
|
+
const returnUrl = `${window.location.origin}${window.location.pathname}?gateway=klarna_direct&sid={{session_id}}`;
|
|
2539
|
+
const successUrl = `${returnUrl}&authorization_token={{authorization_token}}`;
|
|
2540
|
+
|
|
2541
|
+
return {
|
|
2542
|
+
locale: cart.display_locale || get(cart, 'settings.locale') || 'en-US',
|
|
2543
|
+
purchase_country:
|
|
2544
|
+
get(cart, 'billing.country') || get(cart, 'shipping.country'),
|
|
2545
|
+
purchase_currency: cart.currency,
|
|
2546
|
+
billing_address: mapAddressFields(cart, 'billing'),
|
|
2547
|
+
shipping_address: mapAddressFields(cart, 'shipping'),
|
|
2548
|
+
order_amount: Math.round(get(cart, 'capture_total', 0) * 100),
|
|
2549
|
+
order_lines: JSON.stringify(getOrderLines(cart)),
|
|
2550
|
+
merchant_urls: {
|
|
2551
|
+
success: successUrl,
|
|
2552
|
+
back: returnUrl,
|
|
2553
|
+
cancel: returnUrl,
|
|
2554
|
+
error: returnUrl,
|
|
2555
|
+
failure: returnUrl,
|
|
2556
|
+
},
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
class KlarnaDirectPayment extends Payment {
|
|
2561
|
+
constructor(request, options, params, methods) {
|
|
2562
|
+
super(request, options, params, methods.klarna);
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
async tokenize() {
|
|
2566
|
+
const cart = await this.getCart();
|
|
2567
|
+
const sessionData = getKlarnaSessionData(cart);
|
|
2568
|
+
const session = await this.createIntent({
|
|
2569
|
+
gateway: 'klarna',
|
|
2570
|
+
intent: sessionData,
|
|
2571
|
+
});
|
|
2572
|
+
|
|
2573
|
+
if (!session) {
|
|
2574
|
+
throw new Error('Klarna session is not defined');
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
window.location.replace(session.redirect_url);
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
async handleRedirect(queryParams) {
|
|
2581
|
+
const { authorization_token } = queryParams;
|
|
2582
|
+
|
|
2583
|
+
if (!authorization_token) {
|
|
2584
|
+
throw new UnableAuthenticatePaymentMethodError();
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
await this.updateCart({
|
|
2588
|
+
billing: {
|
|
2589
|
+
method: 'klarna',
|
|
2590
|
+
klarna: {
|
|
2591
|
+
token: authorization_token,
|
|
2592
|
+
},
|
|
2593
|
+
},
|
|
2594
|
+
});
|
|
2595
|
+
|
|
2596
|
+
this.onSuccess();
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
class PaypalDirectPayment extends Payment {
|
|
2601
|
+
constructor(request, options, params, methods) {
|
|
2602
|
+
super(request, options, params, methods.paypal);
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
get scripts() {
|
|
2606
|
+
const { client_id } = this.method;
|
|
2607
|
+
|
|
2608
|
+
return [
|
|
2609
|
+
{
|
|
2610
|
+
id: 'paypal-sdk',
|
|
2611
|
+
params: {
|
|
2612
|
+
client_id,
|
|
2613
|
+
merchant_id: this.merchantId,
|
|
2614
|
+
cart: ['currency'],
|
|
2615
|
+
},
|
|
2616
|
+
},
|
|
2617
|
+
];
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
get paypal() {
|
|
2621
|
+
if (!window.paypal) {
|
|
2622
|
+
throw new LibraryNotLoadedError('PayPal');
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
return window.paypal;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
get merchantId() {
|
|
2629
|
+
const { mode, ppcp } = this.method;
|
|
2630
|
+
|
|
2631
|
+
return ppcp ? this.method[`${mode}_merchant_id`] : this.method.merchant_id;
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
get returnUrl() {
|
|
2635
|
+
return `${
|
|
2636
|
+
window.location.origin + window.location.pathname
|
|
2637
|
+
}?gateway=paypal`;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
async createElements() {
|
|
2641
|
+
const {
|
|
2642
|
+
elementId = 'paypal-button',
|
|
2643
|
+
locale = 'en_US',
|
|
2644
|
+
style: {
|
|
2645
|
+
layout = 'horizontal',
|
|
2646
|
+
height = 45,
|
|
2647
|
+
color = 'gold',
|
|
2648
|
+
shape = 'rect',
|
|
2649
|
+
label = 'paypal',
|
|
2650
|
+
tagline = false,
|
|
2651
|
+
} = {},
|
|
2652
|
+
} = this.params;
|
|
2653
|
+
|
|
2654
|
+
this.setElementContainer(elementId);
|
|
2655
|
+
|
|
2656
|
+
const cart = await this.getCart();
|
|
2657
|
+
|
|
2658
|
+
this._validateCart(cart);
|
|
2659
|
+
await this.loadScripts(this.scripts);
|
|
2660
|
+
|
|
2661
|
+
this.element = this.paypal.Buttons({
|
|
2662
|
+
locale,
|
|
2663
|
+
style: {
|
|
2664
|
+
layout,
|
|
2665
|
+
height,
|
|
2666
|
+
color,
|
|
2667
|
+
shape,
|
|
2668
|
+
label,
|
|
2669
|
+
tagline,
|
|
2670
|
+
},
|
|
2671
|
+
createOrder: this._onCreateOrder.bind(this, cart),
|
|
2672
|
+
onShippingChange: this._onShippingChange.bind(this),
|
|
2673
|
+
onApprove: this._onApprove.bind(this),
|
|
2674
|
+
onError: this.onError.bind(this),
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
mountElements() {
|
|
2679
|
+
const { classes = {} } = this.params;
|
|
2680
|
+
const container = this.elementContainer;
|
|
2681
|
+
|
|
2682
|
+
this.element.render(`#${container.id}`);
|
|
2683
|
+
|
|
2684
|
+
if (classes.base) {
|
|
2685
|
+
container.classList.add(classes.base);
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
_validateCart(cart) {
|
|
2690
|
+
const hasSubscriptionProduct = Boolean(cart.subscription_delivery);
|
|
2691
|
+
|
|
2692
|
+
if (hasSubscriptionProduct && !this.method.ppcp) {
|
|
2693
|
+
throw new Error(
|
|
2694
|
+
'Subscriptions are only supported by PayPal Commerce Platform. See Payment settings in the Swell dashboard to enable PayPal Commerce Platform',
|
|
2695
|
+
);
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
if (!(cart.capture_total > 0)) {
|
|
2699
|
+
throw new Error(
|
|
2700
|
+
'Invalid PayPal button amount. Value should be greater than zero.',
|
|
2701
|
+
);
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
async _onCreateOrder(cart, data, actions) {
|
|
2706
|
+
const { require: { shipping: requireShipping = true } = {} } = this.params;
|
|
2707
|
+
const { capture_total, currency, subscription_delivery } = cart;
|
|
2708
|
+
const hasSubscriptionProduct = Boolean(subscription_delivery);
|
|
2709
|
+
const merchantId = this.merchantId;
|
|
2710
|
+
const returnUrl = this.returnUrl;
|
|
2711
|
+
const orderData = {
|
|
2712
|
+
application_context: {
|
|
2713
|
+
shipping_preference: requireShipping ? 'GET_FROM_FILE' : 'NO_SHIPPING',
|
|
2714
|
+
},
|
|
2715
|
+
};
|
|
2716
|
+
const purchaseUnit = {
|
|
2717
|
+
amount: {
|
|
2718
|
+
value: Number(capture_total.toFixed(2)),
|
|
2719
|
+
currency_code: currency,
|
|
2720
|
+
},
|
|
2721
|
+
};
|
|
2722
|
+
|
|
2723
|
+
if (merchantId) {
|
|
2724
|
+
// express checkout and ppcp
|
|
2725
|
+
orderData.intent = 'AUTHORIZE';
|
|
2726
|
+
purchaseUnit.payee = {
|
|
2727
|
+
merchant_id: merchantId,
|
|
2728
|
+
};
|
|
2729
|
+
|
|
2730
|
+
if (hasSubscriptionProduct) {
|
|
2731
|
+
orderData.payment_source = {
|
|
2732
|
+
paypal: {
|
|
2733
|
+
attributes: {
|
|
2734
|
+
vault: {
|
|
2735
|
+
store_in_vault: 'ON_SUCCESS',
|
|
2736
|
+
usage_type: 'MERCHANT',
|
|
2737
|
+
},
|
|
2738
|
+
},
|
|
2739
|
+
experience_context: {
|
|
2740
|
+
return_url: `${returnUrl}&redirect_status=succeeded`,
|
|
2741
|
+
cancel_url: `${returnUrl}&redirect_status=canceled`,
|
|
2742
|
+
},
|
|
2743
|
+
},
|
|
2744
|
+
};
|
|
2745
|
+
}
|
|
2746
|
+
} else {
|
|
2747
|
+
// progressive checkout
|
|
2748
|
+
orderData.intent = 'CAPTURE';
|
|
2749
|
+
purchaseUnit.payee = {
|
|
2750
|
+
email_address: this.method.store_owner_email,
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
orderData.purchase_units = [purchaseUnit];
|
|
2755
|
+
|
|
2756
|
+
const order = await this.createIntent({
|
|
2757
|
+
gateway: 'paypal',
|
|
2758
|
+
intent: orderData,
|
|
2759
|
+
});
|
|
2760
|
+
|
|
2761
|
+
return order.id;
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
async _onShippingChange(data, actions) {
|
|
2765
|
+
try {
|
|
2766
|
+
const { orderID, shipping_address, selected_shipping_option } = data;
|
|
2767
|
+
const updateData = {
|
|
2768
|
+
shipping: {
|
|
2769
|
+
state: shipping_address.state,
|
|
2770
|
+
city: shipping_address.city,
|
|
2771
|
+
zip: shipping_address.postal_code,
|
|
2772
|
+
country: shipping_address.country_code,
|
|
2773
|
+
},
|
|
2774
|
+
shipment_rating: null,
|
|
2775
|
+
};
|
|
2776
|
+
|
|
2777
|
+
if (selected_shipping_option) {
|
|
2778
|
+
updateData.shipping.service = selected_shipping_option.id;
|
|
2779
|
+
updateData.$taxes = true;
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
const cart = await this.updateCart(updateData);
|
|
2783
|
+
const shippingServices = get(cart, 'shipment_rating.services');
|
|
2784
|
+
|
|
2785
|
+
// can't fulfill shipping to selected address
|
|
2786
|
+
if (isEmpty(shippingServices)) {
|
|
2787
|
+
return actions.reject();
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
let selectedShippingService;
|
|
2791
|
+
|
|
2792
|
+
if (selected_shipping_option) {
|
|
2793
|
+
selectedShippingService = shippingServices.find(
|
|
2794
|
+
(shippingService) =>
|
|
2795
|
+
shippingService.id === selected_shipping_option.id,
|
|
2796
|
+
);
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
// need to set first service for cart by default
|
|
2800
|
+
if (!selectedShippingService) {
|
|
2801
|
+
const [firstShippingService] = shippingServices;
|
|
2802
|
+
|
|
2803
|
+
await this.updateCart({
|
|
2804
|
+
shipping: {
|
|
2805
|
+
service: firstShippingService.id,
|
|
2806
|
+
},
|
|
2807
|
+
$taxes: true,
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
await this.updateIntent({
|
|
2812
|
+
gateway: 'paypal',
|
|
2813
|
+
intent: {
|
|
2814
|
+
cart_id: cart.id,
|
|
2815
|
+
paypal_order_id: orderID,
|
|
2816
|
+
},
|
|
2817
|
+
});
|
|
2818
|
+
|
|
2819
|
+
return orderID;
|
|
2820
|
+
} catch (error) {
|
|
2821
|
+
this.onError(error);
|
|
2822
|
+
|
|
2823
|
+
return actions.reject();
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
|
|
2827
|
+
async _onApprove(data, actions) {
|
|
2828
|
+
const { require: { shipping: requireShipping = true } = {} } = this.params;
|
|
2829
|
+
const order = await actions.order.get();
|
|
2830
|
+
const orderId = order.id;
|
|
2831
|
+
const payer = order.payer;
|
|
2832
|
+
const billing = payer.address;
|
|
2833
|
+
const shipping = get(order, 'purchase_units[0].shipping');
|
|
2834
|
+
const name = `${payer.name.given_name} ${payer.name.surname}`;
|
|
2835
|
+
|
|
2836
|
+
await this.updateCart({
|
|
2837
|
+
account: {
|
|
2838
|
+
email: payer.email_address,
|
|
2839
|
+
},
|
|
2840
|
+
billing: {
|
|
2841
|
+
method: 'paypal',
|
|
2842
|
+
paypal: {
|
|
2843
|
+
order_id: orderId,
|
|
2844
|
+
},
|
|
2845
|
+
name,
|
|
2846
|
+
...this._mapAddress(billing),
|
|
2847
|
+
},
|
|
2848
|
+
...(requireShipping && {
|
|
2849
|
+
shipping: {
|
|
2850
|
+
first_name: payer.name.given_name,
|
|
2851
|
+
last_name: payer.name.surname,
|
|
2852
|
+
name: shipping.name.full_name,
|
|
2853
|
+
...this._mapAddress(shipping.address),
|
|
2854
|
+
},
|
|
2855
|
+
}),
|
|
2856
|
+
});
|
|
2857
|
+
|
|
2858
|
+
this.onSuccess();
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
_mapAddress(address) {
|
|
2862
|
+
return {
|
|
2863
|
+
address1: address.address_line_1,
|
|
2864
|
+
address2: address.address_line_2,
|
|
2865
|
+
state: address.admin_area_1,
|
|
2866
|
+
city: address.admin_area_2,
|
|
2867
|
+
zip: address.postal_code,
|
|
2868
|
+
country: address.country_code,
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
class AmazonDirectPayment extends Payment {
|
|
2874
|
+
constructor(request, options, params, methods) {
|
|
2875
|
+
super(request, options, params, methods.amazon);
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
get scripts() {
|
|
2879
|
+
return ['amazon-checkout'];
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
get amazon() {
|
|
2883
|
+
if (!window.amazon) {
|
|
2884
|
+
throw new LibraryNotLoadedError('Amazon');
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
return window.amazon;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
get merchantId() {
|
|
2891
|
+
const merchantId = this.method.merchant_id;
|
|
2892
|
+
|
|
2893
|
+
if (!merchantId) {
|
|
2894
|
+
throw new MethodPropertyMissingError('Amazon', 'merchant_id');
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
return merchantId;
|
|
2898
|
+
}
|
|
2899
|
+
|
|
2900
|
+
get publicKeyId() {
|
|
2901
|
+
const publicKeyId = this.method.public_key_id;
|
|
2902
|
+
|
|
2903
|
+
if (!publicKeyId) {
|
|
2904
|
+
throw new MethodPropertyMissingError('Amazon', 'public_key_id');
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
return publicKeyId;
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
get returnUrl() {
|
|
2911
|
+
return `${
|
|
2912
|
+
window.location.origin + window.location.pathname
|
|
2913
|
+
}?gateway=amazon`;
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
async createElements() {
|
|
2917
|
+
const {
|
|
2918
|
+
elementId = 'amazonpay-button',
|
|
2919
|
+
locale = 'en_US',
|
|
2920
|
+
placement = 'Checkout',
|
|
2921
|
+
style: { color = 'Gold' } = {},
|
|
2922
|
+
require: { shipping: requireShipping } = {},
|
|
2923
|
+
} = this.params;
|
|
2924
|
+
|
|
2925
|
+
this.setElementContainer(elementId);
|
|
2926
|
+
|
|
2927
|
+
const cart = await this.getCart();
|
|
2928
|
+
const session = await this._createSession(cart);
|
|
2929
|
+
|
|
2930
|
+
await this.loadScripts(this.scripts);
|
|
2931
|
+
|
|
2932
|
+
this.element = {
|
|
2933
|
+
ledgerCurrency: cart.currency,
|
|
2934
|
+
checkoutLanguage: locale,
|
|
2935
|
+
productType: Boolean(requireShipping) ? 'PayAndShip' : 'PayOnly',
|
|
2936
|
+
buttonColor: color,
|
|
2937
|
+
placement,
|
|
2938
|
+
merchantId: this.merchantId,
|
|
2939
|
+
publicKeyId: this.publicKeyId,
|
|
2940
|
+
createCheckoutSessionConfig: {
|
|
2941
|
+
payloadJSON: session.payload,
|
|
2942
|
+
signature: session.signature,
|
|
2943
|
+
},
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
mountElements() {
|
|
2948
|
+
const { classes = {} } = this.params;
|
|
2949
|
+
const container = this.elementContainer;
|
|
2950
|
+
const amazon = this.amazon;
|
|
2951
|
+
|
|
2952
|
+
amazon.Pay.renderButton(`#${container.id}`, this.element);
|
|
2953
|
+
|
|
2954
|
+
if (classes.base) {
|
|
2955
|
+
container.classList.add(classes.base);
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
async tokenize() {
|
|
2960
|
+
const cart = await this.getCart();
|
|
2961
|
+
const returnUrl = this.returnUrl;
|
|
2962
|
+
const checkoutSessionId = get(cart, 'billing.amazon.checkout_session_id');
|
|
2963
|
+
|
|
2964
|
+
if (!checkoutSessionId) {
|
|
2965
|
+
throw new Error(
|
|
2966
|
+
'Missing Amazon Pay checkout session ID (billing.amazon.checkout_session_id)',
|
|
2967
|
+
);
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
const intent = await this.createIntent({
|
|
2971
|
+
gateway: 'amazon',
|
|
2972
|
+
intent: {
|
|
2973
|
+
checkoutSessionId,
|
|
2974
|
+
webCheckoutDetails: {
|
|
2975
|
+
checkoutResultReturnUrl: `${returnUrl}&confirm=true&redirect_status=succeeded`,
|
|
2976
|
+
checkoutCancelUrl: `${returnUrl}&redirect_status=canceled`,
|
|
2977
|
+
},
|
|
2978
|
+
paymentDetails:
|
|
2979
|
+
cart.capture_total > 0
|
|
2980
|
+
? {
|
|
2981
|
+
paymentIntent: 'Authorize',
|
|
2982
|
+
canHandlePendingAuthorization: true,
|
|
2983
|
+
chargeAmount: {
|
|
2984
|
+
amount: cart.capture_total,
|
|
2985
|
+
currencyCode: cart.currency,
|
|
2986
|
+
},
|
|
2987
|
+
}
|
|
2988
|
+
: {
|
|
2989
|
+
// Just confirm payment to save payment details when capture total amount is 0.
|
|
2990
|
+
// e.g. trial subscription, 100% discount or items.price = 0
|
|
2991
|
+
paymentIntent: 'Confirm',
|
|
2992
|
+
},
|
|
2993
|
+
},
|
|
2994
|
+
});
|
|
2995
|
+
|
|
2996
|
+
return window.location.replace(intent.redirect_url);
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
async handleRedirect(queryParams) {
|
|
3000
|
+
const { redirect_status } = queryParams;
|
|
3001
|
+
|
|
3002
|
+
switch (redirect_status) {
|
|
3003
|
+
case 'succeeded':
|
|
3004
|
+
return this._handleSuccessfulRedirect(queryParams);
|
|
3005
|
+
case 'canceled':
|
|
3006
|
+
throw new UnableAuthenticatePaymentMethodError();
|
|
3007
|
+
default:
|
|
3008
|
+
throw new Error(`Unknown redirect status: ${redirect_status}`);
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
|
|
3012
|
+
_createSession(cart) {
|
|
3013
|
+
const returnUrl = this.returnUrl;
|
|
3014
|
+
const isSubscription = Boolean(cart.subscription_delivery);
|
|
3015
|
+
|
|
3016
|
+
return this.authorizeGateway({
|
|
3017
|
+
gateway: 'amazon',
|
|
3018
|
+
params: {
|
|
3019
|
+
chargePermissionType: isSubscription ? 'Recurring' : 'OneTime',
|
|
3020
|
+
...(isSubscription
|
|
3021
|
+
? {
|
|
3022
|
+
recurringMetadata: {
|
|
3023
|
+
frequency: {
|
|
3024
|
+
unit: 'Variable',
|
|
3025
|
+
value: '0',
|
|
3026
|
+
},
|
|
3027
|
+
},
|
|
3028
|
+
}
|
|
3029
|
+
: {}),
|
|
3030
|
+
webCheckoutDetails: {
|
|
3031
|
+
checkoutReviewReturnUrl: `${returnUrl}&redirect_status=succeeded`,
|
|
3032
|
+
checkoutCancelUrl: `${returnUrl}&redirect_status=canceled`,
|
|
3033
|
+
},
|
|
3034
|
+
},
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
async _handleSuccessfulRedirect(queryParams) {
|
|
3039
|
+
const { confirm, amazonCheckoutSessionId } = queryParams;
|
|
3040
|
+
|
|
3041
|
+
if (!confirm) {
|
|
3042
|
+
await this.updateCart({
|
|
3043
|
+
billing: {
|
|
3044
|
+
method: 'amazon',
|
|
3045
|
+
amazon: {
|
|
3046
|
+
checkout_session_id: amazonCheckoutSessionId,
|
|
3047
|
+
},
|
|
3048
|
+
},
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3052
|
+
this.onSuccess();
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
function adjustConfig(params) {
|
|
3057
|
+
if (!params.config) {
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
if (params.card) {
|
|
3062
|
+
console.warn('Please move the "config" field to the "card.config"');
|
|
3063
|
+
|
|
3064
|
+
params.card.config = params.config;
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3067
|
+
if (params.ideal) {
|
|
3068
|
+
console.warn('Please move the "config" field to the "ideal.config"');
|
|
3069
|
+
|
|
3070
|
+
params.ideal.config = params.config;
|
|
3071
|
+
}
|
|
3072
|
+
|
|
3073
|
+
delete params.config;
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
function adjustElementId(methodParams) {
|
|
3077
|
+
if (methodParams.cardNumber) {
|
|
3078
|
+
adjustElementId(methodParams.cardNumber);
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
if (methodParams.cardExpiry) {
|
|
3082
|
+
adjustElementId(methodParams.cardExpiry);
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
if (methodParams.cardCvc) {
|
|
3086
|
+
adjustElementId(methodParams.cardCvc);
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
if (!methodParams.elementId) {
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
if (methodParams.elementId.startsWith('#')) {
|
|
3094
|
+
console.warn(
|
|
3095
|
+
`Please remove the "#" sign from the "${methodParams.elementId}" element ID`,
|
|
3096
|
+
);
|
|
3097
|
+
|
|
3098
|
+
methodParams.elementId = methodParams.elementId.substring(1);
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
function adjustParams(_params) {
|
|
3103
|
+
const params = { ..._params };
|
|
3104
|
+
|
|
3105
|
+
adjustConfig(params);
|
|
3106
|
+
|
|
3107
|
+
return params;
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
function adjustMethodParams(_methodParams) {
|
|
3111
|
+
const methodParams = { ..._methodParams };
|
|
3112
|
+
|
|
3113
|
+
adjustElementId(methodParams);
|
|
3114
|
+
|
|
3115
|
+
return methodParams;
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
class PaymentController {
|
|
3119
|
+
constructor(request, options) {
|
|
3120
|
+
this.request = request;
|
|
3121
|
+
this.options = options;
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
get(id) {
|
|
3125
|
+
return this.request('get', '/payments', id);
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
async methods() {
|
|
3129
|
+
if (this.methodSettings) {
|
|
3130
|
+
return this.methodSettings;
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
this.methodSettings = await this.request('get', '/payment/methods');
|
|
3134
|
+
|
|
3135
|
+
return this.methodSettings;
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
async createElements(params) {
|
|
3139
|
+
this.params = params;
|
|
3140
|
+
|
|
3141
|
+
if (!params) {
|
|
3142
|
+
throw new Error('Payment element parameters are not provided');
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
const paymentInstances = await this._createPaymentInstances();
|
|
3146
|
+
|
|
3147
|
+
await this._performPaymentAction(paymentInstances, 'createElements').then(
|
|
3148
|
+
(paymentInstances) =>
|
|
3149
|
+
this._performPaymentAction(paymentInstances, 'mountElements'),
|
|
3150
|
+
);
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
async tokenize(params = this.params) {
|
|
3154
|
+
this.params = params;
|
|
3155
|
+
|
|
3156
|
+
if (!this.params) {
|
|
3157
|
+
throw new Error('Tokenization parameters are not provided');
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
const paymentInstances = await this._createPaymentInstances();
|
|
3161
|
+
|
|
3162
|
+
await this._performPaymentAction(paymentInstances, 'tokenize');
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
async handleRedirect(params = this.params) {
|
|
3166
|
+
const queryParams = getLocationParams(window.location);
|
|
3167
|
+
|
|
3168
|
+
if (!queryParams || !queryParams.gateway) {
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
this.params = params;
|
|
3173
|
+
|
|
3174
|
+
if (!params) {
|
|
3175
|
+
throw new Error('Redirect parameters are not provided');
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
removeUrlParams();
|
|
3179
|
+
|
|
3180
|
+
const paymentInstances = await this._createPaymentInstances();
|
|
3181
|
+
|
|
3182
|
+
await this._performPaymentAction(
|
|
3183
|
+
paymentInstances,
|
|
3184
|
+
'handleRedirect',
|
|
3185
|
+
queryParams,
|
|
3186
|
+
);
|
|
3187
|
+
}
|
|
3188
|
+
|
|
3189
|
+
async authenticate(id) {
|
|
3190
|
+
try {
|
|
3191
|
+
const payment = await this.get(id);
|
|
3192
|
+
|
|
3193
|
+
if (!payment) {
|
|
3194
|
+
throw new Error('Payment not found');
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
const { method, gateway } = payment;
|
|
3198
|
+
const PaymentClass = this._getPaymentClass(method, gateway);
|
|
3199
|
+
|
|
3200
|
+
if (!PaymentClass) {
|
|
3201
|
+
throw new UnsupportedPaymentMethodError(method, gateway);
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
const paymentMethods = await this._getPaymentMethods();
|
|
3205
|
+
const methodSettings = paymentMethods[method];
|
|
3206
|
+
|
|
3207
|
+
if (!methodSettings) {
|
|
3208
|
+
throw new PaymentMethodDisabledError(method);
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
const paymentInstance = new PaymentClass(
|
|
3212
|
+
this.request,
|
|
3213
|
+
this.options,
|
|
3214
|
+
null,
|
|
3215
|
+
paymentMethods,
|
|
3216
|
+
);
|
|
3217
|
+
|
|
3218
|
+
await paymentInstance.loadScripts(paymentInstance.scripts);
|
|
3219
|
+
return await paymentInstance.authenticate(payment);
|
|
3220
|
+
} catch (error) {
|
|
3221
|
+
return { error };
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
async createIntent(data) {
|
|
3226
|
+
return this._vaultRequest('post', '/intent', data);
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
async updateIntent(data) {
|
|
3230
|
+
return this._vaultRequest('put', '/intent', data);
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
async authorizeGateway(data) {
|
|
3234
|
+
return this._vaultRequest('post', '/authorization', data);
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
async _getPaymentMethods() {
|
|
3238
|
+
const paymentMethods = await methods$1(
|
|
3239
|
+
this.request,
|
|
3240
|
+
this.options,
|
|
3241
|
+
).payments();
|
|
3242
|
+
|
|
3243
|
+
if (paymentMethods.error) {
|
|
3244
|
+
throw new Error(paymentMethods.error);
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
return toSnake(paymentMethods);
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
async _vaultRequest(method, url, data) {
|
|
3251
|
+
const response = await vaultRequest(method, url, data);
|
|
3252
|
+
|
|
3253
|
+
if (response.errors) {
|
|
3254
|
+
const param = Object.keys(response.errors)[0];
|
|
3255
|
+
const err = new Error(response.errors[param].message || 'Unknown error');
|
|
3256
|
+
err.code = 'vault_error';
|
|
3257
|
+
err.status = 402;
|
|
3258
|
+
err.param = param;
|
|
3259
|
+
throw err;
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
if (this.options.useCamelCase) {
|
|
3263
|
+
return toCamel(response);
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
return response;
|
|
3267
|
+
}
|
|
3268
|
+
|
|
3269
|
+
async _createPaymentInstances() {
|
|
3270
|
+
const paymentMethods = await this._getPaymentMethods();
|
|
3271
|
+
const params = adjustParams(this.params);
|
|
3272
|
+
|
|
3273
|
+
return Object.entries(params).reduce((acc, [method, params]) => {
|
|
3274
|
+
const methodSettings = paymentMethods[method];
|
|
3275
|
+
|
|
3276
|
+
if (!methodSettings) {
|
|
3277
|
+
console.error(new PaymentMethodDisabledError(method));
|
|
3278
|
+
|
|
3279
|
+
return acc;
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
const PaymentClass = this._getPaymentClass(
|
|
3283
|
+
method,
|
|
3284
|
+
methodSettings.gateway,
|
|
3285
|
+
);
|
|
3286
|
+
|
|
3287
|
+
if (!PaymentClass) {
|
|
3288
|
+
console.error(
|
|
3289
|
+
new UnsupportedPaymentMethodError(method, methodSettings.gateway),
|
|
3290
|
+
);
|
|
3291
|
+
|
|
3292
|
+
return acc;
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
const methodParams = adjustMethodParams(params);
|
|
3296
|
+
|
|
3297
|
+
try {
|
|
3298
|
+
const paymentInstance = new PaymentClass(
|
|
3299
|
+
this.request,
|
|
3300
|
+
this.options,
|
|
3301
|
+
methodParams,
|
|
3302
|
+
paymentMethods,
|
|
3303
|
+
);
|
|
3304
|
+
|
|
3305
|
+
acc.push(paymentInstance);
|
|
3306
|
+
} catch (error) {
|
|
3307
|
+
console.error(error);
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
return acc;
|
|
3311
|
+
}, []);
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
async _performPaymentAction(paymentInstances, action, ...args) {
|
|
3315
|
+
const nextPaymentInstances = [];
|
|
3316
|
+
|
|
3317
|
+
for (const paymentInstance of paymentInstances) {
|
|
3318
|
+
try {
|
|
3319
|
+
const paymentAction = paymentInstance[action];
|
|
3320
|
+
|
|
3321
|
+
if (paymentAction) {
|
|
3322
|
+
await paymentAction.call(paymentInstance, ...args);
|
|
3323
|
+
nextPaymentInstances.push(paymentInstance);
|
|
3324
|
+
}
|
|
3325
|
+
} catch (error) {
|
|
3326
|
+
const onPaymentError = paymentInstance.onError.bind(paymentInstance);
|
|
3327
|
+
|
|
3328
|
+
onPaymentError(error);
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
return nextPaymentInstances;
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
_getPaymentClass(method, gateway) {
|
|
3336
|
+
switch (method) {
|
|
3337
|
+
case 'card':
|
|
3338
|
+
return this._getCardPaymentClass(gateway);
|
|
3339
|
+
case 'ideal':
|
|
3340
|
+
return this._getIDealPaymentClass(gateway);
|
|
3341
|
+
case 'bancontact':
|
|
3342
|
+
return this._getBancontactPaymentClass(gateway);
|
|
3343
|
+
case 'klarna':
|
|
3344
|
+
return this._getKlarnaPaymentClass(gateway);
|
|
3345
|
+
case 'paysafecard':
|
|
3346
|
+
return this._getPaysafecardPaymentClass(gateway);
|
|
3347
|
+
case 'paypal':
|
|
3348
|
+
return this._getPaypalPaymentClass(gateway);
|
|
3349
|
+
case 'google':
|
|
3350
|
+
return this._getGooglePaymentClass(gateway);
|
|
3351
|
+
case 'apple':
|
|
3352
|
+
return this._getApplePaymentClass(gateway);
|
|
3353
|
+
case 'amazon':
|
|
3354
|
+
return this._getAmazonPaymentClass(gateway);
|
|
3355
|
+
default:
|
|
3356
|
+
return null;
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
_getCardPaymentClass(gateway) {
|
|
3361
|
+
switch (gateway) {
|
|
3362
|
+
case 'stripe':
|
|
3363
|
+
return StripeCardPayment;
|
|
3364
|
+
case 'quickpay':
|
|
3365
|
+
return QuickpayCardPayment;
|
|
3366
|
+
default:
|
|
3367
|
+
return null;
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
_getIDealPaymentClass(gateway) {
|
|
3372
|
+
switch (gateway) {
|
|
3373
|
+
case 'stripe':
|
|
3374
|
+
return StripeIDealPayment;
|
|
3375
|
+
default:
|
|
3376
|
+
return null;
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
_getBancontactPaymentClass(gateway) {
|
|
3381
|
+
switch (gateway) {
|
|
3382
|
+
case 'stripe':
|
|
3383
|
+
return StripeBancontactPayment;
|
|
3384
|
+
default:
|
|
3385
|
+
return null;
|
|
3386
|
+
}
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
_getKlarnaPaymentClass(gateway) {
|
|
3390
|
+
switch (gateway) {
|
|
3391
|
+
case 'stripe':
|
|
3392
|
+
return StripeKlarnaPayment;
|
|
3393
|
+
case 'klarna':
|
|
3394
|
+
return KlarnaDirectPayment;
|
|
3395
|
+
default:
|
|
3396
|
+
return null;
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
_getPaysafecardPaymentClass(gateway) {
|
|
3401
|
+
switch (gateway) {
|
|
3402
|
+
default:
|
|
3403
|
+
return PaysafecardDirectPayment;
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
_getPaypalPaymentClass(gateway) {
|
|
3408
|
+
switch (gateway) {
|
|
3409
|
+
case 'braintree':
|
|
3410
|
+
return BraintreePaypalPayment;
|
|
3411
|
+
default:
|
|
3412
|
+
return PaypalDirectPayment;
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3416
|
+
_getGooglePaymentClass(gateway) {
|
|
3417
|
+
switch (gateway) {
|
|
3418
|
+
case 'stripe':
|
|
3419
|
+
return StripeGooglePayment;
|
|
3420
|
+
case 'braintree':
|
|
3421
|
+
return BraintreeGooglePayment;
|
|
3422
|
+
default:
|
|
3423
|
+
return null;
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
_getApplePaymentClass(gateway) {
|
|
3428
|
+
switch (gateway) {
|
|
3429
|
+
case 'stripe':
|
|
3430
|
+
return StripeApplePayment;
|
|
3431
|
+
case 'braintree':
|
|
3432
|
+
return BraintreeApplePayment;
|
|
3433
|
+
default:
|
|
3434
|
+
return null;
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
_getAmazonPaymentClass(gateway) {
|
|
3439
|
+
switch (gateway) {
|
|
3440
|
+
default:
|
|
3441
|
+
return AmazonDirectPayment;
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
export { PaymentController as P };
|