StackTrading Docs

UC 10.1.8

Send Goodwill Merch — Flow 37 (Goodwill Shirt) + BPS Admin Endpoint

FieldValue
BA in ChargeHuyen Dinh
Date Created2026-10-21
Versionv3
Document ReferencesZapier Integration V7.pdf (§Flow 37, §Table H Perks_Registry) · RFQ_ Stack Trading Prop Tech V7.pdf (§BPS Endpoints — "Send Goodwill Merch")

UC_10.1.8 — Send Goodwill Merch

1. Overview

FieldContent
IDUC_10.1.8
Use CaseSend Goodwill Merch (Flow 37)
DescriptionThe Support team manually issues a free Goodwill Shirt to a trader via the BPS Admin UI. The system checks perk inventory in PostgreSQL Perks_Registry (business name "Table H"), calls Apliiq to fulfill the gift, and atomically updates the Claimed count to avoid a race condition that would exceed Limit.
Zapier FlowFlow 37 (Fulfillment — Goodwill Shirt)
TableTable H / Perks_Registry (Level, Active, Claimed, Limit, Fulfillment_Zap) — managed 100% inside PostgreSQL as the single source of truth (all reads/writes/atomic increments in this UC hit PostgreSQL directly, Ref: BR_10.1.8.1). The Zapier-side copy of this table (per the Tables A-J convention) is removed — there is no duplicate Table H in Zapier
.
3rd PartyApliiq (merch fulfillment API — Tee) · Klaviyo (Perk_Unlocked_General event) · Slack (#ops-marketing, Ref: slack_channel_usage.md)

Trigger

  • Webhook from Middleware Hub, fired by the BPS Admin endpoint POST /bps/send-goodwill-merch — triggered manually by the Support team via the BPS Admin UI.

Pre-conditions

  • The trader already has a shirt_size stored in the Users table (collected at checkout, Ref: UC_2.8.2).
  • The trader already has a shipping address stored in the SQL DB (collected at checkout).
  • The 1-per-user rule applies across all gift flows. A single trader cannot receive more than one Goodwill Shirt from the pool, regardless of reason/transcript_url justification.

Post-conditions

  • If Active == Yes AND Claimed < Limit at processing time: Claimed is incremented exactly once and the Apliiq order is created.
  • If this increment is the one that causes Claimed >= Limit (i.e. it crosses the threshold for the first time): that row's Active is set to No, and a Slack alert is sent to #ops-marketing (Ref: BR_10.1.8.1 items 1 and 3).
  • If Active == No at processing time (limit was already reached earlier, by a prior request): the webhook/Apliiq order is bypassed and HTTP 403 is returned.

2. Basic Flow — Flow 37: Fulfillment - Goodwill Shirt (Apliiq) + BPS Admin Endpoint

2.1 BPS Admin Endpoint — Send Goodwill Merch

FieldValue
MethodPOST
Path/bps/send-goodwill-merch
AuthAdmin JWT authorization layer (shared by the whole §BPS Endpoints section, does NOT require a specific RBAC role; admin identity/role sourced per CR-21 §21.2)

Input: common payload envelope per CR-21 §21.1 — user_id, admin_id, reason, transcript_url, all mandatory. reason validated per §21.1.1, transcript_url per §21.1.2.

2.2 Fulfillment Flow (Zapier Flow 37)

  1. POST /bps/send-goodwill-merch is called by the Support team. Pre-check: reject with an error if a row already exists in perk_claimed for (user_id, "Goodwill_Shirt") — 1-per-user rule
  2. The Middleware performs an atomic conditional update on PostgreSQL Perks_Registry per Ref: BR_10.1.8.1 for Level = "Support"and Perk = "T-Shirt"
  3. [If the increment succeeds] → the Middleware fires a webhook containing the trader's full name, shipping address and shirt_size to Zapier (Flow 37), which calls the Apliiq API to place an order, then inserts a row (user_id, "Goodwill_Shirt") into perk_claimed (this perk can never be claimed by this trader again):
    • Product: "Stack Trading Core T-Shirt" (SKU stored as a test placeholder in environment variable for now — production Apliiq SKU ID to be provided by the client).
    • Options: shirt_size mapped from the Users table via the static size-mapping dictionary (Ref: UC_8.5 §BR_8.5.2.
    • Recipient: Trader Name/Address from the SQL DB.
  4. The Middleware sends the Perk_Unlocked_General event to Klaviyo with payload: user_name, user_email, perk_name ("Stack Trading Core T-Shirt") — same event and mechanism as Flow 25 (Ref: UC_8.5), without a new_level field since this perk is not tied to any level.
  5. Audit: write a record to BPS_Audit_Log per CR-21 §21.5, with Action Type = SEND_GOODWILL_MERCH.

2.3 Exceptional Flow — Flow 37

  • [If the increment at step 2 fails because Limit was already reached]
    • System behavior: return HTTP 403 Forbidden with message "Merch Inventory Depleted" . Do not fire the webhook, do not trigger Flow 37/Apliiq
    • A Slack alert is also sent to #ops-marketing: "Goodwill Merch Inventory Depleted. Support can no longer issue free shirts." (Ref: BR_10.1.8.1 item 3).
    • This is returned directly by the backend before firing the webhook — Flow 37 (Zapier) is never triggered in this case.
    • Outcome: the perk remains unclaimed; no row is written to perk_claimed for this flow
  • [If a row already exists in perk_claimed for (user_id, "Goodwill_Shirt") — 1-per-user rule]
    • System behavior: return an error to the Support team via the BPS Admin UI (e.g. "Trader has already received a Goodwill Shirt"). Do not query/increment Perks_Registry, do not fire the webhook.
    • Outcome: nothing in PostgreSQL Perks_Registry is touched for this attempt.
  • [If the Apliiq API call fails after the increment at step 2 has already succeeded]
    • System behavior: retry the Apliiq call up to 3 times. If all 3 attempts fail: decrement the Claimed counter on Perks_Registry back by 1; create a Freshdesk ticket for Operations with user_id, admin_id, action_type (SEND_GOODWILL_MERCH, per CR-21 §21.5), and timestamp; do not mark the perk as claimed (no perk_claimed insert); do not show any error to the user beyond the standard request-failed response.

3. Business Rules

BR_10.1.8.1: Perk Claim Atomic Increment Pattern (PostgreSQL Perks_Registry)

Applies to Flow 37 when updating the Claimed count on PostgreSQL Perks_Registry (business name "Table H"). The same pattern also applies to Flow 25/26 — Ref: UC_8.5 §BR_8.5.1.

Which physical table this hits: the increment below runs against the PostgreSQL Perks_Registry table.

  1. Atomic conditional increment: the Middleware perform the increment (Claimed = current_claimed_number + 1) and the limit check (Claimed < limit).

    • If the increment succeeds (the trader's request was within the limit) → continue with the corresponding fulfillment flow (Apliiq order).
    • If the increment fails (the Limit was already reached earlier, by another request) → bypass the entire fulfillment flow — no retry, no error shown to the trader.
  2. Execution order (increment-first): incrementing PostgreSQL Perks_Registry always runs BEFORE the dependent actions (Apliiq order, Klaviyo event).

  3. Limit Reached Alert: if the increment causes Claimed = Limit (i.e. Active has just flipped to No), the Middleware sends a Slack alert to #ops-marketing (C0BQX3AV679):

    FlowMessage
    Flow 37"Goodwill Merch Inventory Depleted. Support can no longer issue free shirts."

Changelog

DateVersionUpdated itemBeforeAfterNotes
2026-10-21v1Split out from UC_8.5Flow 37 content embedded in UC_8.5 (§4, part of BR_8.5.1)Standalone UC_10.1.8Content preserved as reviewed; cross-refs to UC_8.5 added where Flow 37 shares mechanics with Flow 25/26
2026-10-29v2Enforced 1-per-user rule (removed "Support may re-issue" pre-condition); Table H confirmed PostgreSQL-only (Zapier-side copy removed, no sync cron); added Apliiq SKU test-placeholder note; added size-mapping dictionary reference; added Exceptional Flow for already-claimed and for Apliiq API failure/retry (with Claimed counter rollback + Freshdesk ticket)§Pre-conditions allowed Support to re-issue with justification; Table H described as having an async Zapier copy; SKU hardcoded; no retry/failure handlingSee updated §Pre-conditions, §Overview Table row, §2.2, §2.3Client QnA 2026-10-29
2026-11-02v3Replaced locally-defined input validation (reason max 500 chars) and Audit Table description with references to CR-21 — common payload shape, field validation, admin identity source, and BPS_Audit_Log (Action Type = SEND_GOODWILL_MERCH)§2.1 locally defined reason max 500 chars (conflicts with CR-21's 1,000 chars); §2.2 step 5 described a generic "Audit Table" without naming BPS_Audit_Log or the standard Action Type valueSee updated §2.1, §2.2 step 5, §2.3CR-21 (common_rules.md), raised from CHR question review 2026-11-02

On this page