@misiki/gocommerce-connector 0.29.0 → 0.31.0
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 +1 -0
- package/dist/{index-DOlNI60Y.d.cts → index-CSqvRZQ4.d.cts} +136 -3
- package/dist/{index-DOlNI60Y.d.ts → index-CSqvRZQ4.d.ts} +136 -3
- package/dist/index.cjs +185 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +185 -24
- package/dist/index.js.map +1 -1
- package/dist/services/index.cjs +183 -23
- package/dist/services/index.cjs.map +1 -1
- package/dist/services/index.d.cts +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +183 -23
- package/dist/services/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -134,8 +134,17 @@ function remember(key, load, ttl = TTL_MS) {
|
|
|
134
134
|
});
|
|
135
135
|
return value;
|
|
136
136
|
}
|
|
137
|
+
var known = /* @__PURE__ */ new Map();
|
|
138
|
+
function keep(key, value) {
|
|
139
|
+
known.set(key, value);
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
function recall(key) {
|
|
143
|
+
return known.get(key);
|
|
144
|
+
}
|
|
137
145
|
function forgetAll() {
|
|
138
146
|
entries.clear();
|
|
147
|
+
known.clear();
|
|
139
148
|
}
|
|
140
149
|
|
|
141
150
|
// src/browser.ts
|
|
@@ -552,6 +561,7 @@ function productImages(p) {
|
|
|
552
561
|
if (url && !out.includes(url)) out.push(url);
|
|
553
562
|
};
|
|
554
563
|
push(mediaUrl(p.image_url));
|
|
564
|
+
push(mediaUrl(p.hover_image_url));
|
|
555
565
|
for (const m of arr(p.media)) if (m.kind === "image") push(mediaUrl(m.url));
|
|
556
566
|
for (const v of arr(p.variants)) for (const url of variantImages(v)) push(url);
|
|
557
567
|
return out;
|
|
@@ -1120,7 +1130,22 @@ function toCartOffer(o, currency) {
|
|
|
1120
1130
|
applies: o.applies,
|
|
1121
1131
|
amount: o.amount ? toMajor(o.amount) : null,
|
|
1122
1132
|
remaining: o.remaining ? toMajor(o.remaining) : null,
|
|
1123
|
-
reason: o.reason ?? ""
|
|
1133
|
+
reason: o.reason ?? "",
|
|
1134
|
+
autoApply: !!o.auto_apply
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
function toCheckoutQuote(q) {
|
|
1138
|
+
const d = q.discount;
|
|
1139
|
+
return {
|
|
1140
|
+
subtotal: toMajor(q.subtotal),
|
|
1141
|
+
shippingCharges: toMajor(q.shipping),
|
|
1142
|
+
shippingMethod: q.shipping_method ?? null,
|
|
1143
|
+
discount: toMajor(d?.amount),
|
|
1144
|
+
coupon: d ? { code: d.code, title: d.title, autoApplied: d.auto_applied, freeShipping: !!d.free_shipping } : null,
|
|
1145
|
+
tax: toMajor(q.tax),
|
|
1146
|
+
taxIncluded: q.tax_inclusive,
|
|
1147
|
+
codCharges: toMajor(q.payment_fee),
|
|
1148
|
+
total: toMajor(q.total)
|
|
1124
1149
|
};
|
|
1125
1150
|
}
|
|
1126
1151
|
function toFreeShipping(f) {
|
|
@@ -2128,6 +2153,7 @@ var customizerService = new CustomizerService();
|
|
|
2128
2153
|
|
|
2129
2154
|
// src/services/checkout-service.ts
|
|
2130
2155
|
var CheckoutService = class extends BaseService {
|
|
2156
|
+
expected = /* @__PURE__ */ new Map();
|
|
2131
2157
|
/** Delivery options for the address on the cart: `{ data: [{ id, name, base_rate }] }`. */
|
|
2132
2158
|
async getShippingRates({ cartId } = {}) {
|
|
2133
2159
|
const id2 = cartId || storedCartId();
|
|
@@ -2299,6 +2325,64 @@ var CheckoutService = class extends BaseService {
|
|
|
2299
2325
|
);
|
|
2300
2326
|
return { methods: options.methods ?? [], currency: options.currency };
|
|
2301
2327
|
}
|
|
2328
|
+
/**
|
|
2329
|
+
* What checkout would charge for the cart now, by the storefront's method
|
|
2330
|
+
* code (`COD`, `RAZORPAY`, `PAYPAL`) or none yet (D185). Null until the
|
|
2331
|
+
* address step has stored an address, and null from an engine before D185.
|
|
2332
|
+
*
|
|
2333
|
+
* A refusal is checkout's own: the same GoCommerceError, with the same
|
|
2334
|
+
* message, that placing the order would raise now.
|
|
2335
|
+
*/
|
|
2336
|
+
async getQuote({ cartId, method = "" } = {}) {
|
|
2337
|
+
const id2 = cartId || storedCartId();
|
|
2338
|
+
if (!id2 || !cartExtras(id2).shippingAddress) return null;
|
|
2339
|
+
const { extras, email, address } = await this.storedCheckout(id2);
|
|
2340
|
+
const code = method.toLowerCase() === "paypal" ? await this.redirectGateway() : method.toLowerCase();
|
|
2341
|
+
try {
|
|
2342
|
+
const quote2 = await this.data("/api/checkout/quote", {
|
|
2343
|
+
query: {
|
|
2344
|
+
cart: id2,
|
|
2345
|
+
country: address.country,
|
|
2346
|
+
state: address.state,
|
|
2347
|
+
shipping_rate_id: extras.shippingRateId || void 0,
|
|
2348
|
+
method: code || void 0,
|
|
2349
|
+
email: email || void 0
|
|
2350
|
+
}
|
|
2351
|
+
});
|
|
2352
|
+
return toCheckoutQuote(quote2);
|
|
2353
|
+
} catch (err) {
|
|
2354
|
+
if (err instanceof GoCommerceError && (err.status === 405 || err.status === 404 && err.message.startsWith("no route for "))) {
|
|
2355
|
+
return null;
|
|
2356
|
+
}
|
|
2357
|
+
throw err;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* The total on screen as the shopper confirms, from getQuote, or null when nothing was quoted
|
|
2362
|
+
* (D185). Every order placed for the cart then carries it, and checkout refuses one whose own
|
|
2363
|
+
* total differs instead of charging it: `onChange` gets checkout's figures now and the sentence
|
|
2364
|
+
* the placing call fails with.
|
|
2365
|
+
*/
|
|
2366
|
+
expectTotal({
|
|
2367
|
+
cartId,
|
|
2368
|
+
total,
|
|
2369
|
+
onChange
|
|
2370
|
+
}) {
|
|
2371
|
+
const id2 = cartId || storedCartId();
|
|
2372
|
+
if (!id2) return;
|
|
2373
|
+
if (typeof total === "number") this.expected.set(id2, { total, onChange });
|
|
2374
|
+
else this.expected.delete(id2);
|
|
2375
|
+
}
|
|
2376
|
+
async storedCheckout(cartId) {
|
|
2377
|
+
const id2 = cartId || storedCartId();
|
|
2378
|
+
if (!id2) throw new GoCommerceError("Your bag is empty.", 400);
|
|
2379
|
+
const extras = cartExtras(id2);
|
|
2380
|
+
const ship = extras.shippingAddress;
|
|
2381
|
+
if (!ship) throw new GoCommerceError("Add a delivery address first.", 400);
|
|
2382
|
+
const cart = await this.data(`/api/carts/${encodeURIComponent(id2)}`);
|
|
2383
|
+
const email = cart.email || (ship.email ? String(ship.email) : "");
|
|
2384
|
+
return { id: id2, extras, email, address: toEngineAddress(ship), currency: cart.currency };
|
|
2385
|
+
}
|
|
2302
2386
|
async redirectGateway() {
|
|
2303
2387
|
const { methods } = await this.listMethods();
|
|
2304
2388
|
return methods.find((m) => REDIRECT_GATEWAYS.has(m.code))?.code;
|
|
@@ -2314,15 +2398,8 @@ var CheckoutService = class extends BaseService {
|
|
|
2314
2398
|
* the engine refuses a key replayed with a different body.
|
|
2315
2399
|
*/
|
|
2316
2400
|
async place(code, cartId, extra = {}) {
|
|
2317
|
-
const id2 =
|
|
2318
|
-
if (!id2) throw new GoCommerceError("Your bag is empty.", 400);
|
|
2319
|
-
const extras = cartExtras(id2);
|
|
2320
|
-
const ship = extras.shippingAddress;
|
|
2321
|
-
if (!ship) throw new GoCommerceError("Add a delivery address first.", 400);
|
|
2322
|
-
const cart = await this.data(`/api/carts/${encodeURIComponent(id2)}`);
|
|
2323
|
-
const email = cart.email || (ship.email ? String(ship.email) : "");
|
|
2401
|
+
const { id: id2, extras, email, address, currency } = await this.storedCheckout(cartId);
|
|
2324
2402
|
if (!email) throw new GoCommerceError("Add an email address so we can send your receipt.", 400);
|
|
2325
|
-
const address = toEngineAddress(ship);
|
|
2326
2403
|
const body = {
|
|
2327
2404
|
cart_id: id2,
|
|
2328
2405
|
email,
|
|
@@ -2345,10 +2422,23 @@ var CheckoutService = class extends BaseService {
|
|
|
2345
2422
|
...meta ? { meta_capi: meta } : {},
|
|
2346
2423
|
...attribution ? { attribution } : {}
|
|
2347
2424
|
};
|
|
2425
|
+
const expected = this.expected.get(id2);
|
|
2348
2426
|
const result = await this.data(`/api/checkout/${encodeURIComponent(code)}`, {
|
|
2349
2427
|
method: "POST",
|
|
2350
|
-
body:
|
|
2428
|
+
body: {
|
|
2429
|
+
...Object.keys(metadata).length ? { ...body, metadata } : body,
|
|
2430
|
+
...expected ? { quoted_total_minor: toMinor(expected.total, currency) } : {}
|
|
2431
|
+
},
|
|
2351
2432
|
headers: { "Idempotency-Key": key }
|
|
2433
|
+
}).catch((err) => {
|
|
2434
|
+
if (keyAlreadyUsed(err)) {
|
|
2435
|
+
throw new GoCommerceError("This order was already placed in another tab. Check that tab for its confirmation.", 400, err.code);
|
|
2436
|
+
}
|
|
2437
|
+
const now = movedTotal(err);
|
|
2438
|
+
if (!expected || !now) throw err;
|
|
2439
|
+
const message = `Your total changed to ${shopperAmount(now.total, currency)}. Check the new total and confirm again.`;
|
|
2440
|
+
expected.onChange?.(now, message);
|
|
2441
|
+
throw new GoCommerceError(message, 409, "total_changed", now);
|
|
2352
2442
|
});
|
|
2353
2443
|
if (result.order.access_token) {
|
|
2354
2444
|
rememberOrder({
|
|
@@ -2371,6 +2461,21 @@ var CheckoutService = class extends BaseService {
|
|
|
2371
2461
|
};
|
|
2372
2462
|
var REDIRECT_GATEWAYS = /* @__PURE__ */ new Set(["adyen", "paddle", "lemonsqueezy", "creem", "hyperswitch", "revenuecat"]);
|
|
2373
2463
|
var unsupported = (method, why) => new NotSupportedError("CheckoutService", method, why);
|
|
2464
|
+
function keyAlreadyUsed(err) {
|
|
2465
|
+
return err instanceof GoCommerceError && err.status === 400 && err.message.includes("Idempotency-Key was already used");
|
|
2466
|
+
}
|
|
2467
|
+
function movedTotal(err) {
|
|
2468
|
+
if (!(err instanceof GoCommerceError) || err.code !== "total_changed") return null;
|
|
2469
|
+
const now = err.details;
|
|
2470
|
+
return now?.total ? toCheckoutQuote(now) : null;
|
|
2471
|
+
}
|
|
2472
|
+
function shopperAmount(amount, currency) {
|
|
2473
|
+
return new Intl.NumberFormat(currency === "INR" ? "en-IN" : "en", {
|
|
2474
|
+
style: "currency",
|
|
2475
|
+
currency,
|
|
2476
|
+
...Number.isInteger(amount) ? { minimumFractionDigits: 0 } : {}
|
|
2477
|
+
}).format(amount);
|
|
2478
|
+
}
|
|
2374
2479
|
function newKey() {
|
|
2375
2480
|
const c = globalThis.crypto;
|
|
2376
2481
|
if (c?.randomUUID) return c.randomUUID();
|
|
@@ -2432,7 +2537,8 @@ var CouponService = class extends BaseService {
|
|
|
2432
2537
|
minAmount: o.minAmount,
|
|
2433
2538
|
description: o.applies ? o.title : o.remaining !== null ? `${o.title}. Add ${o.remaining} more to use this.` : o.reason || o.title,
|
|
2434
2539
|
applies: o.applies,
|
|
2435
|
-
remaining: o.remaining
|
|
2540
|
+
remaining: o.remaining,
|
|
2541
|
+
autoApply: o.autoApply
|
|
2436
2542
|
}));
|
|
2437
2543
|
return { data, count: data.length, pageSize: data.length, noOfPage: 1, page: 1 };
|
|
2438
2544
|
}
|
|
@@ -3185,15 +3291,18 @@ var StoreService = class extends BaseService {
|
|
|
3185
3291
|
async getStoreByIdOrDomain(_args = {}) {
|
|
3186
3292
|
const base = await readStaticStore();
|
|
3187
3293
|
const store = this.store;
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
if (isStoreMissing(err)) {
|
|
3294
|
+
const [plugins, live] = await Promise.all([
|
|
3295
|
+
this.facts.plugins().catch((err) => {
|
|
3296
|
+
if (store && isStoreMissing(err)) {
|
|
3191
3297
|
throw new GoCommerceError(`store not found: no store "${store}" on this engine`, 404, "store_not_found");
|
|
3192
3298
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3299
|
+
return void 0;
|
|
3300
|
+
}),
|
|
3301
|
+
this.live().catch(() => ({}))
|
|
3302
|
+
]);
|
|
3303
|
+
const known2 = `${this.creds.apiUrl ?? ""}|${store ?? ""}|closed`;
|
|
3304
|
+
const closed = plugins ? keep(known2, closedOf(plugins)) : recall(known2);
|
|
3305
|
+
return { ...base, ...live, closed, id: base.id || "gocommerce" };
|
|
3197
3306
|
}
|
|
3198
3307
|
async getStore(storeId) {
|
|
3199
3308
|
return this.getStoreByIdOrDomain({ storeId });
|
|
@@ -3228,6 +3337,46 @@ var StoreService = class extends BaseService {
|
|
|
3228
3337
|
}
|
|
3229
3338
|
return super.post(path, data);
|
|
3230
3339
|
}
|
|
3340
|
+
/**
|
|
3341
|
+
* A closed store's password (D170), traded for a pass. Never throws. The
|
|
3342
|
+
* engine allows 20 tries an hour per client, and sees this server as the
|
|
3343
|
+
* client unless `clientAddress`, the shopper's, travels as `X-Forwarded-For`.
|
|
3344
|
+
*/
|
|
3345
|
+
async unlockStore(password, { clientAddress } = {}) {
|
|
3346
|
+
const typed = password.trim();
|
|
3347
|
+
if (!typed) return { outcome: "wrong" };
|
|
3348
|
+
try {
|
|
3349
|
+
return { outcome: "unlocked", pass: await this.storePass({ password: typed }, clientAddress) };
|
|
3350
|
+
} catch (err) {
|
|
3351
|
+
return { outcome: err instanceof GoCommerceError && UNLOCK_REFUSED[err.status] || "unavailable" };
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
/**
|
|
3355
|
+
* A pass checked without spending a try: the pass, or null when it is wrong,
|
|
3356
|
+
* expired, ended by a new password, or the store takes none now. Throws when
|
|
3357
|
+
* the engine cannot say.
|
|
3358
|
+
*/
|
|
3359
|
+
async checkStorePass(token) {
|
|
3360
|
+
if (!token) return null;
|
|
3361
|
+
try {
|
|
3362
|
+
return await this.storePass({ token });
|
|
3363
|
+
} catch (err) {
|
|
3364
|
+
if (err instanceof GoCommerceError && (err.status === 422 || err.status === 404)) return null;
|
|
3365
|
+
throw err;
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
async storePass(body, clientAddress) {
|
|
3369
|
+
const answer = await this.data("/api/store/password", {
|
|
3370
|
+
method: "POST",
|
|
3371
|
+
body,
|
|
3372
|
+
headers: clientAddress && ONE_IP_ADDRESS.test(clientAddress) ? { "X-Forwarded-For": clientAddress } : void 0
|
|
3373
|
+
});
|
|
3374
|
+
const expiresAt = new Date(answer?.expires_at ?? NaN);
|
|
3375
|
+
if (!answer?.token || Number.isNaN(expiresAt.getTime())) {
|
|
3376
|
+
throw new GoCommerceError("The commerce engine answered a store pass without its token or expiry.", 502);
|
|
3377
|
+
}
|
|
3378
|
+
return { token: answer.token, expiresAt };
|
|
3379
|
+
}
|
|
3231
3380
|
async live() {
|
|
3232
3381
|
const [ready, plugins, menus, base, profile, ships] = await Promise.all([
|
|
3233
3382
|
this.facts.ready(),
|
|
@@ -3345,6 +3494,17 @@ var StoreService = class extends BaseService {
|
|
|
3345
3494
|
}
|
|
3346
3495
|
};
|
|
3347
3496
|
var storeService = new StoreService();
|
|
3497
|
+
var ONE_IP_ADDRESS = /^[\da-f.:]{2,45}$/i;
|
|
3498
|
+
var UNLOCK_REFUSED = {
|
|
3499
|
+
422: "wrong",
|
|
3500
|
+
429: "throttled",
|
|
3501
|
+
404: "open"
|
|
3502
|
+
};
|
|
3503
|
+
function closedOf(plugins) {
|
|
3504
|
+
const plugin = plugins?.get("store-close");
|
|
3505
|
+
if (!plugin) return void 0;
|
|
3506
|
+
return { message: String(plugin.settings?.message ?? ""), password: plugin.settings?.password === true };
|
|
3507
|
+
}
|
|
3348
3508
|
var AD_TAGS = [
|
|
3349
3509
|
["meta-pixel", "pixel_id", "facebookPixel"],
|
|
3350
3510
|
["microsoft-clarity", "project_id", "microsoftClarity"],
|
|
@@ -3373,10 +3533,10 @@ function googleTags(plugins) {
|
|
|
3373
3533
|
}
|
|
3374
3534
|
function deliverable(base, ships) {
|
|
3375
3535
|
if (ships.everywhere || !ships.countries.length) return {};
|
|
3376
|
-
const
|
|
3377
|
-
const countries = ships.countries.map((code) =>
|
|
3378
|
-
const
|
|
3379
|
-
return { countries, ...
|
|
3536
|
+
const known2 = new Map((base.countries ?? []).map((c) => [String(c.code).toUpperCase(), c]));
|
|
3537
|
+
const countries = ships.countries.map((code) => known2.get(code) ?? { code, name: regionName(code) });
|
|
3538
|
+
const keep2 = base.country && ships.countries.includes(String(base.country.code).toUpperCase());
|
|
3539
|
+
return { countries, ...keep2 ? {} : { country: countries[0] } };
|
|
3380
3540
|
}
|
|
3381
3541
|
function regionName(code) {
|
|
3382
3542
|
try {
|
|
@@ -3531,7 +3691,8 @@ function toSection(s) {
|
|
|
3531
3691
|
title: c.title,
|
|
3532
3692
|
slug: c.slug,
|
|
3533
3693
|
link: `/collections/${c.slug}`,
|
|
3534
|
-
image: mediaUrl(c.image) ?? null
|
|
3694
|
+
image: mediaUrl(c.image) ?? null,
|
|
3695
|
+
description: c.description || null
|
|
3535
3696
|
}));
|
|
3536
3697
|
}
|
|
3537
3698
|
return out;
|
|
@@ -4250,6 +4411,7 @@ exports.toCart = toCart;
|
|
|
4250
4411
|
exports.toCartLine = toCartLine;
|
|
4251
4412
|
exports.toCartOffer = toCartOffer;
|
|
4252
4413
|
exports.toCategory = toCategory;
|
|
4414
|
+
exports.toCheckoutQuote = toCheckoutQuote;
|
|
4253
4415
|
exports.toCmsPage = toCmsPage;
|
|
4254
4416
|
exports.toCollection = toCollection;
|
|
4255
4417
|
exports.toEngineAddress = toEngineAddress;
|