Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ export interface BigCommercePaymentsInitializationData {
shouldRenderFields?: boolean;
shouldRunAcceleratedCheckout?: boolean;
paymentButtonStyles?: Record<string, PayPalButtonStyleOptions>;
isAppSwitchEnabled?: boolean;
}

/**
Expand Down Expand Up @@ -357,6 +358,8 @@ export interface BigCommercePaymentsButtons {
render(id: string): void;
close(): void;
isEligible(): boolean;
hasReturned?(): boolean;
resume?(): void;
}

export interface BigCommercePaymentsButtonsOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default class BigCommercePaymentsButtonStrategy implements CheckoutButton
false,
);

this.renderButton(containerId, methodId, bigcommerce_payments);
this.renderButton(containerId, methodId, bigcommerce_payments, isBuyNowFlow);
}

deinitialize(): Promise<void> {
Expand All @@ -99,6 +99,7 @@ export default class BigCommercePaymentsButtonStrategy implements CheckoutButton
containerId: string,
methodId: string,
bigcommerce_payments: BigCommercePaymentsButtonInitializeOptions,
isBuyNowFlow?: boolean,
): void {
const { buyNowInitializeOptions, style, onComplete, onEligibilityFailure } =
bigcommerce_payments;
Expand All @@ -107,9 +108,14 @@ export default class BigCommercePaymentsButtonStrategy implements CheckoutButton
const state = this.paymentIntegrationService.getState();
const paymentMethod =
state.getPaymentMethodOrThrow<BigCommercePaymentsInitializationData>(methodId);
const { isHostedCheckoutEnabled } = paymentMethod.initializationData || {};
const { isHostedCheckoutEnabled, isAppSwitchEnabled = true } =
paymentMethod.initializationData || {};

const defaultCallbacks = {
...(!isBuyNowFlow &&
this.isPaypalCommerceAppSwitchEnabled(methodId) && {
appSwitchWhenAvailable: true,
}),
Comment on lines +115 to +118
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The isBuyNowFlow parameter is not passed to renderButton(), causing it to be undefined and incorrectly enabling app switch for buy now flows.
Severity: CRITICAL | Confidence: 0.95

🔍 Detailed Analysis

The isBuyNowFlow parameter is calculated in the initialize method but not passed to the renderButton method. As a result, isBuyNowFlow is undefined within renderButton(). This causes the condition !isBuyNowFlow to evaluate to true, leading to appSwitchWhenAvailable: true being incorrectly included in the button options for buy now flows. This can result in unintended app switch behavior during buy now checkout flows, breaking the feature.

💡 Suggested Fix

Pass the isBuyNowFlow parameter from the initialize method to the renderButton method when calling it, similar to the PayPalCommerceButtonStrategy implementation.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location:
packages/bigcommerce-payments-integration/src/bigcommerce-payments/bigcommerce-payments-button-strategy.ts#L115-L118

Potential issue: The `isBuyNowFlow` parameter is calculated in the `initialize` method
but not passed to the `renderButton` method. As a result, `isBuyNowFlow` is `undefined`
within `renderButton()`. This causes the condition `!isBuyNowFlow` to evaluate to
`true`, leading to `appSwitchWhenAvailable: true` being incorrectly included in the
button options for buy now flows. This can result in unintended app switch behavior
during buy now checkout flows, breaking the feature.

Did we get this right? 👍 / 👎 to inform future reviews.

createOrder: () =>
this.bigCommercePaymentsIntegrationService.createOrder('bigcommerce_payments'),
onApprove: ({ orderID }: ApproveCallbackPayload) =>
Expand All @@ -122,10 +128,12 @@ export default class BigCommercePaymentsButtonStrategy implements CheckoutButton
};

const hostedCheckoutCallbacks = {
onShippingAddressChange: (data: ShippingAddressChangeCallbackPayload) =>
this.onShippingAddressChange(data),
onShippingOptionsChange: (data: ShippingOptionChangeCallbackPayload) =>
this.onShippingOptionsChange(data),
...(!isAppSwitchEnabled && {
onShippingAddressChange: (data: ShippingAddressChangeCallbackPayload) =>
this.onShippingAddressChange(data),
onShippingOptionsChange: (data: ShippingOptionChangeCallbackPayload) =>
this.onShippingOptionsChange(data),
}),
onApprove: (data: ApproveCallbackPayload, actions: ApproveCallbackActions) =>
this.onHostedCheckoutApprove(data, actions, methodId, onComplete),
};
Expand All @@ -141,7 +149,11 @@ export default class BigCommercePaymentsButtonStrategy implements CheckoutButton
const paypalButton = paypalSdk.Buttons(buttonRenderOptions);

if (paypalButton.isEligible()) {
paypalButton.render(`#${containerId}`);
if (paypalButton.hasReturned?.() && this.isPaypalCommerceAppSwitchEnabled(methodId)) {
paypalButton.resume?.();
} else {
paypalButton.render(`#${containerId}`);
}
} else if (onEligibilityFailure && typeof onEligibilityFailure === 'function') {
onEligibilityFailure();
} else {
Expand Down Expand Up @@ -259,4 +271,17 @@ export default class BigCommercePaymentsButtonStrategy implements CheckoutButton
throw error;
}
}

/**
*
* PayPal AppSwitch enabling handling
*
*/
private isPaypalCommerceAppSwitchEnabled(methodId: string): boolean {
const state = this.paymentIntegrationService.getState();
const paymentMethod =
state.getPaymentMethodOrThrow<BigCommercePaymentsInitializationData>(methodId);

return paymentMethod.initializationData?.isAppSwitchEnabled ?? true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,17 @@ export default class BigCommercePaymentsCustomerStrategy implements CustomerStra
const state = this.paymentIntegrationService.getState();
const paymentMethod =
state.getPaymentMethodOrThrow<BigCommercePaymentsInitializationData>(methodId);
const { isHostedCheckoutEnabled, paymentButtonStyles } =
paymentMethod.initializationData || {};
const {
isHostedCheckoutEnabled,
paymentButtonStyles,
isAppSwitchEnabled = true,
} = paymentMethod.initializationData || {};
const { checkoutTopButtonStyles } = paymentButtonStyles || {};

const defaultCallbacks = {
...(this.isPaypalCommerceAppSwitchEnabled(methodId) && {
appSwitchWhenAvailable: true,
}),
createOrder: () =>
this.bigCommercePaymentsIntegrationService.createOrder('bigcommerce_payments'),
onApprove: ({ orderID }: ApproveCallbackPayload) =>
Expand All @@ -132,10 +138,12 @@ export default class BigCommercePaymentsCustomerStrategy implements CustomerStra
};

const hostedCheckoutCallbacks = {
onShippingAddressChange: (data: ShippingAddressChangeCallbackPayload) =>
this.onShippingAddressChange(data),
onShippingOptionsChange: (data: ShippingOptionChangeCallbackPayload) =>
this.onShippingOptionsChange(data),
...(!isAppSwitchEnabled && {
onShippingAddressChange: (data: ShippingAddressChangeCallbackPayload) =>
this.onShippingAddressChange(data),
onShippingOptionsChange: (data: ShippingOptionChangeCallbackPayload) =>
this.onShippingOptionsChange(data),
}),
onApprove: (data: ApproveCallbackPayload, actions: ApproveCallbackActions) =>
this.onHostedCheckoutApprove(data, actions, methodId, onComplete),
};
Expand All @@ -153,7 +161,11 @@ export default class BigCommercePaymentsCustomerStrategy implements CustomerStra
const paypalButton = paypalSdk.Buttons(buttonRenderOptions);

if (paypalButton.isEligible()) {
paypalButton.render(`#${container}`);
if (paypalButton.hasReturned?.() && this.isPaypalCommerceAppSwitchEnabled(methodId)) {
paypalButton.resume?.();
} else {
paypalButton.render(`#${container}`);
}
} else {
this.bigCommercePaymentsIntegrationService.removeElement(container);
}
Expand Down Expand Up @@ -249,4 +261,17 @@ export default class BigCommercePaymentsCustomerStrategy implements CustomerStra
throw error;
}
}

/**
*
* PayPal AppSwitch enabling handling
*
*/
private isPaypalCommerceAppSwitchEnabled(methodId: string): boolean {
const state = this.paymentIntegrationService.getState();
const paymentMethod =
state.getPaymentMethodOrThrow<BigCommercePaymentsInitializationData>(methodId);

return paymentMethod.initializationData?.isAppSwitchEnabled ?? true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export default function getBigCommercePaymentsPaymentMethod(): PaymentMethod {
},
},
],
isAppSwitchEnabled: false,
},
skipRedirectConfirmationAlert: false,
type: 'PAYMENT_TYPE_API',
Expand Down