@reactionary/source 0.0.48 → 0.0.52
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/package.json +4 -3
- package/core/src/cache/cache.interface.ts +1 -1
- package/core/src/cache/memory-cache.ts +2 -2
- package/core/src/cache/noop-cache.ts +1 -1
- package/core/src/cache/redis-cache.ts +1 -1
- package/core/src/client/client-builder.ts +4 -4
- package/core/src/client/client.ts +12 -12
- package/core/src/decorators/reactionary.decorator.ts +22 -2
- package/core/src/index.ts +14 -14
- package/core/src/initialization.ts +1 -1
- package/core/src/providers/analytics.provider.ts +2 -2
- package/core/src/providers/base.provider.ts +5 -5
- package/core/src/providers/cart.provider.ts +6 -6
- package/core/src/providers/category.provider.ts +4 -9
- package/core/src/providers/checkout.provider.ts +156 -0
- package/core/src/providers/identity.provider.ts +5 -5
- package/core/src/providers/index.ts +13 -12
- package/core/src/providers/inventory.provider.ts +4 -4
- package/core/src/providers/order.provider.ts +31 -0
- package/core/src/providers/price.provider.ts +5 -5
- package/core/src/providers/product.provider.ts +5 -5
- package/core/src/providers/profile.provider.ts +5 -5
- package/core/src/providers/search.provider.ts +4 -4
- package/core/src/providers/store.provider.ts +4 -4
- package/core/src/schemas/capabilities.schema.ts +2 -1
- package/core/src/schemas/models/analytics.model.ts +1 -1
- package/core/src/schemas/models/cart.model.ts +3 -28
- package/core/src/schemas/models/category.model.ts +2 -2
- package/core/src/schemas/models/checkout.model.ts +66 -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 +8 -5
- package/core/src/schemas/models/index.ts +19 -15
- package/core/src/schemas/models/inventory.model.ts +2 -2
- package/core/src/schemas/models/order.model.ts +46 -0
- package/core/src/schemas/models/payment.model.ts +5 -12
- package/core/src/schemas/models/price.model.ts +3 -3
- package/core/src/schemas/models/product.model.ts +6 -3
- package/core/src/schemas/models/profile.model.ts +2 -2
- package/core/src/schemas/models/search.model.ts +2 -3
- package/core/src/schemas/models/shipping-method.model.ts +34 -3
- package/core/src/schemas/models/store.model.ts +2 -2
- package/core/src/schemas/mutations/analytics.mutation.ts +2 -2
- package/core/src/schemas/mutations/cart.mutation.ts +5 -5
- package/core/src/schemas/mutations/checkout.mutation.ts +50 -0
- package/core/src/schemas/mutations/identity.mutation.ts +1 -1
- package/core/src/schemas/mutations/index.ts +10 -10
- package/core/src/schemas/mutations/profile.mutation.ts +1 -1
- package/core/src/schemas/queries/cart.query.ts +2 -2
- package/core/src/schemas/queries/category.query.ts +3 -3
- package/core/src/schemas/queries/checkout.query.ts +22 -0
- package/core/src/schemas/queries/identity.query.ts +1 -1
- package/core/src/schemas/queries/index.ts +13 -12
- package/core/src/schemas/queries/inventory.query.ts +2 -2
- package/core/src/schemas/queries/order.query.ts +9 -0
- package/core/src/schemas/queries/price.query.ts +2 -2
- package/core/src/schemas/queries/product.query.ts +9 -2
- package/core/src/schemas/queries/profile.query.ts +1 -1
- package/core/src/schemas/queries/search.query.ts +2 -2
- package/core/src/schemas/queries/store.query.ts +1 -1
- package/core/src/schemas/session.schema.ts +3 -3
- package/core/tsconfig.json +3 -2
- package/examples/next/next.config.js +17 -6
- package/examples/next/src/app/page.tsx +1 -2
- package/examples/node/package.json +2 -1
- package/examples/node/src/basic/basic-node-setup.spec.ts +1 -1
- package/examples/node/tsconfig.json +2 -1
- package/examples/node/tsconfig.spec.json +3 -2
- package/package.json +3 -1
- package/providers/algolia/package.json +2 -1
- package/providers/algolia/src/core/initialize.ts +5 -5
- package/providers/algolia/src/index.ts +5 -5
- package/providers/algolia/src/providers/product.provider.ts +8 -2
- package/providers/algolia/src/providers/search.provider.ts +1 -1
- package/providers/algolia/src/test/search.provider.spec.ts +1 -1
- package/providers/algolia/tsconfig.json +2 -1
- package/providers/algolia/tsconfig.spec.json +3 -2
- package/providers/commercetools/{jest.config.ts → jest.config.cjs} +1 -1
- package/providers/commercetools/package.json +3 -2
- package/providers/commercetools/src/core/client.ts +63 -32
- package/providers/commercetools/src/core/initialize.ts +20 -16
- package/providers/commercetools/src/index.ts +10 -10
- package/providers/commercetools/src/providers/cart.provider.ts +14 -19
- package/providers/commercetools/src/providers/category.provider.ts +3 -12
- package/providers/commercetools/src/providers/checkout.provider.ts +644 -0
- package/providers/commercetools/src/providers/identity.provider.ts +8 -8
- package/providers/commercetools/src/providers/index.ts +12 -9
- package/providers/commercetools/src/providers/inventory.provider.ts +2 -4
- package/providers/commercetools/src/providers/order.provider.ts +163 -0
- package/providers/commercetools/src/providers/price.provider.ts +3 -3
- package/providers/commercetools/src/providers/product.provider.ts +24 -6
- package/providers/commercetools/src/providers/profile.provider.ts +2 -2
- package/providers/commercetools/src/providers/search.provider.ts +3 -5
- package/providers/commercetools/src/providers/store.provider.ts +3 -3
- 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 +24 -4
- package/providers/commercetools/src/test/category.provider.spec.ts +3 -3
- package/providers/commercetools/src/test/checkout.provider.spec.ts +312 -0
- package/providers/commercetools/src/test/identity.provider.spec.ts +3 -3
- package/providers/commercetools/src/test/inventory.provider.spec.ts +2 -2
- package/providers/commercetools/src/test/price.provider.spec.ts +4 -4
- package/providers/commercetools/src/test/product.provider.spec.ts +22 -5
- package/providers/commercetools/src/test/profile.provider.spec.ts +3 -3
- package/providers/commercetools/src/test/search.provider.spec.ts +2 -2
- package/providers/commercetools/src/test/store.provider.spec.ts +2 -2
- package/providers/commercetools/src/test/test-utils.ts +14 -0
- package/providers/commercetools/tsconfig.json +2 -1
- package/providers/commercetools/tsconfig.spec.json +4 -3
- package/providers/fake/{jest.config.ts → jest.config.cjs} +1 -1
- package/providers/fake/package.json +2 -2
- package/providers/fake/src/core/initialize.ts +6 -6
- package/providers/fake/src/index.ts +4 -4
- package/providers/fake/src/providers/analytics.provider.ts +1 -1
- package/providers/fake/src/providers/cart.provider.ts +2 -2
- package/providers/fake/src/providers/category.provider.ts +7 -3
- package/providers/fake/src/providers/identity.provider.ts +1 -1
- package/providers/fake/src/providers/index.ts +9 -9
- package/providers/fake/src/providers/inventory.provider.ts +1 -1
- package/providers/fake/src/providers/price.provider.ts +1 -1
- package/providers/fake/src/providers/product.provider.ts +10 -4
- package/providers/fake/src/providers/search.provider.ts +2 -5
- package/providers/fake/src/providers/store.provider.ts +2 -3
- package/providers/fake/src/test/cart.provider.spec.ts +3 -3
- package/providers/fake/src/test/category.provider.spec.ts +2 -2
- package/providers/fake/src/test/price.provider.spec.ts +2 -2
- package/providers/fake/src/test/product.provider.spec.ts +8 -8
- package/providers/fake/src/test/test-utils.ts +1 -1
- package/providers/fake/tsconfig.json +2 -1
- package/providers/fake/tsconfig.spec.json +1 -3
- package/providers/posthog/package.json +4 -4
- package/providers/posthog/project.json +2 -2
- package/providers/posthog/src/core/initialize.ts +2 -2
- package/providers/posthog/src/index.ts +3 -3
- package/providers/posthog/tsconfig.json +2 -1
- package/tsconfig.base.json +3 -4
- 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/otel/README.md +0 -227
- package/otel/eslint.config.mjs +0 -23
- package/otel/package.json +0 -11
- package/otel/pnpm-lock.yaml +0 -805
- package/otel/project.json +0 -33
- package/otel/src/index.ts +0 -22
- package/otel/src/metrics.ts +0 -76
- package/otel/src/provider-instrumentation.ts +0 -108
- package/otel/src/test/otel.spec.ts +0 -8
- package/otel/src/trace-decorator.ts +0 -226
- package/otel/src/tracer.ts +0 -83
- package/otel/src/trpc-middleware.ts +0 -128
- package/otel/tsconfig.json +0 -23
- package/otel/tsconfig.lib.json +0 -23
- package/otel/tsconfig.spec.json +0 -28
- package/otel/vite.config.ts +0 -24
- 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/README.md +0 -7
- package/trpc/__mocks__/superjson.js +0 -25
- package/trpc/eslint.config.mjs +0 -19
- package/trpc/jest.config.ts +0 -14
- package/trpc/package.json +0 -14
- package/trpc/project.json +0 -31
- package/trpc/src/client.ts +0 -175
- package/trpc/src/index.ts +0 -44
- package/trpc/src/integration.spec.ts +0 -223
- package/trpc/src/server.ts +0 -125
- package/trpc/src/test-utils.ts +0 -31
- package/trpc/src/transparent-client.spec.ts +0 -162
- package/trpc/src/types.ts +0 -144
- package/trpc/tsconfig.json +0 -16
- package/trpc/tsconfig.lib.json +0 -10
- package/trpc/tsconfig.spec.json +0 -15
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
|
|
2
|
+
import type {
|
|
3
|
+
Cache,
|
|
4
|
+
Checkout,
|
|
5
|
+
RequestContext,
|
|
6
|
+
PaymentMethod,
|
|
7
|
+
ShippingMethod,
|
|
8
|
+
CheckoutMutationInitiateCheckout,
|
|
9
|
+
CheckoutMutationSetShippingAddress,
|
|
10
|
+
CheckoutMutationFinalizeCheckout,
|
|
11
|
+
CheckoutMutationAddPaymentInstruction,
|
|
12
|
+
CheckoutMutationRemovePaymentInstruction,
|
|
13
|
+
CheckoutMutationSetShippingInstruction,
|
|
14
|
+
CheckoutQueryById,
|
|
15
|
+
CheckoutQueryForAvailablePaymentMethods,
|
|
16
|
+
CheckoutQueryForAvailableShippingMethods,
|
|
17
|
+
CheckoutIdentifier,
|
|
18
|
+
Currency,
|
|
19
|
+
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
|
+
|
|
30
|
+
|
|
31
|
+
export class CheckoutNotReadyForFinalizationError extends Error {
|
|
32
|
+
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";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
export class CommercetoolsCheckoutProvider<
|
|
40
|
+
T extends Checkout = Checkout
|
|
41
|
+
> extends CheckoutProvider<T> {
|
|
42
|
+
protected config: CommercetoolsConfiguration;
|
|
43
|
+
|
|
44
|
+
constructor(config: CommercetoolsConfiguration, schema: z.ZodType<T>, cache: Cache) {
|
|
45
|
+
super(schema, cache);
|
|
46
|
+
|
|
47
|
+
this.config = config;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
protected async getClient(reqCtx: RequestContext) {
|
|
51
|
+
const client = await new CommercetoolsClient(this.config).getClient(reqCtx);
|
|
52
|
+
|
|
53
|
+
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()
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public async initiateCheckoutForCart(payload: CheckoutMutationInitiateCheckout, reqCtx: RequestContext): Promise<T> {
|
|
62
|
+
// so......we could copy the cart......
|
|
63
|
+
|
|
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();
|
|
75
|
+
// set the custom type to mark it as a checkout
|
|
76
|
+
|
|
77
|
+
const actions: MyCartUpdateAction[] = [
|
|
78
|
+
{
|
|
79
|
+
action: 'setCustomType',
|
|
80
|
+
type: {
|
|
81
|
+
typeId: 'type',
|
|
82
|
+
key: 'reactionaryCheckout'
|
|
83
|
+
},
|
|
84
|
+
fields: {
|
|
85
|
+
commerceToolsCartId: payload.cart.key,
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
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
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
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);
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
public async getById(payload: CheckoutQueryById, reqCtx: RequestContext): Promise<T | null> {
|
|
133
|
+
|
|
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();
|
|
140
|
+
|
|
141
|
+
return this.parseSingle(checkoutResponse.body, reqCtx);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
public async setShippingAddress(payload: CheckoutMutationSetShippingAddress, reqCtx: RequestContext): Promise<T> {
|
|
145
|
+
const client = await this.getClient(reqCtx);
|
|
146
|
+
|
|
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();
|
|
167
|
+
|
|
168
|
+
return this.parseSingle(checkoutResponse.body, reqCtx);
|
|
169
|
+
}
|
|
170
|
+
|
|
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();
|
|
178
|
+
|
|
179
|
+
const result: Array<ShippingMethod> = [];
|
|
180
|
+
const inputShippingMethods: CTShippingMethod[] = shippingMethodsResponse.body.results as CTShippingMethod[];
|
|
181
|
+
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
|
+
});
|
|
193
|
+
result.push(shippingMethod);
|
|
194
|
+
}
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
public async getAvailablePaymentMethods(payload: CheckoutQueryForAvailablePaymentMethods, reqCtx: RequestContext): Promise<PaymentMethod[]> {
|
|
199
|
+
// Commercetools does not have a concept of payment methods, as these are handled by the payment providers.
|
|
200
|
+
// So for now, we will return an empty array.
|
|
201
|
+
const staticMethods = this.getStaticPaymentMethods(payload.checkout, reqCtx);
|
|
202
|
+
|
|
203
|
+
const dynamicMethods: PaymentMethod[] = [];
|
|
204
|
+
// later we will also fetch any stored payment methods the user has...
|
|
205
|
+
|
|
206
|
+
return [...staticMethods, ...dynamicMethods];
|
|
207
|
+
}
|
|
208
|
+
|
|
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
|
|
223
|
+
},
|
|
224
|
+
paymentInterface: payload.paymentInstruction.paymentMethod.paymentProcessor
|
|
225
|
+
},
|
|
226
|
+
custom: {
|
|
227
|
+
type: {
|
|
228
|
+
typeId: 'type',
|
|
229
|
+
key: 'reactionaryPaymentCustomFields',
|
|
230
|
+
},
|
|
231
|
+
fields: {
|
|
232
|
+
'commerceToolsCartId': payload.checkout.key,
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
}).execute();
|
|
237
|
+
|
|
238
|
+
const version = (payload.checkout as CommercetoolsCheckoutIdentifier).version;
|
|
239
|
+
const actions: MyCartUpdateAction[] = [
|
|
240
|
+
{
|
|
241
|
+
action: 'addPayment',
|
|
242
|
+
payment: {
|
|
243
|
+
typeId: 'payment',
|
|
244
|
+
id: response.body.id
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
];
|
|
248
|
+
|
|
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
|
+
|
|
255
|
+
|
|
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.
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
// get newest version
|
|
262
|
+
/*
|
|
263
|
+
const newestVersion = await client.payments.withId({ ID: payload.paymentInstruction.key }).get().execute();
|
|
264
|
+
|
|
265
|
+
const cancelledVersion = await client.payments.withId({ ID: payload.paymentInstruction.key }).post({
|
|
266
|
+
body: {
|
|
267
|
+
version: newestVersion.body.version || 0,
|
|
268
|
+
actions: [
|
|
269
|
+
{
|
|
270
|
+
action: "addTransaction",
|
|
271
|
+
transaction: {
|
|
272
|
+
type: "CancelAuthorization",
|
|
273
|
+
amount: newestVersion.body.amountPlanned,
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
}).execute();
|
|
279
|
+
*/
|
|
280
|
+
// we set the planned amount to 0, which effectively cancels the payment, and also allows the backend to clean it up.
|
|
281
|
+
// Note: This does NOT remove the payment from the cart, as that would be a breaking change to the cart, and we want to avoid that during checkout.
|
|
282
|
+
// Instead, the payment will remain on the cart, but with status 'canceled', and can be removed later if needed.
|
|
283
|
+
// This also allows us to keep a record of the payment instruction for auditing purposes.
|
|
284
|
+
// The cart can be re-used, and a new payment instruction can be added to it later.
|
|
285
|
+
// The frontend should ignore any payment instructions with status 'canceled' when displaying payment options to the user.
|
|
286
|
+
|
|
287
|
+
// Now add the payment to the cart
|
|
288
|
+
/*
|
|
289
|
+
const ctId = payload.checkout as CommercetoolsCheckoutIdentifier
|
|
290
|
+
const updatedCart = await client.carts.withId({ ID: ctId.key }).post({
|
|
291
|
+
body: {
|
|
292
|
+
version: ctId.version,
|
|
293
|
+
actions: [
|
|
294
|
+
{
|
|
295
|
+
'action': 'removePayment',
|
|
296
|
+
'payment': {
|
|
297
|
+
'typeId': 'payment',
|
|
298
|
+
'id': payload.paymentInstruction.key
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
}).execute();
|
|
304
|
+
|
|
305
|
+
const response = await client.payments.withId({ ID: payload.paymentInstruction.key }).delete({
|
|
306
|
+
queryArgs: {
|
|
307
|
+
version: newestVersion.body.version || 0
|
|
308
|
+
}
|
|
309
|
+
}).execute();
|
|
310
|
+
*/
|
|
311
|
+
|
|
312
|
+
const checkout = await this.getById({ identifier: payload.checkout }, reqCtx);
|
|
313
|
+
return checkout!
|
|
314
|
+
|
|
315
|
+
}
|
|
316
|
+
|
|
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
|
+
|
|
322
|
+
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
|
+
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
return this.applyActions(payload.checkout as CommercetoolsCheckoutIdentifier, actions, reqCtx);
|
|
349
|
+
}
|
|
350
|
+
|
|
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) {
|
|
354
|
+
throw new CheckoutNotReadyForFinalizationError(payload.checkout);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const client = await this.getClient(reqCtx);
|
|
358
|
+
const ctId = payload.checkout as CommercetoolsCheckoutIdentifier;
|
|
359
|
+
|
|
360
|
+
// 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
|
+
});
|
|
374
|
+
|
|
375
|
+
return this.applyActions(payload.checkout as CommercetoolsCheckoutIdentifier, actions, reqCtx);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
protected async applyActions(
|
|
382
|
+
checkout: CheckoutIdentifier,
|
|
383
|
+
actions: MyCartUpdateAction[],
|
|
384
|
+
reqCtx: RequestContext
|
|
385
|
+
): Promise<T> {
|
|
386
|
+
const client = await this.getClient(reqCtx);
|
|
387
|
+
const ctId = checkout as CommercetoolsCheckoutIdentifier;
|
|
388
|
+
|
|
389
|
+
|
|
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();
|
|
403
|
+
|
|
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
|
+
}
|
|
412
|
+
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Extension point, to allow filtering the options, or adding new ones, like invoicing for b2b.
|
|
417
|
+
*
|
|
418
|
+
* Usecase: Override this, if you need to change the payment options based on the request context.
|
|
419
|
+
* @param reqCtx
|
|
420
|
+
* @returns
|
|
421
|
+
*/
|
|
422
|
+
protected getStaticPaymentMethods(_checkout: CheckoutIdentifier, reqCtx: RequestContext): PaymentMethod[] {
|
|
423
|
+
return this.config.paymentMethods || [];
|
|
424
|
+
}
|
|
425
|
+
|
|
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,
|
|
490
|
+
currency,
|
|
491
|
+
},
|
|
492
|
+
totalSurcharge: {
|
|
493
|
+
value: surchargeTotal / 100,
|
|
494
|
+
currency,
|
|
495
|
+
},
|
|
496
|
+
totalShipping: {
|
|
497
|
+
value: shippingTotal / 100,
|
|
498
|
+
currency: remote.shippingInfo?.price.currencyCode as Currency,
|
|
499
|
+
},
|
|
500
|
+
totalProductPrice: {
|
|
501
|
+
value: productTotal / 100,
|
|
502
|
+
currency,
|
|
503
|
+
},
|
|
504
|
+
grandTotal: {
|
|
505
|
+
value: grandTotal / 100,
|
|
506
|
+
currency,
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
for (const remoteItem of remote.lineItems) {
|
|
511
|
+
const item = CheckoutItemSchema.parse({});
|
|
512
|
+
|
|
513
|
+
item.identifier.key = remoteItem.id;
|
|
514
|
+
item.sku.key = remoteItem.variant.sku || '';
|
|
515
|
+
item.quantity = remoteItem.quantity;
|
|
516
|
+
|
|
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;
|
|
521
|
+
|
|
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
|
+
};
|
|
540
|
+
|
|
541
|
+
result.items.push(item);
|
|
542
|
+
}
|
|
543
|
+
|
|
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
|
+
};
|
|
552
|
+
|
|
553
|
+
return this.assert(result);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
protected isReadyForFinalization(checkout: T): boolean {
|
|
557
|
+
// we should have a billing address
|
|
558
|
+
if (!checkout.billingAddress) return false;
|
|
559
|
+
|
|
560
|
+
// we should know how to ship it
|
|
561
|
+
if (!checkout.shippingInstruction) return false;
|
|
562
|
+
|
|
563
|
+
// and it should ship either to an address or a pickup point
|
|
564
|
+
if (!checkout.shippingAddress && !checkout.shippingInstruction.pickupPoint) return false;
|
|
565
|
+
|
|
566
|
+
// and it should be paid for
|
|
567
|
+
if (checkout.paymentInstructions.length === 0) return false;
|
|
568
|
+
|
|
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;
|
|
571
|
+
|
|
572
|
+
return true
|
|
573
|
+
}
|
|
574
|
+
|
|
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
|
+
};
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
|
|
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
|
+
});
|
|
593
|
+
|
|
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';
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return PaymentInstructionSchema.parse(newModel);
|
|
608
|
+
}
|
|
609
|
+
|
|
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
|
+
}
|
|
621
|
+
|
|
622
|
+
protected parseShippingInstruction(remote: CTCart): ShippingInstruction | undefined {
|
|
623
|
+
if (!remote.shippingInfo) return undefined;
|
|
624
|
+
|
|
625
|
+
const instructions = remote.custom?.fields['shippingInstruction'] || '';
|
|
626
|
+
const consentForUnattendedDelivery = remote.custom?.fields['consentForUnattendedDelivery'] === 'true' || false;
|
|
627
|
+
const pickupPoint = remote.custom?.fields['pickupPointId'] || '';
|
|
628
|
+
|
|
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
|
+
});
|
|
641
|
+
|
|
642
|
+
return shippingInstruction;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
IdentityProvider,
|
|
8
8
|
type IdentityMutationRegister,
|
|
9
9
|
} from '@reactionary/core';
|
|
10
|
-
import type { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
10
|
+
import type { CommercetoolsConfiguration } from '../schema/configuration.schema.js';
|
|
11
11
|
import type z from 'zod';
|
|
12
|
-
import { CommercetoolsClient } from '../core/client';
|
|
12
|
+
import { CommercetoolsClient } from '../core/client.js';
|
|
13
13
|
|
|
14
14
|
export class CommercetoolsIdentityProvider<
|
|
15
15
|
T extends Identity = Identity
|
|
@@ -37,26 +37,26 @@ export class CommercetoolsIdentityProvider<
|
|
|
37
37
|
payload: IdentityMutationLogin,
|
|
38
38
|
reqCtx: RequestContext
|
|
39
39
|
): Promise<T> {
|
|
40
|
-
await new CommercetoolsClient(this.config).login(payload.username, payload.password, reqCtx);
|
|
40
|
+
const identity = await new CommercetoolsClient(this.config).login(payload.username, payload.password, reqCtx);
|
|
41
41
|
|
|
42
|
-
return
|
|
42
|
+
return identity as T;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
public override async logout(
|
|
46
46
|
payload: Record<string, never>,
|
|
47
47
|
reqCtx: RequestContext
|
|
48
48
|
): Promise<T> {
|
|
49
|
-
await new CommercetoolsClient(this.config).logout(reqCtx);
|
|
49
|
+
const identity = await new CommercetoolsClient(this.config).logout(reqCtx);
|
|
50
50
|
|
|
51
|
-
return
|
|
51
|
+
return identity as T;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
public override async register(
|
|
55
55
|
payload: IdentityMutationRegister,
|
|
56
56
|
reqCtx: RequestContext
|
|
57
57
|
): Promise<T> {
|
|
58
|
-
await new CommercetoolsClient(this.config).register(payload.username, payload.password, reqCtx);
|
|
58
|
+
const identity = await new CommercetoolsClient(this.config).register(payload.username, payload.password, reqCtx);
|
|
59
59
|
|
|
60
|
-
return
|
|
60
|
+
return identity as T;
|
|
61
61
|
}
|
|
62
62
|
}
|