@reactionary/source 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.
Files changed (55) hide show
  1. package/core/src/client/client.ts +2 -2
  2. package/core/src/providers/checkout.provider.ts +156 -0
  3. package/core/src/providers/index.ts +3 -2
  4. package/core/src/providers/order.provider.ts +31 -0
  5. package/core/src/providers/product.provider.ts +2 -2
  6. package/core/src/schemas/capabilities.schema.ts +2 -1
  7. package/core/src/schemas/models/cart.model.ts +1 -20
  8. package/core/src/schemas/models/checkout.model.ts +68 -0
  9. package/core/src/schemas/models/cost.model.ts +21 -0
  10. package/core/src/schemas/models/identifiers.model.ts +23 -2
  11. package/core/src/schemas/models/identity.model.ts +6 -3
  12. package/core/src/schemas/models/index.ts +5 -1
  13. package/core/src/schemas/models/order.model.ts +47 -0
  14. package/core/src/schemas/models/payment.model.ts +3 -10
  15. package/core/src/schemas/models/product.model.ts +6 -3
  16. package/core/src/schemas/models/shipping-method.model.ts +32 -1
  17. package/core/src/schemas/mutations/checkout.mutation.ts +50 -0
  18. package/core/src/schemas/mutations/index.ts +1 -1
  19. package/core/src/schemas/queries/checkout.query.ts +22 -0
  20. package/core/src/schemas/queries/index.ts +3 -2
  21. package/core/src/schemas/queries/order.query.ts +9 -0
  22. package/core/src/schemas/queries/product.query.ts +8 -1
  23. package/otel/src/trace-decorator.ts +8 -8
  24. package/package.json +3 -1
  25. package/providers/algolia/src/providers/product.provider.ts +7 -1
  26. package/providers/commercetools/package.json +1 -0
  27. package/providers/commercetools/src/core/client.ts +62 -31
  28. package/providers/commercetools/src/core/initialize.ts +11 -7
  29. package/providers/commercetools/src/providers/cart.provider.ts +10 -1
  30. package/providers/commercetools/src/providers/checkout.provider.ts +644 -0
  31. package/providers/commercetools/src/providers/identity.provider.ts +6 -6
  32. package/providers/commercetools/src/providers/index.ts +4 -1
  33. package/providers/commercetools/src/providers/order.provider.ts +165 -0
  34. package/providers/commercetools/src/providers/price.provider.ts +1 -1
  35. package/providers/commercetools/src/providers/product.provider.ts +24 -1
  36. package/providers/commercetools/src/schema/capabilities.schema.ts +2 -1
  37. package/providers/commercetools/src/schema/commercetools.schema.ts +7 -5
  38. package/providers/commercetools/src/schema/configuration.schema.ts +2 -0
  39. package/providers/commercetools/src/test/cart.provider.spec.ts +21 -1
  40. package/providers/commercetools/src/test/checkout.provider.spec.ts +312 -0
  41. package/providers/commercetools/src/test/price.provider.spec.ts +1 -1
  42. package/providers/commercetools/src/test/product.provider.spec.ts +19 -2
  43. package/providers/commercetools/src/test/test-utils.ts +14 -0
  44. package/providers/fake/src/providers/category.provider.ts +6 -2
  45. package/providers/fake/src/providers/product.provider.ts +9 -3
  46. package/providers/fake/src/test/product.provider.spec.ts +7 -7
  47. package/trpc/src/server.ts +1 -1
  48. package/trpc/src/transparent-client.spec.ts +4 -5
  49. package/.claude/settings.local.json +0 -28
  50. package/core/src/providers/cart-payment.provider.ts +0 -57
  51. package/core/src/schemas/mutations/cart-payment.mutation.ts +0 -21
  52. package/core/src/schemas/queries/cart-payment.query.ts +0 -12
  53. package/providers/commercetools/src/providers/cart-payment.provider.ts +0 -193
  54. package/providers/commercetools/src/test/cart-payment.provider.spec.ts +0 -145
  55. package/trpc/src/test-utils.ts +0 -31
@@ -0,0 +1,165 @@
1
+ import type {
2
+ Inventory,
3
+ RequestContext,
4
+ Cache,
5
+ InventoryQueryBySKU,
6
+ Order,
7
+ OrderQueryById,
8
+ Currency} from '@reactionary/core';
9
+ import { InventoryProvider, OrderItemSchema, OrderProvider } from '@reactionary/core';
10
+ import type z from 'zod';
11
+ import type { CommercetoolsConfiguration } from '../schema/configuration.schema';
12
+ import { CommercetoolsClient } from '../core/client';
13
+ import type { Order as CTOrder } from '@commercetools/platform-sdk';
14
+ import { CommercetoolsOrderIdentifierSchema } from '../schema/commercetools.schema';
15
+ export class CommercetoolsOrderProvider<
16
+ T extends Order = Order
17
+ > extends OrderProvider<T> {
18
+ protected config: CommercetoolsConfiguration;
19
+
20
+ constructor(
21
+ config: CommercetoolsConfiguration,
22
+ schema: z.ZodType<T>,
23
+ cache: Cache
24
+ ) {
25
+ super(schema, cache);
26
+
27
+ this.config = config;
28
+ }
29
+
30
+ protected async getClient(reqCtx: RequestContext) {
31
+
32
+ const client = await new CommercetoolsClient(this.config).getClient(
33
+ reqCtx
34
+ );
35
+ return client.withProjectKey({ projectKey: this.config.projectKey }).me().orders();
36
+ }
37
+
38
+
39
+ public override async getById(payload: OrderQueryById, reqCtx: RequestContext): Promise<T> {
40
+ const client = await new CommercetoolsClient(this.config).getClient(reqCtx);
41
+
42
+ try {
43
+ const remote = await client
44
+ .withProjectKey({ projectKey: this.config.projectKey })
45
+ .orders()
46
+ .withId({ ID: payload.order.key })
47
+ .get()
48
+ .execute();
49
+
50
+ return this.parseSingle(remote.body, reqCtx);
51
+ } catch (e) {
52
+ return this.createEmptyOrder();
53
+ }
54
+ }
55
+
56
+
57
+ protected override parseSingle(_body: unknown, reqCtx: RequestContext): T {
58
+ const remote = _body as CTOrder;
59
+ const result = this.newModel();
60
+
61
+ result.identifier = CommercetoolsOrderIdentifierSchema.parse({
62
+ key: remote.id,
63
+ version: remote.version || 0,
64
+ });
65
+
66
+
67
+
68
+ result.name = remote.custom?.fields['name'] || '';
69
+ result.description = remote.custom?.fields['description'] || '';
70
+
71
+ const grandTotal = remote.totalPrice.centAmount || 0;
72
+ const shippingTotal = remote.shippingInfo?.price.centAmount || 0;
73
+ const productTotal = grandTotal - shippingTotal;
74
+ const taxTotal = remote.taxedPrice?.totalTax?.centAmount || 0;
75
+ const discountTotal =
76
+ remote.discountOnTotalPrice?.discountedAmount.centAmount || 0;
77
+ const surchargeTotal = 0;
78
+ const currency = remote.totalPrice.currencyCode as Currency;
79
+
80
+ result.price = {
81
+ totalTax: {
82
+ value: taxTotal / 100,
83
+ currency,
84
+ },
85
+ totalDiscount: {
86
+ value: discountTotal / 100,
87
+ currency,
88
+ },
89
+ totalSurcharge: {
90
+ value: surchargeTotal / 100,
91
+ currency,
92
+ },
93
+ totalShipping: {
94
+ value: shippingTotal / 100,
95
+ currency: remote.shippingInfo?.price.currencyCode as Currency,
96
+ },
97
+ totalProductPrice: {
98
+ value: productTotal / 100,
99
+ currency,
100
+ },
101
+ grandTotal: {
102
+ value: grandTotal / 100,
103
+ currency,
104
+ },
105
+ };
106
+
107
+ if (remote.paymentState === 'Pending' && remote.orderState === 'Open') {
108
+ result.orderStatus = 'AwaitingPayment';
109
+ } else if (remote.paymentState === 'Paid' && remote.orderState === 'Confirmed') {
110
+ result.orderStatus = 'ReleasedToFulfillment';
111
+ }
112
+ if (remote.shipmentState === 'Ready' && remote.orderState === 'Confirmed') {
113
+ result.orderStatus = 'ReleasedToFulfillment';
114
+ }
115
+ if ( (remote.shipmentState === 'Shipped' || remote.shipmentState === 'Delivered') && remote.orderState === 'Completed') {
116
+ result.orderStatus = 'Shipped';
117
+ }
118
+
119
+ for (const remoteItem of remote.lineItems) {
120
+ const item = OrderItemSchema.parse({});
121
+
122
+ item.identifier.key = remoteItem.id;
123
+ item.sku.key = remoteItem.variant.sku || '';
124
+ item.quantity = remoteItem.quantity;
125
+
126
+ const unitPrice = remoteItem.price.value.centAmount;
127
+ const totalPrice = remoteItem.totalPrice.centAmount || 0;
128
+ const totalDiscount = remoteItem.price.discounted?.value.centAmount || 0;
129
+ const unitDiscount = totalDiscount / remoteItem.quantity;
130
+
131
+ item.price = {
132
+ unitPrice: {
133
+ value: unitPrice / 100,
134
+ currency,
135
+ },
136
+ unitDiscount: {
137
+ value: unitDiscount / 100,
138
+ currency,
139
+ },
140
+ totalPrice: {
141
+ value: (totalPrice || 0) / 100,
142
+ currency,
143
+ },
144
+ totalDiscount: {
145
+ value: totalDiscount / 100,
146
+ currency,
147
+ },
148
+ };
149
+
150
+ result.items.push(item);
151
+ }
152
+
153
+ result.meta = {
154
+ cache: {
155
+ hit: false,
156
+ key: this.generateCacheKeySingle(result.identifier, reqCtx),
157
+ },
158
+ placeholder: false,
159
+ };
160
+
161
+
162
+
163
+ return this.assert(result);
164
+ }
165
+ }
@@ -38,7 +38,7 @@ export class CommercetoolsPriceProvider<
38
38
  const response = await client.get({
39
39
  queryArgs: {
40
40
  staged: false,
41
- priceCountry: 'US',
41
+ priceCountry: reqCtx.taxJurisdiction.countryCode,
42
42
  priceCustomerGroup: undefined,
43
43
  priceChannel: channels.offerChannelGUID,
44
44
  priceCurrency: reqCtx.languageContext.currencyCode,
@@ -6,7 +6,7 @@ import type { z } from 'zod';
6
6
  import type { CommercetoolsConfiguration } from '../schema/configuration.schema';
7
7
  import type { ProductProjection } from '@commercetools/platform-sdk';
8
8
  import { traced } from '@reactionary/otel';
9
- import type { Product, ProductQueryById, ProductQueryBySlug, RequestContext } from '@reactionary/core';
9
+ import type { Product, ProductQueryById, ProductQueryBySKU, ProductQueryBySlug, RequestContext } from '@reactionary/core';
10
10
  import type { Cache } from '@reactionary/core';
11
11
 
12
12
  export class CommercetoolsProductProvider<
@@ -67,6 +67,29 @@ export class CommercetoolsProductProvider<
67
67
  return this.parseSingle(remote.body.results[0], reqCtx);
68
68
  }
69
69
 
70
+
71
+ @traced()
72
+ public override async getBySKU(
73
+ payload: ProductQueryBySKU,
74
+ reqCtx: RequestContext
75
+ ): Promise<T> {
76
+ const client = await this.getClient(reqCtx);
77
+
78
+ const remote = await client
79
+ .get({
80
+ queryArgs: {
81
+ staged: false,
82
+ limit: 1,
83
+ where: 'variants(sku in (:skus)) OR (masterVariant(sku in (:skus))) ',
84
+ 'var.skus': [payload].map(p => p.sku.key),
85
+ }
86
+ })
87
+ .execute();
88
+
89
+ return this.parseSingle(remote.body.results[0], reqCtx);
90
+ }
91
+
92
+
70
93
  protected override parseSingle(dataIn: unknown, reqCtx: RequestContext): T {
71
94
  const data = dataIn as ProductProjection;
72
95
  const base = this.newModel();
@@ -6,7 +6,8 @@ export const CommercetoolsCapabilitiesSchema = CapabilitiesSchema.pick({
6
6
  search: true,
7
7
  identity: true,
8
8
  cart: true,
9
- cartPayment: true,
9
+ checkout: true,
10
+ order: true,
10
11
  inventory: true,
11
12
  price: true,
12
13
  category: true,
@@ -1,18 +1,20 @@
1
- import { CartIdentifierSchema, OrderIdentifierSchema, PaymentInstructionIdentifierSchema } from "@reactionary/core";
1
+ import { CartIdentifierSchema, CheckoutIdentifierSchema, OrderIdentifierSchema, PaymentInstructionIdentifierSchema } from "@reactionary/core";
2
2
  import z from "zod";
3
3
 
4
4
  export const CommercetoolsCartIdentifierSchema = CartIdentifierSchema.extend({
5
5
  version: z.number().default(0)
6
6
  });
7
7
 
8
- export const CommercetoolsCartPaymentInstructionIdentifierSchema = PaymentInstructionIdentifierSchema.extend({
9
- version: z.number().default(0),
10
- });
11
8
 
12
9
  export const CommercetoolsOrderIdentifierSchema = OrderIdentifierSchema.extend({
13
10
  version: z.number().default(0)
14
11
  });
15
12
 
13
+ export const CommercetoolsCheckoutIdentifierSchema = CheckoutIdentifierSchema.extend({
14
+ version: z.number().default(0)
15
+ });
16
+
17
+ export type CommercetoolsCheckoutIdentifier = z.infer<typeof CommercetoolsCheckoutIdentifierSchema>;
18
+
16
19
  export type CommercetoolsCartIdentifier = z.infer<typeof CommercetoolsCartIdentifierSchema>;
17
- export type CommercetoolsCartPaymentInstructionIdentifier = z.infer<typeof CommercetoolsCartPaymentInstructionIdentifierSchema>;
18
20
  export type CommercetoolsOrderIdentifier = z.infer<typeof CommercetoolsOrderIdentifierSchema>;
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod';
2
+ import { PaymentMethodSchema } from '@reactionary/core';
2
3
 
3
4
  export const CommercetoolsConfigurationSchema = z.looseObject({
4
5
  projectKey: z.string(),
@@ -7,6 +8,7 @@ export const CommercetoolsConfigurationSchema = z.looseObject({
7
8
  clientId: z.string(),
8
9
  clientSecret: z.string(),
9
10
  scopes: z.array(z.string()).default(() => []),
11
+ paymentMethods: PaymentMethodSchema.array().optional().default(() => []),
10
12
  });
11
13
 
12
14
  export type CommercetoolsConfiguration = z.infer<typeof CommercetoolsConfigurationSchema>;
@@ -1,9 +1,10 @@
1
1
  import 'dotenv/config';
2
2
  import type { RequestContext} from '@reactionary/core';
3
- import { CartSchema, IdentitySchema, NoOpCache, createInitialRequestContext } from '@reactionary/core';
3
+ import { CartSchema, IdentitySchema, NoOpCache, ProductSchema, createInitialRequestContext } from '@reactionary/core';
4
4
  import { getCommercetoolsTestConfiguration } from './test-utils';
5
5
  import { CommercetoolsCartProvider } from '../providers/cart.provider';
6
6
  import { CommercetoolsIdentityProvider } from '../providers/identity.provider';
7
+ import { CommercetoolsProductProvider } from '../providers/product.provider';
7
8
 
8
9
 
9
10
  const testData = {
@@ -13,12 +14,14 @@ const testData = {
13
14
 
14
15
  describe('Commercetools Cart Provider', () => {
15
16
  let provider: CommercetoolsCartProvider;
17
+ let productProvider: CommercetoolsProductProvider;
16
18
  let identityProvider: CommercetoolsIdentityProvider;
17
19
  let reqCtx: RequestContext;
18
20
 
19
21
  beforeAll( () => {
20
22
  provider = new CommercetoolsCartProvider(getCommercetoolsTestConfiguration(), CartSchema, new NoOpCache());
21
23
  identityProvider = new CommercetoolsIdentityProvider(getCommercetoolsTestConfiguration(), IdentitySchema, new NoOpCache());
24
+ productProvider = new CommercetoolsProductProvider(getCommercetoolsTestConfiguration(), ProductSchema, new NoOpCache());
22
25
  });
23
26
 
24
27
  beforeEach( () => {
@@ -162,6 +165,23 @@ describe('Commercetools Cart Provider', () => {
162
165
  expect(originalCart.items.length).toBe(0);
163
166
  });
164
167
 
168
+ it('can load the product information for cart items', async () => {
169
+
170
+ const cart = await provider.add({
171
+ cart: { key: '' },
172
+ sku: {
173
+ key: testData.skuWithoutTiers,
174
+ },
175
+ quantity: 1
176
+ }, reqCtx);
177
+ expect(cart.items[0].sku).toBeDefined();
178
+
179
+ const product = await productProvider.getBySKU( { sku: cart.items[0].sku }, reqCtx);
180
+ expect(product).toBeTruthy();
181
+ if (product) {
182
+ expect(product.skus.some(s => s.identifier.key === cart.items[0].sku.key)).toBe(true);
183
+ }
184
+ });
165
185
  /**
166
186
  it('should be able to create a cart for an anonymous user, then login and merge the cart', async () => {
167
187
  });
@@ -0,0 +1,312 @@
1
+ import 'dotenv/config';
2
+ import type { Cart, Checkout, RequestContext } from '@reactionary/core';
3
+ import {
4
+ CartSchema,
5
+ CheckoutSchema,
6
+ IdentitySchema,
7
+ NoOpCache,
8
+ PaymentInstructionSchema,
9
+ ShippingInstructionSchema,
10
+ createInitialRequestContext,
11
+ } from '@reactionary/core';
12
+ import { getCommercetoolsTestConfiguration } from './test-utils';
13
+ import { CommercetoolsCartProvider } from '../providers/cart.provider';
14
+ import { CommercetoolsIdentityProvider } from '../providers/identity.provider';
15
+ import { CommercetoolsCheckoutProvider } from '../providers/checkout.provider';
16
+
17
+ const testData = {
18
+ skuWithoutTiers: 'SGB-01',
19
+ skuWithTiers: 'GMCT-01',
20
+ };
21
+
22
+ describe('Commercetools Checkout Provider', () => {
23
+ let provider: CommercetoolsCheckoutProvider;
24
+ let cartProvider: CommercetoolsCartProvider;
25
+ let identityProvider: CommercetoolsIdentityProvider;
26
+ let reqCtx: RequestContext;
27
+
28
+ beforeAll(() => {
29
+ provider = new CommercetoolsCheckoutProvider(
30
+ getCommercetoolsTestConfiguration(),
31
+ CheckoutSchema,
32
+ new NoOpCache()
33
+ );
34
+ cartProvider = new CommercetoolsCartProvider(
35
+ getCommercetoolsTestConfiguration(),
36
+ CartSchema,
37
+ new NoOpCache()
38
+ );
39
+ identityProvider = new CommercetoolsIdentityProvider(
40
+ getCommercetoolsTestConfiguration(),
41
+ IdentitySchema,
42
+ new NoOpCache()
43
+ );
44
+ });
45
+
46
+ beforeEach(() => {
47
+ reqCtx = createInitialRequestContext();
48
+ });
49
+
50
+ describe('anonymous sessions', () => {
51
+ let cart: Cart;
52
+
53
+ beforeEach(async () => {
54
+ cart = await cartProvider.add(
55
+ {
56
+ cart: { key: '', version: 0 },
57
+ sku: {
58
+ key: testData.skuWithoutTiers,
59
+ },
60
+ quantity: 1,
61
+ },
62
+ reqCtx
63
+ );
64
+ });
65
+
66
+ it('can create a checkout session from a cart', async () => {
67
+ // we have either an anonymous user, or an authenticated user.
68
+ // if it is anonymous, we assume you will have collected some basic info by now ?
69
+
70
+ const checkout = await provider.initiateCheckoutForCart(
71
+ {
72
+ cart: cart.identifier,
73
+ billingAddress: {
74
+ countryCode: 'US',
75
+ firstName: 'John',
76
+ lastName: 'Doe',
77
+ streetAddress: '123 Main St',
78
+ streetNumber: '1A',
79
+ postalCode: '12345',
80
+ city: 'Anytown',
81
+ region: '',
82
+ },
83
+ notificationEmail: 'sample@example.com',
84
+ notificationPhone: '+4512345678',
85
+ },
86
+ reqCtx
87
+ );
88
+
89
+ expect(checkout.identifier.key).toBeDefined();
90
+ expect(checkout.originalCartReference.key).toBe(cart.identifier.key);
91
+ expect(checkout.billingAddress?.firstName).toBe('John');
92
+ expect(checkout.items.length).toBe(1);
93
+ expect(checkout.items[0].sku.key).toBe(testData.skuWithoutTiers);
94
+ });
95
+
96
+ describe('checkout actions', () => {
97
+ let checkout: Checkout;
98
+ beforeEach(async () => {
99
+ checkout = await provider.initiateCheckoutForCart(
100
+ {
101
+ cart: cart.identifier,
102
+ billingAddress: {
103
+ countryCode: 'US',
104
+ firstName: 'John',
105
+ lastName: 'Doe',
106
+ streetAddress: '123 Main St',
107
+ streetNumber: '1A',
108
+ postalCode: '12345',
109
+ city: 'Anytown',
110
+ region: '',
111
+ },
112
+ notificationEmail: 'sample@example.com',
113
+ notificationPhone: '+4512345678',
114
+ },
115
+ reqCtx
116
+ );
117
+ });
118
+
119
+ it('can list payment methods', async () => {
120
+ const paymentMethods = await provider.getAvailablePaymentMethods(
121
+ {
122
+ checkout: checkout.identifier,
123
+ },
124
+ reqCtx
125
+ );
126
+ expect(paymentMethods.length).toBeGreaterThan(0);
127
+ expect(
128
+ paymentMethods.find((x) => x.identifier.method === 'stripe')
129
+ ).toBeDefined();
130
+ });
131
+
132
+ it('can list shipping methods', async () => {
133
+ const shippingMethods = await provider.getAvailableShippingMethods(
134
+ {
135
+ checkout: checkout.identifier,
136
+ },
137
+ reqCtx
138
+ );
139
+ expect(shippingMethods.length).toBeGreaterThan(0);
140
+ expect(
141
+ shippingMethods.find((x) => x.identifier.key === 'us-delivery')
142
+ ).toBeDefined();
143
+ });
144
+
145
+ it('can add a payment instruction', async () => {
146
+ const paymentMethods = await provider.getAvailablePaymentMethods(
147
+ {
148
+ checkout: checkout.identifier,
149
+ },
150
+ reqCtx
151
+ );
152
+ const pm = paymentMethods.find((x) => x.identifier.method === 'stripe');
153
+ expect(pm).toBeDefined();
154
+
155
+ const checkoutWithPi = await provider.addPaymentInstruction(
156
+ {
157
+ checkout: checkout.identifier,
158
+ paymentInstruction: PaymentInstructionSchema.parse({
159
+ paymentMethod: pm?.identifier,
160
+ amount: checkout.price.grandTotal,
161
+ protocolData: [{ key: 'test-key', value: 'test-value' }],
162
+ }),
163
+ },
164
+ reqCtx
165
+ );
166
+
167
+ expect(checkoutWithPi.paymentInstructions.length).toBe(1);
168
+ expect(checkoutWithPi.paymentInstructions[0].paymentMethod.method).toBe(
169
+ 'stripe'
170
+ );
171
+ expect(checkoutWithPi.paymentInstructions[0].protocolData.find(x => x.key === 'stripe_clientSecret')?.value).toBeDefined();
172
+
173
+ });
174
+
175
+ xit('can cancel an in-progress payment', async () => {
176
+ const paymentMethods = await provider.getAvailablePaymentMethods(
177
+ {
178
+ checkout: checkout.identifier,
179
+ },
180
+ reqCtx
181
+ );
182
+ const pm = paymentMethods.find((x) => x.identifier.method === 'stripe');
183
+ expect(pm).toBeDefined();
184
+
185
+ const checkoutWithPi = await provider.addPaymentInstruction(
186
+ {
187
+ checkout: checkout.identifier,
188
+ paymentInstruction: PaymentInstructionSchema.parse({
189
+ paymentMethod: pm?.identifier,
190
+ amount: checkout.price.grandTotal,
191
+ protocolData: [{ key: 'test-key', value: 'test-value' }],
192
+ }),
193
+ },
194
+ reqCtx
195
+ );
196
+
197
+ expect(checkoutWithPi.paymentInstructions.length).toBe(1);
198
+
199
+ const checkoutAfterCancel = await provider.removePaymentInstruction(
200
+ {
201
+ checkout: checkout.identifier,
202
+ paymentInstruction:
203
+ checkoutWithPi.paymentInstructions[0].identifier,
204
+ },
205
+ reqCtx
206
+ );
207
+
208
+ expect(checkoutAfterCancel.paymentInstructions.length).toBe(0);
209
+ });
210
+
211
+ it('can set shipping address', async () => {
212
+ const checkoutWithShipping = await provider.setShippingAddress(
213
+ {
214
+ checkout: checkout.identifier,
215
+ shippingAddress: {
216
+ countryCode: 'US',
217
+ firstName: 'Jane',
218
+ lastName: 'Doe',
219
+ streetAddress: '456 Other St',
220
+ streetNumber: '2B',
221
+ postalCode: '54321',
222
+ city: 'Othertown',
223
+ region: '',
224
+ },
225
+ },
226
+ reqCtx
227
+ );
228
+
229
+ expect(checkoutWithShipping.shippingAddress).toBeDefined();
230
+ expect(checkoutWithShipping.shippingAddress?.firstName).toBe('Jane');
231
+ });
232
+
233
+ it('can set shipping instructions', async () => {
234
+ const shippingMethods = await provider.getAvailableShippingMethods(
235
+ {
236
+ checkout: checkout.identifier,
237
+ },
238
+ reqCtx
239
+ );
240
+ const sm = shippingMethods.find((x) => x.identifier.key === 'us-delivery');
241
+ expect(sm).toBeDefined();
242
+
243
+ const shippingInstruction = ShippingInstructionSchema.parse({
244
+ shippingMethod: sm?.identifier || { key: '' },
245
+ amount: checkout.price.totalShipping,
246
+ instructions: 'Leave at front door if not home',
247
+ consentForUnattendedDelivery: true,
248
+ pickupPoint: '4190asx141', // this would be a real pickup point ID in a real scenario
249
+ });
250
+
251
+ const checkoutWithShipping = await provider.setShippingInstruction(
252
+ {
253
+ checkout: checkout.identifier,
254
+ shippingInstruction,
255
+ },
256
+ reqCtx
257
+ );
258
+
259
+ expect(checkout.price.totalShipping.value).toBe(0);
260
+ expect(checkoutWithShipping.price.totalShipping.value).toBeGreaterThan(0);
261
+ expect(checkoutWithShipping.shippingInstruction).toBeDefined();
262
+ expect(
263
+ checkoutWithShipping.shippingInstruction?.shippingMethod.key
264
+ ).toBe('us-delivery');
265
+ expect(checkoutWithShipping.shippingInstruction?.instructions).toBe(
266
+ 'Leave at front door if not home'
267
+ );
268
+ expect(checkoutWithShipping.shippingInstruction?.pickupPoint).toBe(
269
+ '4190asx141'
270
+ );
271
+ expect(
272
+ checkoutWithShipping.shippingInstruction?.consentForUnattendedDelivery
273
+ ).toBe(true);
274
+ });
275
+
276
+ xit('wont report it finalizable until everything is paid/authorized', async () => {
277
+ expect(checkout.readyForFinalization).toBe(false);
278
+ const pm = (
279
+ await provider.getAvailablePaymentMethods(
280
+ {
281
+ checkout: checkout.identifier,
282
+ },
283
+ reqCtx
284
+ )
285
+ ).find((x) => x.identifier.method === 'stripe');
286
+ expect(pm).toBeDefined();
287
+
288
+ const checkoutWithPi = await provider.addPaymentInstruction(
289
+ {
290
+ checkout: checkout.identifier,
291
+ paymentInstruction: PaymentInstructionSchema.parse({
292
+ paymentMethod: pm?.identifier,
293
+ amount: checkout.price.grandTotal,
294
+ protocolData: [{ key: 'test-key', value: 'test-value' }],
295
+ }),
296
+ },
297
+ reqCtx
298
+ );
299
+
300
+ // do something to simulate payment authorization ?
301
+ const checkoutReady = await provider.getById(
302
+ { identifier: checkoutWithPi.identifier },
303
+ reqCtx
304
+ );
305
+ if (!checkoutReady) {
306
+ fail('checkout not found');
307
+ }
308
+ expect(checkoutReady.readyForFinalization).toBe(true);
309
+ });
310
+ });
311
+ });
312
+ });
@@ -39,7 +39,7 @@ describe('Commercetools Price Provider', () => {
39
39
  }
40
40
  });
41
41
 
42
- it('should be able to get prices for a product with tiers', async () => {
42
+ xit('should be able to get prices for a product with tiers', async () => {
43
43
  const result = await provider.getBySKU({ sku: { key: testData.skuWithTiers }}, reqCtx);
44
44
 
45
45
  expect(result).toBeTruthy();
@@ -8,8 +8,11 @@ const testData = {
8
8
  product : {
9
9
  id: '4d28f98d-c446-446e-b59a-d9f718e5b98a',
10
10
  name: 'Sunnai Glass Bowl',
11
- image: 'https://storage.googleapis.com/merchant-center-europe/sample-data/goodstore/Sunnai_Glass_Bowl-1.1.jpeg'
12
- }
11
+ image: 'https://storage.googleapis.com/merchant-center-europe/sample-data/goodstore/Sunnai_Glass_Bowl-1.1.jpeg',
12
+ sku: 'SGB-01',
13
+
14
+ },
15
+
13
16
  }
14
17
 
15
18
  describe('Commercetools Product Provider', () => {
@@ -48,12 +51,26 @@ describe('Commercetools Product Provider', () => {
48
51
  }
49
52
  });
50
53
 
54
+ it('should be able to get a product by sku', async () => {
55
+ const result = await provider.getBySKU( { sku: { key: testData.product.sku } }, reqCtx);
56
+
57
+ expect(result).toBeTruthy();
58
+ if (result) {
59
+ expect(result.meta.placeholder).toBe(false);
60
+ expect(result.identifier.key).toBe(testData.product.id);
61
+ expect(result.name).toBe(testData.product.name);
62
+ expect(result.image).toBe(testData.product.image);
63
+ }
64
+ });
65
+
51
66
  it('should return null for unknown slug', async () => {
52
67
  const result = await provider.getBySlug( { slug: 'unknown-slug' }, reqCtx);
53
68
 
54
69
  expect(result).toBeNull();
55
70
  });
56
71
 
72
+
73
+
57
74
  it('should return a placeholder product for unknown id', async () => {
58
75
  const result = await provider.getById( { id: 'unknown-id' }, reqCtx);
59
76