Skip to content

Commit 1d7a044

Browse files
committed
feat(payment): PAYPAL-4935 changes related to gql logic
1 parent 7d966d5 commit 1d7a044

15 files changed

+468
-76
lines changed

packages/core/src/cart/cart-action-creator.spec.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import CartActionCreator from './cart-action-creator';
1212
import { CartActionType } from './cart-actions';
1313
import CartRequestSender from './cart-request-sender';
1414
import { getCart } from './carts.mock';
15+
import { getGQLCartResponse, getGQLCurrencyResponse } from './gql-cart/mocks/gql-cart.mock';
1516

1617
describe('CartActionCreator', () => {
1718
let cartActionCreator: CartActionCreator;
@@ -29,7 +30,11 @@ describe('CartActionCreator', () => {
2930
store = createCheckoutStore(getCheckoutStoreState());
3031

3132
jest.spyOn(cartRequestSender, 'loadCart').mockReturnValue(
32-
Promise.resolve(getResponse(getCart())),
33+
Promise.resolve(getResponse(getGQLCartResponse())),
34+
);
35+
36+
jest.spyOn(cartRequestSender, 'loadCartCurrency').mockReturnValue(
37+
Promise.resolve(getResponse(getGQLCurrencyResponse())),
3338
);
3439

3540
cartActionCreator = new CartActionCreator(cartRequestSender);
@@ -47,7 +52,29 @@ describe('CartActionCreator', () => {
4752
{ type: CartActionType.LoadCartRequested },
4853
{
4954
type: CartActionType.LoadCartSucceeded,
50-
payload: getCart(),
55+
payload: expect.objectContaining({
56+
id: cart.id,
57+
currency: {
58+
code: cart.currency.code,
59+
name: cart.currency.name,
60+
symbol: cart.currency.symbol,
61+
decimalPlaces: cart.currency.decimalPlaces,
62+
},
63+
lineItems: expect.objectContaining({
64+
physicalItems: cart.lineItems.physicalItems.map((item) =>
65+
expect.objectContaining({
66+
id: item.id,
67+
variantId: item.variantId,
68+
productId: item.productId,
69+
sku: item.sku,
70+
name: item.name,
71+
url: item.url,
72+
quantity: item.quantity,
73+
isShippingRequired: item.isShippingRequired,
74+
}),
75+
),
76+
}),
77+
}),
5178
},
5279
]),
5380
);
Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { createAction, createErrorAction, ThunkAction } from '@bigcommerce/data-store';
2+
import { Response } from '@bigcommerce/request-sender';
3+
import { merge } from 'lodash';
24
import { Observable, Observer } from 'rxjs';
35

46
import { RequestOptions } from '@bigcommerce/checkout-sdk/payment-integration-api';
@@ -7,8 +9,10 @@ import { InternalCheckoutSelectors } from '../checkout';
79
import { cachableAction } from '../common/data-store';
810
import ActionOptions from '../common/data-store/action-options';
911

12+
import Cart from './cart';
1013
import { CartActionType, LoadCartAction } from './cart-actions';
1114
import CartRequestSender from './cart-request-sender';
15+
import { GQLCartResponse, GQLCurrencyResponse, GQLRequestResponse, mapToCart } from './gql-cart';
1216

1317
export default class CartActionCreator {
1418
constructor(private _cartRequestSender: CartRequestSender) {}
@@ -19,24 +23,49 @@ export default class CartActionCreator {
1923
options?: RequestOptions & ActionOptions,
2024
): ThunkAction<LoadCartAction, InternalCheckoutSelectors> {
2125
return (store) => {
22-
return Observable.create((observer: Observer<LoadCartAction>) => {
26+
return new Observable((observer: Observer<LoadCartAction>) => {
2327
const state = store.getState();
2428
const host = state.config.getHost();
2529

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

2832
this._cartRequestSender
2933
.loadCart(cartId, host, options)
30-
.then((response) => {
31-
observer.next(
32-
createAction(CartActionType.LoadCartSucceeded, response.body),
33-
);
34-
observer.complete();
34+
.then((cartResponse) => {
35+
return this._cartRequestSender
36+
.loadCartCurrency(
37+
cartResponse.body.data.site.cart.currencyCode,
38+
host,
39+
options,
40+
)
41+
.then((currencyResponse) => {
42+
observer.next(
43+
createAction(
44+
CartActionType.LoadCartSucceeded,
45+
this.transformToCartResponse(
46+
merge(cartResponse, currencyResponse),
47+
),
48+
),
49+
);
50+
observer.complete();
51+
});
3552
})
3653
.catch((response) => {
3754
observer.error(createErrorAction(CartActionType.LoadCartFailed, response));
3855
});
3956
});
4057
};
4158
}
59+
60+
private transformToCartResponse(
61+
response: Response<GQLRequestResponse<GQLCartResponse & GQLCurrencyResponse>>,
62+
): Cart {
63+
const {
64+
body: {
65+
data: { site },
66+
},
67+
} = response;
68+
69+
return mapToCart(site);
70+
}
4271
}

packages/core/src/cart/cart-request-sender.spec.ts

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ import BuyNowCartRequestBody from './buy-now-cart-request-body';
1414
import Cart from './cart';
1515
import CartRequestSender from './cart-request-sender';
1616
import { getCart } from './carts.mock';
17-
import { GQLCartRequestResponse } from './gql-cart';
18-
import { getGQLCartResponse } from './gql-cart/mocks/gql-cart.mock';
17+
import { GQLCartResponse, GQLCurrencyResponse, GQLRequestResponse } from './gql-cart';
18+
import getCartCurrencyQuery from './gql-cart/get-cart-currency-query';
19+
import getCartQuery from './gql-cart/get-cart-query';
20+
import { getGQLCartResponse, getGQLCurrencyResponse } from './gql-cart/mocks/gql-cart.mock';
1921

2022
describe('CartRequestSender', () => {
2123
let cart: Cart;
2224
let cartRequestSender: CartRequestSender;
2325
let requestSender: RequestSender;
2426
let response: Response<Cart>;
25-
let headlessResponse: Response<GQLCartRequestResponse>;
27+
let gqlResponse: Response<GQLRequestResponse<GQLCartResponse>>;
28+
let gqlCurrencyResponse: Response<GQLRequestResponse<GQLCurrencyResponse>>;
2629

2730
beforeEach(() => {
2831
requestSender = createRequestSender();
@@ -81,24 +84,63 @@ describe('CartRequestSender', () => {
8184

8285
describe('#loadCart', () => {
8386
const cartId = '123123';
87+
const host = 'https://test.com';
8488

8589
beforeEach(() => {
86-
headlessResponse = getResponse(getGQLCartResponse());
90+
gqlResponse = getResponse(getGQLCartResponse());
8791

88-
jest.spyOn(requestSender, 'get').mockResolvedValue(headlessResponse);
92+
jest.spyOn(requestSender, 'post').mockResolvedValue(gqlResponse);
8993
});
9094

91-
it('get headless cart', async () => {
95+
it('get gql cart', async () => {
9296
await cartRequestSender.loadCart(cartId);
9397

94-
expect(requestSender.get).toHaveBeenCalledWith(
95-
'http://localhost/api/wallet-buttons/cart-information',
96-
{
97-
params: {
98-
cartId,
99-
},
98+
expect(requestSender.post).toHaveBeenCalledWith('http://localhost/wallet-buttons', {
99+
body: {
100+
query: getCartQuery(cartId),
101+
},
102+
});
103+
});
104+
105+
it('get gql cart with host url', async () => {
106+
await cartRequestSender.loadCart(cartId, host);
107+
108+
expect(requestSender.post).toHaveBeenCalledWith('https://test.com/wallet-buttons', {
109+
body: {
110+
query: getCartQuery(cartId),
111+
},
112+
});
113+
});
114+
});
115+
116+
describe('#loadCartCurrency', () => {
117+
const currencyCode = 'USD';
118+
const host = 'https://test.com';
119+
120+
beforeEach(() => {
121+
gqlCurrencyResponse = getResponse(getGQLCurrencyResponse());
122+
123+
jest.spyOn(requestSender, 'post').mockResolvedValue(gqlCurrencyResponse);
124+
});
125+
126+
it('get gql cart currency', async () => {
127+
await cartRequestSender.loadCartCurrency(currencyCode);
128+
129+
expect(requestSender.post).toHaveBeenCalledWith('http://localhost/wallet-buttons', {
130+
body: {
131+
query: getCartCurrencyQuery(currencyCode),
100132
},
101-
);
133+
});
134+
});
135+
136+
it('get gql cart currency with host url', async () => {
137+
await cartRequestSender.loadCartCurrency(currencyCode, host);
138+
139+
expect(requestSender.post).toHaveBeenCalledWith('https://test.com/wallet-buttons', {
140+
body: {
141+
query: getCartCurrencyQuery(currencyCode),
142+
},
143+
});
102144
});
103145
});
104146
});

packages/core/src/cart/cart-request-sender.ts

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@ import { BuyNowCartRequestBody, Cart } from '@bigcommerce/checkout-sdk/payment-i
44

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

7-
import { GQLCartRequestResponse, mapToCart } from './gql-cart';
7+
import {
8+
GQLCartResponse,
9+
GQLCurrencyResponse,
10+
GQLRequestOptions,
11+
GQLRequestResponse,
12+
} from './gql-cart';
13+
import CartRetrievalError from './gql-cart/errors/cart-retrieval-error';
14+
import getCartCurrencyQuery from './gql-cart/get-cart-currency-query';
15+
import getCartQuery from './gql-cart/get-cart-query';
816

917
export default class CartRequestSender {
1018
constructor(private _requestSender: RequestSender) {}
@@ -22,40 +30,54 @@ export default class CartRequestSender {
2230
return this._requestSender.post(url, { body, headers, timeout });
2331
}
2432

25-
async loadCart(
26-
cartId: string,
27-
host?: string,
28-
options?: RequestOptions,
29-
): Promise<Response<Cart | undefined>> {
30-
const path = 'api/wallet-buttons/cart-information';
33+
async loadCart(cartId: string, host?: string, options?: RequestOptions) {
34+
const path = 'wallet-buttons';
3135
const url = host ? `${host}/${path}` : `${window.location.origin}/${path}`;
3236

33-
const requestOptions: RequestOptions = {
37+
const requestOptions: GQLRequestOptions = {
3438
...options,
35-
params: {
36-
cartId,
39+
body: {
40+
query: getCartQuery(cartId),
3741
},
3842
};
3943

40-
const response = await this._requestSender.get<GQLCartRequestResponse>(url, {
44+
const response = await this._requestSender.post<GQLRequestResponse<GQLCartResponse>>(url, {
4145
...requestOptions,
4246
});
4347

44-
return this.transformToCartResponse(response);
48+
if (!response.body.data.site.cart) {
49+
throw new CartRetrievalError(
50+
`Could not retrieve cart information by cartId: ${cartId}`,
51+
);
52+
}
53+
54+
return response;
4555
}
4656

47-
private transformToCartResponse(
48-
response: Response<GQLCartRequestResponse>,
49-
): Response<Cart | undefined> {
50-
const {
57+
async loadCartCurrency(currencyCode: string, host?: string, options?: RequestOptions) {
58+
const path = 'wallet-buttons';
59+
const url = host ? `${host}/${path}` : `${window.location.origin}/${path}`;
60+
61+
const requestOptions: GQLRequestOptions = {
62+
...options,
5163
body: {
52-
data: { site },
64+
query: getCartCurrencyQuery(currencyCode),
5365
},
54-
} = response;
55-
56-
return {
57-
...response,
58-
body: mapToCart(site),
5966
};
67+
68+
const response = await this._requestSender.post<GQLRequestResponse<GQLCurrencyResponse>>(
69+
url,
70+
{
71+
...requestOptions,
72+
},
73+
);
74+
75+
if (!response.body.data.site.currency) {
76+
throw new CartRetrievalError(
77+
`Could not retrieve currency information by currencyCode: ${currencyCode}`,
78+
);
79+
}
80+
81+
return response;
6082
}
6183
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import CartRetrievalError from './cart-retrieval-error';
2+
3+
describe('init', () => {
4+
it('sets type to cart_retrieval', () => {
5+
const error = new CartRetrievalError();
6+
7+
expect(error.type).toBe('cart_retrieval');
8+
});
9+
10+
it('returns error name', () => {
11+
const error = new CartRetrievalError();
12+
13+
expect(error.name).toBe('CartRetrievalError');
14+
});
15+
16+
it('sets the message as `body.title`', () => {
17+
const error = new CartRetrievalError('test message');
18+
19+
expect(error.message).toBe('test message');
20+
});
21+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { StandardError } from '../../../common/error/errors';
2+
3+
export default class CartRetrievalError extends StandardError {
4+
constructor(message?: string) {
5+
super(message || 'Cart not available.');
6+
7+
this.name = 'CartRetrievalError';
8+
this.type = 'cart_retrieval';
9+
}
10+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
const getCartCurrencyQuery = (currencyCode: string) => {
2+
return `
3+
query Currency {
4+
site {
5+
currency(currencyCode: ${currencyCode}) {
6+
display {
7+
decimalPlaces
8+
symbol
9+
}
10+
name
11+
code
12+
}
13+
}
14+
}
15+
`;
16+
};
17+
18+
export default getCartCurrencyQuery;

0 commit comments

Comments
 (0)