Skip to content

Commit df840cc

Browse files
feat(payment): Added liability shift processing for BCP FL (#2940)
1 parent a08509c commit df840cc

File tree

6 files changed

+83
-38
lines changed

6 files changed

+83
-38
lines changed

packages/bigcommerce-payments-integration/src/bigcommerce-payments-fastlane/bigcommerce-payments-fastlane-payment-strategy.spec.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
PaymentArgumentInvalidError,
2020
PaymentIntegrationService,
2121
PaymentMethod,
22+
PaymentMethodInvalidError,
2223
UntrustedShippingCardVerificationType,
2324
} from '@bigcommerce/checkout-sdk/payment-integration-api';
2425
import {
@@ -548,7 +549,10 @@ describe('BigCommercePaymentsFastlanePaymentStrategy', () => {
548549
...paypalFastlaneSdk,
549550
ThreeDomainSecureClient: {
550551
...threeDomainSecureComponentMock,
551-
isEligible: jest.fn().mockReturnValue(Promise.resolve(false)),
552+
isEligible: jest.fn().mockReturnValue(Promise.resolve(true)),
553+
show: jest.fn().mockResolvedValue({
554+
liabilityShift: 'YES',
555+
}),
552556
},
553557
};
554558

@@ -569,6 +573,32 @@ describe('BigCommercePaymentsFastlanePaymentStrategy', () => {
569573
);
570574
});
571575

576+
it('does not create order if 3ds on and liability shift not YES', async () => {
577+
const bigCommerceFastlaneSdkMock = {
578+
...paypalFastlaneSdk,
579+
ThreeDomainSecureClient: {
580+
...threeDomainSecureComponentMock,
581+
isEligible: jest.fn().mockReturnValue(Promise.resolve(true)),
582+
show: jest.fn().mockResolvedValue({
583+
liabilityShift: 'UNKNOWN',
584+
}),
585+
},
586+
};
587+
588+
jest.spyOn(bigCommercePaymentsSdk, 'getPayPalFastlaneSdk').mockImplementation(() =>
589+
Promise.resolve(bigCommerceFastlaneSdkMock),
590+
);
591+
592+
await strategy.initialize(initializationOptions);
593+
594+
try {
595+
await strategy.execute(executeOptions);
596+
} catch (error) {
597+
expect(error).toBeInstanceOf(PaymentMethodInvalidError);
598+
expect(bigCommercePaymentsRequestSender.createOrder).not.toHaveBeenCalled();
599+
}
600+
});
601+
572602
it('calls threeDomainSecureComponent isEligible', async () => {
573603
const bigCommerceFastlaneSdkMock = {
574604
...paypalFastlaneSdk,

packages/bigcommerce-payments-integration/src/bigcommerce-payments-fastlane/bigcommerce-payments-fastlane-payment-strategy.ts

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
4040
private paypalComponentMethods?: PayPalFastlaneCardComponentMethods;
4141
private threeDSVerificationMethod?: string;
4242
private paypalFastlaneSdk?: PayPalFastlaneSdk;
43+
private methodId?: string;
44+
private orderId?: string;
4345

4446
constructor(
4547
private paymentIntegrationService: PaymentIntegrationService,
@@ -64,6 +66,8 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
6466
);
6567
}
6668

69+
this.methodId = methodId;
70+
6771
if (!bigcommerce_payments_fastlane) {
6872
throw new InvalidArgumentError(
6973
'Unable to initialize payment because "options.bigcommerce_payments_fastlane" argument is not provided.',
@@ -144,11 +148,11 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
144148
const isVaultedFlow = paymentData && isVaultedInstrument(paymentData);
145149

146150
try {
151+
await this.paymentIntegrationService.submitOrder(order, options);
147152
const paymentPayload = isVaultedFlow
148153
? await this.prepareVaultedInstrumentPaymentPayload(methodId, paymentData)
149154
: await this.preparePaymentPayload(methodId, paymentData);
150155

151-
await this.paymentIntegrationService.submitOrder(order, options);
152156
await this.paymentIntegrationService.submitPayment<PayPalFastlanePaymentFormattedPayload>(
153157
paymentPayload,
154158
);
@@ -303,27 +307,25 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
303307
): Promise<Payment<PayPalFastlanePaymentFormattedPayload>> {
304308
const { instrumentId } = paymentData;
305309
const state = this.paymentIntegrationService.getState();
306-
const cartId = state.getCartOrThrow().id;
307310
const paymentMethod =
308311
state.getPaymentMethodOrThrow<BigCommercePaymentsInitializationData>(methodId);
309312

310-
const { orderId } = await this.bigCommercePaymentsRequestSender.createOrder(methodId, {
311-
cartId,
312-
fastlaneToken: instrumentId,
313-
});
314-
315-
const fastlaneToken =
313+
const is3DSEnabled =
316314
this.isBigcommercePaymentsFastlaneThreeDSAvailable() &&
317-
paymentMethod.config.is3dsEnabled
318-
? await this.get3DSNonce(instrumentId)
319-
: instrumentId;
315+
paymentMethod.config.is3dsEnabled;
316+
317+
if (!is3DSEnabled) {
318+
await this.createOrder(instrumentId);
319+
}
320+
321+
const fastlaneToken = is3DSEnabled ? await this.get3DSNonce(instrumentId) : instrumentId;
320322

321323
return {
322324
methodId,
323325
paymentData: {
324326
formattedPayload: {
325327
paypal_fastlane_token: {
326-
order_id: orderId,
328+
order_id: this.orderId,
327329
token: fastlaneToken,
328330
},
329331
},
@@ -336,7 +338,6 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
336338
paymentData: OrderPaymentRequestBody['paymentData'],
337339
): Promise<Payment<PayPalFastlanePaymentFormattedPayload>> {
338340
const state = this.paymentIntegrationService.getState();
339-
const cartId = state.getCartOrThrow().id;
340341
const billingAddress = state.getBillingAddressOrThrow();
341342
const paymentMethod =
342343
state.getPaymentMethodOrThrow<BigCommercePaymentsInitializationData>(methodId);
@@ -351,19 +352,18 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
351352
this.bigCommercePaymentsFastlaneUtils.mapBcToPayPalAddress(billingAddress),
352353
});
353354

354-
const { orderId } = await this.bigCommercePaymentsRequestSender.createOrder(methodId, {
355-
cartId,
356-
fastlaneToken: id,
357-
});
355+
const is3DSEnabled =
356+
this.isBigcommercePaymentsFastlaneThreeDSAvailable() &&
357+
paymentMethod.config.is3dsEnabled;
358+
359+
if (!is3DSEnabled) {
360+
await this.createOrder(id);
361+
}
358362

359363
const { shouldSaveInstrument = false, shouldSetAsDefaultInstrument = false } =
360364
isHostedInstrumentLike(paymentData) ? paymentData : {};
361365

362-
const fastlaneToken =
363-
this.isBigcommercePaymentsFastlaneThreeDSAvailable() &&
364-
paymentMethod.config.is3dsEnabled
365-
? await this.get3DSNonce(id)
366-
: id;
366+
const fastlaneToken = is3DSEnabled ? await this.get3DSNonce(id) : id;
367367

368368
return {
369369
methodId,
@@ -373,14 +373,30 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
373373
shouldSetAsDefaultInstrument,
374374
formattedPayload: {
375375
paypal_fastlane_token: {
376-
order_id: orderId,
376+
order_id: this.orderId,
377377
token: fastlaneToken,
378378
},
379379
},
380380
},
381381
};
382382
}
383383

384+
private async createOrder(id: string): Promise<void> {
385+
const state = this.paymentIntegrationService.getState();
386+
const cartId = state.getCartOrThrow().id;
387+
if (this.methodId) {
388+
const { orderId } = await this.bigCommercePaymentsRequestSender.createOrder(
389+
this.methodId,
390+
{
391+
cartId,
392+
fastlaneToken: id,
393+
},
394+
);
395+
396+
this.orderId = orderId;
397+
}
398+
}
399+
384400
/**
385401
*
386402
* 3DSecure methods
@@ -427,6 +443,8 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
427443
throw new PaymentMethodInvalidError();
428444
}
429445

446+
await this.createOrder(paypalNonce);
447+
430448
if (authenticationState === TDSecureAuthenticationState.Succeeded) {
431449
return nonce;
432450
}
@@ -438,6 +456,7 @@ export default class BigCommercePaymentsFastlanePaymentStrategy implements Payme
438456

439457
if (authenticationState === TDSecureAuthenticationState.Cancelled) {
440458
console.error('3DS check was canceled');
459+
throw new PaymentMethodInvalidError();
441460
}
442461
}
443462

packages/bigcommerce-payments-utils/src/bigcommerce-payments-types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export interface PayPalSdkConfig {
7878
'data-user-id-token'?: string;
7979
'data-namespace'?: string;
8080
'data-client-token'?: string;
81+
'data-sdk-client-token'?: string;
8182
};
8283
}
8384

@@ -87,7 +88,7 @@ export enum BigCommercePaymentsIntent {
8788
}
8889

8990
export type PayPalSdkComponents = Array<
90-
'fastlane' | 'messages' | 'buttons' | 'payment-fields' | 'googlepay'
91+
'fastlane' | 'messages' | 'buttons' | 'payment-fields' | 'googlepay' | 'three-domain-secure'
9192
>;
9293

9394
/**

packages/bigcommerce-payments-utils/src/paypal-sdk-helper.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,14 @@ describe('PayPalSdkHelper', () => {
9494
await subject.getPayPalFastlaneSdk(paymentMethod, 'USD', sessionId);
9595

9696
expect(loader.loadScript).toHaveBeenCalledWith(
97-
'https://www.paypal.com/sdk/js?client-id=abc&merchant-id=JTS4DY7XFSQZE&commit=true&components=fastlane&currency=USD&intent=capture',
97+
'https://www.paypal.com/sdk/js?client-id=abc&merchant-id=JTS4DY7XFSQZE&commit=true&components=fastlane%2Cthree-domain-secure&currency=USD&intent=capture',
9898
{
9999
async: true,
100100
attributes: {
101101
'data-client-metadata-id': expectedSessionId,
102102
'data-namespace': 'paypalFastlaneSdk',
103103
'data-partner-attribution-id': '1123JLKJASD12',
104-
'data-user-id-token': 'asdcvY7XFSQasd',
104+
'data-sdk-client-token': 'asdcvY7XFSQasd',
105105
},
106106
},
107107
);
@@ -122,14 +122,14 @@ describe('PayPalSdkHelper', () => {
122122
await subject.getPayPalFastlaneSdk(mockPaymentMethod, 'USD', sessionId);
123123

124124
expect(loader.loadScript).toHaveBeenCalledWith(
125-
'https://www.paypal.com/sdk/js?client-id=abc&merchant-id=JTS4DY7XFSQZE&commit=true&components=fastlane&currency=USD&intent=capture',
125+
'https://www.paypal.com/sdk/js?client-id=abc&merchant-id=JTS4DY7XFSQZE&commit=true&components=fastlane%2Cthree-domain-secure&currency=USD&intent=capture',
126126
{
127127
async: true,
128128
attributes: {
129129
'data-client-metadata-id': expectedSessionId,
130130
'data-namespace': 'paypalFastlaneSdk',
131131
'data-partner-attribution-id': '1123JLKJASD12',
132-
'data-user-id-token': 'connectClientToken123',
132+
'data-sdk-client-token': 'asdcvY7XFSQasd',
133133
},
134134
},
135135
);

packages/bigcommerce-payments-utils/src/paypal-sdk-helper.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -141,28 +141,22 @@ export default class PayPalSdkHelper {
141141
throw new MissingDataError(MissingDataErrorType.MissingPaymentMethod);
142142
}
143143

144-
const {
145-
intent,
146-
clientId,
147-
merchantId,
148-
attributionId,
149-
connectClientToken, // TODO: remove when BCP Fastlane A/B testing will be finished
150-
} = initializationData;
144+
const { intent, clientId, merchantId, attributionId } = initializationData;
151145

152146
return {
153147
options: {
154148
'client-id': clientId,
155149
'merchant-id': merchantId,
156150
commit: true,
157-
components: ['fastlane'],
151+
components: ['fastlane', 'three-domain-secure'],
158152
currency: currencyCode,
159153
intent,
160154
},
161155
attributes: {
162156
'data-client-metadata-id': sessionId.replace(/-/g, ''),
163157
'data-namespace': 'paypalFastlaneSdk',
164158
'data-partner-attribution-id': attributionId,
165-
'data-user-id-token': connectClientToken || clientToken,
159+
'data-sdk-client-token': clientToken,
166160
},
167161
};
168162
}

packages/paypal-commerce-integration/src/paypal-commerce-fastlane/paypal-commerce-fastlane-payment-strategy.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ export default class PaypalCommerceFastlanePaymentStrategy implements PaymentStr
463463

464464
if (authenticationState === TDSecureAuthenticationState.Cancelled) {
465465
console.error('3DS check was canceled');
466+
throw new PaymentMethodInvalidError();
466467
}
467468
}
468469

0 commit comments

Comments
 (0)