Skip to content

Commit

Permalink
feat(payment): PAYPAL-4867 POC of headless wallet buttons
Browse files Browse the repository at this point in the history
  • Loading branch information
bc-nick committed Nov 27, 2024
1 parent 2e6f95f commit ce9e6e7
Show file tree
Hide file tree
Showing 38 changed files with 1,493 additions and 9 deletions.
5 changes: 5 additions & 0 deletions packages/core/auto-export.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
"inputPath": "packages/*/src/index.ts",
"outputPath": "packages/core/src/generated/checkout-button-strategies.ts",
"memberPattern": "^create.+ButtonStrategy$"
},
{
"inputPath": "packages/*/src/index.ts",
"outputPath": "packages/core/src/generated/checkout-headless-button-strategies.ts",
"memberPattern": "^create.+HeadlessButtonStrategy$"
}
]
}
3 changes: 3 additions & 0 deletions packages/core/src/bundles/checkout-headless-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { createTimeout } from '@bigcommerce/request-sender';

export { createCheckoutHeadlessButtonInitializer } from '../checkout-buttons';
53 changes: 53 additions & 0 deletions packages/core/src/cart/cart-action-creator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { createAction, createErrorAction, ThunkAction } from '@bigcommerce/data-store';
import { Observable, Observer } from 'rxjs';

import { RequestOptions } from '@bigcommerce/checkout-sdk/payment-integration-api';

import { InternalCheckoutSelectors } from '../checkout';
import { cachableAction } from '../common/data-store';
import ActionOptions from '../common/data-store/action-options';
import { MissingDataError, MissingDataErrorType } from '../common/error/errors';

import { CartActionType, LoadCartAction } from './cart-actions';
import CartRequestSender from './cart-request-sender';

export default class CartActionCreator {
constructor(private _cartRequestSender: CartRequestSender) {}

@cachableAction
loadCardEntity(
cartId: string,
options?: RequestOptions & ActionOptions,
): ThunkAction<LoadCartAction, InternalCheckoutSelectors> {
return (store) => {
return Observable.create((observer: Observer<LoadCartAction>) => {
const state = store.getState();
const jwtToken = state.config.getStorefrontJwtToken();

if (!jwtToken) {
throw new MissingDataError(MissingDataErrorType.MissingPaymentToken);
}

observer.next(createAction(CartActionType.LoadCartRequested, undefined));

this._cartRequestSender
.loadCardEntity(cartId, {
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
},
...options,
})
.then((response) => {
observer.next(
createAction(CartActionType.LoadCartSucceeded, response.body),
);
observer.complete();
})
.catch((response) => {
observer.error(createErrorAction(CartActionType.LoadCartFailed, response));
});
});
};
}
}
26 changes: 26 additions & 0 deletions packages/core/src/cart/cart-actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Action } from '@bigcommerce/data-store';

import Cart from './cart';

export enum CartActionType {
LoadCartRequested = 'LOAD_CART_REQUESTED',
LoadCartSucceeded = 'LOAD_CART_SUCCEEDED',
LoadCartFailed = 'LOAD_CART_FAILED',
}

export type LoadCartAction =
| LoadCartRequestedAction
| LoadCartSucceededAction
| LoadCartFailedAction;

export interface LoadCartRequestedAction extends Action {
type: CartActionType.LoadCartRequested;
}

export interface LoadCartSucceededAction extends Action<Cart> {
type: CartActionType.LoadCartSucceeded;
}

export interface LoadCartFailedAction extends Action<Error> {
type: CartActionType.LoadCartFailed;
}
7 changes: 6 additions & 1 deletion packages/core/src/cart/cart-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { ConsignmentAction, ConsignmentActionType } from '../shipping';

import Cart from './cart';
import { CartActionType, LoadCartAction } from './cart-actions';
import CartState, { CartErrorsState, CartStatusesState, DEFAULT_STATE } from './cart-state';

export default function cartReducer(state: CartState = DEFAULT_STATE, action: Action): CartState {
Expand All @@ -32,7 +33,8 @@ function dataReducer(
| CheckoutAction
| ConsignmentAction
| CouponAction
| GiftCertificateAction,
| GiftCertificateAction
| LoadCartAction,
): Cart | undefined {
switch (action.type) {
case BillingAddressActionType.UpdateBillingAddressSucceeded:
Expand All @@ -48,6 +50,9 @@ function dataReducer(
case GiftCertificateActionType.RemoveGiftCertificateSucceeded:
return objectMerge(data, action.payload && action.payload.cart);

case CartActionType.LoadCartSucceeded:
return objectMerge(data, action.payload && action.payload);

default:
return data;
}
Expand Down
160 changes: 160 additions & 0 deletions packages/core/src/cart/cart-request-sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,34 @@ import { BuyNowCartRequestBody, Cart } from '@bigcommerce/checkout-sdk/payment-i

import { ContentType, RequestOptions, SDK_VERSION_HEADERS } from '../common/http-request';

import { LineItemMap } from './index';

interface LoadCartRequestOptions extends RequestOptions {
body?: { query: string };
headers: { Authorization: string; [key: string]: string };
}

interface LoadCartResponse {
data: {
site: {
cart: {
amount: {
currencyCode: string;
};
entityId: string;
lineItems: {
physicalItems: Array<{
name: string;
entityId: string;
quantity: string;
productEntityId: string;
}>;
};
};
};
};
}

export default class CartRequestSender {
constructor(private _requestSender: RequestSender) {}

Expand All @@ -19,4 +47,136 @@ export default class CartRequestSender {

return this._requestSender.post(url, { body, headers, timeout });
}

loadCardEntity(cartId: string, options: LoadCartRequestOptions): Promise<Response<Cart>> {
const url = `/graphql`;

const graphQLQuery = `
query {
site {
cart(entityId: "${cartId}") {
currencyCode
entityId
id
isTaxIncluded
discounts {
entityId
discountedAmount {
currencyCode
value
}
}
discountedAmount {
currencyCode
value
}
baseAmount {
currencyCode
value
}
amount {
currencyCode
value
}
lineItems {
physicalItems {
brand
couponAmount {
value
}
discountedAmount {
value
}
discounts {
discountedAmount {
value
}
entityId
}
extendedListPrice {
value
}
extendedSalePrice {
value
}
giftWrapping {
amount {
value
}
message
name
}
isShippingRequired
isTaxable
listPrice {
value
}
name
originalPrice {
value
}
entityId
quantity
salePrice {
value
}
sku
url
}
}
}
}
}`;

const requestOptions: LoadCartRequestOptions = {
...options,
headers: {
...options.headers,
'Content-Type': 'application/json',
},
body: {
query: graphQLQuery,
},
};

return this._requestSender
.post<LoadCartResponse>(url, {
...requestOptions,
})
.then(this.transformToCartResponse);
}

private transformToCartResponse(response: Response<LoadCartResponse>): Response<Cart> {
const {
body: {
data: {
site: {
cart: { amount, entityId, lineItems },
},
},
},
} = response;

const mappedLineItems: LineItemMap = {
// @ts-ignore
physicalItems: lineItems.physicalItems.map((item) => ({
id: item.entityId,
name: item.name,
quantity: item.quantity,
productId: item.productEntityId,
})),
};

return {
...response,
body: {
id: entityId,
// @ts-ignore
currency: {
code: amount.currencyCode,
},
lineItems: mappedLineItems,
},
};
}
}
1 change: 1 addition & 0 deletions packages/core/src/cart/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export { default as LineItemMap } from './line-item-map';
export { default as CartComparator } from './cart-comparator';
export { default as CartRequestSender } from './cart-request-sender';
export { default as cartReducer } from './cart-reducer';
export { default as CartActionCreator } from './cart-action-creator';
export {
default as CartSelector,
CartSelectorFactory,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export default interface CheckoutButtonInitializerOptions {
host?: string;
locale?: string;
storefrontJwtToken?: string;
siteLink?: string;
}
Loading

0 comments on commit ce9e6e7

Please sign in to comment.