@reactionary/source 0.0.52 → 0.2.16

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.
Files changed (293) hide show
  1. package/.env-template +19 -0
  2. package/.github/workflows/pull-request.yml +3 -1
  3. package/.github/workflows/release.yml +9 -0
  4. package/.vscode/extensions.json +0 -2
  5. package/LICENSE +21 -0
  6. package/README.md +175 -23
  7. package/core/package.json +6 -3
  8. package/core/src/cache/cache.interface.ts +1 -0
  9. package/core/src/cache/index.ts +4 -0
  10. package/core/src/cache/memory-cache.ts +30 -2
  11. package/core/src/cache/noop-cache.ts +15 -1
  12. package/core/src/cache/redis-cache.ts +20 -0
  13. package/core/src/client/client-builder.ts +71 -54
  14. package/core/src/client/client.ts +9 -47
  15. package/core/src/client/index.ts +2 -0
  16. package/core/src/decorators/index.ts +1 -0
  17. package/core/src/decorators/reactionary.decorator.ts +203 -34
  18. package/core/src/index.ts +6 -19
  19. package/core/src/initialization.ts +1 -18
  20. package/core/src/metrics/metrics.ts +67 -0
  21. package/core/src/providers/analytics.provider.ts +1 -6
  22. package/core/src/providers/base.provider.ts +5 -69
  23. package/core/src/providers/cart.provider.ts +15 -55
  24. package/core/src/providers/category.provider.ts +7 -11
  25. package/core/src/providers/checkout.provider.ts +17 -15
  26. package/core/src/providers/identity.provider.ts +6 -8
  27. package/core/src/providers/index.ts +2 -1
  28. package/core/src/providers/inventory.provider.ts +15 -5
  29. package/core/src/providers/order-search.provider.ts +29 -0
  30. package/core/src/providers/order.provider.ts +47 -15
  31. package/core/src/providers/price.provider.ts +30 -36
  32. package/core/src/providers/product-search.provider.ts +61 -0
  33. package/core/src/providers/product.provider.ts +71 -12
  34. package/core/src/providers/profile.provider.ts +74 -14
  35. package/core/src/providers/store.provider.ts +3 -5
  36. package/core/src/schemas/capabilities.schema.ts +10 -3
  37. package/core/src/schemas/errors/generic.error.ts +9 -0
  38. package/core/src/schemas/errors/index.ts +4 -0
  39. package/core/src/schemas/errors/invalid-input.error.ts +9 -0
  40. package/core/src/schemas/errors/invalid-output.error.ts +9 -0
  41. package/core/src/schemas/errors/not-found.error.ts +9 -0
  42. package/core/src/schemas/index.ts +7 -0
  43. package/core/src/schemas/models/analytics.model.ts +2 -1
  44. package/core/src/schemas/models/base.model.ts +6 -24
  45. package/core/src/schemas/models/cart.model.ts +5 -8
  46. package/core/src/schemas/models/category.model.ts +4 -9
  47. package/core/src/schemas/models/checkout.model.ts +6 -7
  48. package/core/src/schemas/models/cost.model.ts +4 -3
  49. package/core/src/schemas/models/currency.model.ts +2 -1
  50. package/core/src/schemas/models/identifiers.model.ts +106 -62
  51. package/core/src/schemas/models/identity.model.ts +10 -19
  52. package/core/src/schemas/models/index.ts +2 -1
  53. package/core/src/schemas/models/inventory.model.ts +8 -5
  54. package/core/src/schemas/models/order-search.model.ts +28 -0
  55. package/core/src/schemas/models/order.model.ts +20 -26
  56. package/core/src/schemas/models/payment.model.ts +14 -17
  57. package/core/src/schemas/models/price.model.ts +11 -11
  58. package/core/src/schemas/models/product-search.model.ts +42 -0
  59. package/core/src/schemas/models/product.model.ts +64 -22
  60. package/core/src/schemas/models/profile.model.ts +19 -22
  61. package/core/src/schemas/models/shipping-method.model.ts +24 -29
  62. package/core/src/schemas/models/store.model.ts +9 -5
  63. package/core/src/schemas/mutations/analytics.mutation.ts +8 -7
  64. package/core/src/schemas/mutations/base.mutation.ts +2 -1
  65. package/core/src/schemas/mutations/cart.mutation.ts +33 -33
  66. package/core/src/schemas/mutations/checkout.mutation.ts +23 -30
  67. package/core/src/schemas/mutations/identity.mutation.ts +4 -3
  68. package/core/src/schemas/mutations/profile.mutation.ts +38 -3
  69. package/core/src/schemas/queries/base.query.ts +2 -1
  70. package/core/src/schemas/queries/cart.query.ts +3 -3
  71. package/core/src/schemas/queries/category.query.ts +18 -18
  72. package/core/src/schemas/queries/checkout.query.ts +7 -9
  73. package/core/src/schemas/queries/identity.query.ts +2 -1
  74. package/core/src/schemas/queries/index.ts +2 -1
  75. package/core/src/schemas/queries/inventory.query.ts +5 -5
  76. package/core/src/schemas/queries/order-search.query.ts +10 -0
  77. package/core/src/schemas/queries/order.query.ts +3 -2
  78. package/core/src/schemas/queries/price.query.ts +10 -4
  79. package/core/src/schemas/queries/product-search.query.ts +16 -0
  80. package/core/src/schemas/queries/product.query.ts +13 -6
  81. package/core/src/schemas/queries/profile.query.ts +5 -2
  82. package/core/src/schemas/queries/store.query.ts +6 -5
  83. package/core/src/schemas/result.ts +107 -0
  84. package/core/src/schemas/session.schema.ts +4 -4
  85. package/core/src/test/reactionary.decorator.spec.ts +249 -0
  86. package/core/src/zod-utils.ts +19 -0
  87. package/core/tsconfig.json +1 -1
  88. package/core/tsconfig.spec.json +2 -26
  89. package/core/vitest.config.ts +14 -0
  90. package/documentation/1-purpose.md +114 -0
  91. package/documentation/2-getting-started.md +229 -0
  92. package/documentation/3-querying-and-changing-data.md +74 -0
  93. package/documentation/4-product-data.md +107 -0
  94. package/documentation/5-cart-and-checkout.md +211 -0
  95. package/documentation/6-product-search.md +143 -0
  96. package/documentation/7-marketing.md +3 -0
  97. package/eslint.config.mjs +1 -0
  98. package/examples/node/eslint.config.mjs +1 -4
  99. package/examples/node/package.json +10 -3
  100. package/examples/node/project.json +4 -1
  101. package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +22 -23
  102. package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +15 -11
  103. package/examples/node/src/basic/basic-node-setup.spec.ts +44 -28
  104. package/examples/node/src/basic/client-creation.spec.ts +53 -0
  105. package/examples/node/src/capabilities/cart.spec.ts +255 -0
  106. package/examples/node/src/capabilities/category.spec.ts +193 -0
  107. package/examples/node/src/capabilities/checkout.spec.ts +341 -0
  108. package/examples/node/src/capabilities/identity.spec.ts +93 -0
  109. package/examples/node/src/capabilities/inventory.spec.ts +66 -0
  110. package/examples/node/src/capabilities/order-search.spec.ts +265 -0
  111. package/examples/node/src/capabilities/order.spec.ts +91 -0
  112. package/examples/node/src/capabilities/price.spec.ts +51 -0
  113. package/examples/node/src/capabilities/product-search.spec.ts +293 -0
  114. package/examples/node/src/capabilities/product.spec.ts +122 -0
  115. package/examples/node/src/capabilities/profile.spec.ts +316 -0
  116. package/examples/node/src/capabilities/store.spec.ts +26 -0
  117. package/examples/node/src/utils.ts +147 -0
  118. package/examples/node/tsconfig.json +9 -12
  119. package/examples/node/tsconfig.lib.json +1 -2
  120. package/examples/node/tsconfig.spec.json +2 -14
  121. package/examples/node/vitest.config.ts +14 -0
  122. package/migrations.json +22 -5
  123. package/nx.json +8 -47
  124. package/package.json +24 -96
  125. package/providers/algolia/README.md +39 -2
  126. package/providers/algolia/package.json +2 -1
  127. package/providers/algolia/src/core/initialize.ts +7 -14
  128. package/providers/algolia/src/index.ts +2 -4
  129. package/providers/algolia/src/providers/index.ts +1 -0
  130. package/providers/algolia/src/providers/product-search.provider.ts +241 -0
  131. package/providers/algolia/src/schema/capabilities.schema.ts +2 -3
  132. package/providers/algolia/src/schema/index.ts +3 -0
  133. package/providers/algolia/src/schema/search.schema.ts +8 -8
  134. package/providers/algolia/tsconfig.json +1 -1
  135. package/providers/algolia/tsconfig.lib.json +1 -1
  136. package/providers/algolia/tsconfig.spec.json +2 -14
  137. package/providers/algolia/vitest.config.ts +14 -0
  138. package/providers/commercetools/README.md +30 -3
  139. package/providers/commercetools/package.json +2 -1
  140. package/providers/commercetools/src/core/client.ts +178 -99
  141. package/providers/commercetools/src/core/initialize.ts +130 -74
  142. package/providers/commercetools/src/core/token-cache.ts +45 -0
  143. package/providers/commercetools/src/index.ts +3 -2
  144. package/providers/commercetools/src/providers/cart.provider.ts +281 -341
  145. package/providers/commercetools/src/providers/category.provider.ts +223 -138
  146. package/providers/commercetools/src/providers/checkout.provider.ts +631 -449
  147. package/providers/commercetools/src/providers/identity.provider.ts +50 -29
  148. package/providers/commercetools/src/providers/index.ts +2 -2
  149. package/providers/commercetools/src/providers/inventory.provider.ts +76 -74
  150. package/providers/commercetools/src/providers/order-search.provider.ts +220 -0
  151. package/providers/commercetools/src/providers/order.provider.ts +96 -61
  152. package/providers/commercetools/src/providers/price.provider.ts +147 -117
  153. package/providers/commercetools/src/providers/product-search.provider.ts +528 -0
  154. package/providers/commercetools/src/providers/product.provider.ts +249 -74
  155. package/providers/commercetools/src/providers/profile.provider.ts +445 -28
  156. package/providers/commercetools/src/providers/store.provider.ts +54 -40
  157. package/providers/commercetools/src/schema/capabilities.schema.ts +3 -1
  158. package/providers/commercetools/src/schema/commercetools.schema.ts +17 -3
  159. package/providers/commercetools/src/schema/configuration.schema.ts +1 -0
  160. package/providers/commercetools/src/schema/session.schema.ts +7 -0
  161. package/providers/commercetools/src/test/caching.spec.ts +82 -0
  162. package/providers/commercetools/src/test/identity.spec.ts +109 -0
  163. package/providers/commercetools/src/test/test-utils.ts +21 -19
  164. package/providers/commercetools/tsconfig.json +1 -1
  165. package/providers/commercetools/tsconfig.lib.json +1 -1
  166. package/providers/commercetools/tsconfig.spec.json +2 -14
  167. package/providers/commercetools/vitest.config.ts +15 -0
  168. package/providers/fake/README.md +20 -4
  169. package/providers/fake/package.json +2 -1
  170. package/providers/fake/src/core/initialize.ts +47 -49
  171. package/providers/fake/src/providers/analytics.provider.ts +5 -7
  172. package/providers/fake/src/providers/cart.provider.ts +163 -92
  173. package/providers/fake/src/providers/category.provider.ts +78 -50
  174. package/providers/fake/src/providers/checkout.provider.ts +254 -0
  175. package/providers/fake/src/providers/identity.provider.ts +57 -65
  176. package/providers/fake/src/providers/index.ts +6 -2
  177. package/providers/fake/src/providers/inventory.provider.ts +40 -36
  178. package/providers/fake/src/providers/order-search.provider.ts +78 -0
  179. package/providers/fake/src/providers/order.provider.ts +106 -0
  180. package/providers/fake/src/providers/price.provider.ts +93 -41
  181. package/providers/fake/src/providers/product-search.provider.ts +206 -0
  182. package/providers/fake/src/providers/product.provider.ts +56 -41
  183. package/providers/fake/src/providers/profile.provider.ts +147 -0
  184. package/providers/fake/src/providers/store.provider.ts +30 -20
  185. package/providers/fake/src/schema/capabilities.schema.ts +5 -1
  186. package/providers/fake/src/test/cart.provider.spec.ts +59 -80
  187. package/providers/fake/src/test/category.provider.spec.ts +145 -87
  188. package/providers/fake/src/test/checkout.provider.spec.ts +222 -0
  189. package/providers/fake/src/test/order-search.provider.spec.ts +50 -0
  190. package/providers/fake/src/test/order.provider.spec.ts +44 -0
  191. package/providers/fake/src/test/price.provider.spec.ts +50 -45
  192. package/providers/fake/src/test/product.provider.spec.ts +15 -7
  193. package/providers/fake/src/test/profile.provider.spec.ts +167 -0
  194. package/providers/fake/tsconfig.json +1 -1
  195. package/providers/fake/tsconfig.lib.json +1 -1
  196. package/providers/fake/tsconfig.spec.json +2 -12
  197. package/providers/fake/vitest.config.ts +14 -0
  198. package/providers/medusa/README.md +30 -0
  199. package/providers/medusa/TESTING.md +98 -0
  200. package/providers/medusa/eslint.config.mjs +19 -0
  201. package/providers/medusa/package.json +22 -0
  202. package/providers/medusa/project.json +34 -0
  203. package/providers/medusa/src/core/client.ts +370 -0
  204. package/providers/medusa/src/core/initialize.ts +78 -0
  205. package/providers/medusa/src/index.ts +13 -0
  206. package/providers/medusa/src/providers/cart.provider.ts +575 -0
  207. package/providers/medusa/src/providers/category.provider.ts +247 -0
  208. package/providers/medusa/src/providers/checkout.provider.ts +636 -0
  209. package/providers/medusa/src/providers/identity.provider.ts +137 -0
  210. package/providers/medusa/src/providers/inventory.provider.ts +173 -0
  211. package/providers/medusa/src/providers/order-search.provider.ts +202 -0
  212. package/providers/medusa/src/providers/order.provider.ts +226 -0
  213. package/providers/medusa/src/providers/price.provider.ts +140 -0
  214. package/providers/medusa/src/providers/product-search.provider.ts +243 -0
  215. package/providers/medusa/src/providers/product.provider.ts +261 -0
  216. package/providers/medusa/src/providers/profile.provider.ts +392 -0
  217. package/providers/medusa/src/schema/capabilities.schema.ts +18 -0
  218. package/providers/medusa/src/schema/configuration.schema.ts +11 -0
  219. package/providers/medusa/src/schema/medusa.schema.ts +31 -0
  220. package/providers/medusa/src/test/cart.provider.spec.ts +240 -0
  221. package/providers/medusa/src/test/category.provider.spec.ts +231 -0
  222. package/providers/medusa/src/test/checkout.spec.ts +349 -0
  223. package/providers/medusa/src/test/identity.provider.spec.ts +122 -0
  224. package/providers/medusa/src/test/inventory.provider.spec.ts +88 -0
  225. package/providers/medusa/src/test/large-cart.provider.spec.ts +103 -0
  226. package/providers/medusa/src/test/price.provider.spec.ts +104 -0
  227. package/providers/medusa/src/test/product.provider.spec.ts +146 -0
  228. package/providers/medusa/src/test/search.provider.spec.ts +203 -0
  229. package/providers/medusa/src/test/test-utils.ts +13 -0
  230. package/providers/medusa/src/utils/medusa-helpers.ts +89 -0
  231. package/providers/medusa/tsconfig.json +21 -0
  232. package/providers/medusa/tsconfig.lib.json +9 -0
  233. package/providers/medusa/tsconfig.spec.json +4 -0
  234. package/providers/medusa/vitest.config.ts +15 -0
  235. package/providers/meilisearch/README.md +48 -0
  236. package/providers/meilisearch/eslint.config.mjs +22 -0
  237. package/providers/meilisearch/package.json +13 -0
  238. package/providers/meilisearch/project.json +34 -0
  239. package/providers/meilisearch/src/core/initialize.ts +21 -0
  240. package/providers/meilisearch/src/index.ts +6 -0
  241. package/providers/meilisearch/src/providers/index.ts +1 -0
  242. package/providers/meilisearch/src/providers/order-search.provider.ts +222 -0
  243. package/providers/meilisearch/src/providers/product-search.provider.ts +251 -0
  244. package/providers/meilisearch/src/schema/capabilities.schema.ts +10 -0
  245. package/providers/meilisearch/src/schema/configuration.schema.ts +11 -0
  246. package/providers/meilisearch/src/schema/index.ts +3 -0
  247. package/providers/meilisearch/src/schema/search.schema.ts +14 -0
  248. package/providers/meilisearch/tsconfig.json +24 -0
  249. package/providers/meilisearch/tsconfig.lib.json +10 -0
  250. package/providers/meilisearch/tsconfig.spec.json +4 -0
  251. package/providers/meilisearch/vitest.config.ts +14 -0
  252. package/providers/posthog/package.json +2 -1
  253. package/providers/posthog/tsconfig.json +1 -1
  254. package/tsconfig.base.json +5 -0
  255. package/vitest.config.ts +10 -0
  256. package/core/src/providers/search.provider.ts +0 -18
  257. package/core/src/schemas/models/search.model.ts +0 -36
  258. package/core/src/schemas/queries/search.query.ts +0 -9
  259. package/examples/next/.swcrc +0 -30
  260. package/examples/next/eslint.config.mjs +0 -21
  261. package/examples/next/index.d.ts +0 -6
  262. package/examples/next/next-env.d.ts +0 -5
  263. package/examples/next/next.config.js +0 -31
  264. package/examples/next/project.json +0 -9
  265. package/examples/next/public/.gitkeep +0 -0
  266. package/examples/next/public/favicon.ico +0 -0
  267. package/examples/next/src/app/global.css +0 -0
  268. package/examples/next/src/app/layout.tsx +0 -18
  269. package/examples/next/src/app/page.module.scss +0 -2
  270. package/examples/next/src/app/page.tsx +0 -47
  271. package/examples/next/src/instrumentation.ts +0 -9
  272. package/examples/next/tsconfig.json +0 -44
  273. package/examples/node/jest.config.ts +0 -10
  274. package/jest.config.ts +0 -6
  275. package/jest.preset.js +0 -3
  276. package/providers/algolia/jest.config.ts +0 -10
  277. package/providers/algolia/src/providers/product.provider.ts +0 -66
  278. package/providers/algolia/src/providers/search.provider.ts +0 -106
  279. package/providers/algolia/src/test/search.provider.spec.ts +0 -91
  280. package/providers/commercetools/jest.config.cjs +0 -10
  281. package/providers/commercetools/src/providers/search.provider.ts +0 -96
  282. package/providers/commercetools/src/test/cart.provider.spec.ts +0 -199
  283. package/providers/commercetools/src/test/category.provider.spec.ts +0 -168
  284. package/providers/commercetools/src/test/checkout.provider.spec.ts +0 -312
  285. package/providers/commercetools/src/test/identity.provider.spec.ts +0 -88
  286. package/providers/commercetools/src/test/inventory.provider.spec.ts +0 -41
  287. package/providers/commercetools/src/test/price.provider.spec.ts +0 -81
  288. package/providers/commercetools/src/test/product.provider.spec.ts +0 -80
  289. package/providers/commercetools/src/test/profile.provider.spec.ts +0 -49
  290. package/providers/commercetools/src/test/search.provider.spec.ts +0 -61
  291. package/providers/commercetools/src/test/store.provider.spec.ts +0 -37
  292. package/providers/fake/jest.config.cjs +0 -10
  293. package/providers/fake/src/providers/search.provider.ts +0 -132
@@ -1,262 +1,401 @@
1
-
1
+ import type { Address as CTAddress, Cart as CTCart, Payment as CTPayment, ShippingMethod as CTShippingMethod, LineItem, MyCartUpdateAction } from '@commercetools/platform-sdk';
2
2
  import type {
3
+ Address,
3
4
  Cache,
4
5
  Checkout,
5
- RequestContext,
6
- PaymentMethod,
7
- ShippingMethod,
8
- CheckoutMutationInitiateCheckout,
9
- CheckoutMutationSetShippingAddress,
10
- CheckoutMutationFinalizeCheckout,
6
+ CheckoutIdentifier,
7
+ CheckoutItem,
11
8
  CheckoutMutationAddPaymentInstruction,
9
+ CheckoutMutationFinalizeCheckout,
10
+ CheckoutMutationInitiateCheckout,
12
11
  CheckoutMutationRemovePaymentInstruction,
12
+ CheckoutMutationSetShippingAddress,
13
13
  CheckoutMutationSetShippingInstruction,
14
14
  CheckoutQueryById,
15
15
  CheckoutQueryForAvailablePaymentMethods,
16
16
  CheckoutQueryForAvailableShippingMethods,
17
- CheckoutIdentifier,
17
+ CostBreakDown,
18
18
  Currency,
19
+ MonetaryAmount,
20
+ NotFoundError,
21
+ PaymentInstruction,
22
+ PaymentInstructionIdentifier,
23
+ PaymentMethod,
24
+ PaymentMethodIdentifier,
25
+ PaymentStatus,
26
+ RequestContext,
27
+ Result,
19
28
  ShippingInstruction,
20
- PaymentInstruction
21
- } from "@reactionary/core";
22
- import { AddressSchema, CheckoutItemSchema, CheckoutProvider, PaymentInstructionIdentifierSchema, PaymentInstructionSchema, PaymentMethodIdentifierSchema, ShippingInstructionSchema, ShippingMethodSchema } from "@reactionary/core";
23
- import type z from "zod";
24
- import { CommercetoolsClient } from "../core/client.js";
25
- import type { CommercetoolsConfiguration } from "../schema/configuration.schema.js";
26
- import type { MyCartUpdateAction } from "@commercetools/platform-sdk";
27
- import { CommercetoolsCartIdentifierSchema, CommercetoolsCheckoutIdentifierSchema, CommercetoolsOrderIdentifierSchema, type CommercetoolsCheckoutIdentifier } from "../schema/commercetools.schema.js";
28
- import type { Address as CTAddress, Payment as CTPayment, Cart as CTCart, ShippingMethod as CTShippingMethod } from "@commercetools/platform-sdk";
29
-
29
+ ShippingMethod,
30
+ ShippingMethodIdentifier,
31
+ } from '@reactionary/core';
32
+ import {
33
+ CheckoutItemSchema,
34
+ CheckoutMutationAddPaymentInstructionSchema,
35
+ CheckoutMutationFinalizeCheckoutSchema,
36
+ CheckoutMutationInitiateCheckoutSchema,
37
+ CheckoutMutationRemovePaymentInstructionSchema,
38
+ CheckoutMutationSetShippingAddressSchema,
39
+ CheckoutMutationSetShippingInstructionSchema,
40
+ CheckoutProvider,
41
+ CheckoutQueryByIdSchema,
42
+ CheckoutQueryForAvailablePaymentMethodsSchema,
43
+ CheckoutQueryForAvailableShippingMethodsSchema,
44
+ CheckoutSchema,
45
+ PaymentMethodSchema,
46
+ Reactionary,
47
+ ShippingMethodSchema,
48
+ success,
49
+ error,
50
+ unwrapValue
51
+ } from '@reactionary/core';
52
+ import z from 'zod';
53
+ import type { CommercetoolsAPI } from '../core/client.js';
54
+ import {
55
+ type CommercetoolsCartIdentifier,
56
+ type CommercetoolsCheckoutIdentifier,
57
+ } from '../schema/commercetools.schema.js';
58
+ import type { CommercetoolsConfiguration } from '../schema/configuration.schema.js';
30
59
 
31
60
  export class CheckoutNotReadyForFinalizationError extends Error {
32
61
  constructor(public checkoutIdentifier: CheckoutIdentifier) {
33
- super("Checkout is not ready for finalization. Ensure all required fields are set and valid. " + (checkoutIdentifier ? `Checkout ID: ${JSON.stringify(checkoutIdentifier)}` : ''));
34
- this.name = "CheckoutNotReadyForFinalizationError";
62
+ super(
63
+ 'Checkout is not ready for finalization. Ensure all required fields are set and valid. ' +
64
+ (checkoutIdentifier
65
+ ? `Checkout ID: ${JSON.stringify(checkoutIdentifier)}`
66
+ : '')
67
+ );
68
+ this.name = 'CheckoutNotReadyForFinalizationError';
35
69
  }
36
70
  }
37
71
 
38
-
39
- export class CommercetoolsCheckoutProvider<
40
- T extends Checkout = Checkout
41
- > extends CheckoutProvider<T> {
72
+ export class CommercetoolsCheckoutProvider extends CheckoutProvider {
42
73
  protected config: CommercetoolsConfiguration;
74
+ protected commercetools: CommercetoolsAPI;
43
75
 
44
- constructor(config: CommercetoolsConfiguration, schema: z.ZodType<T>, cache: Cache) {
45
- super(schema, cache);
76
+ constructor(
77
+ config: CommercetoolsConfiguration,
78
+ cache: Cache,
79
+ context: RequestContext,
80
+ commercetools: CommercetoolsAPI
81
+ ) {
82
+ super(cache, context);
46
83
 
47
84
  this.config = config;
85
+ this.commercetools = commercetools;
48
86
  }
49
87
 
50
- protected async getClient(reqCtx: RequestContext) {
51
- const client = await new CommercetoolsClient(this.config).getClient(reqCtx);
88
+ protected async getClient() {
89
+ const client = await this.commercetools.getClient();
52
90
 
53
91
  return {
54
- payments: client.withProjectKey({ projectKey: this.config.projectKey }).me().payments(),
55
- carts: client.withProjectKey({ projectKey: this.config.projectKey }).me().carts(),
56
- shippingMethods: client.withProjectKey({ projectKey: this.config.projectKey }).shippingMethods(),
57
- orders: client.withProjectKey({ projectKey: this.config.projectKey }).me().orders()
92
+ payments: client
93
+ .withProjectKey({ projectKey: this.config.projectKey })
94
+ .me()
95
+ .payments(),
96
+ carts: client
97
+ .withProjectKey({ projectKey: this.config.projectKey })
98
+ .me()
99
+ .carts(),
100
+ shippingMethods: client
101
+ .withProjectKey({ projectKey: this.config.projectKey })
102
+ .shippingMethods(),
103
+ orders: client
104
+ .withProjectKey({ projectKey: this.config.projectKey })
105
+ .me()
106
+ .orders(),
58
107
  };
59
108
  }
60
109
 
61
- public async initiateCheckoutForCart(payload: CheckoutMutationInitiateCheckout, reqCtx: RequestContext): Promise<T> {
110
+ @Reactionary({
111
+ inputSchema: CheckoutMutationInitiateCheckoutSchema,
112
+ outputSchema: CheckoutSchema,
113
+ })
114
+ public async initiateCheckoutForCart(
115
+ payload: CheckoutMutationInitiateCheckout
116
+ ): Promise<Result<Checkout>> {
62
117
  // so......we could copy the cart......
63
118
 
64
- const client = await this.getClient(reqCtx);
65
-
66
- const cart = await client.carts.withId({ ID: (payload.cart as any).key }).get().execute();
67
- const replicationResponse = await client.carts.replicate().post({
68
- body: {
69
- reference: {
70
- typeId: 'cart',
71
- id: cart.body.id
72
- }
73
- }
74
- }).execute();
119
+ const client = await this.getClient();
120
+
121
+ const cart = await client.carts
122
+ .withId({ ID: payload.cart.identifier.key })
123
+ .get()
124
+ .execute();
125
+ const replicationResponse = await client.carts
126
+ .replicate()
127
+ .post({
128
+ body: {
129
+ reference: {
130
+ typeId: 'cart',
131
+ id: cart.body.id,
132
+ },
133
+ },
134
+ })
135
+ .execute();
75
136
  // set the custom type to mark it as a checkout
76
137
 
77
138
  const actions: MyCartUpdateAction[] = [
78
- {
79
- action: 'setCustomType',
80
- type: {
81
- typeId: 'type',
82
- key: 'reactionaryCheckout'
83
- },
84
- fields: {
85
- commerceToolsCartId: payload.cart.key,
86
- }
139
+ {
140
+ action: 'setCustomType',
141
+ type: {
142
+ typeId: 'type',
143
+ key: 'reactionaryCheckout',
144
+ },
145
+ fields: {
146
+ commerceToolsCartId: payload.cart.identifier.key,
87
147
  },
148
+ },
88
149
  ];
89
150
 
90
151
  if (payload.billingAddress) {
91
- actions.push({
92
- action: 'setBillingAddress',
93
- address: {
94
- country: payload.billingAddress.countryCode,
95
- firstName: payload.billingAddress.firstName || '',
96
- lastName: payload.billingAddress.lastName || '',
97
- streetName: payload.billingAddress.streetAddress || '',
98
- streetNumber: payload.billingAddress.streetNumber || '',
99
- postalCode: payload.billingAddress.postalCode || '',
100
- city: payload.billingAddress.city || '',
101
- email: payload.notificationEmail || '',
102
- phone: payload.notificationPhone || ''
103
- }
104
- });
105
- actions.push({
106
- action: 'setShippingAddress',
107
- address: {
108
- country: payload.billingAddress.countryCode,
109
- firstName: payload.billingAddress.firstName || '',
110
- lastName: payload.billingAddress.lastName || '',
111
- streetName: payload.billingAddress.streetAddress || '',
112
- streetNumber: payload.billingAddress.streetNumber || '',
113
- postalCode: payload.billingAddress.postalCode || '',
114
- city: payload.billingAddress.city || '',
115
- }
116
- });
152
+ actions.push({
153
+ action: 'setBillingAddress',
154
+ address: {
155
+ country: payload.billingAddress.countryCode,
156
+ firstName: payload.billingAddress.firstName || '',
157
+ lastName: payload.billingAddress.lastName || '',
158
+ streetName: payload.billingAddress.streetAddress || '',
159
+ streetNumber: payload.billingAddress.streetNumber || '',
160
+ postalCode: payload.billingAddress.postalCode || '',
161
+ city: payload.billingAddress.city || '',
162
+ email: payload.notificationEmail || '',
163
+ phone: payload.notificationPhone || '',
164
+ },
165
+ });
166
+ actions.push({
167
+ action: 'setShippingAddress',
168
+ address: {
169
+ country: payload.billingAddress.countryCode,
170
+ firstName: payload.billingAddress.firstName || '',
171
+ lastName: payload.billingAddress.lastName || '',
172
+ streetName: payload.billingAddress.streetAddress || '',
173
+ streetNumber: payload.billingAddress.streetNumber || '',
174
+ postalCode: payload.billingAddress.postalCode || '',
175
+ city: payload.billingAddress.city || '',
176
+ },
177
+ });
117
178
  }
118
179
 
119
- const checkoutResponse = await client.carts.withId({ ID: replicationResponse.body.id }).post({
120
- body: {
121
- version: replicationResponse.body.version || 0,
122
- actions: [
123
- ...actions
124
- ]
125
- }
126
- }).execute();
127
-
128
- return this.parseSingle(checkoutResponse.body, reqCtx);
180
+ const checkoutResponse = await client.carts
181
+ .withId({ ID: replicationResponse.body.id })
182
+ .post({
183
+ body: {
184
+ version: replicationResponse.body.version || 0,
185
+ actions: [...actions],
186
+ },
187
+ })
188
+ .execute();
129
189
 
190
+ return success(this.parseSingle(checkoutResponse.body));
130
191
  }
131
192
 
132
- public async getById(payload: CheckoutQueryById, reqCtx: RequestContext): Promise<T | null> {
193
+ @Reactionary({
194
+ inputSchema: CheckoutQueryByIdSchema,
195
+ outputSchema: CheckoutSchema.nullable(),
196
+ })
197
+ public async getById(payload: CheckoutQueryById): Promise<Result<Checkout, NotFoundError>> {
198
+ const client = await this.getClient();
199
+ const checkoutResponse = await client.carts
200
+ .withId({ ID: payload.identifier.key })
201
+ .get({
202
+ queryArgs: {
203
+ expand: ['paymentInfo.payments[*]', 'shippingInfo.shippingMethod'],
204
+ },
205
+ })
206
+ .execute();
133
207
 
134
- const client = await this.getClient(reqCtx);
135
- const checkoutResponse = await client.carts.withId({ ID: payload.identifier.key }).get({
136
- queryArgs: {
137
- expand: ['paymentInfo.payments[*]', 'shippingInfo.shippingMethod']
138
- }
139
- }).execute();
208
+ const checkout = this.parseSingle(checkoutResponse.body);
140
209
 
141
- return this.parseSingle(checkoutResponse.body, reqCtx);
142
- }
210
+ if (checkoutResponse.body.cartState === 'Ordered') {
211
+ const order = await client.orders
212
+ .get({
213
+ queryArgs: {
214
+ where: `cart(id="${checkout.identifier.key}")`,
215
+ },
216
+ })
217
+ .execute();
143
218
 
144
- public async setShippingAddress(payload: CheckoutMutationSetShippingAddress, reqCtx: RequestContext): Promise<T> {
145
- const client = await this.getClient(reqCtx);
219
+ checkout.resultingOrder = {
220
+ key: order.body.results[0].id,
221
+ };
222
+ }
146
223
 
147
- const version = (payload.checkout as CommercetoolsCheckoutIdentifier).version;
148
- const checkoutResponse = await client.carts.withId({ ID: payload.checkout.key }).post({
149
- body: {
150
- version: version,
151
- actions: [
152
- {
153
- action: 'setShippingAddress',
154
- address: {
155
- country: payload.shippingAddress.countryCode,
156
- firstName: payload.shippingAddress.firstName || '',
157
- lastName: payload.shippingAddress.lastName || '',
158
- streetName: payload.shippingAddress.streetAddress || '',
159
- streetNumber: payload.shippingAddress.streetNumber || '',
160
- postalCode: payload.shippingAddress.postalCode || '',
161
- city: payload.shippingAddress.city || '',
162
- }
163
- }
164
- ]
165
- }
166
- }).execute();
224
+ return success(checkout);
225
+ }
226
+
227
+ @Reactionary({
228
+ inputSchema: CheckoutMutationSetShippingAddressSchema,
229
+ outputSchema: CheckoutSchema,
230
+ })
231
+ public async setShippingAddress(
232
+ payload: CheckoutMutationSetShippingAddress
233
+ ): Promise<Result<Checkout>> {
234
+ const client = await this.getClient();
235
+
236
+ const version = (payload.checkout as CommercetoolsCheckoutIdentifier)
237
+ .version;
238
+ const checkoutResponse = await client.carts
239
+ .withId({ ID: payload.checkout.key })
240
+ .post({
241
+ body: {
242
+ version: version,
243
+ actions: [
244
+ {
245
+ action: 'setShippingAddress',
246
+ address: {
247
+ country: payload.shippingAddress.countryCode,
248
+ firstName: payload.shippingAddress.firstName || '',
249
+ lastName: payload.shippingAddress.lastName || '',
250
+ streetName: payload.shippingAddress.streetAddress || '',
251
+ streetNumber: payload.shippingAddress.streetNumber || '',
252
+ postalCode: payload.shippingAddress.postalCode || '',
253
+ city: payload.shippingAddress.city || '',
254
+ },
255
+ },
256
+ ],
257
+ },
258
+ })
259
+ .execute();
167
260
 
168
- return this.parseSingle(checkoutResponse.body, reqCtx);
261
+ return success(this.parseSingle(checkoutResponse.body));
169
262
  }
170
263
 
171
- public async getAvailableShippingMethods(payload: CheckoutQueryForAvailableShippingMethods, reqCtx: RequestContext): Promise<ShippingMethod[]> {
172
- const client = await this.getClient(reqCtx);
173
- const shippingMethodsResponse = await client.shippingMethods.matchingCart().get({
174
- queryArgs: {
175
- cartId: payload.checkout.key
176
- }
177
- }).execute();
264
+ @Reactionary({
265
+ inputSchema: CheckoutQueryForAvailableShippingMethodsSchema,
266
+ outputSchema: z.array(ShippingMethodSchema),
267
+ })
268
+ public async getAvailableShippingMethods(
269
+ payload: CheckoutQueryForAvailableShippingMethods
270
+ ): Promise<Result<ShippingMethod[]>> {
271
+ const client = await this.getClient();
272
+ const shippingMethodsResponse = await client.shippingMethods
273
+ .matchingCart()
274
+ .get({
275
+ queryArgs: {
276
+ cartId: payload.checkout.key,
277
+ },
278
+ })
279
+ .execute();
178
280
 
179
281
  const result: Array<ShippingMethod> = [];
180
- const inputShippingMethods: CTShippingMethod[] = shippingMethodsResponse.body.results as CTShippingMethod[];
282
+ const inputShippingMethods: CTShippingMethod[] = shippingMethodsResponse
283
+ .body.results as CTShippingMethod[];
181
284
  for (const sm of inputShippingMethods) {
182
- const shippingMethod = ShippingMethodSchema.parse({
183
- identifier: {
184
- key: sm.key,
185
- },
186
- name: sm.name,
187
- description: sm.localizedDescription?.[ reqCtx.languageContext.locale ] || '',
188
- price: sm.zoneRates[0].shippingRates[0].price ? {
189
- value: (sm.zoneRates[0].shippingRates[0].price.centAmount || 0) / 100,
190
- currency: sm.zoneRates[0].shippingRates[0].price.currencyCode || reqCtx.languageContext.currencyCode
191
- } : { value: 0, currency: reqCtx.languageContext.currencyCode },
192
- });
285
+ const identifier = {
286
+ key: sm.key!,
287
+ } satisfies ShippingMethodIdentifier;
288
+ const name = sm.name;
289
+ const description = sm.localizedDescription?.[this.context.languageContext.locale] || '';
290
+ const shippingMethod = {
291
+ deliveryTime: '',
292
+ description,
293
+ identifier,
294
+ name,
295
+ price: sm.zoneRates[0].shippingRates[0].price
296
+ ? {
297
+ value:
298
+ (sm.zoneRates[0].shippingRates[0].price.centAmount || 0) / 100,
299
+ currency:
300
+ sm.zoneRates[0].shippingRates[0].price.currencyCode as Currency ||
301
+ this.context.languageContext.currencyCode,
302
+ }
303
+ : { value: 0, currency: this.context.languageContext.currencyCode },
304
+ } satisfies ShippingMethod;
305
+
193
306
  result.push(shippingMethod);
194
307
  }
195
- return result;
308
+ return success(result);
196
309
  }
197
310
 
198
- public async getAvailablePaymentMethods(payload: CheckoutQueryForAvailablePaymentMethods, reqCtx: RequestContext): Promise<PaymentMethod[]> {
311
+ @Reactionary({
312
+ inputSchema: CheckoutQueryForAvailablePaymentMethodsSchema,
313
+ outputSchema: z.array(PaymentMethodSchema),
314
+ })
315
+ public async getAvailablePaymentMethods(
316
+ payload: CheckoutQueryForAvailablePaymentMethods
317
+ ): Promise<Result<PaymentMethod[]>> {
199
318
  // Commercetools does not have a concept of payment methods, as these are handled by the payment providers.
200
319
  // So for now, we will return an empty array.
201
- const staticMethods = this.getStaticPaymentMethods(payload.checkout, reqCtx);
320
+ const staticMethods = this.getStaticPaymentMethods(payload.checkout);
202
321
 
203
322
  const dynamicMethods: PaymentMethod[] = [];
204
323
  // later we will also fetch any stored payment methods the user has...
205
324
 
206
- return [...staticMethods, ...dynamicMethods];
325
+ return success([...staticMethods, ...dynamicMethods]);
207
326
  }
208
327
 
209
- public async addPaymentInstruction(payload: CheckoutMutationAddPaymentInstruction, reqCtx: RequestContext): Promise<T> {
210
- const client = await this.getClient(reqCtx);
211
-
212
- const response = await client.payments.post({
213
- body: {
214
-
215
- amountPlanned: {
216
- centAmount: Math.round(payload.paymentInstruction.amount.value * 100),
217
- currencyCode: payload.paymentInstruction.amount.currency
218
- },
219
- paymentMethodInfo: {
220
- method: payload.paymentInstruction.paymentMethod.method,
221
- name: {
222
- [reqCtx.languageContext.locale]: payload.paymentInstruction.paymentMethod.name
328
+ @Reactionary({
329
+ inputSchema: CheckoutMutationAddPaymentInstructionSchema,
330
+ outputSchema: CheckoutSchema,
331
+ })
332
+ public async addPaymentInstruction(
333
+ payload: CheckoutMutationAddPaymentInstruction
334
+ ): Promise<Result<Checkout>> {
335
+ const client = await this.getClient();
336
+
337
+ const response = await client.payments
338
+ .post({
339
+ body: {
340
+ amountPlanned: {
341
+ centAmount: Math.round(
342
+ payload.paymentInstruction.amount.value * 100
343
+ ),
344
+ currencyCode: payload.paymentInstruction.amount.currency,
223
345
  },
224
- paymentInterface: payload.paymentInstruction.paymentMethod.paymentProcessor
225
- },
226
- custom: {
227
- type: {
228
- typeId: 'type',
229
- key: 'reactionaryPaymentCustomFields',
346
+ paymentMethodInfo: {
347
+ method: payload.paymentInstruction.paymentMethod.method,
348
+ name: {
349
+ [this.context.languageContext.locale]:
350
+ payload.paymentInstruction.paymentMethod.name,
351
+ },
352
+ paymentInterface:
353
+ payload.paymentInstruction.paymentMethod.paymentProcessor,
230
354
  },
231
- fields: {
232
- 'commerceToolsCartId': payload.checkout.key,
233
- }
234
- }
235
- },
236
- }).execute();
355
+ custom: {
356
+ type: {
357
+ typeId: 'type',
358
+ key: 'reactionaryPaymentCustomFields',
359
+ },
360
+ fields: {
361
+ commerceToolsCartId: payload.checkout.key,
362
+ },
363
+ },
364
+ },
365
+ })
366
+ .execute();
237
367
 
238
- const version = (payload.checkout as CommercetoolsCheckoutIdentifier).version;
368
+ const version = (payload.checkout as CommercetoolsCheckoutIdentifier)
369
+ .version;
239
370
  const actions: MyCartUpdateAction[] = [
240
371
  {
241
372
  action: 'addPayment',
242
373
  payment: {
243
374
  typeId: 'payment',
244
- id: response.body.id
245
- }
246
- }
375
+ id: response.body.id,
376
+ },
377
+ },
247
378
  ];
248
379
 
249
- return this.applyActions(payload.checkout as CommercetoolsCheckoutIdentifier, actions, reqCtx);
250
- }
251
-
252
- public async removePaymentInstruction(payload: CheckoutMutationRemovePaymentInstruction, reqCtx: RequestContext): Promise<T> {
253
- const client = await this.getClient(reqCtx);
254
-
380
+ const result = await this.applyActions(
381
+ payload.checkout as CommercetoolsCheckoutIdentifier,
382
+ actions
383
+ );
255
384
 
256
- // FIXME: Need to get full-endpoint rights, if we want to cancel the authorization on the payment. The MyPayment endpoint does not support
257
- // changing a payment intent after it has transactions.
385
+ return success(result);
386
+ }
258
387
 
388
+ @Reactionary({
389
+ inputSchema: CheckoutMutationRemovePaymentInstructionSchema,
390
+ outputSchema: CheckoutSchema,
391
+ })
392
+ public async removePaymentInstruction(
393
+ payload: CheckoutMutationRemovePaymentInstruction
394
+ ): Promise<Result<Checkout>> {
395
+ const client = await this.getClient();
259
396
 
397
+ // FIXME: Need to get full-endpoint rights, if we want to cancel the authorization on the payment. The MyPayment endpoint does not support
398
+ // changing a payment intent after it has transactions.
260
399
 
261
400
  // get newest version
262
401
  /*
@@ -284,8 +423,8 @@ export class CommercetoolsCheckoutProvider<
284
423
  // The cart can be re-used, and a new payment instruction can be added to it later.
285
424
  // The frontend should ignore any payment instructions with status 'canceled' when displaying payment options to the user.
286
425
 
287
- // Now add the payment to the cart
288
- /*
426
+ // Now add the payment to the cart
427
+ /*
289
428
  const ctId = payload.checkout as CommercetoolsCheckoutIdentifier
290
429
  const updatedCart = await client.carts.withId({ ID: ctId.key }).post({
291
430
  body: {
@@ -309,336 +448,379 @@ export class CommercetoolsCheckoutProvider<
309
448
  }).execute();
310
449
  */
311
450
 
312
- const checkout = await this.getById({ identifier: payload.checkout }, reqCtx);
313
- return checkout!
314
-
451
+ const checkout = unwrapValue(await this.getById({ identifier: payload.checkout }));
452
+ return success(checkout);
315
453
  }
316
454
 
317
- public async setShippingInstruction(payload: CheckoutMutationSetShippingInstruction, reqCtx: RequestContext): Promise<T> {
318
-
319
- const client = await this.getClient(reqCtx);
320
- const ctId = payload.checkout as CommercetoolsCheckoutIdentifier;
321
-
455
+ @Reactionary({
456
+ inputSchema: CheckoutMutationSetShippingInstructionSchema,
457
+ outputSchema: CheckoutSchema,
458
+ })
459
+ public async setShippingInstruction(
460
+ payload: CheckoutMutationSetShippingInstruction
461
+ ): Promise<Result<Checkout>> {
322
462
  const actions: MyCartUpdateAction[] = [];
323
- actions.push({
324
- action: 'setShippingMethod',
325
- shippingMethod: {
326
- typeId: 'shipping-method',
327
- key: payload.shippingInstruction.shippingMethod.key
328
- }
329
- });
330
- actions.push({
331
- action: 'setCustomField',
332
- name: 'shippingInstruction',
333
- value: payload.shippingInstruction.instructions
334
- });
335
- actions.push({
336
- action: 'setCustomField',
337
- name: 'consentForUnattendedDelivery',
338
- value: payload.shippingInstruction.consentForUnattendedDelivery + ''
339
- });
340
- actions.push({
341
- action: 'setCustomField',
342
- name: 'pickupPointId',
343
- value: payload.shippingInstruction.pickupPoint
344
- });
345
-
463
+ actions.push({
464
+ action: 'setShippingMethod',
465
+ shippingMethod: {
466
+ typeId: 'shipping-method',
467
+ key: payload.shippingInstruction.shippingMethod.key,
468
+ },
469
+ });
470
+ actions.push({
471
+ action: 'setCustomField',
472
+ name: 'shippingInstruction',
473
+ value: payload.shippingInstruction.instructions,
474
+ });
475
+ actions.push({
476
+ action: 'setCustomField',
477
+ name: 'consentForUnattendedDelivery',
478
+ value: payload.shippingInstruction.consentForUnattendedDelivery + '',
479
+ });
480
+ actions.push({
481
+ action: 'setCustomField',
482
+ name: 'pickupPointId',
483
+ value: payload.shippingInstruction.pickupPoint,
484
+ });
346
485
 
486
+ const result = await this.applyActions(
487
+ payload.checkout as CommercetoolsCheckoutIdentifier,
488
+ actions
489
+ );
347
490
 
348
- return this.applyActions(payload.checkout as CommercetoolsCheckoutIdentifier, actions, reqCtx);
491
+ return success(result);
349
492
  }
350
493
 
351
- public async finalizeCheckout(payload: CheckoutMutationFinalizeCheckout, reqCtx: RequestContext): Promise<T> {
352
- const checkout = await this.getById({ identifier: payload.checkout }, reqCtx);
353
- if (!checkout || !checkout.readyForFinalization) {
494
+ @Reactionary({
495
+ inputSchema: CheckoutMutationFinalizeCheckoutSchema,
496
+ outputSchema: CheckoutSchema,
497
+ })
498
+ public async finalizeCheckout(
499
+ payload: CheckoutMutationFinalizeCheckout
500
+ ): Promise<Result<Checkout>> {
501
+ const checkout = await this.getById({ identifier: payload.checkout });
502
+ if (!checkout.success || !checkout.value.readyForFinalization) {
354
503
  throw new CheckoutNotReadyForFinalizationError(payload.checkout);
355
504
  }
356
505
 
357
- const client = await this.getClient(reqCtx);
506
+ const client = await this.getClient();
358
507
  const ctId = payload.checkout as CommercetoolsCheckoutIdentifier;
359
508
 
360
509
  // create the order from the cart
361
- const orderResponse = await client.orders.post({
362
- body: {
363
- id: ctId.key,
364
- version: ctId.version
365
- }
366
- }).execute();
367
-
368
- const actions: MyCartUpdateAction[] = [];
369
- actions.push({
370
- action: 'setCustomField',
371
- name: 'commerceToolsOrderId',
372
- value: orderResponse.body.id
373
- });
510
+ const orderResponse = await client.orders
511
+ .post({
512
+ body: {
513
+ id: ctId.key,
514
+ version: ctId.version,
515
+ },
516
+ })
517
+ .execute();
374
518
 
375
- return this.applyActions(payload.checkout as CommercetoolsCheckoutIdentifier, actions, reqCtx);
519
+ return this.getById({
520
+ identifier: payload.checkout,
521
+ }) as unknown as Result<Checkout>;
376
522
  }
377
523
 
378
-
379
-
380
-
381
524
  protected async applyActions(
382
525
  checkout: CheckoutIdentifier,
383
- actions: MyCartUpdateAction[],
384
- reqCtx: RequestContext
385
- ): Promise<T> {
386
- const client = await this.getClient(reqCtx);
526
+ actions: MyCartUpdateAction[]
527
+ ): Promise<Checkout> {
528
+ const client = await this.getClient();
387
529
  const ctId = checkout as CommercetoolsCheckoutIdentifier;
388
530
 
531
+ try {
532
+ const response = await client.carts
533
+ .withId({ ID: ctId.key })
534
+ .post({
535
+ queryArgs: {
536
+ expand: ['paymentInfo.payments[*]', 'shippingInfo.shippingMethod'],
537
+ },
538
+ body: {
539
+ version: ctId.version,
540
+ actions,
541
+ },
542
+ })
543
+ .execute();
389
544
 
390
- try {
391
- const response = await client.carts
392
- .withId({ ID: ctId.key })
393
- .post({
394
- queryArgs: {
395
- expand: ['paymentInfo.payments[*]', 'shippingInfo.shippingMethod']
396
- },
397
- body: {
398
- version: ctId.version,
399
- actions,
400
- },
401
- })
402
- .execute();
545
+ if (response.error) {
546
+ console.error(response.error);
547
+ }
403
548
 
404
- if (response.error) {
405
- console.error(response.error);
406
- }
407
- return this.parseSingle(response.body, reqCtx);
408
- } catch (e: any) {
409
- console.error('Error applying actions to cart:', e);
410
- throw e;
411
- }
549
+ const p = this.parseSingle(response.body);
412
550
 
551
+ return p;
552
+ } catch (e: any) {
553
+ console.error('Error applying actions to cart:', e);
554
+ throw e;
555
+ }
413
556
  }
414
557
 
415
558
  /**
416
559
  * Extension point, to allow filtering the options, or adding new ones, like invoicing for b2b.
417
560
  *
418
561
  * Usecase: Override this, if you need to change the payment options based on the request context.
419
- * @param reqCtx
420
562
  * @returns
421
563
  */
422
- protected getStaticPaymentMethods(_checkout: CheckoutIdentifier, reqCtx: RequestContext): PaymentMethod[] {
564
+ protected getStaticPaymentMethods(
565
+ _checkout: CheckoutIdentifier
566
+ ): PaymentMethod[] {
423
567
  return this.config.paymentMethods || [];
424
568
  }
425
569
 
426
-
427
-
428
- protected override getResourceName(): string {
429
- return "checkout";
430
- }
431
-
432
-
433
- protected override parseSingle(remote: CTCart, reqCtx: RequestContext): T {
434
- const result = this.newModel();
435
-
436
- result.identifier = CommercetoolsCheckoutIdentifierSchema.parse({
437
- key: remote.id,
438
- version: remote.version || 0,
439
- });
440
-
441
- result.name = remote.custom?.fields['name'] || '';
442
- result.description = remote.custom?.fields['description'] || '';
443
-
444
-
445
- result.originalCartReference = CommercetoolsCartIdentifierSchema.parse({
446
- key: remote.custom?.fields['commerceToolsCartId'] || '',
447
- version: 0,
448
- });
449
-
450
- const orderId = remote.custom?.fields['commerceToolsOrderId'];
451
- if (orderId) {
452
- result.resultingOrder = CommercetoolsOrderIdentifierSchema.parse({
453
- key: orderId,
454
- version: 0,
455
- });
456
- }
457
-
458
-
459
- if (remote.shippingAddress) {
460
- result.shippingAddress = this.parseAddress( remote.shippingAddress, reqCtx) ;
461
- }
462
-
463
- if (remote.billingAddress ) {
464
- result.billingAddress = this.parseAddress( remote.billingAddress, reqCtx) ;
465
- }
466
- result.shippingInstruction = this.parseShippingInstruction(remote);
467
-
468
- for(const p of remote.paymentInfo?.payments || []) {
469
- if (p.obj) {
470
- result.paymentInstructions.push( this.parsePaymentInstruction(p.obj, reqCtx) );
471
- }
472
- }
473
-
474
- const grandTotal = remote.totalPrice.centAmount || 0;
475
- const shippingTotal = remote.shippingInfo?.price.centAmount || 0;
476
- const productTotal = grandTotal - shippingTotal;
477
- const taxTotal = remote.taxedPrice?.totalTax?.centAmount || 0;
478
- const discountTotal =
479
- remote.discountOnTotalPrice?.discountedAmount.centAmount || 0;
480
- const surchargeTotal = 0;
481
- const currency = remote.totalPrice.currencyCode as Currency;
482
-
483
- result.price = {
484
- totalTax: {
485
- value: taxTotal / 100,
486
- currency,
487
- },
488
- totalDiscount: {
489
- value: discountTotal / 100,
570
+ protected parseCheckoutItem(remoteItem: LineItem): CheckoutItem {
571
+ const unitPrice = remoteItem.price.value.centAmount;
572
+ const totalPrice = remoteItem.totalPrice.centAmount || 0;
573
+ const totalDiscount = remoteItem.price.discounted?.value.centAmount || 0;
574
+ const unitDiscount = totalDiscount / remoteItem.quantity;
575
+ const currency =
576
+ remoteItem.price.value.currencyCode.toUpperCase() as Currency;
577
+
578
+ const item = {
579
+ identifier: {
580
+ key: remoteItem.id,
581
+ },
582
+ variant: {
583
+ sku: remoteItem.variant.sku || '',
584
+ },
585
+ quantity: remoteItem.quantity,
586
+ price: {
587
+ unitPrice: {
588
+ value: unitPrice / 100,
490
589
  currency,
491
590
  },
492
- totalSurcharge: {
493
- value: surchargeTotal / 100,
591
+ unitDiscount: {
592
+ value: unitDiscount / 100,
494
593
  currency,
495
594
  },
496
- totalShipping: {
497
- value: shippingTotal / 100,
498
- currency: remote.shippingInfo?.price.currencyCode as Currency,
499
- },
500
- totalProductPrice: {
501
- value: productTotal / 100,
595
+ totalPrice: {
596
+ value: (totalPrice || 0) / 100,
502
597
  currency,
503
598
  },
504
- grandTotal: {
505
- value: grandTotal / 100,
599
+ totalDiscount: {
600
+ value: totalDiscount / 100,
506
601
  currency,
507
602
  },
508
- };
509
-
510
- for (const remoteItem of remote.lineItems) {
511
- const item = CheckoutItemSchema.parse({});
603
+ },
604
+ } satisfies CheckoutItem;
512
605
 
513
- item.identifier.key = remoteItem.id;
514
- item.sku.key = remoteItem.variant.sku || '';
515
- item.quantity = remoteItem.quantity;
606
+ return CheckoutItemSchema.parse(item);
607
+ }
516
608
 
517
- const unitPrice = remoteItem.price.value.centAmount;
518
- const totalPrice = remoteItem.totalPrice.centAmount || 0;
519
- const totalDiscount = remoteItem.price.discounted?.value.centAmount || 0;
520
- const unitDiscount = totalDiscount / remoteItem.quantity;
609
+ protected parseSingle(remote: CTCart): Checkout {
610
+ const identifier = {
611
+ key: remote.id,
612
+ version: remote.version || 0,
613
+ } satisfies CommercetoolsCheckoutIdentifier;
521
614
 
522
- item.price = {
523
- unitPrice: {
524
- value: unitPrice / 100,
525
- currency,
526
- },
527
- unitDiscount: {
528
- value: unitDiscount / 100,
529
- currency,
530
- },
531
- totalPrice: {
532
- value: (totalPrice || 0) / 100,
533
- currency,
534
- },
535
- totalDiscount: {
536
- value: totalDiscount / 100,
537
- currency,
538
- },
539
- };
615
+ const originalCartReference = {
616
+ key: remote.custom?.fields['commerceToolsCartId'] || '',
617
+ version: 0,
618
+ } satisfies CommercetoolsCartIdentifier;
540
619
 
541
- result.items.push(item);
542
- }
620
+ let shippingAddress: Address | undefined;
621
+ if (remote.shippingAddress) {
622
+ shippingAddress = this.parseAddress(remote.shippingAddress);
623
+ }
543
624
 
544
- result.readyForFinalization = this.isReadyForFinalization(result);
545
- result.meta = {
546
- cache: {
547
- hit: false,
548
- key: this.generateCacheKeySingle(result.identifier, reqCtx),
549
- },
550
- placeholder: false,
551
- };
625
+ let billingAddress: Address | undefined;
626
+ if (remote.billingAddress) {
627
+ billingAddress = this.parseAddress(remote.billingAddress);
628
+ }
552
629
 
553
- return this.assert(result);
630
+ const paymentInstructions = new Array<PaymentInstruction>();
631
+ for (const p of remote.paymentInfo?.payments || []) {
632
+ if (p.obj) {
633
+ paymentInstructions.push(this.parsePaymentInstruction(p.obj));
634
+ }
554
635
  }
555
636
 
556
- protected isReadyForFinalization(checkout: T): boolean {
557
- // we should have a billing address
558
- if (!checkout.billingAddress) return false;
637
+ const grandTotal = remote.totalPrice.centAmount || 0;
638
+ const shippingTotal = remote.shippingInfo?.price.centAmount || 0;
639
+ const productTotal = grandTotal - shippingTotal;
640
+ const taxTotal = remote.taxedPrice?.totalTax?.centAmount || 0;
641
+ const discountTotal =
642
+ remote.discountOnTotalPrice?.discountedAmount.centAmount || 0;
643
+ const surchargeTotal = 0;
644
+ const currency = remote.totalPrice.currencyCode as Currency;
645
+
646
+ const price = {
647
+ totalTax: {
648
+ value: taxTotal / 100,
649
+ currency,
650
+ },
651
+ totalDiscount: {
652
+ value: discountTotal / 100,
653
+ currency,
654
+ },
655
+ totalSurcharge: {
656
+ value: surchargeTotal / 100,
657
+ currency,
658
+ },
659
+ totalShipping: {
660
+ value: shippingTotal / 100,
661
+ currency: currency
662
+ },
663
+ totalProductPrice: {
664
+ value: productTotal / 100,
665
+ currency,
666
+ },
667
+ grandTotal: {
668
+ value: grandTotal / 100,
669
+ currency,
670
+ },
671
+ } satisfies CostBreakDown;
559
672
 
560
- // we should know how to ship it
561
- if (!checkout.shippingInstruction) return false;
673
+ const items = new Array<CheckoutItem>();
674
+ for (const remoteItem of remote.lineItems) {
675
+ const item = this.parseCheckoutItem(remoteItem);
676
+ items.push(item);
677
+ }
562
678
 
563
- // and it should ship either to an address or a pickup point
564
- if (!checkout.shippingAddress && !checkout.shippingInstruction.pickupPoint) return false;
679
+ const shippingInstruction = this.parseShippingInstruction(remote);
680
+ const readyForFinalization = this.isReadyForFinalization(
681
+ price,
682
+ paymentInstructions,
683
+ billingAddress,
684
+ shippingAddress,
685
+ shippingInstruction
686
+ );
687
+
688
+ const result = {
689
+ identifier,
690
+ originalCartReference,
691
+ name: remote.custom?.fields['name'] || '',
692
+ description: remote.custom?.fields['description'] || '',
693
+ readyForFinalization,
694
+ billingAddress,
695
+ shippingAddress,
696
+ shippingInstruction,
697
+ paymentInstructions,
698
+ items,
699
+ price,
700
+ } satisfies Checkout;
565
701
 
566
- // and it should be paid for
567
- if (checkout.paymentInstructions.length === 0) return false;
702
+ return result;
703
+ }
568
704
 
569
- const authorizedPayments = checkout.paymentInstructions.filter(pi => pi.status === 'authorized').map(x => x.amount.value).reduce((a, b) => a + b, 0);
570
- if (checkout.price.grandTotal.value !== authorizedPayments) return false;
705
+ protected isReadyForFinalization(
706
+ price: CostBreakDown,
707
+ paymentInstructions: Array<PaymentInstruction>,
708
+ billingAddress?: Address,
709
+ shippingAddress?: Address,
710
+ shippingInstruction?: ShippingInstruction
711
+ ): boolean {
712
+ // we should have a billing address
713
+ if (!billingAddress) return false;
571
714
 
572
- return true
573
- }
715
+ // we should know how to ship it
716
+ if (!shippingInstruction) return false;
574
717
 
575
- protected parsePaymentInstruction(remote: CTPayment, reqCtx: RequestContext): PaymentInstruction {
576
- const newModel = PaymentInstructionSchema.parse({});
577
- newModel.identifier = PaymentInstructionIdentifierSchema.parse({ key: remote.id || '' });
578
- newModel.amount = {
579
- value: remote.amountPlanned.centAmount / 100,
580
- currency: remote.amountPlanned.currencyCode as Currency,
581
- };
718
+ // and it should ship either to an address or a pickup point
719
+ if (!shippingAddress && !shippingInstruction.pickupPoint) return false;
582
720
 
721
+ // and it should be paid for
722
+ if (paymentInstructions.length === 0) return false;
583
723
 
724
+ const authorizedPayments = paymentInstructions
725
+ .filter((pi) => pi.status === 'authorized')
726
+ .map((x) => x.amount.value)
727
+ .reduce((a, b) => a + b, 0);
728
+ if (price.grandTotal.value !== authorizedPayments) return false;
584
729
 
585
- const method = remote.paymentMethodInfo?.method || 'unknown';
586
- const paymentProcessor = remote.paymentMethodInfo?.paymentInterface || method;
587
- const paymentName = remote.paymentMethodInfo.name![reqCtx.languageContext.locale];
588
- newModel.paymentMethod = PaymentMethodIdentifierSchema.parse({
589
- method,
590
- paymentProcessor,
591
- name: paymentName || method || 'Unknown',
592
- });
730
+ return true;
731
+ }
593
732
 
594
- const customData = remote.custom?.fields || {};
595
- newModel.protocolData = Object.keys(customData).map(x => ({ key: x, value: customData[x] })) || [];
596
- if (remote.transactions && remote.transactions.length > 0) {
597
- const lastTransaction = remote.transactions[remote.transactions.length - 1];
598
- if (lastTransaction.type === 'Authorization' && lastTransaction.state === 'Pending') {
599
- newModel.status = 'pending';
600
- } else if (lastTransaction.type === 'Authorization' && lastTransaction.state === 'Success') {
601
- newModel.status = 'authorized';
602
- }
603
- } else {
604
- newModel.status = 'pending';
733
+ protected parsePaymentInstruction(remote: CTPayment): PaymentInstruction {
734
+ const identifier = {
735
+ key: remote.id,
736
+ } satisfies PaymentInstructionIdentifier;
737
+ const amount = {
738
+ value: remote.amountPlanned.centAmount / 100,
739
+ currency: remote.amountPlanned.currencyCode as Currency,
740
+ } satisfies MonetaryAmount;
741
+
742
+ const method = remote.paymentMethodInfo?.method || 'unknown';
743
+ const paymentProcessor =
744
+ remote.paymentMethodInfo?.paymentInterface || method;
745
+ const paymentName =
746
+ remote.paymentMethodInfo.name![this.context.languageContext.locale];
747
+
748
+ const paymentMethod = {
749
+ method,
750
+ paymentProcessor,
751
+ name: paymentName || method || 'Unknown',
752
+ } satisfies PaymentMethodIdentifier;
753
+
754
+ const customData = remote.custom?.fields || {};
755
+ const protocolData =
756
+ Object.keys(customData).map((x) => ({ key: x, value: customData[x] })) ||
757
+ [];
758
+
759
+ let status: PaymentStatus = 'pending';
760
+ if (remote.transactions && remote.transactions.length > 0) {
761
+ const lastTransaction =
762
+ remote.transactions[remote.transactions.length - 1];
763
+ if (
764
+ lastTransaction.type === 'Authorization' &&
765
+ lastTransaction.state === 'Pending'
766
+ ) {
767
+ status = 'pending';
768
+ } else if (
769
+ lastTransaction.type === 'Authorization' &&
770
+ lastTransaction.state === 'Success'
771
+ ) {
772
+ status = 'authorized';
605
773
  }
606
-
607
- return PaymentInstructionSchema.parse(newModel);
774
+ } else {
775
+ status = 'pending';
608
776
  }
609
777
 
610
- protected parseAddress(remote: CTAddress, reqCtx: RequestContext) {
611
- return AddressSchema.parse({
612
- countryCode: remote.country || '',
613
- firstName: remote.firstName || '',
614
- lastName: remote.lastName || '',
615
- streetAddress: remote.streetName || '',
616
- streetNumber: remote.streetNumber || '',
617
- postalCode: remote.postalCode || '',
618
- city: remote.city || '',
619
- });
620
- }
778
+ const result = {
779
+ amount,
780
+ identifier,
781
+ paymentMethod,
782
+ protocolData,
783
+ status
784
+ } satisfies PaymentInstruction;
621
785
 
622
- protected parseShippingInstruction(remote: CTCart): ShippingInstruction | undefined {
623
- if (!remote.shippingInfo) return undefined;
786
+ return result;
787
+ }
624
788
 
625
- const instructions = remote.custom?.fields['shippingInstruction'] || '';
626
- const consentForUnattendedDelivery = remote.custom?.fields['consentForUnattendedDelivery'] === 'true' || false;
627
- const pickupPoint = remote.custom?.fields['pickupPointId'] || '';
789
+ protected parseAddress(remote: CTAddress) {
790
+ return {
791
+ countryCode: remote.country || '',
792
+ firstName: remote.firstName || '',
793
+ lastName: remote.lastName || '',
794
+ streetAddress: remote.streetName || '',
795
+ streetNumber: remote.streetNumber || '',
796
+ postalCode: remote.postalCode || '',
797
+ city: remote.city || '',
798
+ identifier: {
799
+ nickName: '',
800
+ },
801
+ region: '',
802
+ } satisfies Address;
803
+ }
628
804
 
629
- const shippingInstruction = ShippingInstructionSchema.parse({
630
- amount: {
631
- value: (remote.shippingInfo.price.centAmount || 0) / 100,
632
- currency: remote.shippingInfo.price.currencyCode as Currency,
633
- },
634
- shippingMethod: {
635
- key: remote.shippingInfo.shippingMethod?.obj?.key || '',
636
- },
637
- pickupPoint: pickupPoint || '',
638
- instructions: instructions || '',
639
- consentForUnattendedDelivery: consentForUnattendedDelivery || false,
640
- });
805
+ protected parseShippingInstruction(
806
+ remote: CTCart
807
+ ): ShippingInstruction | undefined {
808
+ if (!remote.shippingInfo) return undefined;
641
809
 
642
- return shippingInstruction;
643
- }
810
+ const instructions = remote.custom?.fields['shippingInstruction'] || '';
811
+ const consentForUnattendedDelivery =
812
+ remote.custom?.fields['consentForUnattendedDelivery'] === 'true' || false;
813
+ const pickupPoint = remote.custom?.fields['pickupPointId'] || '';
814
+
815
+ const shippingInstruction = {
816
+ shippingMethod: {
817
+ key: remote.shippingInfo.shippingMethod?.obj?.key || '',
818
+ },
819
+ pickupPoint: pickupPoint || '',
820
+ instructions: instructions || '',
821
+ consentForUnattendedDelivery: consentForUnattendedDelivery || false,
822
+ } satisfies ShippingInstruction;
823
+
824
+ return shippingInstruction;
825
+ }
644
826
  }