@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.
- package/core/src/client/client.ts +2 -2
- package/core/src/providers/checkout.provider.ts +156 -0
- package/core/src/providers/index.ts +3 -2
- package/core/src/providers/order.provider.ts +31 -0
- package/core/src/providers/product.provider.ts +2 -2
- package/core/src/schemas/capabilities.schema.ts +2 -1
- package/core/src/schemas/models/cart.model.ts +1 -20
- package/core/src/schemas/models/checkout.model.ts +68 -0
- package/core/src/schemas/models/cost.model.ts +21 -0
- package/core/src/schemas/models/identifiers.model.ts +23 -2
- package/core/src/schemas/models/identity.model.ts +6 -3
- package/core/src/schemas/models/index.ts +5 -1
- package/core/src/schemas/models/order.model.ts +47 -0
- package/core/src/schemas/models/payment.model.ts +3 -10
- package/core/src/schemas/models/product.model.ts +6 -3
- package/core/src/schemas/models/shipping-method.model.ts +32 -1
- package/core/src/schemas/mutations/checkout.mutation.ts +50 -0
- package/core/src/schemas/mutations/index.ts +1 -1
- package/core/src/schemas/queries/checkout.query.ts +22 -0
- package/core/src/schemas/queries/index.ts +3 -2
- package/core/src/schemas/queries/order.query.ts +9 -0
- package/core/src/schemas/queries/product.query.ts +8 -1
- package/otel/src/trace-decorator.ts +8 -8
- package/package.json +3 -1
- package/providers/algolia/src/providers/product.provider.ts +7 -1
- package/providers/commercetools/package.json +1 -0
- package/providers/commercetools/src/core/client.ts +62 -31
- package/providers/commercetools/src/core/initialize.ts +11 -7
- package/providers/commercetools/src/providers/cart.provider.ts +10 -1
- package/providers/commercetools/src/providers/checkout.provider.ts +644 -0
- package/providers/commercetools/src/providers/identity.provider.ts +6 -6
- package/providers/commercetools/src/providers/index.ts +4 -1
- package/providers/commercetools/src/providers/order.provider.ts +165 -0
- package/providers/commercetools/src/providers/price.provider.ts +1 -1
- package/providers/commercetools/src/providers/product.provider.ts +24 -1
- package/providers/commercetools/src/schema/capabilities.schema.ts +2 -1
- package/providers/commercetools/src/schema/commercetools.schema.ts +7 -5
- package/providers/commercetools/src/schema/configuration.schema.ts +2 -0
- package/providers/commercetools/src/test/cart.provider.spec.ts +21 -1
- package/providers/commercetools/src/test/checkout.provider.spec.ts +312 -0
- package/providers/commercetools/src/test/price.provider.spec.ts +1 -1
- package/providers/commercetools/src/test/product.provider.spec.ts +19 -2
- package/providers/commercetools/src/test/test-utils.ts +14 -0
- package/providers/fake/src/providers/category.provider.ts +6 -2
- package/providers/fake/src/providers/product.provider.ts +9 -3
- package/providers/fake/src/test/product.provider.spec.ts +7 -7
- package/trpc/src/server.ts +1 -1
- package/trpc/src/transparent-client.spec.ts +4 -5
- package/.claude/settings.local.json +0 -28
- package/core/src/providers/cart-payment.provider.ts +0 -57
- package/core/src/schemas/mutations/cart-payment.mutation.ts +0 -21
- package/core/src/schemas/queries/cart-payment.query.ts +0 -12
- package/providers/commercetools/src/providers/cart-payment.provider.ts +0 -193
- package/providers/commercetools/src/test/cart-payment.provider.spec.ts +0 -145
- 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:
|
|
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();
|
|
@@ -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
|
-
|
|
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
|
|