hookwright 1.7.0 → 1.8.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/dist/cli.js +45 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ var init_package = __esm({
|
|
|
24
24
|
"package.json"() {
|
|
25
25
|
package_default = {
|
|
26
26
|
name: "hookwright",
|
|
27
|
-
version: "1.
|
|
27
|
+
version: "1.8.0",
|
|
28
28
|
description: "Build real, signed e-commerce webhooks from a live Shopify catalogue \u2014 interactive terminal UI, no backend",
|
|
29
29
|
keywords: [
|
|
30
30
|
"webhook",
|
|
@@ -163,6 +163,9 @@ var DEFAULT_CONFIG = {
|
|
|
163
163
|
shopify: {
|
|
164
164
|
domain: "",
|
|
165
165
|
accessToken: "",
|
|
166
|
+
// Storefront API token. Creating a checkout needs one: the admin checkout API
|
|
167
|
+
// was removed, so a fresh checkout can only be minted through the storefront.
|
|
168
|
+
storefrontAccessToken: "",
|
|
166
169
|
apiVersion: "2024-10",
|
|
167
170
|
// cached from the last successful connection, for display only
|
|
168
171
|
shop: null
|
|
@@ -2493,6 +2496,33 @@ var ShopifyClient = class {
|
|
|
2493
2496
|
const { json } = await this.request("/checkouts.json", { query: { limit } });
|
|
2494
2497
|
return json?.checkouts ?? [];
|
|
2495
2498
|
}
|
|
2499
|
+
/**
|
|
2500
|
+
* Creates a real checkout through the storefront API and returns its recovery
|
|
2501
|
+
* link. The admin checkout API was removed, so this is the only way to mint a
|
|
2502
|
+
* new one; it needs a storefront token, which is separate from the admin one.
|
|
2503
|
+
*/
|
|
2504
|
+
async createCheckout({ storefrontAccessToken, lineItems, email }) {
|
|
2505
|
+
if (!storefrontAccessToken) throw new ShopifyError("Storefront access token missing", { hint: "run `hookwright configure` and add it, or the checkout event reuses an existing checkout" });
|
|
2506
|
+
const mutation = `mutation checkoutCreate($input: CheckoutCreateInput!) {
|
|
2507
|
+
checkoutCreate(input: $input) {
|
|
2508
|
+
checkout { id webUrl totalPrice { amount currencyCode } }
|
|
2509
|
+
checkoutUserErrors { field message }
|
|
2510
|
+
}
|
|
2511
|
+
}`;
|
|
2512
|
+
const input = { lineItems: lineItems.map(({ variantId, quantity }) => ({ variantId: `gid://shopify/ProductVariant/${variantId}`, quantity })) };
|
|
2513
|
+
if (email) input.email = email;
|
|
2514
|
+
const res = await fetch(`https://${this.domain}/api/2024-01/graphql.json`, {
|
|
2515
|
+
method: "POST",
|
|
2516
|
+
headers: { "Content-Type": "application/json", "X-Shopify-Storefront-Access-Token": storefrontAccessToken },
|
|
2517
|
+
body: JSON.stringify({ query: mutation, variables: { input } })
|
|
2518
|
+
});
|
|
2519
|
+
const body = await res.json();
|
|
2520
|
+
const errors = body.errors ?? body.data?.checkoutCreate?.checkoutUserErrors;
|
|
2521
|
+
if (errors?.length) throw new ShopifyError(`Shopify rejected the checkout: ${errors.map((e) => e.message).join("; ")}`, { hint: "the storefront token needs unauthenticated_write_checkouts" });
|
|
2522
|
+
const checkout = body.data?.checkoutCreate?.checkout;
|
|
2523
|
+
if (!checkout) throw new ShopifyError("Shopify returned no checkout", { hint: "check the storefront token and api version" });
|
|
2524
|
+
return checkout;
|
|
2525
|
+
}
|
|
2496
2526
|
/** Orders, newest first. */
|
|
2497
2527
|
async orders({ limit = 10, status = "any" } = {}) {
|
|
2498
2528
|
const { json } = await this.request("/orders.json", { query: { limit, status } });
|
|
@@ -2991,6 +3021,19 @@ function clientFor(config) {
|
|
|
2991
3021
|
});
|
|
2992
3022
|
}
|
|
2993
3023
|
async function liveCheckout({ config, items = [], client = clientFor(config) }) {
|
|
3024
|
+
const storefrontAccessToken = config.shopify?.storefrontAccessToken;
|
|
3025
|
+
const lineItems = items.map(({ variant, product, quantity }) => ({ variantId: Number(variant?.id ?? product?.id), quantity: Number(quantity) || 1 })).filter((l) => Number.isFinite(l.variantId) && l.variantId > 0);
|
|
3026
|
+
if (storefrontAccessToken && lineItems.length) {
|
|
3027
|
+
const checkout2 = await client.createCheckout({ storefrontAccessToken, lineItems, email: config.customer?.email || void 0 });
|
|
3028
|
+
return {
|
|
3029
|
+
kind: "checkout",
|
|
3030
|
+
created: true,
|
|
3031
|
+
url: checkout2.webUrl,
|
|
3032
|
+
cartValue: money2(checkout2.totalPrice?.amount ?? 0).toFixed(2),
|
|
3033
|
+
cartToken: /\/checkouts\/([^/?]+)/.exec(checkout2.webUrl ?? "")?.[1],
|
|
3034
|
+
lineItems: []
|
|
3035
|
+
};
|
|
3036
|
+
}
|
|
2994
3037
|
const checkouts = await client.abandonedCheckouts({ limit: 20 });
|
|
2995
3038
|
if (checkouts.length === 0) return null;
|
|
2996
3039
|
const wanted = new Set(items.map(({ variant }) => Number(variant?.id)).filter(Boolean));
|
|
@@ -2998,6 +3041,7 @@ async function liveCheckout({ config, items = [], client = clientFor(config) })
|
|
|
2998
3041
|
const checkout = matching ?? checkouts[0];
|
|
2999
3042
|
return {
|
|
3000
3043
|
kind: "checkout",
|
|
3044
|
+
created: false,
|
|
3001
3045
|
url: checkout.abandoned_checkout_url,
|
|
3002
3046
|
cartValue: money2(checkout.total_price ?? 0).toFixed(2),
|
|
3003
3047
|
cartToken: checkout.cart_token,
|