@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,316 @@
|
|
|
1
|
+
import { getGiftCardPlannedAmountFromContext } from '@api';
|
|
2
|
+
import type { ContextProvider, RequestContextData } from '@api-types';
|
|
3
|
+
import type {
|
|
4
|
+
Address,
|
|
5
|
+
Cart,
|
|
6
|
+
Payment,
|
|
7
|
+
Shipping,
|
|
8
|
+
Transaction,
|
|
9
|
+
} from '@commercetools/platform-sdk';
|
|
10
|
+
import {
|
|
11
|
+
ErrorInvalidOperation,
|
|
12
|
+
ErrorReferencedResourceNotFound,
|
|
13
|
+
} from '@errorx';
|
|
14
|
+
import type { Logger } from '@logger-types';
|
|
15
|
+
import { CommercetoolsAPIError } from '@/commercetools/errors/ct-api.error.js';
|
|
16
|
+
import type {
|
|
17
|
+
AddPayment,
|
|
18
|
+
CommercetoolsAPI,
|
|
19
|
+
} from '@/commercetools/types/api.type.js';
|
|
20
|
+
import type {
|
|
21
|
+
CartService,
|
|
22
|
+
CartServiceOptions,
|
|
23
|
+
GetCart,
|
|
24
|
+
GetCartByPaymentIdRequest,
|
|
25
|
+
GetNormalizedShipping,
|
|
26
|
+
GetOneShippingAddress,
|
|
27
|
+
GetPaymentAmount,
|
|
28
|
+
NormalizedShipping,
|
|
29
|
+
} from '@/commercetools/types/cart.type.js';
|
|
30
|
+
import type { PaymentAmount } from '@/commercetools/types/payment.type.js';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Default implementation of the CartService interface.
|
|
34
|
+
*/
|
|
35
|
+
export class DefaultCartService implements CartService {
|
|
36
|
+
private ctAPI: CommercetoolsAPI;
|
|
37
|
+
private logger: Logger;
|
|
38
|
+
private contextProvider: ContextProvider<RequestContextData>;
|
|
39
|
+
|
|
40
|
+
constructor(opts: CartServiceOptions) {
|
|
41
|
+
this.ctAPI = opts.ctAPI;
|
|
42
|
+
this.logger = opts.logger;
|
|
43
|
+
this.contextProvider = opts.contextProvider;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public async getCartByPaymentId(
|
|
47
|
+
opts: GetCartByPaymentIdRequest,
|
|
48
|
+
): Promise<Cart> {
|
|
49
|
+
const queryPredicate = `paymentInfo(payments(id = "${opts.paymentId}"))`;
|
|
50
|
+
|
|
51
|
+
const result = await this.ctAPI.cart.find(queryPredicate);
|
|
52
|
+
|
|
53
|
+
if (result.count !== 1) {
|
|
54
|
+
throw new ErrorReferencedResourceNotFound(
|
|
55
|
+
'cart',
|
|
56
|
+
queryPredicate,
|
|
57
|
+
undefined,
|
|
58
|
+
{
|
|
59
|
+
privateFields: {
|
|
60
|
+
queryPredicate,
|
|
61
|
+
paymentId: opts.paymentId,
|
|
62
|
+
},
|
|
63
|
+
privateMessage: 'Could not find a cart with the given paymentId',
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return result.results[0];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public async getCart(opts: GetCart): Promise<Cart> {
|
|
72
|
+
return await this.ctAPI.cart.getCartById(opts.id);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public async getPlannedPaymentAmount(
|
|
76
|
+
opts: GetPaymentAmount,
|
|
77
|
+
): Promise<PaymentAmount> {
|
|
78
|
+
const giftCardPlannedAmount = getGiftCardPlannedAmountFromContext(
|
|
79
|
+
this.contextProvider.getContextData(),
|
|
80
|
+
);
|
|
81
|
+
const paidAmount = await this.calculateTotalPaidAmount(opts.cart);
|
|
82
|
+
|
|
83
|
+
let cartAmount: PaymentAmount;
|
|
84
|
+
|
|
85
|
+
if (opts.cart.taxedPrice) {
|
|
86
|
+
cartAmount = {
|
|
87
|
+
currencyCode: opts.cart.taxedPrice.totalGross.currencyCode,
|
|
88
|
+
centAmount: opts.cart.taxedPrice.totalGross.centAmount,
|
|
89
|
+
fractionDigits: opts.cart.taxedPrice.totalGross.fractionDigits,
|
|
90
|
+
};
|
|
91
|
+
} else {
|
|
92
|
+
cartAmount = {
|
|
93
|
+
currencyCode: opts.cart.totalPrice.currencyCode,
|
|
94
|
+
centAmount: opts.cart.totalPrice.centAmount,
|
|
95
|
+
fractionDigits: opts.cart.totalPrice.fractionDigits,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (paidAmount >= cartAmount.centAmount) {
|
|
100
|
+
throw new ErrorInvalidOperation(
|
|
101
|
+
'The cart has already been paid in full',
|
|
102
|
+
{
|
|
103
|
+
fields: {
|
|
104
|
+
resource: opts.cart.id,
|
|
105
|
+
paidAmount,
|
|
106
|
+
cartAmount,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const currentAmountToPay =
|
|
113
|
+
giftCardPlannedAmount &&
|
|
114
|
+
giftCardPlannedAmount.currencyCode === cartAmount.currencyCode
|
|
115
|
+
? cartAmount.centAmount - giftCardPlannedAmount.centAmount
|
|
116
|
+
: cartAmount.centAmount;
|
|
117
|
+
|
|
118
|
+
this.logger.info(
|
|
119
|
+
{
|
|
120
|
+
currentAmountToPay,
|
|
121
|
+
},
|
|
122
|
+
'current amount to pay after gift card amount deduction',
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
currencyCode: cartAmount.currencyCode,
|
|
127
|
+
centAmount: currentAmountToPay - paidAmount,
|
|
128
|
+
fractionDigits: cartAmount.fractionDigits,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public async getPaymentAmount(
|
|
133
|
+
opts: GetPaymentAmount,
|
|
134
|
+
): Promise<PaymentAmount> {
|
|
135
|
+
const paidAmount = await this.calculateTotalPaidAmount(opts.cart);
|
|
136
|
+
|
|
137
|
+
let cartAmount: PaymentAmount;
|
|
138
|
+
|
|
139
|
+
if (opts.cart.taxedPrice) {
|
|
140
|
+
cartAmount = {
|
|
141
|
+
currencyCode: opts.cart.taxedPrice.totalGross.currencyCode,
|
|
142
|
+
centAmount: opts.cart.taxedPrice.totalGross.centAmount,
|
|
143
|
+
fractionDigits: opts.cart.taxedPrice.totalGross.fractionDigits,
|
|
144
|
+
};
|
|
145
|
+
} else {
|
|
146
|
+
cartAmount = {
|
|
147
|
+
currencyCode: opts.cart.totalPrice.currencyCode,
|
|
148
|
+
centAmount: opts.cart.totalPrice.centAmount,
|
|
149
|
+
fractionDigits: opts.cart.totalPrice.fractionDigits,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (paidAmount >= cartAmount.centAmount) {
|
|
154
|
+
throw new ErrorInvalidOperation(
|
|
155
|
+
'The cart has already been paid in full',
|
|
156
|
+
{
|
|
157
|
+
fields: {
|
|
158
|
+
resource: opts.cart.id,
|
|
159
|
+
paidAmount,
|
|
160
|
+
cartAmount,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const currentAmountToPay = cartAmount.centAmount - paidAmount;
|
|
167
|
+
|
|
168
|
+
this.logger.info(
|
|
169
|
+
{
|
|
170
|
+
currentAmountToPay,
|
|
171
|
+
},
|
|
172
|
+
'current amount to pay',
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
currencyCode: cartAmount.currencyCode,
|
|
177
|
+
centAmount: currentAmountToPay,
|
|
178
|
+
fractionDigits: cartAmount.fractionDigits,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Get only one shipping address from the cart. If the cart has multiple shipping addresses, it returns the first one.
|
|
184
|
+
* @param cart
|
|
185
|
+
* @returns
|
|
186
|
+
*/
|
|
187
|
+
public getOneShippingAddress(
|
|
188
|
+
opts: GetOneShippingAddress,
|
|
189
|
+
): Address | undefined {
|
|
190
|
+
if (opts.cart.shippingMode === 'Single') {
|
|
191
|
+
return opts.cart.shippingAddress;
|
|
192
|
+
}
|
|
193
|
+
if (
|
|
194
|
+
opts.cart.shippingMode === 'Multiple' &&
|
|
195
|
+
opts.cart.shipping &&
|
|
196
|
+
opts.cart.shipping.length > 0
|
|
197
|
+
) {
|
|
198
|
+
return opts.cart.shipping[0].shippingAddress;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Normalizes the shipping info from the cart. If the cart has a single shipping info, it returns an array with that shipping info.
|
|
204
|
+
* When the cart has multiple shipping infos, it returns an array with all the shipping infos.
|
|
205
|
+
* @param opts
|
|
206
|
+
* @returns
|
|
207
|
+
*/
|
|
208
|
+
public getNormalizedShipping(
|
|
209
|
+
opts: GetNormalizedShipping,
|
|
210
|
+
): NormalizedShipping[] {
|
|
211
|
+
if (opts.cart.shippingMode === 'Single' && opts.cart.shippingInfo) {
|
|
212
|
+
return opts.cart.shippingInfo && opts.cart.shippingAddress
|
|
213
|
+
? [
|
|
214
|
+
{
|
|
215
|
+
shippingAddress: opts.cart.shippingAddress,
|
|
216
|
+
shippingInfo: opts.cart.shippingInfo,
|
|
217
|
+
},
|
|
218
|
+
]
|
|
219
|
+
: [];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
opts.cart.shippingMode === 'Multiple' &&
|
|
224
|
+
opts.cart.shipping &&
|
|
225
|
+
opts.cart.shipping.length > 0
|
|
226
|
+
) {
|
|
227
|
+
return opts.cart.shipping.map((shipping: Shipping) => ({
|
|
228
|
+
shippingAddress: shipping.shippingAddress,
|
|
229
|
+
shippingInfo: shipping.shippingInfo,
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public async addPayment(opts: AddPayment): Promise<Cart> {
|
|
237
|
+
const maxRetries = 6;
|
|
238
|
+
let cartVersion = opts.resource.version;
|
|
239
|
+
let err: unknown;
|
|
240
|
+
for (let retries = 0; retries < maxRetries; retries++) {
|
|
241
|
+
try {
|
|
242
|
+
return await this.ctAPI.cart.addPayment({
|
|
243
|
+
resource: { id: opts.resource.id, version: cartVersion },
|
|
244
|
+
paymentId: opts.paymentId,
|
|
245
|
+
});
|
|
246
|
+
} catch (e) {
|
|
247
|
+
err = e;
|
|
248
|
+
if (e instanceof CommercetoolsAPIError && e.httpErrorStatus === 409) {
|
|
249
|
+
const cart = await this.getCart({ id: opts.resource.id });
|
|
250
|
+
cartVersion = cart.version;
|
|
251
|
+
retries++;
|
|
252
|
+
} else {
|
|
253
|
+
throw e;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
throw err;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
public isRecurringCart(cart: Cart): boolean {
|
|
261
|
+
if (!cart.customerId) return false;
|
|
262
|
+
|
|
263
|
+
const cartOrigin = cart.origin === 'RecurringOrder';
|
|
264
|
+
const hasRecurringLineItems =
|
|
265
|
+
cart.lineItems?.some((item) => item.recurrenceInfo) ?? false;
|
|
266
|
+
const hasRecurringCustomLineItems =
|
|
267
|
+
cart.customLineItems?.some((item) => item.recurrenceInfo) ?? false;
|
|
268
|
+
|
|
269
|
+
return cartOrigin || hasRecurringLineItems || hasRecurringCustomLineItems;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private async calculateTotalPaidAmount(cart: Cart): Promise<number> {
|
|
273
|
+
if (!cart.paymentInfo || cart.paymentInfo.payments.length === 0) {
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
const payments = await Promise.all(
|
|
277
|
+
cart.paymentInfo.payments.map((p) =>
|
|
278
|
+
this.ctAPI.payment.getPaymentById(p.id),
|
|
279
|
+
),
|
|
280
|
+
);
|
|
281
|
+
return payments.reduce(
|
|
282
|
+
(total: number, payment: Payment) =>
|
|
283
|
+
total + this.calculatePaymentAmount(payment),
|
|
284
|
+
0,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private calculatePaymentAmount(payment: Payment): number {
|
|
289
|
+
//If an "approved" payment has been reverted, the amount should not be considered as paid
|
|
290
|
+
if (this.wasPaymentReverted(payment)) {
|
|
291
|
+
return 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (this.isPaymentApproved(payment)) {
|
|
295
|
+
return payment.amountPlanned.centAmount;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return 0;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private wasPaymentReverted(payment: Payment): boolean {
|
|
302
|
+
return payment.transactions.some(
|
|
303
|
+
(tx: Transaction) =>
|
|
304
|
+
(tx.type === 'CancelAuthorization' || tx.type === 'Refund') &&
|
|
305
|
+
(tx.state === 'Success' || tx.state === 'Pending'),
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private isPaymentApproved(payment: Payment): boolean {
|
|
310
|
+
return payment.transactions.some(
|
|
311
|
+
(transaction: Transaction) =>
|
|
312
|
+
(transaction.state === 'Success' || transaction.state === 'Pending') &&
|
|
313
|
+
(transaction.type === 'Authorization' || transaction.type === 'Charge'),
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Type,
|
|
3
|
+
TypeDraft,
|
|
4
|
+
TypePagedQueryResponse,
|
|
5
|
+
} from '@commercetools/platform-sdk';
|
|
6
|
+
import type { Logger } from '@logger-types';
|
|
7
|
+
import type { CommercetoolsAPI } from '../types/api.type.js';
|
|
8
|
+
import type {
|
|
9
|
+
CustomTypeService,
|
|
10
|
+
FindCustomType,
|
|
11
|
+
GetCustomType,
|
|
12
|
+
} from '../types/custom-type.type.js';
|
|
13
|
+
import type { PaymentServiceOptions } from '../types/payment.type.js';
|
|
14
|
+
import {
|
|
15
|
+
CreateCardDetailsTypePayload,
|
|
16
|
+
CreatePaymentInterfaceInteractionTypePayload,
|
|
17
|
+
CreateSepaDetailsTypePayload,
|
|
18
|
+
} from '../types/predefined-custom-types.type.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* This is the default implementation of the CustomTypeService interface.
|
|
22
|
+
*/
|
|
23
|
+
export class DefaultCustomTypeService implements CustomTypeService {
|
|
24
|
+
private ctAPI: CommercetoolsAPI;
|
|
25
|
+
private logger: Logger;
|
|
26
|
+
|
|
27
|
+
constructor(opts: PaymentServiceOptions) {
|
|
28
|
+
this.ctAPI = opts.ctAPI;
|
|
29
|
+
this.logger = opts.logger;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async getCustomType(opts: GetCustomType): Promise<Type> {
|
|
33
|
+
return await this.ctAPI.customType.getByKey(opts.key);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async findCustomType(opts: FindCustomType): Promise<TypePagedQueryResponse> {
|
|
37
|
+
return await this.ctAPI.customType.find(opts.predicate);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async createCustomType(draft: TypeDraft): Promise<Type> {
|
|
41
|
+
return await this.ctAPI.customType.create(draft);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async createPredefinedInterfaceInteractionType(): Promise<Type> {
|
|
45
|
+
return await this.createCustomType(
|
|
46
|
+
CreatePaymentInterfaceInteractionTypePayload,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async createPredefinedPaymentMethodsTypes(): Promise<Type[]> {
|
|
51
|
+
const createdTypes: Type[] = [];
|
|
52
|
+
|
|
53
|
+
createdTypes.push(
|
|
54
|
+
await this.createCustomType(CreateCardDetailsTypePayload),
|
|
55
|
+
);
|
|
56
|
+
createdTypes.push(
|
|
57
|
+
await this.createCustomType(CreateSepaDetailsTypePayload),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return createdTypes;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { RequestContextProvider } from '@api';
|
|
3
|
+
import type {
|
|
4
|
+
Order,
|
|
5
|
+
OrderPagedQueryResponse,
|
|
6
|
+
} from '@commercetools/platform-sdk';
|
|
7
|
+
import { CommercetoolsLogger } from '@logger';
|
|
8
|
+
import { HttpResponse, http } from 'msw';
|
|
9
|
+
import { setupServer } from 'msw/node';
|
|
10
|
+
import { DefaultCommercetoolsAPI } from '@/commercetools/api/root-api.js';
|
|
11
|
+
import { DefaultOrderService } from '@/commercetools/services/ct-order.service.js';
|
|
12
|
+
import { authToken } from '@/mocks/auth.mock.js';
|
|
13
|
+
|
|
14
|
+
const mockServer = setupServer();
|
|
15
|
+
const projectKey = 'test';
|
|
16
|
+
const contextProvider = new RequestContextProvider({
|
|
17
|
+
getContextFn: () => ({
|
|
18
|
+
correlationId: 'correlation-id',
|
|
19
|
+
requestId: 'request-id',
|
|
20
|
+
}),
|
|
21
|
+
updateContextFn: () => {},
|
|
22
|
+
});
|
|
23
|
+
const logger = new CommercetoolsLogger({ contextProvider, projectKey });
|
|
24
|
+
|
|
25
|
+
const ctAPI = new DefaultCommercetoolsAPI({
|
|
26
|
+
apiUrl: 'http://api.test.com',
|
|
27
|
+
authUrl: 'http://auth.test.com',
|
|
28
|
+
clientId: 'clientId',
|
|
29
|
+
clientSecret: 'clientSecret',
|
|
30
|
+
projectKey,
|
|
31
|
+
contextProvider,
|
|
32
|
+
logger,
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
httpClient: (input: string | URL | Request, init?: RequestInit) => {
|
|
35
|
+
return fetch(input, init);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const ctOrderService = new DefaultOrderService({ ctAPI, logger });
|
|
40
|
+
|
|
41
|
+
describe('commercetools order service', () => {
|
|
42
|
+
beforeAll(() =>
|
|
43
|
+
mockServer.listen({
|
|
44
|
+
onUnhandledRequest: 'error',
|
|
45
|
+
}),
|
|
46
|
+
);
|
|
47
|
+
afterAll(() => mockServer.close());
|
|
48
|
+
|
|
49
|
+
describe('getOrderByPaymentId', () => {
|
|
50
|
+
test('should return a order', async () => {
|
|
51
|
+
//Given
|
|
52
|
+
const paymentId = randomUUID();
|
|
53
|
+
const order: OrderPagedQueryResponse = {
|
|
54
|
+
count: 1,
|
|
55
|
+
limit: 100,
|
|
56
|
+
offset: 0,
|
|
57
|
+
results: [
|
|
58
|
+
{
|
|
59
|
+
id: 'order-id',
|
|
60
|
+
version: 1,
|
|
61
|
+
paymentInfo: {
|
|
62
|
+
payments: [
|
|
63
|
+
{
|
|
64
|
+
id: paymentId,
|
|
65
|
+
typeId: 'payment',
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
} as Order,
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
mockServer.use(
|
|
74
|
+
mockGetAuthToken(200, authToken),
|
|
75
|
+
mockFindOrder(200, order),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Actual
|
|
79
|
+
const actual = await ctOrderService.getOrderByPaymentId({ paymentId });
|
|
80
|
+
|
|
81
|
+
// Expected
|
|
82
|
+
const expected: Order = {
|
|
83
|
+
id: 'order-id',
|
|
84
|
+
version: 1,
|
|
85
|
+
paymentInfo: {
|
|
86
|
+
payments: [
|
|
87
|
+
{
|
|
88
|
+
id: paymentId,
|
|
89
|
+
typeId: 'payment',
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
} as Order;
|
|
94
|
+
|
|
95
|
+
expect(actual).toEqual(expected);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('should return ErrorResourceNotFound if no order by the given paymentId could be found', async () => {
|
|
99
|
+
//Given
|
|
100
|
+
const paymentId = randomUUID();
|
|
101
|
+
const order: OrderPagedQueryResponse = {
|
|
102
|
+
count: 0,
|
|
103
|
+
limit: 100,
|
|
104
|
+
offset: 0,
|
|
105
|
+
results: [],
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
mockServer.use(
|
|
109
|
+
mockGetAuthToken(200, authToken),
|
|
110
|
+
mockFindOrder(200, order),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Actual
|
|
114
|
+
const actual = ctOrderService.getOrderByPaymentId({ paymentId });
|
|
115
|
+
|
|
116
|
+
// Expected
|
|
117
|
+
await expect(actual).rejects.toThrow(
|
|
118
|
+
`The referenced object of type order paymentInfo(payments(id = "${paymentId}")) was not found. It either doesn't exist, or it can't be accessed from this endpoint (e.g., if the endpoint filters by store or customer account).`,
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const mockGetAuthToken = (respCode: number, data?: object) =>
|
|
125
|
+
http.post('http://auth.test.com/oauth/token', async () => {
|
|
126
|
+
return HttpResponse.json(data, {
|
|
127
|
+
status: respCode,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const mockFindOrder = (respCode: number, data?: object) =>
|
|
132
|
+
http.get('http://api.test.com/test/orders', async () => {
|
|
133
|
+
return HttpResponse.json(data, {
|
|
134
|
+
status: respCode,
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Order } from '@commercetools/platform-sdk';
|
|
2
|
+
import { ErrorReferencedResourceNotFound } from '@errorx';
|
|
3
|
+
import type { Logger } from '@logger-types';
|
|
4
|
+
import type { CommercetoolsAPI } from '@/commercetools/types/api.type.js';
|
|
5
|
+
import type {
|
|
6
|
+
GetOrderByPaymentIdRequest,
|
|
7
|
+
OrderService,
|
|
8
|
+
OrderServiceOptions,
|
|
9
|
+
} from '@/commercetools/types/order.type.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* This is the default implementation of the OrderService interface.
|
|
13
|
+
*/
|
|
14
|
+
export class DefaultOrderService implements OrderService {
|
|
15
|
+
private ctAPI: CommercetoolsAPI;
|
|
16
|
+
private logger: Logger;
|
|
17
|
+
|
|
18
|
+
constructor(opts: OrderServiceOptions) {
|
|
19
|
+
this.ctAPI = opts.ctAPI;
|
|
20
|
+
this.logger = opts.logger;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public async getOrderByPaymentId(
|
|
24
|
+
opts: GetOrderByPaymentIdRequest,
|
|
25
|
+
): Promise<Order> {
|
|
26
|
+
const queryPredicate = `paymentInfo(payments(id = "${opts.paymentId}"))`;
|
|
27
|
+
|
|
28
|
+
const result = await this.ctAPI.order.find(queryPredicate);
|
|
29
|
+
|
|
30
|
+
if (result.count !== 1) {
|
|
31
|
+
throw new ErrorReferencedResourceNotFound(
|
|
32
|
+
'order',
|
|
33
|
+
queryPredicate,
|
|
34
|
+
undefined,
|
|
35
|
+
{
|
|
36
|
+
privateFields: {
|
|
37
|
+
queryPredicate,
|
|
38
|
+
paymentId: opts.paymentId,
|
|
39
|
+
},
|
|
40
|
+
privateMessage: 'Could not find a order with the given paymentId',
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result.results[0];
|
|
46
|
+
}
|
|
47
|
+
}
|