Skip to content
Open
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
389 changes: 200 additions & 189 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions src/checkout-buttons/checkout-button-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CheckoutButtonMethodType } from './strategies';
import { BraintreePaypalButtonInitializeOptions } from './strategies/braintree';
import { GooglePayButtonInitializeOptions } from './strategies/googlepay';
import { PaypalButtonInitializeOptions } from './strategies/paypal';
import { PaypalCommerceButtonInitializeOptions } from './strategies/paypalCommerce';

/**
* The set of options for configuring the checkout button.
Expand Down Expand Up @@ -34,6 +35,12 @@ export interface CheckoutButtonInitializeOptions extends CheckoutButtonOptions {
*/
paypal?: PaypalButtonInitializeOptions;

/**
* The options that are required to facilitate PayPal Commerce. They can be omitted
* unless you need to support Paypal.
*/
paypalCommerce?: PaypalCommerceButtonInitializeOptions;

/**
* The ID of a container which the checkout button should be inserted.
*/
Expand Down
12 changes: 12 additions & 0 deletions src/checkout-buttons/create-checkout-button-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import { BraintreeScriptLoader, BraintreeSDKCreator } from '../payment/strategie
import { createGooglePayPaymentProcessor, GooglePayBraintreeInitializer, GooglePayStripeInitializer } from '../payment/strategies/googlepay';
import { MasterpassScriptLoader } from '../payment/strategies/masterpass';
import { PaypalScriptLoader } from '../payment/strategies/paypal';
import { PaypalCommerceScriptLoader } from '../payment/strategies/paypalCommerce';

import { CheckoutButtonMethodType, CheckoutButtonStrategy } from './strategies';
import { BraintreePaypalButtonStrategy } from './strategies/braintree';
import { GooglePayButtonStrategy } from './strategies/googlepay';
import { MasterpassButtonStrategy } from './strategies/masterpass';
import { PaypalButtonStrategy } from './strategies/paypal';
import { PaypalCommerceButtonStrategy } from './strategies/paypalCommerce';

export default function createCheckoutButtonRegistry(
store: CheckoutStore,
Expand Down Expand Up @@ -95,5 +97,15 @@ export default function createCheckoutButtonRegistry(
)
);

registry.register(CheckoutButtonMethodType.PAYPALCOMMERCE, () =>
new PaypalCommerceButtonStrategy(
store,
checkoutActionCreator,
new PaypalCommerceScriptLoader(scriptLoader),
formPoster,
requestSender
)
);

return registry;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ enum CheckoutButtonMethodType {
GOOGLEPAY_STRIPE = 'googlepaystripe',
MASTERPASS = 'masterpass',
PAYPALEXPRESS = 'paypalexpress',
PAYPALCOMMERCE = 'paypalcommerce',
}

export default CheckoutButtonMethodType;
2 changes: 2 additions & 0 deletions src/checkout-buttons/strategies/paypalCommerce/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { PaypalCommerceButtonInitializeOptions } from './paypalCommerce-button-options';
export { default as PaypalCommerceButtonStrategy } from './paypalCommerce-button-strategy';
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface PaypalCommerceButtonInitializeOptions {
/**
* The Client ID of the Paypal App
*/
clientId: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { FormPoster } from '@bigcommerce/form-poster';
import { RequestSender } from '@bigcommerce/request-sender';

import { CheckoutActionCreator, CheckoutStore } from '../../../checkout';
import { InvalidArgumentError, MissingDataError, MissingDataErrorType } from '../../../common/error/errors';
import { INTERNAL_USE_ONLY } from '../../../common/http-request';
import { PaypalCommerceScriptLoader } from '../../../payment/strategies/paypalCommerce';
import { CheckoutButtonInitializeOptions } from '../../checkout-button-options';
import CheckoutButtonStrategy from '../checkout-button-strategy';

export default class PaypalCommerceButtonStrategy implements CheckoutButtonStrategy {

constructor(
private _store: CheckoutStore,
private _checkoutActionCreator: CheckoutActionCreator,
private _paypalScriptLoader: PaypalCommerceScriptLoader,
private _formPoster: FormPoster,
private _requestSender: RequestSender
) {}

initialize(options: CheckoutButtonInitializeOptions): Promise<void> {
const state = this._store.getState();
const paymentMethod = state.paymentMethods.getPaymentMethod(options.methodId);
const paypalOptions = options.paypalCommerce;

if (!paypalOptions) {
throw new InvalidArgumentError();
}

if (!paymentMethod) {
throw new MissingDataError(MissingDataErrorType.MissingPaymentMethod);
}

return this._paypalScriptLoader.loadPaypalCommerce(paypalOptions.clientId)
.then(paypal => {
if (!paymentMethod || !paymentMethod.config.merchantId) {
throw new MissingDataError(MissingDataErrorType.MissingPaymentMethod);
}

return paypal.Buttons({
createOrder: () => this._setupPayment(),
onApprove: data => {
this._formPoster.postForm('/checkout.php', {
payment_type: 'paypal',
provider: paymentMethod.id,
action: 'set_external_checkout',
order_id: data.orderID,
});
},
}).render(`#${options.containerId}`);
});
}

deinitialize(): Promise<void> {
return Promise.resolve();
}

private _setupPayment(): Promise<string> {

return this._store.dispatch(this._checkoutActionCreator.loadDefaultCheckout())
.then(state => {
const url = '/api/storefront/payment/paypalcommerce';
const headers = {
'X-API-INTERNAL': INTERNAL_USE_ONLY,
'Content-Type': 'application/x-www-form-urlencoded',
};
const cart = state.cart.getCart();
const currentCartId = cart ? cart.id : '';

return this._requestSender.post(url, {
headers,
body: {cartId: currentCartId},
});
})
.then(res => res.body.orderId)
.catch(error => {
throw error;
});
}
}
2 changes: 2 additions & 0 deletions src/payment/strategies/paypalCommerce/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { PaypalCommerceSDK } from './paypalCommerce-sdk';
export { default as PaypalCommerceScriptLoader } from './paypalCommerce-script-loader';
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ScriptLoader } from '@bigcommerce/script-loader';

import { PaymentMethodClientUnavailableError } from '../../errors';

import { PaypalCommerceHostWindow, PaypalCommerceSDK } from './paypalCommerce-sdk';

export default class PaypalCommerceScriptLoader {
private _window: PaypalCommerceHostWindow;

constructor(
private _scriptLoader: ScriptLoader
) {
this._window = window;
}

async loadPaypalCommerce(clientId: string = '', currency: string = 'USD'): Promise<PaypalCommerceSDK> {
const scriptSrc = `https://www.paypal.com/sdk/js?currency=${currency}&client-id=${clientId}`;

await this._scriptLoader.loadScript(scriptSrc, { async: true, attributes: {} });

if (!this._window.paypal) {
throw new PaymentMethodClientUnavailableError();
}

return this._window.paypal;
}
}
20 changes: 20 additions & 0 deletions src/payment/strategies/paypalCommerce/paypalCommerce-sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

interface ApproveDataOptions {
orderID: string;
}

interface ButtonsOptions {
enableStandardCardFields?: boolean;
createOrder(data: any): void;
onApprove(data: ApproveDataOptions): void;
}

export interface PaypalCommerceSDK {
Buttons({createOrder, onApprove}: ButtonsOptions): {
render(id: string): void;
};
}

export interface PaypalCommerceHostWindow extends Window {
paypal?: PaypalCommerceSDK;
}