@reactionary/provider-commercetools 0.0.48 → 0.0.51

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/core/client.js CHANGED
@@ -3,22 +3,25 @@ import {
3
3
  } from "@commercetools/ts-client";
4
4
  import { createApiBuilderFromCtpClient } from "@commercetools/platform-sdk";
5
5
  import { randomUUID } from "crypto";
6
- import { GuestIdentitySchema, IdentitySchema } from "@reactionary/core";
6
+ import {
7
+ AnonymousIdentitySchema,
8
+ GuestIdentitySchema,
9
+ RegisteredIdentitySchema
10
+ } from "@reactionary/core";
7
11
  import * as crypto from "crypto";
12
+ import createDebug from "debug";
13
+ const debug = createDebug("commercetools:debug");
8
14
  class RequestContextTokenCache {
9
15
  constructor(context) {
10
16
  this.context = context;
11
17
  }
12
18
  async get(tokenCacheOptions) {
13
19
  const identity = this.context.identity;
14
- if (identity.type !== "Anonymous") {
15
- return {
16
- refreshToken: identity.refresh_token,
17
- token: identity.token || "",
18
- expirationTime: identity.expiry.getTime()
19
- };
20
- }
21
- return void 0;
20
+ return {
21
+ refreshToken: identity.refresh_token,
22
+ token: identity.token || "",
23
+ expirationTime: identity.expiry.getTime()
24
+ };
22
25
  }
23
26
  async set(cache, tokenCacheOptions) {
24
27
  const identity = this.context.identity;
@@ -35,7 +38,6 @@ class CommercetoolsClient {
35
38
  return this.createClient(reqCtx);
36
39
  }
37
40
  async register(username, password, reqCtx) {
38
- const cache = new RequestContextTokenCache(reqCtx);
39
41
  const registrationBuilder = this.createBaseClientBuilder().withAnonymousSessionFlow({
40
42
  host: this.config.authUrl,
41
43
  projectKey: this.config.projectKey,
@@ -43,8 +45,7 @@ class CommercetoolsClient {
43
45
  clientId: this.config.clientId,
44
46
  clientSecret: this.config.clientSecret
45
47
  },
46
- scopes: this.config.scopes,
47
- tokenCache: cache
48
+ scopes: this.config.scopes
48
49
  });
49
50
  const registrationClient = createApiBuilderFromCtpClient(
50
51
  registrationBuilder.build()
@@ -56,10 +57,10 @@ class CommercetoolsClient {
56
57
  }
57
58
  }).execute();
58
59
  const login = await this.login(username, password, reqCtx);
60
+ return login;
59
61
  }
60
62
  async login(username, password, reqCtx) {
61
63
  const cache = new RequestContextTokenCache(reqCtx);
62
- const identity = reqCtx.identity;
63
64
  const loginBuilder = this.createBaseClientBuilder().withPasswordFlow({
64
65
  host: this.config.authUrl,
65
66
  projectKey: this.config.projectKey,
@@ -72,17 +73,27 @@ class CommercetoolsClient {
72
73
  scopes: this.config.scopes
73
74
  });
74
75
  const loginClient = createApiBuilderFromCtpClient(loginBuilder.build());
75
- const login = await loginClient.withProjectKey({ projectKey: this.config.projectKey }).me().get().execute();
76
- identity.type = "Registered";
77
- identity.logonId = username;
78
- identity.id = {
79
- userId: login.body.id
80
- };
76
+ const login = await loginClient.withProjectKey({ projectKey: this.config.projectKey }).me().login().post({
77
+ body: {
78
+ email: username,
79
+ password
80
+ }
81
+ }).execute();
82
+ reqCtx.identity = RegisteredIdentitySchema.parse({
83
+ ...reqCtx.identity,
84
+ type: "Registered",
85
+ logonId: username,
86
+ id: {
87
+ userId: login.body.customer.id
88
+ }
89
+ });
90
+ return reqCtx.identity;
81
91
  }
82
92
  async logout(reqCtx) {
83
93
  const cache = new RequestContextTokenCache(reqCtx);
84
94
  await cache.set({ token: "", refreshToken: "", expirationTime: 0 });
85
- reqCtx.identity = IdentitySchema.parse({});
95
+ reqCtx.identity = AnonymousIdentitySchema.parse({});
96
+ return reqCtx.identity;
86
97
  }
87
98
  createClient(reqCtx) {
88
99
  const cache = new RequestContextTokenCache(reqCtx);
@@ -95,7 +106,7 @@ class CommercetoolsClient {
95
106
  });
96
107
  }
97
108
  const identity = reqCtx.identity;
98
- let builder = this.createBaseClientBuilder();
109
+ let builder = this.createBaseClientBuilder(reqCtx);
99
110
  if (!identity.token || !identity.refresh_token) {
100
111
  builder = builder.withAnonymousSessionFlow({
101
112
  host: this.config.authUrl,
@@ -121,8 +132,8 @@ class CommercetoolsClient {
121
132
  }
122
133
  return createApiBuilderFromCtpClient(builder.build());
123
134
  }
124
- createBaseClientBuilder() {
125
- const builder = new ClientBuilder().withProjectKey(this.config.projectKey).withQueueMiddleware({
135
+ createBaseClientBuilder(reqCtx) {
136
+ let builder = new ClientBuilder().withProjectKey(this.config.projectKey).withQueueMiddleware({
126
137
  concurrency: 20
127
138
  }).withConcurrentModificationMiddleware({
128
139
  concurrentModificationHandlerFn: (version, request) => {
@@ -133,9 +144,6 @@ class CommercetoolsClient {
133
144
  body["version"] = version;
134
145
  return Promise.resolve(body);
135
146
  }
136
- }).withCorrelationIdMiddleware({
137
- // ideally this would be pushed in as part of the session context, so we can trace it end-to-end
138
- generate: () => `REACTIONARY-${randomUUID()}`
139
147
  }).withHttpMiddleware({
140
148
  retryConfig: {
141
149
  backoff: true,
@@ -151,6 +159,13 @@ class CommercetoolsClient {
151
159
  host: this.config.apiUrl,
152
160
  httpClient: fetch
153
161
  });
162
+ const correlationId = reqCtx?.correlationId || "REACTIONARY-" + (typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : randomUUID());
163
+ builder = builder.withCorrelationIdMiddleware({
164
+ generate: () => correlationId
165
+ });
166
+ if (debug.enabled) {
167
+ builder = builder.withLoggerMiddleware();
168
+ }
154
169
  return builder;
155
170
  }
156
171
  }
@@ -6,7 +6,7 @@ import {
6
6
  ProductSchema,
7
7
  SearchResultSchema,
8
8
  CategorySchema,
9
- CartPaymentInstructionSchema
9
+ CheckoutSchema
10
10
  } from "@reactionary/core";
11
11
  import { CommercetoolsSearchProvider } from "../providers/search.provider";
12
12
  import { CommercetoolsProductProvider } from "../providers/product.provider";
@@ -15,7 +15,7 @@ import { CommercetoolsCartProvider } from "../providers/cart.provider";
15
15
  import { CommercetoolsInventoryProvider } from "../providers/inventory.provider";
16
16
  import { CommercetoolsPriceProvider } from "../providers/price.provider";
17
17
  import { CommercetoolsCategoryProvider } from "../providers/category.provider";
18
- import { CommercetoolsCartPaymentProvider } from "../providers/cart-payment.provider";
18
+ import { CommercetoolsCheckoutProvider } from "../providers";
19
19
  function withCommercetoolsCapabilities(configuration, capabilities) {
20
20
  return (cache) => {
21
21
  const client = {};
@@ -40,8 +40,8 @@ function withCommercetoolsCapabilities(configuration, capabilities) {
40
40
  if (capabilities.category) {
41
41
  client.category = new CommercetoolsCategoryProvider(configuration, CategorySchema, cache);
42
42
  }
43
- if (capabilities.cartPayment) {
44
- client.cartPayment = new CommercetoolsCartPaymentProvider(configuration, CartPaymentInstructionSchema, cache);
43
+ if (capabilities.checkout) {
44
+ client.checkout = new CommercetoolsCheckoutProvider(configuration, CheckoutSchema, cache);
45
45
  }
46
46
  return client;
47
47
  };
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@reactionary/provider-commercetools",
3
- "version": "0.0.48",
3
+ "version": "0.0.51",
4
4
  "main": "index.js",
5
5
  "types": "src/index.d.ts",
6
6
  "dependencies": {
7
- "@reactionary/core": "0.0.48",
8
- "@reactionary/otel": "0.0.48",
7
+ "@reactionary/core": "0.0.51",
8
+ "@reactionary/otel": "0.0.51",
9
+ "debug": "^4.4.3",
9
10
  "zod": "4.1.9",
10
11
  "@commercetools/ts-client": "^4.2.1",
11
12
  "@commercetools/platform-sdk": "^8.8.0"
@@ -279,13 +279,21 @@ class CommercetoolsCartProvider extends CartProvider {
279
279
  async applyActions(cart, actions, reqCtx) {
280
280
  const client = await this.getClient(reqCtx);
281
281
  const ctId = cart;
282
- const response = await client.carts.withId({ ID: ctId.key }).post({
283
- body: {
284
- version: ctId.version,
285
- actions
282
+ try {
283
+ const response = await client.carts.withId({ ID: ctId.key }).post({
284
+ body: {
285
+ version: ctId.version,
286
+ actions
287
+ }
288
+ }).execute();
289
+ if (response.error) {
290
+ console.error(response.error);
286
291
  }
287
- }).execute();
288
- return this.parseSingle(response.body, reqCtx);
292
+ return this.parseSingle(response.body, reqCtx);
293
+ } catch (e) {
294
+ console.error("Error applying actions to cart:", e);
295
+ throw e;
296
+ }
289
297
  }
290
298
  async getClient(reqCtx) {
291
299
  const client = await new CommercetoolsClient(this.config).getClient(reqCtx);
@@ -0,0 +1,457 @@
1
+ import { AddressSchema, CheckoutItemSchema, CheckoutProvider, PaymentInstructionIdentifierSchema, PaymentInstructionSchema, PaymentMethodIdentifierSchema, ShippingInstructionSchema, ShippingMethodSchema } from "@reactionary/core";
2
+ import { CommercetoolsClient } from "../core/client";
3
+ import { CommercetoolsCartIdentifierSchema, CommercetoolsCheckoutIdentifierSchema, CommercetoolsOrderIdentifierSchema } from "../schema/commercetools.schema";
4
+ class CheckoutNotReadyForFinalizationError extends Error {
5
+ constructor(checkoutIdentifier) {
6
+ super("Checkout is not ready for finalization. Ensure all required fields are set and valid. " + (checkoutIdentifier ? `Checkout ID: ${JSON.stringify(checkoutIdentifier)}` : ""));
7
+ this.checkoutIdentifier = checkoutIdentifier;
8
+ this.name = "CheckoutNotReadyForFinalizationError";
9
+ }
10
+ }
11
+ class CommercetoolsCheckoutProvider extends CheckoutProvider {
12
+ constructor(config, schema, cache) {
13
+ super(schema, cache);
14
+ this.config = config;
15
+ }
16
+ async getClient(reqCtx) {
17
+ const client = await new CommercetoolsClient(this.config).getClient(reqCtx);
18
+ return {
19
+ payments: client.withProjectKey({ projectKey: this.config.projectKey }).me().payments(),
20
+ carts: client.withProjectKey({ projectKey: this.config.projectKey }).me().carts(),
21
+ shippingMethods: client.withProjectKey({ projectKey: this.config.projectKey }).shippingMethods(),
22
+ orders: client.withProjectKey({ projectKey: this.config.projectKey }).me().orders()
23
+ };
24
+ }
25
+ async initiateCheckoutForCart(payload, reqCtx) {
26
+ const client = await this.getClient(reqCtx);
27
+ const cart = await client.carts.withId({ ID: payload.cart.key }).get().execute();
28
+ const replicationResponse = await client.carts.replicate().post({
29
+ body: {
30
+ reference: {
31
+ typeId: "cart",
32
+ id: cart.body.id
33
+ }
34
+ }
35
+ }).execute();
36
+ const actions = [
37
+ {
38
+ action: "setCustomType",
39
+ type: {
40
+ typeId: "type",
41
+ key: "reactionaryCheckout"
42
+ },
43
+ fields: {
44
+ commerceToolsCartId: payload.cart.key
45
+ }
46
+ }
47
+ ];
48
+ if (payload.billingAddress) {
49
+ actions.push({
50
+ action: "setBillingAddress",
51
+ address: {
52
+ country: payload.billingAddress.countryCode,
53
+ firstName: payload.billingAddress.firstName || "",
54
+ lastName: payload.billingAddress.lastName || "",
55
+ streetName: payload.billingAddress.streetAddress || "",
56
+ streetNumber: payload.billingAddress.streetNumber || "",
57
+ postalCode: payload.billingAddress.postalCode || "",
58
+ city: payload.billingAddress.city || "",
59
+ email: payload.notificationEmail || "",
60
+ phone: payload.notificationPhone || ""
61
+ }
62
+ });
63
+ actions.push({
64
+ action: "setShippingAddress",
65
+ address: {
66
+ country: payload.billingAddress.countryCode,
67
+ firstName: payload.billingAddress.firstName || "",
68
+ lastName: payload.billingAddress.lastName || "",
69
+ streetName: payload.billingAddress.streetAddress || "",
70
+ streetNumber: payload.billingAddress.streetNumber || "",
71
+ postalCode: payload.billingAddress.postalCode || "",
72
+ city: payload.billingAddress.city || ""
73
+ }
74
+ });
75
+ }
76
+ const checkoutResponse = await client.carts.withId({ ID: replicationResponse.body.id }).post({
77
+ body: {
78
+ version: replicationResponse.body.version || 0,
79
+ actions: [
80
+ ...actions
81
+ ]
82
+ }
83
+ }).execute();
84
+ return this.parseSingle(checkoutResponse.body, reqCtx);
85
+ }
86
+ async getById(payload, reqCtx) {
87
+ const client = await this.getClient(reqCtx);
88
+ const checkoutResponse = await client.carts.withId({ ID: payload.identifier.key }).get({
89
+ queryArgs: {
90
+ expand: ["paymentInfo.payments[*]", "shippingInfo.shippingMethod"]
91
+ }
92
+ }).execute();
93
+ return this.parseSingle(checkoutResponse.body, reqCtx);
94
+ }
95
+ async setShippingAddress(payload, reqCtx) {
96
+ const client = await this.getClient(reqCtx);
97
+ const version = payload.checkout.version;
98
+ const checkoutResponse = await client.carts.withId({ ID: payload.checkout.key }).post({
99
+ body: {
100
+ version,
101
+ actions: [
102
+ {
103
+ action: "setShippingAddress",
104
+ address: {
105
+ country: payload.shippingAddress.countryCode,
106
+ firstName: payload.shippingAddress.firstName || "",
107
+ lastName: payload.shippingAddress.lastName || "",
108
+ streetName: payload.shippingAddress.streetAddress || "",
109
+ streetNumber: payload.shippingAddress.streetNumber || "",
110
+ postalCode: payload.shippingAddress.postalCode || "",
111
+ city: payload.shippingAddress.city || ""
112
+ }
113
+ }
114
+ ]
115
+ }
116
+ }).execute();
117
+ return this.parseSingle(checkoutResponse.body, reqCtx);
118
+ }
119
+ async getAvailableShippingMethods(payload, reqCtx) {
120
+ const client = await this.getClient(reqCtx);
121
+ const shippingMethodsResponse = await client.shippingMethods.matchingCart().get({
122
+ queryArgs: {
123
+ cartId: payload.checkout.key
124
+ }
125
+ }).execute();
126
+ const result = [];
127
+ const inputShippingMethods = shippingMethodsResponse.body.results;
128
+ for (const sm of inputShippingMethods) {
129
+ const shippingMethod = ShippingMethodSchema.parse({
130
+ identifier: {
131
+ key: sm.key
132
+ },
133
+ name: sm.name,
134
+ description: sm.localizedDescription?.[reqCtx.languageContext.locale] || "",
135
+ price: sm.zoneRates[0].shippingRates[0].price ? {
136
+ value: (sm.zoneRates[0].shippingRates[0].price.centAmount || 0) / 100,
137
+ currency: sm.zoneRates[0].shippingRates[0].price.currencyCode || reqCtx.languageContext.currencyCode
138
+ } : { value: 0, currency: reqCtx.languageContext.currencyCode }
139
+ });
140
+ result.push(shippingMethod);
141
+ }
142
+ return result;
143
+ }
144
+ async getAvailablePaymentMethods(payload, reqCtx) {
145
+ const staticMethods = this.getStaticPaymentMethods(payload.checkout, reqCtx);
146
+ const dynamicMethods = [];
147
+ return [...staticMethods, ...dynamicMethods];
148
+ }
149
+ async addPaymentInstruction(payload, reqCtx) {
150
+ const client = await this.getClient(reqCtx);
151
+ const response = await client.payments.post({
152
+ body: {
153
+ amountPlanned: {
154
+ centAmount: Math.round(payload.paymentInstruction.amount.value * 100),
155
+ currencyCode: payload.paymentInstruction.amount.currency
156
+ },
157
+ paymentMethodInfo: {
158
+ method: payload.paymentInstruction.paymentMethod.method,
159
+ name: {
160
+ [reqCtx.languageContext.locale]: payload.paymentInstruction.paymentMethod.name
161
+ },
162
+ paymentInterface: payload.paymentInstruction.paymentMethod.paymentProcessor
163
+ },
164
+ custom: {
165
+ type: {
166
+ typeId: "type",
167
+ key: "reactionaryPaymentCustomFields"
168
+ },
169
+ fields: {
170
+ "commerceToolsCartId": payload.checkout.key
171
+ }
172
+ }
173
+ }
174
+ }).execute();
175
+ const version = payload.checkout.version;
176
+ const actions = [
177
+ {
178
+ action: "addPayment",
179
+ payment: {
180
+ typeId: "payment",
181
+ id: response.body.id
182
+ }
183
+ }
184
+ ];
185
+ return this.applyActions(payload.checkout, actions, reqCtx);
186
+ }
187
+ async removePaymentInstruction(payload, reqCtx) {
188
+ const client = await this.getClient(reqCtx);
189
+ const checkout = await this.getById({ identifier: payload.checkout }, reqCtx);
190
+ return checkout;
191
+ }
192
+ async setShippingInstruction(payload, reqCtx) {
193
+ const client = await this.getClient(reqCtx);
194
+ const ctId = payload.checkout;
195
+ const actions = [];
196
+ actions.push({
197
+ action: "setShippingMethod",
198
+ shippingMethod: {
199
+ typeId: "shipping-method",
200
+ key: payload.shippingInstruction.shippingMethod.key
201
+ }
202
+ });
203
+ actions.push({
204
+ action: "setCustomField",
205
+ name: "shippingInstruction",
206
+ value: payload.shippingInstruction.instructions
207
+ });
208
+ actions.push({
209
+ action: "setCustomField",
210
+ name: "consentForUnattendedDelivery",
211
+ value: payload.shippingInstruction.consentForUnattendedDelivery + ""
212
+ });
213
+ actions.push({
214
+ action: "setCustomField",
215
+ name: "pickupPointId",
216
+ value: payload.shippingInstruction.pickupPoint
217
+ });
218
+ return this.applyActions(payload.checkout, actions, reqCtx);
219
+ }
220
+ async finalizeCheckout(payload, reqCtx) {
221
+ const checkout = await this.getById({ identifier: payload.checkout }, reqCtx);
222
+ if (!checkout || !checkout.readyForFinalization) {
223
+ throw new CheckoutNotReadyForFinalizationError(payload.checkout);
224
+ }
225
+ const client = await this.getClient(reqCtx);
226
+ const ctId = payload.checkout;
227
+ const orderResponse = await client.orders.post({
228
+ body: {
229
+ id: ctId.key,
230
+ version: ctId.version
231
+ }
232
+ }).execute();
233
+ const actions = [];
234
+ actions.push({
235
+ action: "setCustomField",
236
+ name: "commerceToolsOrderId",
237
+ value: orderResponse.body.id
238
+ });
239
+ return this.applyActions(payload.checkout, actions, reqCtx);
240
+ }
241
+ async applyActions(checkout, actions, reqCtx) {
242
+ const client = await this.getClient(reqCtx);
243
+ const ctId = checkout;
244
+ try {
245
+ const response = await client.carts.withId({ ID: ctId.key }).post({
246
+ queryArgs: {
247
+ expand: ["paymentInfo.payments[*]", "shippingInfo.shippingMethod"]
248
+ },
249
+ body: {
250
+ version: ctId.version,
251
+ actions
252
+ }
253
+ }).execute();
254
+ if (response.error) {
255
+ console.error(response.error);
256
+ }
257
+ return this.parseSingle(response.body, reqCtx);
258
+ } catch (e) {
259
+ console.error("Error applying actions to cart:", e);
260
+ throw e;
261
+ }
262
+ }
263
+ /**
264
+ * Extension point, to allow filtering the options, or adding new ones, like invoicing for b2b.
265
+ *
266
+ * Usecase: Override this, if you need to change the payment options based on the request context.
267
+ * @param reqCtx
268
+ * @returns
269
+ */
270
+ getStaticPaymentMethods(_checkout, reqCtx) {
271
+ return this.config.paymentMethods || [];
272
+ }
273
+ getResourceName() {
274
+ return "checkout";
275
+ }
276
+ parseSingle(remote, reqCtx) {
277
+ const result = this.newModel();
278
+ result.identifier = CommercetoolsCheckoutIdentifierSchema.parse({
279
+ key: remote.id,
280
+ version: remote.version || 0
281
+ });
282
+ result.name = remote.custom?.fields["name"] || "";
283
+ result.description = remote.custom?.fields["description"] || "";
284
+ result.originalCartReference = CommercetoolsCartIdentifierSchema.parse({
285
+ key: remote.custom?.fields["commerceToolsCartId"] || "",
286
+ version: 0
287
+ });
288
+ const orderId = remote.custom?.fields["commerceToolsOrderId"];
289
+ if (orderId) {
290
+ result.resultingOrder = CommercetoolsOrderIdentifierSchema.parse({
291
+ key: orderId,
292
+ version: 0
293
+ });
294
+ }
295
+ if (remote.shippingAddress) {
296
+ result.shippingAddress = this.parseAddress(remote.shippingAddress, reqCtx);
297
+ }
298
+ if (remote.billingAddress) {
299
+ result.billingAddress = this.parseAddress(remote.billingAddress, reqCtx);
300
+ }
301
+ result.shippingInstruction = this.parseShippingInstruction(remote);
302
+ for (const p of remote.paymentInfo?.payments || []) {
303
+ if (p.obj) {
304
+ result.paymentInstructions.push(this.parsePaymentInstruction(p.obj, reqCtx));
305
+ }
306
+ }
307
+ const grandTotal = remote.totalPrice.centAmount || 0;
308
+ const shippingTotal = remote.shippingInfo?.price.centAmount || 0;
309
+ const productTotal = grandTotal - shippingTotal;
310
+ const taxTotal = remote.taxedPrice?.totalTax?.centAmount || 0;
311
+ const discountTotal = remote.discountOnTotalPrice?.discountedAmount.centAmount || 0;
312
+ const surchargeTotal = 0;
313
+ const currency = remote.totalPrice.currencyCode;
314
+ result.price = {
315
+ totalTax: {
316
+ value: taxTotal / 100,
317
+ currency
318
+ },
319
+ totalDiscount: {
320
+ value: discountTotal / 100,
321
+ currency
322
+ },
323
+ totalSurcharge: {
324
+ value: surchargeTotal / 100,
325
+ currency
326
+ },
327
+ totalShipping: {
328
+ value: shippingTotal / 100,
329
+ currency: remote.shippingInfo?.price.currencyCode
330
+ },
331
+ totalProductPrice: {
332
+ value: productTotal / 100,
333
+ currency
334
+ },
335
+ grandTotal: {
336
+ value: grandTotal / 100,
337
+ currency
338
+ }
339
+ };
340
+ for (const remoteItem of remote.lineItems) {
341
+ const item = CheckoutItemSchema.parse({});
342
+ item.identifier.key = remoteItem.id;
343
+ item.sku.key = remoteItem.variant.sku || "";
344
+ item.quantity = remoteItem.quantity;
345
+ const unitPrice = remoteItem.price.value.centAmount;
346
+ const totalPrice = remoteItem.totalPrice.centAmount || 0;
347
+ const totalDiscount = remoteItem.price.discounted?.value.centAmount || 0;
348
+ const unitDiscount = totalDiscount / remoteItem.quantity;
349
+ item.price = {
350
+ unitPrice: {
351
+ value: unitPrice / 100,
352
+ currency
353
+ },
354
+ unitDiscount: {
355
+ value: unitDiscount / 100,
356
+ currency
357
+ },
358
+ totalPrice: {
359
+ value: (totalPrice || 0) / 100,
360
+ currency
361
+ },
362
+ totalDiscount: {
363
+ value: totalDiscount / 100,
364
+ currency
365
+ }
366
+ };
367
+ result.items.push(item);
368
+ }
369
+ result.readyForFinalization = this.isReadyForFinalization(result);
370
+ result.meta = {
371
+ cache: {
372
+ hit: false,
373
+ key: this.generateCacheKeySingle(result.identifier, reqCtx)
374
+ },
375
+ placeholder: false
376
+ };
377
+ return this.assert(result);
378
+ }
379
+ isReadyForFinalization(checkout) {
380
+ if (!checkout.billingAddress)
381
+ return false;
382
+ if (!checkout.shippingInstruction)
383
+ return false;
384
+ if (!checkout.shippingAddress && !checkout.shippingInstruction.pickupPoint)
385
+ return false;
386
+ if (checkout.paymentInstructions.length === 0)
387
+ return false;
388
+ const authorizedPayments = checkout.paymentInstructions.filter((pi) => pi.status === "authorized").map((x) => x.amount.value).reduce((a, b) => a + b, 0);
389
+ if (checkout.price.grandTotal.value !== authorizedPayments)
390
+ return false;
391
+ return true;
392
+ }
393
+ parsePaymentInstruction(remote, reqCtx) {
394
+ const newModel = PaymentInstructionSchema.parse({});
395
+ newModel.identifier = PaymentInstructionIdentifierSchema.parse({ key: remote.id || "" });
396
+ newModel.amount = {
397
+ value: remote.amountPlanned.centAmount / 100,
398
+ currency: remote.amountPlanned.currencyCode
399
+ };
400
+ const method = remote.paymentMethodInfo?.method || "unknown";
401
+ const paymentProcessor = remote.paymentMethodInfo?.paymentInterface || method;
402
+ const paymentName = remote.paymentMethodInfo.name[reqCtx.languageContext.locale];
403
+ newModel.paymentMethod = PaymentMethodIdentifierSchema.parse({
404
+ method,
405
+ paymentProcessor,
406
+ name: paymentName || method || "Unknown"
407
+ });
408
+ const customData = remote.custom?.fields || {};
409
+ newModel.protocolData = Object.keys(customData).map((x) => ({ key: x, value: customData[x] })) || [];
410
+ if (remote.transactions && remote.transactions.length > 0) {
411
+ const lastTransaction = remote.transactions[remote.transactions.length - 1];
412
+ if (lastTransaction.type === "Authorization" && lastTransaction.state === "Pending") {
413
+ newModel.status = "pending";
414
+ } else if (lastTransaction.type === "Authorization" && lastTransaction.state === "Success") {
415
+ newModel.status = "authorized";
416
+ }
417
+ } else {
418
+ newModel.status = "pending";
419
+ }
420
+ return PaymentInstructionSchema.parse(newModel);
421
+ }
422
+ parseAddress(remote, reqCtx) {
423
+ return AddressSchema.parse({
424
+ countryCode: remote.country || "",
425
+ firstName: remote.firstName || "",
426
+ lastName: remote.lastName || "",
427
+ streetAddress: remote.streetName || "",
428
+ streetNumber: remote.streetNumber || "",
429
+ postalCode: remote.postalCode || "",
430
+ city: remote.city || ""
431
+ });
432
+ }
433
+ parseShippingInstruction(remote) {
434
+ if (!remote.shippingInfo)
435
+ return void 0;
436
+ const instructions = remote.custom?.fields["shippingInstruction"] || "";
437
+ const consentForUnattendedDelivery = remote.custom?.fields["consentForUnattendedDelivery"] === "true" || false;
438
+ const pickupPoint = remote.custom?.fields["pickupPointId"] || "";
439
+ const shippingInstruction = ShippingInstructionSchema.parse({
440
+ amount: {
441
+ value: (remote.shippingInfo.price.centAmount || 0) / 100,
442
+ currency: remote.shippingInfo.price.currencyCode
443
+ },
444
+ shippingMethod: {
445
+ key: remote.shippingInfo.shippingMethod?.obj?.key || ""
446
+ },
447
+ pickupPoint: pickupPoint || "",
448
+ instructions: instructions || "",
449
+ consentForUnattendedDelivery: consentForUnattendedDelivery || false
450
+ });
451
+ return shippingInstruction;
452
+ }
453
+ }
454
+ export {
455
+ CheckoutNotReadyForFinalizationError,
456
+ CommercetoolsCheckoutProvider
457
+ };