Advarsler om fjernede rabatkoder

Hvornår skal denne arbejdsgang anvendes?

Brug denne arbejdsgang, når en rabatkode fjernes, og du har brug for en øjeblikkelig oversigt over, hvad der er berørt. Den hjælper dig med at registrere den fjernede kode, antallet af anvendelser, kampagnedetaljerne samt en oversigt, som du kan sende til økonomi- eller driftsafdelingen via Slack.

Dette er især nyttigt i forbindelse med lækkede influencerkoder, utilsigtet offentliggørelse af kuponer eller i alle tilfælde, hvor du har brug for en dokumenteret effektanalyse, efter at en kode er blevet fjernet.

Hvad du skal bygge

  • En Slack-workflow, der modtager variablen »text« fra en webhook og sender den samme tekst til en Slack-kanal.
  • En »Shopify Flow«-arbejdsgang, der udløses af »Rabatkode fjernet«.
  • Et datatrin af typen »Hent rabat«, der slår rabatten op ved hjælp af ID’et fra triggeren.
  • Et »Kør kode«-trin, der formaterer en fjernelsesrapport.
  • Et HTTP-anmodningstrin, der sender rapporten til Slack.

Før du går i gang

  • Sørg for, at appen er installeret og kan ses i Shopify admin under Apps > Workflow Trigger Extensions.
  • Sørg for, at du har adgang til Shopify Flow.
  • Opret eller vælg en Slack-kanal, hvor meddelelserne skal vises.
  • Opret en Slack-workflow, der starter med en webhook og accepterer et felt med navnet »text«.

Videovejledning

Du kan også se den fulde video om implementeringen.

Opret først Slack-workflowet

I Slack skal du oprette en arbejdsgang med udløseren »Starter med en webhook«. Tilføj en tekstvariabel med navnet »text«, og tilføj derefter trinnet »besked«, der sender en besked til den valgte kanal ved hjælp af netop denne variabel »text«.

Slack-workflow, der starter med en webhook og sender tekstvariablen til en Slack-kanal
Slack-workflow, der starter med en webhook og sender tekstvariablen til en Slack-kanal

Giv adgang med rabat i »Workflow Trigger Extensions«

Inden triggeren vises i Shopify Flow, skal du aktivere adgang til rabatter i appen.

Gå til **Apps > Workflow Trigger Extensions **i Shopify-administrationspanelet.

Dashboardet skal du kigge efter opsætningschecklisten.

Aktivér »Giv adgang til rabatter«. Dette aktiverer rabatrelaterede triggere såsom »Rabatt oprettet«, »Rabatt opdateret«, »Rabatt slettet«, »Rabatkode tilføjet«, »Rabatkode fjernet« og »Rabatt udløbet«.

Workflow Trigger Extensions Dashboard, der viser, at »Grant Discount Access« er aktiveret
Workflow Trigger Extensions Dashboard, der viser, at »Grant Discount Access« er aktiveret

Byg »Shopify Flow«

Åbn Shopify Flow og opret en ny arbejdsgang.

Søg efter udløsere relateret til rabatter, og vælg »Rabatkode fjernet« fra »Workflow Trigger Extensions«.

Shopify Flow Trigger-vælger med »Rabatkode fjernet« valgt
Shopify Flow Trigger-vælger med rabatkode - »Valgt« fjernet

Tilføj handlingen »Shopify« - »Hent rabatoplysninger«.

I afsnittet »Vælg en forespørgsel for at filtrere data« skal du vælge »Avanceret«. I »Rediger forespørgsel« skal du bruge rabat-ID’et fra triggeren og fjerne GID-præfikset »Shopify«:

bash
id:"{{discount.id|remove:'gid://shopify/DiscountCodeNode/'}}"
Få oplysninger om rabatter ved hjælp af en avanceret forespørgsel med det rensede rabat-ID
Få oplysninger om rabatter ved hjælp af en avanceret forespørgsel med det rensede rabat-ID

Indsæt et »Kør kode«-trin efter »Hent rabatdata«.

Brug følgende GraphQL-forespørgsel i indtastningsfelterne »Vælg« fra de foregående trin:

graphql
query {
  redeemCode {
    code
    usageCount
    bulkRemoval
  }
  getDiscountData {
    discount {
      ... on DiscountCodeBasic {
        title
        startsAt
        endsAt
        asyncUsageCount
        codes {
          code
          asyncUsageCount
        }
        customerGets {
          value {
            ... on DiscountAmount {
              amount { amount currencyCode }
            }
            ... on DiscountPercentage {
              percentage
            }
          }
        }
      }
    }
  }
}

Definer udgangene på følgende måde:

typescript
"Output of discount code removal report"
type Output {
  "Discount code that was removed"
  code: String!
  "Discount campaign title"
  title: String!
  "Campaign start date"
  startsAt: String!
  "Campaign end date"
  endsAt: String!
  "Number of times it was redeemed"
  usageCount: Int!
  "Total discount impact as a formatted string"
  totalImpact: String!
  "When it was removed"
  removedAt: String!
  "Full summary for alerts"
  summary: String!
}

Indsæt derefter denne kode i feltet »Skriv kode«:

javascript
export default function main(input) {
  // ── Trigger: removed code ──
  const removedCode  = input?.redeemCode?.code         ?? "Unknown";
  const usageCount   = input?.redeemCode?.usageCount   ?? null; // null = bulk removal or unknown
  const bulkRemoval  = input?.redeemCode?.bulkRemoval  ?? false;

  const currency = input?.shop?.currencyCode ?? "";
  const shopName = input?.shop?.name         ?? "(not bound)";
  const removedAt = new Date().toISOString();

  // ── Find matching discount ──
  const dataItems = input?.getDiscountData ?? [];
  let matchedDiscount = null;
  for (const item of dataItems) {
    const d = item?.discount?.DiscountCodeBasic ?? item?.discount;
    if (!d) continue;
    const codesArray = Array.isArray(d.codes)
      ? d.codes.map(c => (typeof c === "string" ? c : c?.code ?? ""))
      : [];
    if (codesArray.includes(removedCode)) {
      matchedDiscount = d;
      break;
    }
  }
  // fallback to first entry if removed code already gone from list
  if (!matchedDiscount && dataItems.length > 0) {
    matchedDiscount =
      dataItems[0]?.discount?.DiscountCodeBasic ?? dataItems[0]?.discount ?? null;
  }

  const title              = matchedDiscount?.title            ?? "Untitled";
  const startsAt           = matchedDiscount?.startsAt         ?? "N/A";
  const endsAt             = matchedDiscount?.endsAt           ?? "None (no expiry)";
  const totalUsageCount    = matchedDiscount?.asyncUsageCount  ?? null;

  // ── Discount value ──
  const valueWrapper = matchedDiscount?.customerGets?.value ?? {};
  const pctObj       = valueWrapper?.DiscountPercentage ?? null;
  const amtObj       = valueWrapper?.DiscountAmount ?? valueWrapper?.amount ?? null;

  let discountLabel = "Unknown";
  if (pctObj?.percentage != null) {
    discountLabel = `${(pctObj.percentage * 100).toFixed(0)}% off`;
  } else if (amtObj?.amount != null) {
    const cur = amtObj.currencyCode || currency;
    discountLabel = `${cur} ${parseFloat(amtObj.amount).toFixed(2)} off`;
  }

  // ── This code's impact ──
  let thisCodeImpact;
  if (bulkRemoval) {
    thisCodeImpact = `Not available - removed as part of bulk deletion`;
  } else if (usageCount === null) {
    thisCodeImpact = `Could not be determined`;
  } else if (pctObj?.percentage != null) {
    thisCodeImpact = `${discountLabel} × ${usageCount} use(s)`;
  } else if (amtObj?.amount != null) {
    const perUse = parseFloat(amtObj.amount);
    const cur    = amtObj.currencyCode || currency;
    thisCodeImpact = `${cur} ${(perUse * usageCount).toFixed(2)} (${cur} ${perUse.toFixed(2)} × ${usageCount} uses)`;
  } else {
    thisCodeImpact = "Unknown";
  }

  // ── Campaign-wide impact using asyncUsageCount on the discount ──
  let campaignImpact;
  if (totalUsageCount === null) {
    campaignImpact = "Not available";
  } else if (pctObj?.percentage != null) {
    campaignImpact = `${discountLabel} × ${totalUsageCount} total use(s) across all codes`;
  } else if (amtObj?.amount != null) {
    const perUse = parseFloat(amtObj.amount);
    const cur    = amtObj.currencyCode || currency;
    campaignImpact = `${cur} ${(perUse * totalUsageCount).toFixed(2)} total (${cur} ${perUse.toFixed(2)} × ${totalUsageCount} uses)`;
  } else {
    campaignImpact = "Unknown";
  }

  // ── Per-code breakdown ──
  const codesNodes = Array.isArray(matchedDiscount?.codes)
    ? matchedDiscount.codes
    : [];

  const allCodes = codesNodes
    .map(c => (typeof c === "string" ? c : c?.code ?? ""))
    .filter(Boolean);

  const codesRoster = codesNodes.length
    ? codesNodes.map(c => {
        const code = typeof c === "string" ? c : c?.code ?? "";
        const used = c?.asyncUsageCount ?? "?";
        const flag = code === removedCode ? " ← REMOVED" : "";
        return `${code} (used ${used}×)${flag}`;
      }).join("\n                  ")
    : "N/A";

  const usageCountLabel = bulkRemoval
    ? "N/A (bulk removal)"
    : usageCount !== null
      ? String(usageCount)
      : "Unknown";

  const summary =
    `DISCOUNT CODE REMOVED\n` +
    `─────────────────────\n` +
    `Store:            ${shopName}\n` +
    `Code removed:     ${removedCode}\n` +
    `Bulk removal:     ${bulkRemoval ? "Yes" : "No"}\n` +
    `Campaign:         ${title}\n` +
    `Discount:         ${discountLabel}\n` +
    `Valid from:       ${startsAt}\n` +
    `Valid until:      ${endsAt}\n` +
    `\n` +
    `THIS CODE\n` +
    `  Times used:     ${usageCountLabel}\n` +
    `  Impact:         ${thisCodeImpact}\n` +
    `\n` +
    `CAMPAIGN TOTAL\n` +
    `  Total uses:     ${totalUsageCount ?? "Unknown"}\n` +
    `  Total impact:   ${campaignImpact}\n` +
    `\n` +
    `CODE BREAKDOWN\n` +
    `  ${codesRoster}\n` +
    `\n` +
    `Removed at:       ${removedAt}`;

const safeSummary = summary
  .replace(/\\/g, "\\\\")   // escape backslashes first
  .replace(/"/g, '\\"')     // escape quotes
  .replace(/\n/g, "\\n")    // escape newlines
  .replace(/\r/g, "\\r");

  return {
    code:           removedCode,
    bulkRemoval,
    title,
    discountLabel,
    startsAt,
    endsAt,
    usageCount:     usageCount ?? 0,
    usageCountLabel,
    thisCodeImpact,
    totalUsageCount: totalUsageCount ?? 0,
    campaignImpact,
    allCodes,
    codesRoster,
    removedAt,
    summary: safeSummary,
  };
}
Kør et kodetrin i »Shopify Flow« med forespørgslen, resultaterne og JavaScript-rapportgeneratoren
Kør et kodetrin i Shopify Flow med forespørgslen, uddataene og JavaScript-rapportgeneratoren

Tilføj handlingen »HTTP-anmodning«. I feltet »Variabler« eller »Body Override (JSON)« skal du sende det resumé, der returneres af kodetrinnet »Kør«:

css
{
  "body": {
    "text": "{{runCode.summary}}"
  }
}

Dette sender det genererede resumé til Slack-webhook-workflowet ved hjælp af variablen text.

HTTP-anmodningshandlingen i Shopify Flow, hvor runCode-oversigten sendes i tekstfeltet
HTTP-anmodningshandlingen i Shopify Flow, hvor runCode-oversigten sendes i tekstfeltet

Gem arbejdsgangen, når alle trin er på plads.

For at teste det skal du slette en rabatkode fra din rabatkampagne og kontrollere, at Slack-kanalen modtager oversigtsbeskeden.

Slack-besked, der viser, at rabatkoden er fjernet - resumé
Slack-besked, der viser, at rabatkoden er fjernet - resumé

Hvad Slack-beskeden indeholder

  • Den fjernede rabatkode
  • Uanset om der var tale om en større udflytning
  • Kampagnens titel
  • Rabatbeløbet
  • Start- og slutdatoer
  • Tider anvendt til den fjernede kode
  • Anslået indvirkning af den fjernede kode
  • Anvendelse og virkning på tværs af kampagnen
  • En opdeling kode for kode, når den foreligger
  • Tidspunktet for, hvornår sletningen blev gennemført

Fejlfinding

Gå tilbage til Apps > Workflow Trigger Extensions, og kontroller, at »Grant Discount Access« er aktiveret. Rabattriggerne vises først, når adgangen er bevilget.

Kontroller den avancerede forespørgsel nøje under »Rediger forespørgsel«. ID’et skal renses nøjagtigt som vist:

bash
id:"{{discount.id|remove:'gid://shopify/DiscountCodeNode/'}}"

Sørg for, at Slack-workflowet starter med en webhook, at webhook-URL’en er den samme som den, der bruges i din HTTP-anmodning, og at anmodningens indhold indeholder feltet text.

Nogle sletninger kan være en del af en samlet handling eller medfører, at der ikke returneres fuldstændige brugsoplysninger. I sådanne tilfælde vil oversigten stadig indeholde de bedst tilgængelige oplysninger og tydeligt markere manglende værdier.

Gode eksempler

  • Send disse advarsler til en dedikeret kanal for økonomi eller drift i stedet for en generel supportkanal.
  • Giv arbejdsgangen et konkret navn, f.eks. »Rabatkode fjernet«, så den er nem at genkende i Shopify Flow og Slack.
  • Prøv først med en rabatkode, der ikke er til produktionsbrug, for at kontrollere meddelelsesformatet og webhookens funktion.