← back to Dw Signup Fulfillment

scripts/activate-shopify.js

81 lines

'use strict';

// Idempotently activates the two Shopify resources owned by this app:
// the Function-backed automatic discount and the paid-order ledger webhook.
const config = require('../lib/config');
const shopify = require('../lib/shopify');

const TITLE = 'DW Free Samples (auto)';
const FUNCTION_ID = '01a0475d-d202-743e-bdc8-7a659f6ff512';
const CALLBACK = 'https://signup.designerwallcoverings.com/webhooks/orders-paid';

async function call(query, variables) {
  const response = await shopify.graphql(query, variables);
  if (!response.ok || response.json?.errors?.length) throw new Error(JSON.stringify(response.json?.errors || response.json));
  return response.json.data;
}

async function main() {
  if (!config.SHOPIFY_FULFILLMENT_TOKEN) throw new Error('SHOPIFY_FULFILLMENT_TOKEN is unset');
  const existing = await call(`query Existing($search: String!) {
    webhookSubscriptions(first: 50, topics: [ORDERS_PAID]) { nodes { id topic uri } }
    discountNodes(first: 50, query: $search) {
      nodes { id discount { __typename ... on DiscountAutomaticApp { title status startsAt combinesWith { productDiscounts orderDiscounts shippingDiscounts } appDiscountType { functionId } } } }
    }
  }`, { search: `title:${TITLE}` });

  let webhook = existing.webhookSubscriptions.nodes.find(x => x.uri === CALLBACK);
  let discount = existing.discountNodes.nodes.find(x => x.discount?.title === TITLE && x.discount?.appDiscountType?.functionId === FUNCTION_ID);
  if (!process.argv.includes('--apply')) {
    console.log(JSON.stringify({ apply: false, webhook: webhook || null, discount: discount || null }, null, 2));
    return;
  }

  if (!webhook) {
    const made = await call(`mutation CreateWebhook($input: WebhookSubscriptionInput!) {
      webhookSubscriptionCreate(topic: ORDERS_PAID, webhookSubscription: $input) {
        webhookSubscription { id topic uri }
        userErrors { field message }
      }
    }`, { input: { callbackUrl: CALLBACK, format: 'JSON' } });
    if (made.webhookSubscriptionCreate.userErrors.length) throw new Error(JSON.stringify(made.webhookSubscriptionCreate.userErrors));
    webhook = made.webhookSubscriptionCreate.webhookSubscription;
  }

  if (!discount) {
    const made = await call(`mutation CreateDiscount($input: DiscountAutomaticAppInput!) {
      discountAutomaticAppCreate(automaticAppDiscount: $input) {
        automaticAppDiscount { discountId title status startsAt appDiscountType { functionId } }
        userErrors { field message code }
      }
    }`, { input: {
      title: TITLE,
      functionId: FUNCTION_ID,
      discountClasses: ['PRODUCT'],
      startsAt: new Date(Date.now() - 60_000).toISOString(),
      combinesWith: { orderDiscounts: true, productDiscounts: true, shippingDiscounts: true },
    } });
    if (made.discountAutomaticAppCreate.userErrors.length) throw new Error(JSON.stringify(made.discountAutomaticAppCreate.userErrors));
    discount = made.discountAutomaticAppCreate.automaticAppDiscount;
  } else if (!discount.discount.combinesWith.productDiscounts) {
    const made = await call(`mutation UpdateDiscount($id: ID!, $input: DiscountAutomaticAppInput!) {
      discountAutomaticAppUpdate(id: $id, automaticAppDiscount: $input) {
        automaticAppDiscount { discountId title status startsAt combinesWith { productDiscounts orderDiscounts shippingDiscounts } appDiscountType { functionId } }
        userErrors { field message code }
      }
    }`, { id: discount.id, input: {
      title: TITLE,
      functionId: FUNCTION_ID,
      discountClasses: ['PRODUCT'],
      startsAt: discount.discount.startsAt,
      combinesWith: { orderDiscounts: true, productDiscounts: true, shippingDiscounts: true },
    } });
    if (made.discountAutomaticAppUpdate.userErrors.length) throw new Error(JSON.stringify(made.discountAutomaticAppUpdate.userErrors));
    discount = made.discountAutomaticAppUpdate.automaticAppDiscount;
  }

  console.log(JSON.stringify({ apply: true, webhook, discount }, null, 2));
}

main().catch(error => { console.error(error.message); process.exit(1); });