@reactionary/commercetools 0.6.3
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/README.md +11 -0
- package/capabilities/cart.capability.js +324 -0
- package/capabilities/category.capability.js +198 -0
- package/capabilities/checkout.capability.js +374 -0
- package/capabilities/identity.capability.js +95 -0
- package/capabilities/index.js +15 -0
- package/capabilities/inventory.capability.js +63 -0
- package/capabilities/order-search.capability.js +106 -0
- package/capabilities/order.capability.js +48 -0
- package/capabilities/price.capability.js +114 -0
- package/capabilities/product-associations.capability.js +120 -0
- package/capabilities/product-list.capability.js +393 -0
- package/capabilities/product-reviews.capability.js +193 -0
- package/capabilities/product-search.capability.js +297 -0
- package/capabilities/product.capability.js +103 -0
- package/capabilities/profile.capability.js +337 -0
- package/capabilities/store.capability.js +51 -0
- package/core/capability-descriptors.js +274 -0
- package/core/client.js +318 -0
- package/core/initialize.js +47 -0
- package/core/initialize.types.js +8 -0
- package/core/token-cache.js +36 -0
- package/factories/cart/cart.factory.js +110 -0
- package/factories/category/category.factory.js +40 -0
- package/factories/checkout/checkout-initializer-overrides.example.js +67 -0
- package/factories/checkout/checkout.factory.js +235 -0
- package/factories/identity/identity.factory.js +14 -0
- package/factories/inventory/inventory.factory.js +30 -0
- package/factories/order/order.factory.js +114 -0
- package/factories/order-search/order-search.factory.js +68 -0
- package/factories/price/price.factory.js +52 -0
- package/factories/product/product-capability-custom-method-only.example.js +38 -0
- package/factories/product/product-capability-schema-signature-extension.example.js +46 -0
- package/factories/product/product-factory-baseline.example.js +10 -0
- package/factories/product/product-factory-schema-and-parse-extension.example.js +25 -0
- package/factories/product/product-factory-schema-extension.example.js +18 -0
- package/factories/product/product-initializer-factory-extension.example.js +33 -0
- package/factories/product/product.factory.js +151 -0
- package/factories/product/utils.example.js +8 -0
- package/factories/product-associations/product-associations.factory.js +63 -0
- package/factories/product-list/product-list.factory.js +65 -0
- package/factories/product-reviews/product-reviews.factory.js +42 -0
- package/factories/product-search/product-search.factory.js +114 -0
- package/factories/profile/profile.factory.js +62 -0
- package/factories/store/store.factory.js +29 -0
- package/index.js +28 -0
- package/package.json +17 -0
- package/schema/capabilities.schema.js +46 -0
- package/schema/commercetools.schema.js +30 -0
- package/schema/configuration.schema.js +19 -0
- package/schema/session.schema.js +9 -0
- package/src/capabilities/cart.capability.d.ts +34 -0
- package/src/capabilities/category.capability.d.ts +47 -0
- package/src/capabilities/checkout.capability.d.ts +39 -0
- package/src/capabilities/identity.capability.d.ts +14 -0
- package/src/capabilities/index.d.ts +15 -0
- package/src/capabilities/inventory.capability.d.ts +13 -0
- package/src/capabilities/order-search.capability.d.ts +13 -0
- package/src/capabilities/order.capability.d.ts +13 -0
- package/src/capabilities/price.capability.d.ts +18 -0
- package/src/capabilities/product-associations.capability.d.ts +17 -0
- package/src/capabilities/product-list.capability.d.ts +29 -0
- package/src/capabilities/product-reviews.capability.d.ts +18 -0
- package/src/capabilities/product-search.capability.d.ts +82 -0
- package/src/capabilities/product.capability.d.ts +17 -0
- package/src/capabilities/profile.capability.d.ts +30 -0
- package/src/capabilities/store.capability.d.ts +13 -0
- package/src/core/capability-descriptors.d.ts +16 -0
- package/src/core/client.d.ts +55 -0
- package/src/core/initialize.d.ts +5 -0
- package/src/core/initialize.types.d.ts +118 -0
- package/src/core/token-cache.d.ts +9 -0
- package/src/factories/cart/cart.factory.d.ts +14 -0
- package/src/factories/category/category.factory.d.ts +11 -0
- package/src/factories/checkout/checkout-initializer-overrides.example.d.ts +1 -0
- package/src/factories/checkout/checkout.factory.d.ts +18 -0
- package/src/factories/identity/identity.factory.d.ts +8 -0
- package/src/factories/inventory/inventory.factory.d.ts +9 -0
- package/src/factories/order/order.factory.d.ts +9 -0
- package/src/factories/order-search/order-search.factory.d.ts +11 -0
- package/src/factories/price/price.factory.d.ts +11 -0
- package/src/factories/product/product-capability-custom-method-only.example.d.ts +1 -0
- package/src/factories/product/product-capability-schema-signature-extension.example.d.ts +1 -0
- package/src/factories/product/product-factory-baseline.example.d.ts +1 -0
- package/src/factories/product/product-factory-schema-and-parse-extension.example.d.ts +1 -0
- package/src/factories/product/product-factory-schema-extension.example.d.ts +1 -0
- package/src/factories/product/product-initializer-factory-extension.example.d.ts +1 -0
- package/src/factories/product/product.factory.d.ts +18 -0
- package/src/factories/product/utils.example.d.ts +4 -0
- package/src/factories/product-associations/product-associations.factory.d.ts +15 -0
- package/src/factories/product-list/product-list.factory.d.ts +17 -0
- package/src/factories/product-reviews/product-reviews.factory.d.ts +14 -0
- package/src/factories/product-search/product-search.factory.d.ts +13 -0
- package/src/factories/profile/profile.factory.d.ts +11 -0
- package/src/factories/store/store.factory.d.ts +9 -0
- package/src/index.d.ts +28 -0
- package/src/schema/capabilities.schema.d.ts +104 -0
- package/src/schema/commercetools.schema.d.ts +30 -0
- package/src/schema/configuration.schema.d.ts +30 -0
- package/src/schema/session.schema.d.ts +7 -0
- package/src/test/client-builder-merge-extensions.example.d.ts +1 -0
- package/src/test/test-utils.d.ts +27 -0
- package/test/client-builder-merge-extensions.example.js +94 -0
- package/test/test-utils.js +27 -0
package/core/client.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { ClientBuilder } from "@commercetools/ts-client";
|
|
2
|
+
import {
|
|
3
|
+
createApiBuilderFromCtpClient
|
|
4
|
+
} from "@commercetools/platform-sdk";
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
6
|
+
import {
|
|
7
|
+
AnonymousIdentitySchema,
|
|
8
|
+
RegisteredIdentitySchema
|
|
9
|
+
} from "@reactionary/core";
|
|
10
|
+
import * as crypto from "crypto";
|
|
11
|
+
import createDebug from "debug";
|
|
12
|
+
import { RequestContextTokenCache } from "./token-cache.js";
|
|
13
|
+
import { CommercetoolsSessionSchema } from "../schema/session.schema.js";
|
|
14
|
+
const debug = createDebug("reactionary:commercetools");
|
|
15
|
+
const PROVIDER_SESSION_KEY = "COMMERCETOOLS_PROVIDER";
|
|
16
|
+
class CommercetoolsAPI {
|
|
17
|
+
config;
|
|
18
|
+
context;
|
|
19
|
+
tokenCache;
|
|
20
|
+
client;
|
|
21
|
+
adminClient;
|
|
22
|
+
constructor(config, context) {
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.context = context;
|
|
25
|
+
this.tokenCache = new RequestContextTokenCache(this.context, PROVIDER_SESSION_KEY);
|
|
26
|
+
}
|
|
27
|
+
async getClient() {
|
|
28
|
+
if (!this.client) {
|
|
29
|
+
this.client = this.createClient();
|
|
30
|
+
}
|
|
31
|
+
return this.client;
|
|
32
|
+
}
|
|
33
|
+
async getAdminClient() {
|
|
34
|
+
if (!this.adminClient) {
|
|
35
|
+
this.adminClient = this.createAdminClient();
|
|
36
|
+
}
|
|
37
|
+
return this.adminClient;
|
|
38
|
+
}
|
|
39
|
+
getSessionData() {
|
|
40
|
+
return this.context.session[PROVIDER_SESSION_KEY] ? this.context.session[PROVIDER_SESSION_KEY] : CommercetoolsSessionSchema.parse({});
|
|
41
|
+
}
|
|
42
|
+
setSessionData(sessionData) {
|
|
43
|
+
const existingData = this.context.session[PROVIDER_SESSION_KEY];
|
|
44
|
+
this.context.session[PROVIDER_SESSION_KEY] = {
|
|
45
|
+
...existingData,
|
|
46
|
+
...sessionData
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Only caches it pr session for now...... but still better than every call
|
|
51
|
+
* @param key
|
|
52
|
+
* @returns
|
|
53
|
+
*/
|
|
54
|
+
async resolveChannelIdByKey(key) {
|
|
55
|
+
const sessionData = this.getSessionData();
|
|
56
|
+
const cacheKey = `___channel_${key}`;
|
|
57
|
+
const cachedValue = sessionData[cacheKey];
|
|
58
|
+
if (cachedValue) {
|
|
59
|
+
if (debug.enabled) {
|
|
60
|
+
debug(`Resolved channel ${key} from cache`);
|
|
61
|
+
}
|
|
62
|
+
return cachedValue + "";
|
|
63
|
+
}
|
|
64
|
+
const client = await this.getAdminClient();
|
|
65
|
+
const response = await client.withProjectKey({ projectKey: this.config.projectKey }).channels().withKey({ key }).get().execute();
|
|
66
|
+
const channel = response.body;
|
|
67
|
+
this.setSessionData({
|
|
68
|
+
cacheKey: channel.id
|
|
69
|
+
});
|
|
70
|
+
if (debug.enabled) {
|
|
71
|
+
debug(`Resolved channel ${key} from API and cached it`);
|
|
72
|
+
}
|
|
73
|
+
return channel.id;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Only caches it pr session for now...... but still better than every call
|
|
77
|
+
* @param key
|
|
78
|
+
* @returns
|
|
79
|
+
*/
|
|
80
|
+
async resolveChannelIdByRole(role) {
|
|
81
|
+
const sessionData = this.getSessionData();
|
|
82
|
+
const cacheKey = `___channel_role_${role}`;
|
|
83
|
+
const cachedValue = sessionData[cacheKey];
|
|
84
|
+
if (cachedValue) {
|
|
85
|
+
if (debug.enabled) {
|
|
86
|
+
debug(`Resolved channel ${role} from cache`);
|
|
87
|
+
}
|
|
88
|
+
return cachedValue + "";
|
|
89
|
+
}
|
|
90
|
+
const client = await this.getAdminClient();
|
|
91
|
+
const response = await client.withProjectKey({ projectKey: this.config.projectKey }).channels().get({
|
|
92
|
+
queryArgs: {
|
|
93
|
+
where: `roles contains any (:role)`,
|
|
94
|
+
"var.role": role
|
|
95
|
+
}
|
|
96
|
+
}).execute();
|
|
97
|
+
const channels = response.body;
|
|
98
|
+
if (channels.results.length === 0) {
|
|
99
|
+
throw new Error(`No channel found with role ${role}`);
|
|
100
|
+
}
|
|
101
|
+
const channel = channels.results[0];
|
|
102
|
+
this.setSessionData({
|
|
103
|
+
[cacheKey]: channel.id
|
|
104
|
+
});
|
|
105
|
+
if (debug.enabled) {
|
|
106
|
+
debug(`Resolved channel ${role} from API and cached it`);
|
|
107
|
+
}
|
|
108
|
+
return channel.id;
|
|
109
|
+
}
|
|
110
|
+
async createAdminClient() {
|
|
111
|
+
let builder = this.createBaseClientBuilder();
|
|
112
|
+
builder = builder.withAnonymousSessionFlow({
|
|
113
|
+
credentials: {
|
|
114
|
+
clientId: this.config.clientId,
|
|
115
|
+
clientSecret: this.config.clientSecret
|
|
116
|
+
},
|
|
117
|
+
host: this.config.authUrl,
|
|
118
|
+
projectKey: this.config.projectKey
|
|
119
|
+
});
|
|
120
|
+
return createApiBuilderFromCtpClient(builder.build());
|
|
121
|
+
}
|
|
122
|
+
async createClient() {
|
|
123
|
+
let session = await this.tokenCache.get();
|
|
124
|
+
const isNewSession = !session || !session.refreshToken;
|
|
125
|
+
if (isNewSession) {
|
|
126
|
+
await this.becomeGuest();
|
|
127
|
+
session = await this.tokenCache.get();
|
|
128
|
+
}
|
|
129
|
+
let builder = this.createBaseClientBuilder();
|
|
130
|
+
builder = builder.withRefreshTokenFlow({
|
|
131
|
+
credentials: {
|
|
132
|
+
clientId: this.config.clientId,
|
|
133
|
+
clientSecret: this.config.clientSecret
|
|
134
|
+
},
|
|
135
|
+
host: this.config.authUrl,
|
|
136
|
+
projectKey: this.config.projectKey,
|
|
137
|
+
refreshToken: session?.refreshToken || "",
|
|
138
|
+
tokenCache: this.tokenCache
|
|
139
|
+
});
|
|
140
|
+
return createApiBuilderFromCtpClient(builder.build());
|
|
141
|
+
}
|
|
142
|
+
async register(username, password) {
|
|
143
|
+
const registrationBuilder = this.createBaseClientBuilder().withAnonymousSessionFlow({
|
|
144
|
+
host: this.config.authUrl,
|
|
145
|
+
projectKey: this.config.projectKey,
|
|
146
|
+
credentials: {
|
|
147
|
+
clientId: this.config.clientId,
|
|
148
|
+
clientSecret: this.config.clientSecret
|
|
149
|
+
},
|
|
150
|
+
scopes: this.config.scopes
|
|
151
|
+
});
|
|
152
|
+
const registrationClient = createApiBuilderFromCtpClient(
|
|
153
|
+
registrationBuilder.build()
|
|
154
|
+
);
|
|
155
|
+
const registration = await registrationClient.withProjectKey({ projectKey: this.config.projectKey }).me().signup().post({
|
|
156
|
+
body: {
|
|
157
|
+
email: username,
|
|
158
|
+
password
|
|
159
|
+
}
|
|
160
|
+
}).execute();
|
|
161
|
+
const login = await this.login(username, password);
|
|
162
|
+
return login;
|
|
163
|
+
}
|
|
164
|
+
async login(username, password) {
|
|
165
|
+
const loginBuilder = this.createBaseClientBuilder().withPasswordFlow({
|
|
166
|
+
host: this.config.authUrl,
|
|
167
|
+
projectKey: this.config.projectKey,
|
|
168
|
+
credentials: {
|
|
169
|
+
clientId: this.config.clientId,
|
|
170
|
+
clientSecret: this.config.clientSecret,
|
|
171
|
+
user: { username, password }
|
|
172
|
+
},
|
|
173
|
+
tokenCache: this.tokenCache,
|
|
174
|
+
scopes: this.config.scopes
|
|
175
|
+
});
|
|
176
|
+
const loginClient = createApiBuilderFromCtpClient(loginBuilder.build());
|
|
177
|
+
const login = await loginClient.withProjectKey({ projectKey: this.config.projectKey }).me().login().post({
|
|
178
|
+
body: {
|
|
179
|
+
email: username,
|
|
180
|
+
password
|
|
181
|
+
}
|
|
182
|
+
}).execute();
|
|
183
|
+
const self = await loginClient.withProjectKey({ projectKey: this.config.projectKey }).me().get({}).execute();
|
|
184
|
+
return RegisteredIdentitySchema.parse({
|
|
185
|
+
type: "Registered",
|
|
186
|
+
id: {
|
|
187
|
+
userId: self.body.id
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async logout() {
|
|
192
|
+
await this.tokenCache.set({ token: "", refreshToken: "", expirationTime: 0 });
|
|
193
|
+
const identity = {
|
|
194
|
+
type: "Anonymous"
|
|
195
|
+
};
|
|
196
|
+
return identity;
|
|
197
|
+
}
|
|
198
|
+
// FIXME: This can fail if the short-lived access token has expired. In other words, probably missing a token refresh.
|
|
199
|
+
async introspect() {
|
|
200
|
+
const session = await this.tokenCache.get();
|
|
201
|
+
if (!session || !session.token) {
|
|
202
|
+
const identity = {
|
|
203
|
+
type: "Anonymous"
|
|
204
|
+
};
|
|
205
|
+
return identity;
|
|
206
|
+
}
|
|
207
|
+
const authHeader = "Basic " + Buffer.from(
|
|
208
|
+
`${this.config.clientId}:${this.config.clientSecret}`
|
|
209
|
+
).toString("base64");
|
|
210
|
+
const introspectionUrl = `${this.config.authUrl}/oauth/introspect`;
|
|
211
|
+
const response = await fetch(introspectionUrl, {
|
|
212
|
+
method: "POST",
|
|
213
|
+
headers: {
|
|
214
|
+
Authorization: authHeader,
|
|
215
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
216
|
+
},
|
|
217
|
+
body: new URLSearchParams({
|
|
218
|
+
token: session.token
|
|
219
|
+
})
|
|
220
|
+
});
|
|
221
|
+
const body = await response.json();
|
|
222
|
+
if (!body) {
|
|
223
|
+
return AnonymousIdentitySchema.parse({});
|
|
224
|
+
}
|
|
225
|
+
const scopes = body.scope + "";
|
|
226
|
+
if (scopes.indexOf("anonymous_id") > -1) {
|
|
227
|
+
const s = scopes.split(" ");
|
|
228
|
+
const idScope = s.find((x) => x.startsWith("anonymous_id"));
|
|
229
|
+
const id = idScope?.split(":")[1] || "";
|
|
230
|
+
const identity = {
|
|
231
|
+
id: {
|
|
232
|
+
userId: id
|
|
233
|
+
},
|
|
234
|
+
type: "Guest"
|
|
235
|
+
};
|
|
236
|
+
return identity;
|
|
237
|
+
}
|
|
238
|
+
if (scopes.indexOf("customer_id") > -1) {
|
|
239
|
+
const s = scopes.split(" ");
|
|
240
|
+
const idScope = s.find((x) => x.startsWith("customer_id"));
|
|
241
|
+
const id = idScope?.split(":")[1] || "";
|
|
242
|
+
const identity = {
|
|
243
|
+
id: {
|
|
244
|
+
userId: id
|
|
245
|
+
},
|
|
246
|
+
type: "Registered"
|
|
247
|
+
};
|
|
248
|
+
return identity;
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
type: "Anonymous"
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
async becomeGuest() {
|
|
255
|
+
const credentials = Buffer.from(
|
|
256
|
+
`${this.config.clientId}:${this.config.clientSecret}`
|
|
257
|
+
).toString("base64");
|
|
258
|
+
const response = await fetch(
|
|
259
|
+
`${this.config.authUrl}/oauth/${this.config.projectKey}/anonymous/token?grant_type=client_credentials`,
|
|
260
|
+
{
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: {
|
|
263
|
+
Authorization: `Basic ${credentials}`,
|
|
264
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
265
|
+
},
|
|
266
|
+
body: new URLSearchParams({
|
|
267
|
+
grant_type: "client_credentials"
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
const result = await response.json();
|
|
272
|
+
this.tokenCache.set({
|
|
273
|
+
expirationTime: Date.now() + Number(result.expires_in) * 1e3 - 5 * 60 * 1e3,
|
|
274
|
+
token: result.access_token,
|
|
275
|
+
refreshToken: result.refresh_token
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
createBaseClientBuilder() {
|
|
279
|
+
let builder = new ClientBuilder().withProjectKey(this.config.projectKey).withQueueMiddleware({
|
|
280
|
+
concurrency: 20
|
|
281
|
+
}).withConcurrentModificationMiddleware({
|
|
282
|
+
concurrentModificationHandlerFn: (version, request) => {
|
|
283
|
+
console.log(
|
|
284
|
+
`Concurrent modification error, retry with version ${version}`
|
|
285
|
+
);
|
|
286
|
+
const body = request.body;
|
|
287
|
+
body["version"] = version;
|
|
288
|
+
return Promise.resolve(body);
|
|
289
|
+
}
|
|
290
|
+
}).withHttpMiddleware({
|
|
291
|
+
retryConfig: {
|
|
292
|
+
backoff: true,
|
|
293
|
+
maxRetries: 3,
|
|
294
|
+
retryDelay: 500,
|
|
295
|
+
retryOnAbort: true,
|
|
296
|
+
retryCodes: [500, 429, 420],
|
|
297
|
+
maxDelay: 5e3
|
|
298
|
+
},
|
|
299
|
+
enableRetry: true,
|
|
300
|
+
includeResponseHeaders: true,
|
|
301
|
+
maskSensitiveHeaderData: false,
|
|
302
|
+
host: this.config.apiUrl,
|
|
303
|
+
httpClient: fetch
|
|
304
|
+
});
|
|
305
|
+
const correlationId = this.context.correlationId || "REACTIONARY-" + (typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : randomUUID());
|
|
306
|
+
builder = builder.withCorrelationIdMiddleware({
|
|
307
|
+
generate: () => correlationId
|
|
308
|
+
});
|
|
309
|
+
if (debug.enabled) {
|
|
310
|
+
builder = builder.withLoggerMiddleware();
|
|
311
|
+
}
|
|
312
|
+
return builder;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
export {
|
|
316
|
+
CommercetoolsAPI,
|
|
317
|
+
PROVIDER_SESSION_KEY
|
|
318
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CommercetoolsCapabilitiesSchema
|
|
3
|
+
} from "../schema/capabilities.schema.js";
|
|
4
|
+
import {
|
|
5
|
+
CommercetoolsConfigurationSchema
|
|
6
|
+
} from "../schema/configuration.schema.js";
|
|
7
|
+
import { CommercetoolsAPI } from "./client.js";
|
|
8
|
+
import {
|
|
9
|
+
capabilityDescriptors,
|
|
10
|
+
capabilityKeys
|
|
11
|
+
} from "./capability-descriptors.js";
|
|
12
|
+
import {
|
|
13
|
+
resolveCapabilityWithFactory
|
|
14
|
+
} from "./initialize.types.js";
|
|
15
|
+
function withCommercetoolsCapabilities(configuration, capabilities) {
|
|
16
|
+
return (cache, context) => {
|
|
17
|
+
const client = {};
|
|
18
|
+
const config = CommercetoolsConfigurationSchema.parse(configuration);
|
|
19
|
+
const caps = CommercetoolsCapabilitiesSchema.parse(capabilities);
|
|
20
|
+
const commercetoolsApi = new CommercetoolsAPI(config, context);
|
|
21
|
+
const buildCapabilityArgs = (factory) => ({
|
|
22
|
+
cache,
|
|
23
|
+
context,
|
|
24
|
+
config,
|
|
25
|
+
commercetoolsApi,
|
|
26
|
+
factory
|
|
27
|
+
});
|
|
28
|
+
for (const key of capabilityKeys) {
|
|
29
|
+
const descriptor = capabilityDescriptors[key];
|
|
30
|
+
if (!descriptor.isEnabled(caps)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
client[key] = resolveCapabilityWithFactory(
|
|
34
|
+
descriptor.getOverride(capabilities),
|
|
35
|
+
{
|
|
36
|
+
factory: descriptor.createDefaultFactory(),
|
|
37
|
+
capability: descriptor.createDefaultCapability
|
|
38
|
+
},
|
|
39
|
+
buildCapabilityArgs
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return client;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
withCommercetoolsCapabilities
|
|
47
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
function resolveCapabilityWithFactory(capability, defaults, buildCapabilityArgs) {
|
|
2
|
+
const factory = capability?.factory ?? defaults.factory;
|
|
3
|
+
const capabilityFactory = capability?.capability ?? defaults.capability;
|
|
4
|
+
return capabilityFactory(buildCapabilityArgs(factory));
|
|
5
|
+
}
|
|
6
|
+
export {
|
|
7
|
+
resolveCapabilityWithFactory
|
|
8
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { CommercetoolsSessionSchema } from "../schema/session.schema.js";
|
|
2
|
+
class RequestContextTokenCache {
|
|
3
|
+
constructor(context, sessionProviderKey) {
|
|
4
|
+
this.context = context;
|
|
5
|
+
this.sessionProviderKey = sessionProviderKey;
|
|
6
|
+
}
|
|
7
|
+
async get(tokenCacheOptions) {
|
|
8
|
+
const session = CommercetoolsSessionSchema.parse(
|
|
9
|
+
this.context.session[this.sessionProviderKey] || {}
|
|
10
|
+
);
|
|
11
|
+
if (!session) {
|
|
12
|
+
return {
|
|
13
|
+
refreshToken: void 0,
|
|
14
|
+
token: "",
|
|
15
|
+
expirationTime: (/* @__PURE__ */ new Date()).getTime()
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
refreshToken: session.refreshToken,
|
|
20
|
+
token: session.token,
|
|
21
|
+
expirationTime: session.expirationTime
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async set(cache, tokenCacheOptions) {
|
|
25
|
+
const session = CommercetoolsSessionSchema.parse(
|
|
26
|
+
this.context.session[this.sessionProviderKey] || {}
|
|
27
|
+
);
|
|
28
|
+
this.context.session[this.sessionProviderKey] = session;
|
|
29
|
+
session.refreshToken = cache.refreshToken;
|
|
30
|
+
session.token = cache.token;
|
|
31
|
+
session.expirationTime = cache.expirationTime;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
RequestContextTokenCache
|
|
36
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CartItemSchema
|
|
3
|
+
} from "@reactionary/core";
|
|
4
|
+
class CommercetoolsCartFactory {
|
|
5
|
+
cartSchema;
|
|
6
|
+
cartIdentifierSchema;
|
|
7
|
+
constructor(cartSchema, cartIdentifierSchema) {
|
|
8
|
+
this.cartSchema = cartSchema;
|
|
9
|
+
this.cartIdentifierSchema = cartIdentifierSchema;
|
|
10
|
+
}
|
|
11
|
+
parseCartIdentifier(_context, data) {
|
|
12
|
+
return this.cartIdentifierSchema.parse({
|
|
13
|
+
key: data.key || ""
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
parseCart(context, data) {
|
|
17
|
+
const identifier = this.parseCartIdentifier(context, {
|
|
18
|
+
key: data.id
|
|
19
|
+
});
|
|
20
|
+
const grandTotal = data.totalPrice.centAmount || 0;
|
|
21
|
+
const shippingTotal = data.shippingInfo?.price.centAmount || 0;
|
|
22
|
+
const productTotal = grandTotal - shippingTotal;
|
|
23
|
+
const taxTotal = data.taxedPrice?.totalTax?.centAmount || 0;
|
|
24
|
+
const discountTotal = data.discountOnTotalPrice?.discountedAmount.centAmount || 0;
|
|
25
|
+
const surchargeTotal = 0;
|
|
26
|
+
const currency = data.totalPrice.currencyCode;
|
|
27
|
+
const price = {
|
|
28
|
+
totalTax: {
|
|
29
|
+
value: taxTotal / 100,
|
|
30
|
+
currency
|
|
31
|
+
},
|
|
32
|
+
totalDiscount: {
|
|
33
|
+
value: discountTotal / 100,
|
|
34
|
+
currency
|
|
35
|
+
},
|
|
36
|
+
totalSurcharge: {
|
|
37
|
+
value: surchargeTotal / 100,
|
|
38
|
+
currency
|
|
39
|
+
},
|
|
40
|
+
totalShipping: {
|
|
41
|
+
value: shippingTotal / 100,
|
|
42
|
+
currency
|
|
43
|
+
},
|
|
44
|
+
totalProductPrice: {
|
|
45
|
+
value: productTotal / 100,
|
|
46
|
+
currency
|
|
47
|
+
},
|
|
48
|
+
grandTotal: {
|
|
49
|
+
value: grandTotal / 100,
|
|
50
|
+
currency
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const items = [];
|
|
54
|
+
for (const lineItem of data.lineItems) {
|
|
55
|
+
items.push(this.parseCartItem(lineItem));
|
|
56
|
+
}
|
|
57
|
+
const result = {
|
|
58
|
+
identifier,
|
|
59
|
+
userId: {
|
|
60
|
+
userId: "???"
|
|
61
|
+
},
|
|
62
|
+
name: data.custom?.fields["name"] || "",
|
|
63
|
+
description: data.custom?.fields["description"] || "",
|
|
64
|
+
price,
|
|
65
|
+
items,
|
|
66
|
+
appliedPromotions: []
|
|
67
|
+
};
|
|
68
|
+
return this.cartSchema.parse(result);
|
|
69
|
+
}
|
|
70
|
+
parseCartItem(lineItem) {
|
|
71
|
+
const unitPrice = lineItem.price.value.centAmount;
|
|
72
|
+
const totalPrice = lineItem.totalPrice.centAmount || 0;
|
|
73
|
+
const totalDiscount = lineItem.price.discounted?.value.centAmount || 0;
|
|
74
|
+
const unitDiscount = totalDiscount / lineItem.quantity;
|
|
75
|
+
const currency = lineItem.price.value.currencyCode.toUpperCase();
|
|
76
|
+
return CartItemSchema.parse({
|
|
77
|
+
identifier: {
|
|
78
|
+
key: lineItem.id
|
|
79
|
+
},
|
|
80
|
+
product: {
|
|
81
|
+
key: lineItem.productId
|
|
82
|
+
},
|
|
83
|
+
variant: {
|
|
84
|
+
sku: lineItem.variant.sku || ""
|
|
85
|
+
},
|
|
86
|
+
quantity: lineItem.quantity,
|
|
87
|
+
price: {
|
|
88
|
+
unitPrice: {
|
|
89
|
+
value: unitPrice / 100,
|
|
90
|
+
currency
|
|
91
|
+
},
|
|
92
|
+
unitDiscount: {
|
|
93
|
+
value: unitDiscount / 100,
|
|
94
|
+
currency
|
|
95
|
+
},
|
|
96
|
+
totalPrice: {
|
|
97
|
+
value: totalPrice / 100,
|
|
98
|
+
currency
|
|
99
|
+
},
|
|
100
|
+
totalDiscount: {
|
|
101
|
+
value: totalDiscount / 100,
|
|
102
|
+
currency
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
CommercetoolsCartFactory
|
|
110
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
} from "@reactionary/core";
|
|
3
|
+
class CommercetoolsCategoryFactory {
|
|
4
|
+
categorySchema;
|
|
5
|
+
categoryPaginatedResultSchema;
|
|
6
|
+
constructor(categorySchema, categoryPaginatedResultSchema) {
|
|
7
|
+
this.categorySchema = categorySchema;
|
|
8
|
+
this.categoryPaginatedResultSchema = categoryPaginatedResultSchema;
|
|
9
|
+
}
|
|
10
|
+
parseCategory(context, data) {
|
|
11
|
+
const identifier = { key: data.key || "" };
|
|
12
|
+
const result = {
|
|
13
|
+
identifier,
|
|
14
|
+
name: data.name[context.languageContext.locale] || "No Name",
|
|
15
|
+
slug: data.slug ? data.slug[context.languageContext.locale] || "" : "",
|
|
16
|
+
text: data.description ? data.description[context.languageContext.locale] || "" : "",
|
|
17
|
+
parentCategory: data.parent && data.parent.obj && data.parent.obj?.key ? { key: data.parent.obj.key } : void 0,
|
|
18
|
+
images: (data.assets || []).filter((asset) => asset.sources.length > 0).filter((asset) => asset.sources[0].contentType?.startsWith("image/")).map((asset) => ({
|
|
19
|
+
sourceUrl: asset.sources[0].uri,
|
|
20
|
+
altText: asset.description?.[context.languageContext.locale] || asset.name[context.languageContext.locale] || "",
|
|
21
|
+
height: asset.sources[0].dimensions?.h || 0,
|
|
22
|
+
width: asset.sources[0].dimensions?.w || 0
|
|
23
|
+
}))
|
|
24
|
+
};
|
|
25
|
+
return this.categorySchema.parse(result);
|
|
26
|
+
}
|
|
27
|
+
parseCategoryPaginatedResult(context, data) {
|
|
28
|
+
const result = {
|
|
29
|
+
pageNumber: Math.floor(data.offset / data.count) + 1,
|
|
30
|
+
pageSize: data.count,
|
|
31
|
+
totalCount: data.total || 0,
|
|
32
|
+
totalPages: Math.ceil((data.total ?? 0) / data.count),
|
|
33
|
+
items: data.results.map((category) => this.parseCategory(context, category))
|
|
34
|
+
};
|
|
35
|
+
return this.categoryPaginatedResultSchema.parse(result);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
CommercetoolsCategoryFactory
|
|
40
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CheckoutSchema,
|
|
3
|
+
PaymentMethodSchema,
|
|
4
|
+
ShippingMethodSchema,
|
|
5
|
+
unwrapValue
|
|
6
|
+
} from "@reactionary/core";
|
|
7
|
+
import { withCommercetoolsCapabilities } from "../../core/initialize.js";
|
|
8
|
+
import { CommercetoolsCheckoutCapability } from "../../capabilities/checkout.capability.js";
|
|
9
|
+
import { assertNotAny, assertType } from "../product/utils.example.js";
|
|
10
|
+
import { CommercetoolsCheckoutFactory } from "./checkout.factory.js";
|
|
11
|
+
import * as z from "zod";
|
|
12
|
+
const cache = {};
|
|
13
|
+
const context = {};
|
|
14
|
+
const config = {};
|
|
15
|
+
const ExtendedCheckoutSchema = CheckoutSchema.safeExtend({
|
|
16
|
+
extendedValue: z.string().default("")
|
|
17
|
+
});
|
|
18
|
+
class ExtendedCommercetoolsCheckoutFactory extends CommercetoolsCheckoutFactory {
|
|
19
|
+
constructor() {
|
|
20
|
+
super(ExtendedCheckoutSchema, ShippingMethodSchema, PaymentMethodSchema);
|
|
21
|
+
}
|
|
22
|
+
parseCheckout(context2, data) {
|
|
23
|
+
const base = super.parseCheckout(context2, data);
|
|
24
|
+
return {
|
|
25
|
+
...base,
|
|
26
|
+
extendedValue: "from-factory"
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
class ExtendedCommercetoolsCheckoutCapability extends CommercetoolsCheckoutCapability {
|
|
31
|
+
async getByIdOrThrow(payload) {
|
|
32
|
+
const result = await this.getById(payload);
|
|
33
|
+
return unwrapValue(result);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const extendedFactory = new ExtendedCommercetoolsCheckoutFactory();
|
|
37
|
+
const capabilityFactory = withCommercetoolsCapabilities(config, {
|
|
38
|
+
checkout: {
|
|
39
|
+
enabled: true,
|
|
40
|
+
factory: extendedFactory,
|
|
41
|
+
capability: ({ cache: cache2, context: context2, config: config2, commercetoolsApi }) => new ExtendedCommercetoolsCheckoutCapability(
|
|
42
|
+
config2,
|
|
43
|
+
cache2,
|
|
44
|
+
context2,
|
|
45
|
+
commercetoolsApi,
|
|
46
|
+
extendedFactory
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
const client = capabilityFactory(cache, context);
|
|
51
|
+
client.checkout.getById({
|
|
52
|
+
identifier: { key: "checkout-1" }
|
|
53
|
+
}).then((result) => {
|
|
54
|
+
assertNotAny(result);
|
|
55
|
+
if (result.success) {
|
|
56
|
+
assertNotAny(result.value);
|
|
57
|
+
assertNotAny(result.value.extendedValue);
|
|
58
|
+
assertType(result.value.extendedValue);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
client.checkout.getByIdOrThrow({
|
|
62
|
+
identifier: { key: "checkout-2" }
|
|
63
|
+
}).then((checkout) => {
|
|
64
|
+
assertNotAny(checkout);
|
|
65
|
+
assertNotAny(checkout.extendedValue);
|
|
66
|
+
assertType(checkout.extendedValue);
|
|
67
|
+
});
|