Skip to content

Commit a6ff26d

Browse files
committed
feat(payment): PAYPAL-4936 PayPalCommerceIntegrationService updates
1 parent 0f1fe2c commit a6ff26d

File tree

5 files changed

+319
-0
lines changed

5 files changed

+319
-0
lines changed

packages/paypal-commerce-integration/src/paypal-commerce-integration-service.spec.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,82 @@ describe('PayPalCommerceIntegrationService', () => {
323323
});
324324
});
325325

326+
describe('#createPaymentOrderIntent', () => {
327+
const providerId = 'paypalcommerce.paypal';
328+
const paymentOrderIntentResponse = {
329+
orderId: '10',
330+
approveUrl: 'test-url',
331+
};
332+
333+
beforeEach(() => {
334+
jest.spyOn(paypalCommerceRequestSender, 'createPaymentOrderIntent').mockResolvedValue(
335+
paymentOrderIntentResponse,
336+
);
337+
});
338+
339+
it('throws an error if cart does not exist', async () => {
340+
const err = new Error('cart does not exist');
341+
342+
jest.spyOn(paymentIntegrationService.getState(), 'getCartOrThrow').mockImplementation(
343+
() => {
344+
throw err;
345+
},
346+
);
347+
348+
try {
349+
await subject.createPaymentOrderIntent(providerId);
350+
} catch (error) {
351+
expect(error).toBeInstanceOf(Error);
352+
expect((error as Error).message).toBe('cart does not exist');
353+
}
354+
});
355+
356+
it('successfully creation of payment order intent', async () => {
357+
const orderId = await subject.createPaymentOrderIntent(providerId);
358+
359+
expect(paypalCommerceRequestSender.createPaymentOrderIntent).toHaveBeenCalledWith(
360+
providerId,
361+
cart.id,
362+
undefined,
363+
);
364+
365+
expect(orderId).toEqual(paymentOrderIntentResponse.orderId);
366+
});
367+
});
368+
369+
describe('#proxyTokenizationPayment', () => {
370+
const redirectUrl = 'redirect-url';
371+
372+
beforeEach(() => {
373+
jest.spyOn(paypalCommerceRequestSender, 'getRedirectToCheckoutUrl').mockResolvedValue(
374+
redirectUrl,
375+
);
376+
377+
Object.defineProperty(window, 'location', {
378+
value: {
379+
assign: jest.fn(),
380+
},
381+
});
382+
});
383+
384+
it('throws an error if orderId is not provided', async () => {
385+
try {
386+
await subject.proxyTokenizationPayment();
387+
} catch (error) {
388+
expect(error).toBeInstanceOf(MissingDataError);
389+
}
390+
});
391+
392+
it('successfully tokenization payment', async () => {
393+
await subject.proxyTokenizationPayment('10');
394+
395+
expect(paypalCommerceRequestSender.getRedirectToCheckoutUrl).toHaveBeenCalledWith(
396+
'/redirect-to-checkout',
397+
);
398+
expect(window.location.assign).toHaveBeenCalledWith(redirectUrl);
399+
});
400+
});
401+
326402
describe('#submitPayment', () => {
327403
it('successfully submits payment', async () => {
328404
jest.spyOn(paymentIntegrationService, 'submitPayment').mockImplementation(jest.fn());

packages/paypal-commerce-integration/src/paypal-commerce-integration-service.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
import PayPalCommerceRequestSender from './paypal-commerce-request-sender';
1919
import PayPalCommerceScriptLoader from './paypal-commerce-script-loader';
2020
import {
21+
CreatePaymentOrderIntentOptions,
2122
PayPalButtonStyleOptions,
2223
PayPalBuyNowInitializeOptions,
2324
PayPalCommerceInitializationData,
@@ -202,6 +203,38 @@ export default class PayPalCommerceIntegrationService {
202203
});
203204
}
204205

206+
async proxyTokenizationPayment(orderId?: string): Promise<void> {
207+
const state = this.paymentIntegrationService.getState();
208+
209+
if (!orderId) {
210+
throw new MissingDataError(MissingDataErrorType.MissingOrderId);
211+
}
212+
213+
const host = state.getHost();
214+
const path = 'redirect-to-checkout';
215+
216+
const redirectUrl = await this.paypalCommerceRequestSender.getRedirectToCheckoutUrl(
217+
host ? `${host}/${path}` : `/${path}`,
218+
);
219+
220+
window.location.assign(redirectUrl);
221+
}
222+
223+
async createPaymentOrderIntent(
224+
providerId: string,
225+
options?: CreatePaymentOrderIntentOptions,
226+
): Promise<string> {
227+
const cartId = this.paymentIntegrationService.getState().getCartOrThrow().id;
228+
229+
const { orderId } = await this.paypalCommerceRequestSender.createPaymentOrderIntent(
230+
providerId,
231+
cartId,
232+
options,
233+
);
234+
235+
return orderId;
236+
}
237+
205238
/**
206239
*
207240
* Shipping options methods

packages/paypal-commerce-integration/src/paypal-commerce-request-sender.spec.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,115 @@ describe('PayPalCommerceRequestSender', () => {
117117
}),
118118
);
119119
});
120+
121+
describe('#createPaymentOrderIntent', () => {
122+
const requestBody = {
123+
walletEntityId: 'paypalcommerce.paypal',
124+
cartId: '12341234',
125+
};
126+
127+
const mockRequest = ({
128+
orderId = '10',
129+
errors = [],
130+
}: {
131+
orderId?: string;
132+
errors?: Array<{ message: string }>;
133+
} = {}) => {
134+
const requestResponseMock = getResponse({
135+
data: {
136+
payment: {
137+
paymentWallet: {
138+
createPaymentWalletIntent: {
139+
paymentWalletIntentData: { orderId },
140+
errors,
141+
},
142+
},
143+
},
144+
},
145+
});
146+
147+
jest.spyOn(requestSender, 'post').mockReturnValue(Promise.resolve(requestResponseMock));
148+
};
149+
150+
beforeEach(() => {
151+
mockRequest();
152+
});
153+
154+
it('should throw an error', async () => {
155+
mockRequest({ errors: [{ message: 'error message' }] });
156+
157+
try {
158+
await paypalCommerceRequestSender.createPaymentOrderIntent(
159+
requestBody.walletEntityId,
160+
requestBody.cartId,
161+
);
162+
} catch (error) {
163+
expect(error).toBeInstanceOf(Error);
164+
expect((error as Error).message).toBe('error message');
165+
}
166+
});
167+
168+
describe('create order', () => {
169+
it('with provided data', async () => {
170+
await paypalCommerceRequestSender.createPaymentOrderIntent(
171+
requestBody.walletEntityId,
172+
requestBody.cartId,
173+
);
174+
175+
expect(requestSender.post).toHaveBeenCalledWith(
176+
'http://localhost/api/wallet-buttons/create-payment-wallet-intent',
177+
expect.objectContaining({
178+
body: requestBody,
179+
}),
180+
);
181+
});
182+
});
183+
});
184+
185+
describe('#getRedirectToCheckoutUrl', () => {
186+
const url = 'https://example.com';
187+
const redirectedCheckoutUrl = 'https://redirect-to-checkout.com';
188+
189+
const mockRequest = ({
190+
createCartRedirectUrls = { redirectUrls: { redirectedCheckoutUrl } },
191+
}: {
192+
createCartRedirectUrls?: { redirectUrls: { redirectedCheckoutUrl: string } | null };
193+
} = {}) => {
194+
const requestResponseMock = getResponse({
195+
data: {
196+
cart: {
197+
createCartRedirectUrls,
198+
},
199+
},
200+
});
201+
202+
jest.spyOn(requestSender, 'get').mockReturnValue(Promise.resolve(requestResponseMock));
203+
};
204+
205+
it('get redirect to checkout url', async () => {
206+
mockRequest();
207+
208+
const redirectToCheckoutUrl =
209+
await paypalCommerceRequestSender.getRedirectToCheckoutUrl(url);
210+
211+
expect(requestSender.get).toHaveBeenCalledWith(url, undefined);
212+
213+
expect(redirectToCheckoutUrl).toEqual(redirectedCheckoutUrl);
214+
});
215+
216+
it('should throw an error if there is no redirect url', async () => {
217+
mockRequest({
218+
createCartRedirectUrls: {
219+
redirectUrls: null,
220+
},
221+
});
222+
223+
try {
224+
await paypalCommerceRequestSender.getRedirectToCheckoutUrl(url);
225+
} catch (error) {
226+
expect(error).toBeInstanceOf(Error);
227+
expect((error as Error).message).toBe('Failed to redirection to checkout page');
228+
}
229+
});
230+
});
120231
});

packages/paypal-commerce-integration/src/paypal-commerce-request-sender.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import {
88
} from '@bigcommerce/checkout-sdk/payment-integration-api';
99

1010
import {
11+
CreatePaymentOrderIntentOptions,
12+
CreatePaymentOrderIntentResponse,
13+
CreateRedirectToCheckoutResponse,
1114
PayPalCreateOrderRequestBody,
1215
PayPalOrderData,
1316
PayPalOrderStatusData,
@@ -69,4 +72,67 @@ export default class PayPalCommerceRequestSender {
6972

7073
return res.body;
7174
}
75+
76+
async createPaymentOrderIntent(
77+
walletEntityId: string,
78+
cartId: string,
79+
options?: CreatePaymentOrderIntentOptions,
80+
): Promise<PayPalOrderData> {
81+
const url = `${window.location.origin}/api/wallet-buttons/create-payment-wallet-intent`;
82+
83+
const requestOptions: CreatePaymentOrderIntentOptions = {
84+
body: {
85+
...options?.body,
86+
walletEntityId,
87+
cartId,
88+
},
89+
};
90+
91+
const res = await this.requestSender.post<CreatePaymentOrderIntentResponse>(
92+
url,
93+
requestOptions,
94+
);
95+
96+
const {
97+
data: {
98+
payment: {
99+
paymentWallet: {
100+
createPaymentWalletIntent: { paymentWalletIntentData, errors },
101+
},
102+
},
103+
},
104+
} = res.body;
105+
106+
const errorMessage = errors[0]?.message;
107+
108+
if (errorMessage) {
109+
throw new Error(errorMessage);
110+
}
111+
112+
return {
113+
orderId: paymentWalletIntentData.orderId,
114+
approveUrl: paymentWalletIntentData.approvalUrl,
115+
};
116+
}
117+
118+
async getRedirectToCheckoutUrl(
119+
url: string,
120+
options?: CreatePaymentOrderIntentOptions,
121+
): Promise<string> {
122+
const res = await this.requestSender.get<CreateRedirectToCheckoutResponse>(url, options);
123+
124+
const {
125+
data: {
126+
cart: {
127+
createCartRedirectUrls: { redirectUrls },
128+
},
129+
},
130+
} = res.body;
131+
132+
if (!redirectUrls?.redirectedCheckoutUrl) {
133+
throw new Error('Failed to redirection to checkout page');
134+
}
135+
136+
return redirectUrls.redirectedCheckoutUrl;
137+
}
72138
}

packages/paypal-commerce-integration/src/paypal-commerce-types.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
BuyNowCartRequestBody,
33
HostedInstrument,
4+
RequestOptions,
45
ShippingOption,
56
VaultedInstrument,
67
} from '@bigcommerce/checkout-sdk/payment-integration-api';
@@ -616,3 +617,35 @@ export interface PayPalCreateOrderCardFieldsResponse {
616617
orderId: string;
617618
setupToken?: string;
618619
}
620+
621+
export interface CreatePaymentOrderIntentOptions extends RequestOptions {
622+
body?: { walletEntityId: string; cartId: string };
623+
}
624+
625+
export interface CreatePaymentOrderIntentResponse {
626+
data: {
627+
payment: {
628+
paymentWallet: {
629+
createPaymentWalletIntent: {
630+
errors: Array<{
631+
location: Array<{ line: string; column: string }>;
632+
message: string;
633+
}>;
634+
paymentWalletIntentData: {
635+
__typename: string;
636+
approvalUrl: string;
637+
orderId: string;
638+
};
639+
};
640+
};
641+
};
642+
};
643+
}
644+
645+
export interface CreateRedirectToCheckoutResponse {
646+
data: {
647+
cart: {
648+
createCartRedirectUrls: { redirectUrls: { redirectedCheckoutUrl: string } | null };
649+
};
650+
};
651+
}

0 commit comments

Comments
 (0)