@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/services/index.cjs
CHANGED
|
@@ -520,6 +520,7 @@ function productImages(p) {
|
|
|
520
520
|
if (url && !out.includes(url)) out.push(url);
|
|
521
521
|
};
|
|
522
522
|
push(mediaUrl(p.image_url));
|
|
523
|
+
push(mediaUrl(p.hover_image_url));
|
|
523
524
|
for (const m of arr(p.media)) if (m.kind === "image") push(mediaUrl(m.url));
|
|
524
525
|
for (const v of arr(p.variants)) for (const url of variantImages(v)) push(url);
|
|
525
526
|
return out;
|
|
@@ -1087,7 +1088,22 @@ function toCartOffer(o, currency) {
|
|
|
1087
1088
|
applies: o.applies,
|
|
1088
1089
|
amount: o.amount ? toMajor(o.amount) : null,
|
|
1089
1090
|
remaining: o.remaining ? toMajor(o.remaining) : null,
|
|
1090
|
-
reason: o.reason ?? ""
|
|
1091
|
+
reason: o.reason ?? "",
|
|
1092
|
+
autoApply: !!o.auto_apply
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function toCheckoutQuote(q) {
|
|
1096
|
+
const d = q.discount;
|
|
1097
|
+
return {
|
|
1098
|
+
subtotal: toMajor(q.subtotal),
|
|
1099
|
+
shippingCharges: toMajor(q.shipping),
|
|
1100
|
+
shippingMethod: q.shipping_method ?? null,
|
|
1101
|
+
discount: toMajor(d?.amount),
|
|
1102
|
+
coupon: d ? { code: d.code, title: d.title, autoApplied: d.auto_applied, freeShipping: !!d.free_shipping } : null,
|
|
1103
|
+
tax: toMajor(q.tax),
|
|
1104
|
+
taxIncluded: q.tax_inclusive,
|
|
1105
|
+
codCharges: toMajor(q.payment_fee),
|
|
1106
|
+
total: toMajor(q.total)
|
|
1091
1107
|
};
|
|
1092
1108
|
}
|
|
1093
1109
|
function toFreeShipping(f) {
|
|
@@ -1112,6 +1128,14 @@ function remember(key, load, ttl = TTL_MS) {
|
|
|
1112
1128
|
});
|
|
1113
1129
|
return value;
|
|
1114
1130
|
}
|
|
1131
|
+
var known = /* @__PURE__ */ new Map();
|
|
1132
|
+
function keep(key, value) {
|
|
1133
|
+
known.set(key, value);
|
|
1134
|
+
return value;
|
|
1135
|
+
}
|
|
1136
|
+
function recall(key) {
|
|
1137
|
+
return known.get(key);
|
|
1138
|
+
}
|
|
1115
1139
|
|
|
1116
1140
|
// src/services/facts.ts
|
|
1117
1141
|
var StoreFacts = class extends BaseService {
|
|
@@ -2109,6 +2133,7 @@ var customizerService = new CustomizerService();
|
|
|
2109
2133
|
|
|
2110
2134
|
// src/services/checkout-service.ts
|
|
2111
2135
|
var CheckoutService = class extends BaseService {
|
|
2136
|
+
expected = /* @__PURE__ */ new Map();
|
|
2112
2137
|
/** Delivery options for the address on the cart: `{ data: [{ id, name, base_rate }] }`. */
|
|
2113
2138
|
async getShippingRates({ cartId } = {}) {
|
|
2114
2139
|
const id2 = cartId || storedCartId();
|
|
@@ -2280,6 +2305,64 @@ var CheckoutService = class extends BaseService {
|
|
|
2280
2305
|
);
|
|
2281
2306
|
return { methods: options.methods ?? [], currency: options.currency };
|
|
2282
2307
|
}
|
|
2308
|
+
/**
|
|
2309
|
+
* What checkout would charge for the cart now, by the storefront's method
|
|
2310
|
+
* code (`COD`, `RAZORPAY`, `PAYPAL`) or none yet (D185). Null until the
|
|
2311
|
+
* address step has stored an address, and null from an engine before D185.
|
|
2312
|
+
*
|
|
2313
|
+
* A refusal is checkout's own: the same GoCommerceError, with the same
|
|
2314
|
+
* message, that placing the order would raise now.
|
|
2315
|
+
*/
|
|
2316
|
+
async getQuote({ cartId, method = "" } = {}) {
|
|
2317
|
+
const id2 = cartId || storedCartId();
|
|
2318
|
+
if (!id2 || !cartExtras(id2).shippingAddress) return null;
|
|
2319
|
+
const { extras, email, address } = await this.storedCheckout(id2);
|
|
2320
|
+
const code = method.toLowerCase() === "paypal" ? await this.redirectGateway() : method.toLowerCase();
|
|
2321
|
+
try {
|
|
2322
|
+
const quote2 = await this.data("/api/checkout/quote", {
|
|
2323
|
+
query: {
|
|
2324
|
+
cart: id2,
|
|
2325
|
+
country: address.country,
|
|
2326
|
+
state: address.state,
|
|
2327
|
+
shipping_rate_id: extras.shippingRateId || void 0,
|
|
2328
|
+
method: code || void 0,
|
|
2329
|
+
email: email || void 0
|
|
2330
|
+
}
|
|
2331
|
+
});
|
|
2332
|
+
return toCheckoutQuote(quote2);
|
|
2333
|
+
} catch (err) {
|
|
2334
|
+
if (err instanceof GoCommerceError && (err.status === 405 || err.status === 404 && err.message.startsWith("no route for "))) {
|
|
2335
|
+
return null;
|
|
2336
|
+
}
|
|
2337
|
+
throw err;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* The total on screen as the shopper confirms, from getQuote, or null when nothing was quoted
|
|
2342
|
+
* (D185). Every order placed for the cart then carries it, and checkout refuses one whose own
|
|
2343
|
+
* total differs instead of charging it: `onChange` gets checkout's figures now and the sentence
|
|
2344
|
+
* the placing call fails with.
|
|
2345
|
+
*/
|
|
2346
|
+
expectTotal({
|
|
2347
|
+
cartId,
|
|
2348
|
+
total,
|
|
2349
|
+
onChange
|
|
2350
|
+
}) {
|
|
2351
|
+
const id2 = cartId || storedCartId();
|
|
2352
|
+
if (!id2) return;
|
|
2353
|
+
if (typeof total === "number") this.expected.set(id2, { total, onChange });
|
|
2354
|
+
else this.expected.delete(id2);
|
|
2355
|
+
}
|
|
2356
|
+
async storedCheckout(cartId) {
|
|
2357
|
+
const id2 = cartId || storedCartId();
|
|
2358
|
+
if (!id2) throw new GoCommerceError("Your bag is empty.", 400);
|
|
2359
|
+
const extras = cartExtras(id2);
|
|
2360
|
+
const ship = extras.shippingAddress;
|
|
2361
|
+
if (!ship) throw new GoCommerceError("Add a delivery address first.", 400);
|
|
2362
|
+
const cart = await this.data(`/api/carts/${encodeURIComponent(id2)}`);
|
|
2363
|
+
const email = cart.email || (ship.email ? String(ship.email) : "");
|
|
2364
|
+
return { id: id2, extras, email, address: toEngineAddress(ship), currency: cart.currency };
|
|
2365
|
+
}
|
|
2283
2366
|
async redirectGateway() {
|
|
2284
2367
|
const { methods } = await this.listMethods();
|
|
2285
2368
|
return methods.find((m) => REDIRECT_GATEWAYS.has(m.code))?.code;
|
|
@@ -2295,15 +2378,8 @@ var CheckoutService = class extends BaseService {
|
|
|
2295
2378
|
* the engine refuses a key replayed with a different body.
|
|
2296
2379
|
*/
|
|
2297
2380
|
async place(code, cartId, extra = {}) {
|
|
2298
|
-
const id2 =
|
|
2299
|
-
if (!id2) throw new GoCommerceError("Your bag is empty.", 400);
|
|
2300
|
-
const extras = cartExtras(id2);
|
|
2301
|
-
const ship = extras.shippingAddress;
|
|
2302
|
-
if (!ship) throw new GoCommerceError("Add a delivery address first.", 400);
|
|
2303
|
-
const cart = await this.data(`/api/carts/${encodeURIComponent(id2)}`);
|
|
2304
|
-
const email = cart.email || (ship.email ? String(ship.email) : "");
|
|
2381
|
+
const { id: id2, extras, email, address, currency } = await this.storedCheckout(cartId);
|
|
2305
2382
|
if (!email) throw new GoCommerceError("Add an email address so we can send your receipt.", 400);
|
|
2306
|
-
const address = toEngineAddress(ship);
|
|
2307
2383
|
const body = {
|
|
2308
2384
|
cart_id: id2,
|
|
2309
2385
|
email,
|
|
@@ -2326,10 +2402,23 @@ var CheckoutService = class extends BaseService {
|
|
|
2326
2402
|
...meta ? { meta_capi: meta } : {},
|
|
2327
2403
|
...attribution ? { attribution } : {}
|
|
2328
2404
|
};
|
|
2405
|
+
const expected = this.expected.get(id2);
|
|
2329
2406
|
const result = await this.data(`/api/checkout/${encodeURIComponent(code)}`, {
|
|
2330
2407
|
method: "POST",
|
|
2331
|
-
body:
|
|
2408
|
+
body: {
|
|
2409
|
+
...Object.keys(metadata).length ? { ...body, metadata } : body,
|
|
2410
|
+
...expected ? { quoted_total_minor: toMinor(expected.total, currency) } : {}
|
|
2411
|
+
},
|
|
2332
2412
|
headers: { "Idempotency-Key": key }
|
|
2413
|
+
}).catch((err) => {
|
|
2414
|
+
if (keyAlreadyUsed(err)) {
|
|
2415
|
+
throw new GoCommerceError("This order was already placed in another tab. Check that tab for its confirmation.", 400, err.code);
|
|
2416
|
+
}
|
|
2417
|
+
const now = movedTotal(err);
|
|
2418
|
+
if (!expected || !now) throw err;
|
|
2419
|
+
const message = `Your total changed to ${shopperAmount(now.total, currency)}. Check the new total and confirm again.`;
|
|
2420
|
+
expected.onChange?.(now, message);
|
|
2421
|
+
throw new GoCommerceError(message, 409, "total_changed", now);
|
|
2333
2422
|
});
|
|
2334
2423
|
if (result.order.access_token) {
|
|
2335
2424
|
rememberOrder({
|
|
@@ -2352,6 +2441,21 @@ var CheckoutService = class extends BaseService {
|
|
|
2352
2441
|
};
|
|
2353
2442
|
var REDIRECT_GATEWAYS = /* @__PURE__ */ new Set(["adyen", "paddle", "lemonsqueezy", "creem", "hyperswitch", "revenuecat"]);
|
|
2354
2443
|
var unsupported = (method, why) => new NotSupportedError("CheckoutService", method, why);
|
|
2444
|
+
function keyAlreadyUsed(err) {
|
|
2445
|
+
return err instanceof GoCommerceError && err.status === 400 && err.message.includes("Idempotency-Key was already used");
|
|
2446
|
+
}
|
|
2447
|
+
function movedTotal(err) {
|
|
2448
|
+
if (!(err instanceof GoCommerceError) || err.code !== "total_changed") return null;
|
|
2449
|
+
const now = err.details;
|
|
2450
|
+
return now?.total ? toCheckoutQuote(now) : null;
|
|
2451
|
+
}
|
|
2452
|
+
function shopperAmount(amount, currency) {
|
|
2453
|
+
return new Intl.NumberFormat(currency === "INR" ? "en-IN" : "en", {
|
|
2454
|
+
style: "currency",
|
|
2455
|
+
currency,
|
|
2456
|
+
...Number.isInteger(amount) ? { minimumFractionDigits: 0 } : {}
|
|
2457
|
+
}).format(amount);
|
|
2458
|
+
}
|
|
2355
2459
|
function newKey() {
|
|
2356
2460
|
const c = globalThis.crypto;
|
|
2357
2461
|
if (c?.randomUUID) return c.randomUUID();
|
|
@@ -2413,7 +2517,8 @@ var CouponService = class extends BaseService {
|
|
|
2413
2517
|
minAmount: o.minAmount,
|
|
2414
2518
|
description: o.applies ? o.title : o.remaining !== null ? `${o.title}. Add ${o.remaining} more to use this.` : o.reason || o.title,
|
|
2415
2519
|
applies: o.applies,
|
|
2416
|
-
remaining: o.remaining
|
|
2520
|
+
remaining: o.remaining,
|
|
2521
|
+
autoApply: o.autoApply
|
|
2417
2522
|
}));
|
|
2418
2523
|
return { data, count: data.length, pageSize: data.length, noOfPage: 1, page: 1 };
|
|
2419
2524
|
}
|
|
@@ -3169,15 +3274,18 @@ var StoreService = class extends BaseService {
|
|
|
3169
3274
|
async getStoreByIdOrDomain(_args = {}) {
|
|
3170
3275
|
const base = await readStaticStore();
|
|
3171
3276
|
const store = this.store;
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
if (isStoreMissing(err)) {
|
|
3277
|
+
const [plugins, live] = await Promise.all([
|
|
3278
|
+
this.facts.plugins().catch((err) => {
|
|
3279
|
+
if (store && isStoreMissing(err)) {
|
|
3175
3280
|
throw new GoCommerceError(`store not found: no store "${store}" on this engine`, 404, "store_not_found");
|
|
3176
3281
|
}
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3282
|
+
return void 0;
|
|
3283
|
+
}),
|
|
3284
|
+
this.live().catch(() => ({}))
|
|
3285
|
+
]);
|
|
3286
|
+
const known2 = `${this.creds.apiUrl ?? ""}|${store ?? ""}|closed`;
|
|
3287
|
+
const closed = plugins ? keep(known2, closedOf(plugins)) : recall(known2);
|
|
3288
|
+
return { ...base, ...live, closed, id: base.id || "gocommerce" };
|
|
3181
3289
|
}
|
|
3182
3290
|
async getStore(storeId) {
|
|
3183
3291
|
return this.getStoreByIdOrDomain({ storeId });
|
|
@@ -3212,6 +3320,46 @@ var StoreService = class extends BaseService {
|
|
|
3212
3320
|
}
|
|
3213
3321
|
return super.post(path, data);
|
|
3214
3322
|
}
|
|
3323
|
+
/**
|
|
3324
|
+
* A closed store's password (D170), traded for a pass. Never throws. The
|
|
3325
|
+
* engine allows 20 tries an hour per client, and sees this server as the
|
|
3326
|
+
* client unless `clientAddress`, the shopper's, travels as `X-Forwarded-For`.
|
|
3327
|
+
*/
|
|
3328
|
+
async unlockStore(password, { clientAddress } = {}) {
|
|
3329
|
+
const typed = password.trim();
|
|
3330
|
+
if (!typed) return { outcome: "wrong" };
|
|
3331
|
+
try {
|
|
3332
|
+
return { outcome: "unlocked", pass: await this.storePass({ password: typed }, clientAddress) };
|
|
3333
|
+
} catch (err) {
|
|
3334
|
+
return { outcome: err instanceof GoCommerceError && UNLOCK_REFUSED[err.status] || "unavailable" };
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
/**
|
|
3338
|
+
* A pass checked without spending a try: the pass, or null when it is wrong,
|
|
3339
|
+
* expired, ended by a new password, or the store takes none now. Throws when
|
|
3340
|
+
* the engine cannot say.
|
|
3341
|
+
*/
|
|
3342
|
+
async checkStorePass(token) {
|
|
3343
|
+
if (!token) return null;
|
|
3344
|
+
try {
|
|
3345
|
+
return await this.storePass({ token });
|
|
3346
|
+
} catch (err) {
|
|
3347
|
+
if (err instanceof GoCommerceError && (err.status === 422 || err.status === 404)) return null;
|
|
3348
|
+
throw err;
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
async storePass(body, clientAddress) {
|
|
3352
|
+
const answer = await this.data("/api/store/password", {
|
|
3353
|
+
method: "POST",
|
|
3354
|
+
body,
|
|
3355
|
+
headers: clientAddress && ONE_IP_ADDRESS.test(clientAddress) ? { "X-Forwarded-For": clientAddress } : void 0
|
|
3356
|
+
});
|
|
3357
|
+
const expiresAt = new Date(answer?.expires_at ?? NaN);
|
|
3358
|
+
if (!answer?.token || Number.isNaN(expiresAt.getTime())) {
|
|
3359
|
+
throw new GoCommerceError("The commerce engine answered a store pass without its token or expiry.", 502);
|
|
3360
|
+
}
|
|
3361
|
+
return { token: answer.token, expiresAt };
|
|
3362
|
+
}
|
|
3215
3363
|
async live() {
|
|
3216
3364
|
const [ready, plugins, menus, base, profile, ships] = await Promise.all([
|
|
3217
3365
|
this.facts.ready(),
|
|
@@ -3329,6 +3477,17 @@ var StoreService = class extends BaseService {
|
|
|
3329
3477
|
}
|
|
3330
3478
|
};
|
|
3331
3479
|
var storeService = new StoreService();
|
|
3480
|
+
var ONE_IP_ADDRESS = /^[\da-f.:]{2,45}$/i;
|
|
3481
|
+
var UNLOCK_REFUSED = {
|
|
3482
|
+
422: "wrong",
|
|
3483
|
+
429: "throttled",
|
|
3484
|
+
404: "open"
|
|
3485
|
+
};
|
|
3486
|
+
function closedOf(plugins) {
|
|
3487
|
+
const plugin = plugins?.get("store-close");
|
|
3488
|
+
if (!plugin) return void 0;
|
|
3489
|
+
return { message: String(plugin.settings?.message ?? ""), password: plugin.settings?.password === true };
|
|
3490
|
+
}
|
|
3332
3491
|
var AD_TAGS = [
|
|
3333
3492
|
["meta-pixel", "pixel_id", "facebookPixel"],
|
|
3334
3493
|
["microsoft-clarity", "project_id", "microsoftClarity"],
|
|
@@ -3357,10 +3516,10 @@ function googleTags(plugins) {
|
|
|
3357
3516
|
}
|
|
3358
3517
|
function deliverable(base, ships) {
|
|
3359
3518
|
if (ships.everywhere || !ships.countries.length) return {};
|
|
3360
|
-
const
|
|
3361
|
-
const countries = ships.countries.map((code) =>
|
|
3362
|
-
const
|
|
3363
|
-
return { countries, ...
|
|
3519
|
+
const known2 = new Map((base.countries ?? []).map((c) => [String(c.code).toUpperCase(), c]));
|
|
3520
|
+
const countries = ships.countries.map((code) => known2.get(code) ?? { code, name: regionName(code) });
|
|
3521
|
+
const keep2 = base.country && ships.countries.includes(String(base.country.code).toUpperCase());
|
|
3522
|
+
return { countries, ...keep2 ? {} : { country: countries[0] } };
|
|
3364
3523
|
}
|
|
3365
3524
|
function regionName(code) {
|
|
3366
3525
|
try {
|
|
@@ -3515,7 +3674,8 @@ function toSection(s) {
|
|
|
3515
3674
|
title: c.title,
|
|
3516
3675
|
slug: c.slug,
|
|
3517
3676
|
link: `/collections/${c.slug}`,
|
|
3518
|
-
image: mediaUrl(c.image) ?? null
|
|
3677
|
+
image: mediaUrl(c.image) ?? null,
|
|
3678
|
+
description: c.description || null
|
|
3519
3679
|
}));
|
|
3520
3680
|
}
|
|
3521
3681
|
return out;
|