@commercetools/checkout-payments-processor-sdk 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +42 -0
- package/biome.json +38 -0
- package/package.json +48 -0
- package/src/api/context/request-context.helper.test.ts +98 -0
- package/src/api/context/request-context.helper.ts +89 -0
- package/src/api/context/request-context.provider.ts +27 -0
- package/src/api/context/types/request-context.type.ts +18 -0
- package/src/api/handlers/payment-components.handler.test.ts +378 -0
- package/src/api/handlers/payment-components.handler.ts +80 -0
- package/src/api/handlers/payment-intents.handler.test.ts +475 -0
- package/src/api/handlers/payment-intents.handler.ts +169 -0
- package/src/api/handlers/status.handler.test.ts +551 -0
- package/src/api/handlers/status.handler.ts +216 -0
- package/src/api/handlers/transaction.handler.ts +86 -0
- package/src/api/handlers/types/handler.type.ts +49 -0
- package/src/api/hooks/authorize.hook.test.ts +62 -0
- package/src/api/hooks/authorize.hook.ts +51 -0
- package/src/api/hooks/jwt-auth.hook.test.ts +54 -0
- package/src/api/hooks/jwt-auth.hook.ts +56 -0
- package/src/api/hooks/oauth2-auth.hook.test.ts +173 -0
- package/src/api/hooks/oauth2-auth.hook.ts +46 -0
- package/src/api/hooks/session-header-auth.hook.ts +47 -0
- package/src/api/hooks/session-query-param-auth.hook.ts +46 -0
- package/src/api/hooks/types/hook.type.ts +12 -0
- package/src/api/index.ts +11 -0
- package/src/api/types.ts +3 -0
- package/src/commercetools/api/base-api.ts +28 -0
- package/src/commercetools/api/cart-api.test.ts +95 -0
- package/src/commercetools/api/cart-api.ts +55 -0
- package/src/commercetools/api/custom-type-api.ts +39 -0
- package/src/commercetools/api/order-api.ts +22 -0
- package/src/commercetools/api/payment-api.ts +64 -0
- package/src/commercetools/api/payment-method-api.ts +118 -0
- package/src/commercetools/api/root-api.ts +109 -0
- package/src/commercetools/errors/ct-api.error.ts +27 -0
- package/src/commercetools/helpers/currency.converter.test.ts +121 -0
- package/src/commercetools/helpers/currency.converter.test_data.json +2379 -0
- package/src/commercetools/helpers/currency.converter.ts +102 -0
- package/src/commercetools/helpers/taxrate.converter.test.ts +48 -0
- package/src/commercetools/helpers/taxrate.converter.ts +39 -0
- package/src/commercetools/index.ts +22 -0
- package/src/commercetools/services/ct-cart.service.test.ts +1616 -0
- package/src/commercetools/services/ct-cart.service.ts +316 -0
- package/src/commercetools/services/ct-custom-type.service.ts +62 -0
- package/src/commercetools/services/ct-order.service.test.ts +136 -0
- package/src/commercetools/services/ct-order.service.ts +47 -0
- package/src/commercetools/services/ct-payment-method.service.test.ts +878 -0
- package/src/commercetools/services/ct-payment-method.service.ts +223 -0
- package/src/commercetools/services/ct-payment.service.test.ts +1123 -0
- package/src/commercetools/services/ct-payment.service.ts +574 -0
- package/src/commercetools/types/api.type.ts +162 -0
- package/src/commercetools/types/cart.type.ts +83 -0
- package/src/commercetools/types/custom-type.type.ts +21 -0
- package/src/commercetools/types/order.type.ts +19 -0
- package/src/commercetools/types/payment-method.type.ts +130 -0
- package/src/commercetools/types/payment.type.ts +74 -0
- package/src/commercetools/types/predefined-custom-types.type.ts +240 -0
- package/src/commercetools/types.ts +7 -0
- package/src/errorx/errorx.ts +625 -0
- package/src/errorx/index.ts +1 -0
- package/src/errorx/types.ts +5 -0
- package/src/fetch/decorators/base.decorator.ts +13 -0
- package/src/fetch/decorators/monitoring.decorator.ts +41 -0
- package/src/fetch/fetch.test.ts +57 -0
- package/src/fetch/index.ts +3 -0
- package/src/fetch/types/fetch.type.ts +11 -0
- package/src/fetch/types.ts +1 -0
- package/src/index.ts +15 -0
- package/src/logger/commercetools-logger.ts +69 -0
- package/src/logger/index.ts +1 -0
- package/src/logger/types/logger.type.ts +6 -0
- package/src/logger/types.ts +1 -0
- package/src/mocks/auth.mock.ts +6 -0
- package/src/mocks/cart.mock.ts +239 -0
- package/src/mocks/ct-api-error.mock.ts +23 -0
- package/src/mocks/index.ts +4 -0
- package/src/mocks/payment.mock.ts +35 -0
- package/src/payment-processor.ts +317 -0
- package/src/security/authn/authns.ts +219 -0
- package/src/security/authn/bearer-utils.test.ts +35 -0
- package/src/security/authn/bearer-utils.ts +28 -0
- package/src/security/authn/jwt-authn-manager.ts +69 -0
- package/src/security/authn/oauth2-authn-manager.ts +105 -0
- package/src/security/authn/session-header-authn-manager.ts +58 -0
- package/src/security/authn/session-query-param-authn-manager.ts +53 -0
- package/src/security/authn/types/authn.type.ts +50 -0
- package/src/security/authz/authorization-manager.ts +39 -0
- package/src/security/authz/types/authz.type.ts +13 -0
- package/src/security/index.ts +13 -0
- package/src/security/services/authorization.service.ts +54 -0
- package/src/security/services/jwt.service.test.ts +27 -0
- package/src/security/services/jwt.service.ts +45 -0
- package/src/security/services/oauth2.service.ts +60 -0
- package/src/security/services/session.service.ts +141 -0
- package/src/security/services/types/authorization.type.ts +10 -0
- package/src/security/services/types/jwt.type.ts +3 -0
- package/src/security/services/types/oauth2.type.ts +15 -0
- package/src/security/services/types/session.type.ts +38 -0
- package/src/security/types.ts +6 -0
- package/tsconfig.json +28 -0
- package/tsconfig.prod.json +9 -0
- package/typedoc.json +6 -0
- package/vitest.config.ts +15 -0
- package/wiki/getting-started.md +13 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import type { HandlerResponse } from '@api-types';
|
|
2
|
+
import type { Payment } from '@commercetools/platform-sdk';
|
|
3
|
+
import type { Logger } from '@logger-types';
|
|
4
|
+
import { vi } from 'vitest';
|
|
5
|
+
import {
|
|
6
|
+
PaymentIntentsHandler,
|
|
7
|
+
type PaymentIntentsOutgoingHttpHeaders,
|
|
8
|
+
type PaymentIntentsRequest,
|
|
9
|
+
type PaymentIntentsResponse,
|
|
10
|
+
} from '@/api/handlers/payment-intents.handler.js';
|
|
11
|
+
import { ErrorInvalidOperation } from '@/errorx/errorx.js';
|
|
12
|
+
import type { CheckoutPaymentsProcessor } from '@/payment-processor.js';
|
|
13
|
+
|
|
14
|
+
// Concrete implementation for testing the abstract class
|
|
15
|
+
class TestPaymentIntentsHandler extends PaymentIntentsHandler {
|
|
16
|
+
public handleCancel = vi.fn();
|
|
17
|
+
public handleRefund = vi.fn();
|
|
18
|
+
public handleCapture = vi.fn();
|
|
19
|
+
public handleReverse = vi.fn();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('PaymentIntentsHandler', () => {
|
|
23
|
+
let handler: TestPaymentIntentsHandler;
|
|
24
|
+
let mockLogger: Logger;
|
|
25
|
+
let mockProcessor: CheckoutPaymentsProcessor;
|
|
26
|
+
let mockPayment: Payment;
|
|
27
|
+
|
|
28
|
+
const mockSuccessResponse: HandlerResponse<
|
|
29
|
+
PaymentIntentsResponse,
|
|
30
|
+
PaymentIntentsOutgoingHttpHeaders
|
|
31
|
+
> = {
|
|
32
|
+
status: 200,
|
|
33
|
+
body: { outcome: 'approved' },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
mockLogger = {
|
|
38
|
+
error: vi.fn(),
|
|
39
|
+
info: vi.fn(),
|
|
40
|
+
debug: vi.fn(),
|
|
41
|
+
warn: vi.fn(),
|
|
42
|
+
} as unknown as Logger;
|
|
43
|
+
|
|
44
|
+
mockPayment = { id: 'payment-123' } as Payment;
|
|
45
|
+
mockProcessor = {
|
|
46
|
+
getPaymentService: () => ({
|
|
47
|
+
getPayment: vi.fn().mockResolvedValue(mockPayment),
|
|
48
|
+
}),
|
|
49
|
+
} as unknown as CheckoutPaymentsProcessor;
|
|
50
|
+
|
|
51
|
+
handler = new TestPaymentIntentsHandler({
|
|
52
|
+
logger: mockLogger,
|
|
53
|
+
processor: mockProcessor,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Reset all mocks
|
|
57
|
+
handler.handleCancel.mockResolvedValue(mockSuccessResponse);
|
|
58
|
+
handler.handleRefund.mockResolvedValue(mockSuccessResponse);
|
|
59
|
+
handler.handleCapture.mockResolvedValue(mockSuccessResponse);
|
|
60
|
+
handler.handleReverse.mockResolvedValue(mockSuccessResponse);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
vi.clearAllMocks();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('Public methods', () => {
|
|
68
|
+
test('should return POST as HTTP method', () => {
|
|
69
|
+
expect(handler.method()).toBe('POST');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('should return correct endpoint', () => {
|
|
73
|
+
expect(handler.endpoint()).toBe('/operations/payment-intents/:id');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test('should return correct auth configuration', () => {
|
|
77
|
+
const auth = handler.auth();
|
|
78
|
+
expect(auth).toEqual([
|
|
79
|
+
{
|
|
80
|
+
type: 'oauth2',
|
|
81
|
+
allowedScopes: ['manage_project', 'manage_checkout_payment_intents'],
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('Handler method', () => {
|
|
88
|
+
describe('capturePayment action', () => {
|
|
89
|
+
test('should handle capture payment action successfully', async () => {
|
|
90
|
+
const captureRequest: PaymentIntentsRequest = {
|
|
91
|
+
id: 'payment-123',
|
|
92
|
+
actions: [
|
|
93
|
+
{
|
|
94
|
+
action: 'capturePayment',
|
|
95
|
+
amount: {
|
|
96
|
+
centAmount: 1000,
|
|
97
|
+
currencyCode: 'USD',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const result = await handler.handler({
|
|
104
|
+
request: captureRequest,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const captureAction = captureRequest.actions[0] as Extract<
|
|
108
|
+
PaymentIntentsRequest['actions'][number],
|
|
109
|
+
{ action: 'capturePayment' }
|
|
110
|
+
>;
|
|
111
|
+
|
|
112
|
+
expect(handler.handleCapture).toHaveBeenCalledWith({
|
|
113
|
+
payload: {
|
|
114
|
+
payment: mockPayment,
|
|
115
|
+
amount: captureAction.amount,
|
|
116
|
+
merchantReference: undefined,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
expect(result).toEqual(mockSuccessResponse);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('refundPayment action', () => {
|
|
124
|
+
test('should handle refund payment action successfully', async () => {
|
|
125
|
+
const refundRequest: PaymentIntentsRequest = {
|
|
126
|
+
id: 'payment-123',
|
|
127
|
+
actions: [
|
|
128
|
+
{
|
|
129
|
+
action: 'refundPayment',
|
|
130
|
+
amount: {
|
|
131
|
+
centAmount: 500,
|
|
132
|
+
currencyCode: 'EUR',
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const result = await handler.handler({
|
|
139
|
+
request: refundRequest,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const refundAction = refundRequest.actions[0] as Extract<
|
|
143
|
+
PaymentIntentsRequest['actions'][number],
|
|
144
|
+
{ action: 'refundPayment' }
|
|
145
|
+
>;
|
|
146
|
+
|
|
147
|
+
expect(handler.handleRefund).toHaveBeenCalledWith({
|
|
148
|
+
payload: {
|
|
149
|
+
payment: mockPayment,
|
|
150
|
+
amount: refundAction.amount,
|
|
151
|
+
merchantReference: undefined,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
expect(result).toEqual(mockSuccessResponse);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('cancelPayment action', () => {
|
|
159
|
+
test('should handle cancel payment action successfully', async () => {
|
|
160
|
+
const cancelRequest: PaymentIntentsRequest = {
|
|
161
|
+
id: 'payment-123',
|
|
162
|
+
actions: [
|
|
163
|
+
{
|
|
164
|
+
action: 'cancelPayment',
|
|
165
|
+
merchantReference: 'ref-123',
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const result = await handler.handler({
|
|
171
|
+
request: cancelRequest,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(handler.handleCancel).toHaveBeenCalledWith({
|
|
175
|
+
payload: {
|
|
176
|
+
payment: mockPayment,
|
|
177
|
+
merchantReference: 'ref-123',
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
expect(result).toEqual(mockSuccessResponse);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('should handle cancel payment action without merchantReference', async () => {
|
|
184
|
+
const cancelRequest: PaymentIntentsRequest = {
|
|
185
|
+
id: 'payment-123',
|
|
186
|
+
actions: [
|
|
187
|
+
{
|
|
188
|
+
action: 'cancelPayment',
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
await handler.handler({
|
|
194
|
+
request: cancelRequest,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(handler.handleCancel).toHaveBeenCalledWith({
|
|
198
|
+
payload: {
|
|
199
|
+
payment: mockPayment,
|
|
200
|
+
merchantReference: undefined,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('reversePayment action', () => {
|
|
207
|
+
test('should handle reverse payment action successfully', async () => {
|
|
208
|
+
const reverseRequest: PaymentIntentsRequest = {
|
|
209
|
+
id: 'payment-123',
|
|
210
|
+
actions: [
|
|
211
|
+
{
|
|
212
|
+
action: 'reversePayment',
|
|
213
|
+
merchantReference: 'ref-456',
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const result = await handler.handler({
|
|
219
|
+
request: reverseRequest,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(handler.handleReverse).toHaveBeenCalledWith({
|
|
223
|
+
payload: {
|
|
224
|
+
payment: mockPayment,
|
|
225
|
+
merchantReference: 'ref-456',
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
expect(result).toEqual(mockSuccessResponse);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('should handle reverse payment action without merchantReference', async () => {
|
|
232
|
+
const reverseRequest: PaymentIntentsRequest = {
|
|
233
|
+
id: 'payment-123',
|
|
234
|
+
actions: [
|
|
235
|
+
{
|
|
236
|
+
action: 'reversePayment',
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
await handler.handler({
|
|
242
|
+
request: reverseRequest,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(handler.handleReverse).toHaveBeenCalledWith({
|
|
246
|
+
payload: {
|
|
247
|
+
payment: mockPayment,
|
|
248
|
+
merchantReference: undefined,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('Error handling', () => {
|
|
255
|
+
test('should throw ErrorInvalidOperation for unsupported action', async () => {
|
|
256
|
+
const invalidRequest = {
|
|
257
|
+
id: 'payment-123',
|
|
258
|
+
actions: [
|
|
259
|
+
{
|
|
260
|
+
// biome-ignore lint/suspicious/noExplicitAny: this is a test
|
|
261
|
+
action: 'unsupportedAction' as any,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
await expect(
|
|
267
|
+
handler.handler({
|
|
268
|
+
request: invalidRequest,
|
|
269
|
+
}),
|
|
270
|
+
).rejects.toThrow(ErrorInvalidOperation);
|
|
271
|
+
|
|
272
|
+
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
273
|
+
{},
|
|
274
|
+
'Operation not supported when modifying payment.',
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('should handle errors thrown by action handlers', async () => {
|
|
279
|
+
const captureRequest: PaymentIntentsRequest = {
|
|
280
|
+
id: 'payment-123',
|
|
281
|
+
actions: [
|
|
282
|
+
{
|
|
283
|
+
action: 'capturePayment',
|
|
284
|
+
amount: {
|
|
285
|
+
centAmount: 1000,
|
|
286
|
+
currencyCode: 'USD',
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const testError = new Error('Handler failed');
|
|
293
|
+
handler.handleCapture.mockRejectedValue(testError);
|
|
294
|
+
|
|
295
|
+
await expect(
|
|
296
|
+
handler.handler({
|
|
297
|
+
request: captureRequest,
|
|
298
|
+
}),
|
|
299
|
+
).rejects.toThrow('Handler failed');
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe('Edge cases', () => {
|
|
304
|
+
test('should process only the first action when multiple actions are provided', async () => {
|
|
305
|
+
const multipleActionsRequest: PaymentIntentsRequest = {
|
|
306
|
+
id: 'payment-123',
|
|
307
|
+
actions: [
|
|
308
|
+
{
|
|
309
|
+
action: 'capturePayment',
|
|
310
|
+
amount: {
|
|
311
|
+
centAmount: 1000,
|
|
312
|
+
currencyCode: 'USD',
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
action: 'refundPayment',
|
|
317
|
+
amount: {
|
|
318
|
+
centAmount: 500,
|
|
319
|
+
currencyCode: 'USD',
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
await handler.handler({
|
|
326
|
+
request: multipleActionsRequest,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
expect(handler.handleCapture).toHaveBeenCalledTimes(1);
|
|
330
|
+
expect(handler.handleRefund).not.toHaveBeenCalled();
|
|
331
|
+
expect(handler.handleCancel).not.toHaveBeenCalled();
|
|
332
|
+
expect(handler.handleReverse).not.toHaveBeenCalled();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('should handle different response outcomes', async () => {
|
|
336
|
+
const rejectedResponse: HandlerResponse<
|
|
337
|
+
PaymentIntentsResponse,
|
|
338
|
+
PaymentIntentsOutgoingHttpHeaders
|
|
339
|
+
> = {
|
|
340
|
+
status: 400,
|
|
341
|
+
body: { outcome: 'rejected' },
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
handler.handleCapture.mockResolvedValue(rejectedResponse);
|
|
345
|
+
|
|
346
|
+
const captureRequest: PaymentIntentsRequest = {
|
|
347
|
+
id: 'payment-123',
|
|
348
|
+
actions: [
|
|
349
|
+
{
|
|
350
|
+
action: 'capturePayment',
|
|
351
|
+
amount: {
|
|
352
|
+
centAmount: 1000,
|
|
353
|
+
currencyCode: 'USD',
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const result = await handler.handler({
|
|
360
|
+
request: captureRequest,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
expect(result.body.outcome).toBe('rejected');
|
|
364
|
+
expect(result.status).toBe(400);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test('should handle received outcome', async () => {
|
|
368
|
+
const receivedResponse: HandlerResponse<
|
|
369
|
+
PaymentIntentsResponse,
|
|
370
|
+
PaymentIntentsOutgoingHttpHeaders
|
|
371
|
+
> = {
|
|
372
|
+
status: 202,
|
|
373
|
+
body: { outcome: 'received' },
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
handler.handleRefund.mockResolvedValue(receivedResponse);
|
|
377
|
+
|
|
378
|
+
const refundRequest: PaymentIntentsRequest = {
|
|
379
|
+
id: 'payment-123',
|
|
380
|
+
actions: [
|
|
381
|
+
{
|
|
382
|
+
action: 'refundPayment',
|
|
383
|
+
amount: {
|
|
384
|
+
centAmount: 300,
|
|
385
|
+
currencyCode: 'GBP',
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const result = await handler.handler({
|
|
392
|
+
request: refundRequest,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
expect(result.body.outcome).toBe('received');
|
|
396
|
+
expect(result.status).toBe(202);
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
describe('Type safety', () => {
|
|
402
|
+
test('should enforce correct action type for handleCapture', () => {
|
|
403
|
+
const captureAction = {
|
|
404
|
+
action: 'capturePayment' as const,
|
|
405
|
+
amount: {
|
|
406
|
+
centAmount: 1000,
|
|
407
|
+
currencyCode: 'USD',
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// This should compile without issues
|
|
412
|
+
expect(() => {
|
|
413
|
+
handler.handleCapture({
|
|
414
|
+
payload: {
|
|
415
|
+
payment: mockPayment,
|
|
416
|
+
amount: captureAction.amount,
|
|
417
|
+
merchantReference: undefined,
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
}).not.toThrow();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test('should enforce correct action type for handleRefund', () => {
|
|
424
|
+
const refundAction = {
|
|
425
|
+
action: 'refundPayment' as const,
|
|
426
|
+
amount: {
|
|
427
|
+
centAmount: 500,
|
|
428
|
+
currencyCode: 'EUR',
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
expect(() => {
|
|
433
|
+
handler.handleRefund({
|
|
434
|
+
payload: {
|
|
435
|
+
payment: mockPayment,
|
|
436
|
+
amount: refundAction.amount,
|
|
437
|
+
merchantReference: undefined,
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
}).not.toThrow();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test('should enforce correct action type for handleCancel', () => {
|
|
444
|
+
const cancelAction = {
|
|
445
|
+
action: 'cancelPayment' as const,
|
|
446
|
+
merchantReference: 'ref-123',
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
expect(() => {
|
|
450
|
+
handler.handleCancel({
|
|
451
|
+
payload: {
|
|
452
|
+
payment: mockPayment,
|
|
453
|
+
merchantReference: cancelAction.merchantReference,
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
}).not.toThrow();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test('should enforce correct action type for handleReverse', () => {
|
|
460
|
+
const reverseAction = {
|
|
461
|
+
action: 'reversePayment' as const,
|
|
462
|
+
merchantReference: 'ref-456',
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
expect(() => {
|
|
466
|
+
handler.handleReverse({
|
|
467
|
+
payload: {
|
|
468
|
+
payment: mockPayment,
|
|
469
|
+
merchantReference: reverseAction.merchantReference,
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
}).not.toThrow();
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
});
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AuthType,
|
|
3
|
+
BaseIncomingHttpHeaders,
|
|
4
|
+
BaseOutgoingHttpHeaders,
|
|
5
|
+
HandlerResponse,
|
|
6
|
+
RouteHandler,
|
|
7
|
+
} from '@api-types';
|
|
8
|
+
import type { Money, Payment } from '@commercetools/platform-sdk';
|
|
9
|
+
import { ErrorInvalidOperation } from '@/errorx/errorx.js';
|
|
10
|
+
import type { Logger } from '@/logger/types.js';
|
|
11
|
+
import type { CheckoutPaymentsProcessor } from '@/payment-processor.js';
|
|
12
|
+
|
|
13
|
+
type CapturePayment = {
|
|
14
|
+
action: 'capturePayment';
|
|
15
|
+
amount: Money;
|
|
16
|
+
merchantReference?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type RefundPayment = {
|
|
20
|
+
action: 'refundPayment';
|
|
21
|
+
amount: Money;
|
|
22
|
+
merchantReference?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type CancelPayment = {
|
|
26
|
+
action: 'cancelPayment';
|
|
27
|
+
merchantReference?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type ReversePayment = {
|
|
31
|
+
action: 'reversePayment';
|
|
32
|
+
merchantReference?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type ModificationPayload = {
|
|
36
|
+
payment: Payment;
|
|
37
|
+
amount?: Money;
|
|
38
|
+
merchantReference?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type PaymentIntentsRequest = {
|
|
42
|
+
id: string;
|
|
43
|
+
actions: (CapturePayment | RefundPayment | CancelPayment | ReversePayment)[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type PaymentIntentsResponse = {
|
|
47
|
+
outcome: 'approved' | 'rejected' | 'received';
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type PaymentIntentsIncomingHttpHeaders = BaseIncomingHttpHeaders & {};
|
|
51
|
+
export type PaymentIntentsOutgoingHttpHeaders = BaseOutgoingHttpHeaders & {};
|
|
52
|
+
|
|
53
|
+
export abstract class PaymentIntentsHandler
|
|
54
|
+
implements
|
|
55
|
+
RouteHandler<
|
|
56
|
+
PaymentIntentsRequest,
|
|
57
|
+
PaymentIntentsResponse,
|
|
58
|
+
PaymentIntentsIncomingHttpHeaders,
|
|
59
|
+
PaymentIntentsOutgoingHttpHeaders
|
|
60
|
+
>
|
|
61
|
+
{
|
|
62
|
+
public readonly logger: Logger;
|
|
63
|
+
public readonly processor: CheckoutPaymentsProcessor;
|
|
64
|
+
public constructor(opts: {
|
|
65
|
+
logger: Logger;
|
|
66
|
+
processor: CheckoutPaymentsProcessor;
|
|
67
|
+
}) {
|
|
68
|
+
this.logger = opts.logger;
|
|
69
|
+
this.processor = opts.processor;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public method() {
|
|
73
|
+
return 'POST';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public endpoint() {
|
|
77
|
+
return '/operations/payment-intents/:id';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public auth(): AuthType[] {
|
|
81
|
+
return [
|
|
82
|
+
{
|
|
83
|
+
type: 'oauth2',
|
|
84
|
+
allowedScopes: ['manage_project', 'manage_checkout_payment_intents'],
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public async handler(opts: {
|
|
90
|
+
request: PaymentIntentsRequest;
|
|
91
|
+
}): Promise<
|
|
92
|
+
HandlerResponse<PaymentIntentsResponse, PaymentIntentsOutgoingHttpHeaders>
|
|
93
|
+
> {
|
|
94
|
+
const actions = opts.request.actions;
|
|
95
|
+
const request = actions[0];
|
|
96
|
+
|
|
97
|
+
const payment = await this.processor.getPaymentService().getPayment({
|
|
98
|
+
id: opts.request.id,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
switch (request.action) {
|
|
102
|
+
case 'cancelPayment': {
|
|
103
|
+
return await this.handleCancel({
|
|
104
|
+
payload: {
|
|
105
|
+
payment,
|
|
106
|
+
merchantReference: request.merchantReference,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
case 'refundPayment': {
|
|
111
|
+
return await this.handleRefund({
|
|
112
|
+
payload: {
|
|
113
|
+
payment,
|
|
114
|
+
amount: request.amount,
|
|
115
|
+
merchantReference: request.merchantReference,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
case 'capturePayment': {
|
|
120
|
+
return await this.handleCapture({
|
|
121
|
+
payload: {
|
|
122
|
+
payment,
|
|
123
|
+
amount: request.amount,
|
|
124
|
+
merchantReference: request.merchantReference,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
case 'reversePayment': {
|
|
129
|
+
return await this.handleReverse({
|
|
130
|
+
payload: {
|
|
131
|
+
payment,
|
|
132
|
+
merchantReference: request.merchantReference,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
default: {
|
|
137
|
+
this.logger.error(
|
|
138
|
+
{},
|
|
139
|
+
`Operation not supported when modifying payment.`,
|
|
140
|
+
);
|
|
141
|
+
throw new ErrorInvalidOperation(`Operation not supported.`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public abstract handleCancel(opts: {
|
|
147
|
+
payload: ModificationPayload;
|
|
148
|
+
}): Promise<
|
|
149
|
+
HandlerResponse<PaymentIntentsResponse, PaymentIntentsOutgoingHttpHeaders>
|
|
150
|
+
>;
|
|
151
|
+
|
|
152
|
+
public abstract handleRefund(opts: {
|
|
153
|
+
payload: ModificationPayload;
|
|
154
|
+
}): Promise<
|
|
155
|
+
HandlerResponse<PaymentIntentsResponse, PaymentIntentsOutgoingHttpHeaders>
|
|
156
|
+
>;
|
|
157
|
+
|
|
158
|
+
public abstract handleCapture(opts: {
|
|
159
|
+
payload: ModificationPayload;
|
|
160
|
+
}): Promise<
|
|
161
|
+
HandlerResponse<PaymentIntentsResponse, PaymentIntentsOutgoingHttpHeaders>
|
|
162
|
+
>;
|
|
163
|
+
|
|
164
|
+
public abstract handleReverse(opts: {
|
|
165
|
+
payload: ModificationPayload;
|
|
166
|
+
}): Promise<
|
|
167
|
+
HandlerResponse<PaymentIntentsResponse, PaymentIntentsOutgoingHttpHeaders>
|
|
168
|
+
>;
|
|
169
|
+
}
|