Skip to content

Commit 479676b

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

19 files changed

+479
-79
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: 37 additions & 8 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();
24-
const host = state.config.getHost();
28+
const gqlUrl = state.config.getGQLRequestUrl();
2529

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

2832
this._cartRequestSender
29-
.loadCart(cartId, host, options)
30-
.then((response) => {
31-
observer.next(
32-
createAction(CartActionType.LoadCartSucceeded, response.body),
33-
);
34-
observer.complete();
33+
.loadCart(cartId, gqlUrl, options)
34+
.then((cartResponse) => {
35+
return this._cartRequestSender
36+
.loadCartCurrency(
37+
cartResponse.body.data.site.cart.currencyCode,
38+
gqlUrl,
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: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,26 @@ import {
77

88
import { CartSource } from '@bigcommerce/checkout-sdk/payment-integration-api';
99

10+
import { GQL_REQUEST_URL } from '../common/gql-request';
1011
import { ContentType, SDK_VERSION_HEADERS } from '../common/http-request';
1112
import { getResponse } from '../common/http-request/responses.mock';
1213

1314
import BuyNowCartRequestBody from './buy-now-cart-request-body';
1415
import Cart from './cart';
1516
import CartRequestSender from './cart-request-sender';
1617
import { getCart } from './carts.mock';
17-
import { GQLCartRequestResponse } from './gql-cart';
18-
import { getGQLCartResponse } from './gql-cart/mocks/gql-cart.mock';
18+
import { GQLCartResponse, GQLCurrencyResponse, GQLRequestResponse } from './gql-cart';
19+
import getCartCurrencyQuery from './gql-cart/get-cart-currency-query';
20+
import getCartQuery from './gql-cart/get-cart-query';
21+
import { getGQLCartResponse, getGQLCurrencyResponse } from './gql-cart/mocks/gql-cart.mock';
1922

2023
describe('CartRequestSender', () => {
2124
let cart: Cart;
2225
let cartRequestSender: CartRequestSender;
2326
let requestSender: RequestSender;
2427
let response: Response<Cart>;
25-
let headlessResponse: Response<GQLCartRequestResponse>;
28+
let gqlResponse: Response<GQLRequestResponse<GQLCartResponse>>;
29+
let gqlCurrencyResponse: Response<GQLRequestResponse<GQLCurrencyResponse>>;
2630

2731
beforeEach(() => {
2832
requestSender = createRequestSender();
@@ -81,24 +85,63 @@ describe('CartRequestSender', () => {
8185

8286
describe('#loadCart', () => {
8387
const cartId = '123123';
88+
const gqlUrl = 'https://test.com/graphql';
8489

8590
beforeEach(() => {
86-
headlessResponse = getResponse(getGQLCartResponse());
91+
gqlResponse = getResponse(getGQLCartResponse());
8792

88-
jest.spyOn(requestSender, 'get').mockResolvedValue(headlessResponse);
93+
jest.spyOn(requestSender, 'post').mockResolvedValue(gqlResponse);
8994
});
9095

91-
it('get headless cart', async () => {
96+
it('get gql cart', async () => {
9297
await cartRequestSender.loadCart(cartId);
9398

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

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

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,18 @@ import { RequestSender, Response } from '@bigcommerce/request-sender';
22

33
import { BuyNowCartRequestBody, Cart } from '@bigcommerce/checkout-sdk/payment-integration-api';
44

5+
import { GQL_REQUEST_URL } from '../common/gql-request';
56
import { ContentType, RequestOptions, SDK_VERSION_HEADERS } from '../common/http-request';
67

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

918
export default class CartRequestSender {
1019
constructor(private _requestSender: RequestSender) {}
@@ -22,40 +31,52 @@ export default class CartRequestSender {
2231
return this._requestSender.post(url, { body, headers, timeout });
2332
}
2433

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';
31-
const url = host ? `${host}/${path}` : `${window.location.origin}/${path}`;
34+
async loadCart(cartId: string, gqlUrl?: string, options?: RequestOptions) {
35+
const url = gqlUrl ?? GQL_REQUEST_URL;
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, gqlUrl?: string, options?: RequestOptions) {
58+
const url = gqlUrl ?? GQL_REQUEST_URL;
59+
60+
const requestOptions: GQLRequestOptions = {
61+
...options,
5162
body: {
52-
data: { site },
63+
query: getCartCurrencyQuery(currencyCode),
5364
},
54-
} = response;
55-
56-
return {
57-
...response,
58-
body: mapToCart(site),
5965
};
66+
67+
const response = await this._requestSender.post<GQLRequestResponse<GQLCurrencyResponse>>(
68+
url,
69+
{
70+
...requestOptions,
71+
},
72+
);
73+
74+
if (!response.body.data.site.currency) {
75+
throw new CartRetrievalError(
76+
`Could not retrieve currency information by currencyCode: ${currencyCode}`,
77+
);
78+
}
79+
80+
return response;
6081
}
6182
}
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)