UC 10.1.8
Send Goodwill Merch — Flow 37 (Goodwill Shirt) + BPS Admin Endpoint
| Field | Value |
|---|---|
| BA in Charge | Huyen Dinh |
| Date Created | 2026-10-21 |
| Version | v3 |
| Document References | Zapier 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
| Field | Content |
|---|---|
| ID | UC_10.1.8 |
| Use Case | Send Goodwill Merch (Flow 37) |
| Description | The 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 Flow | Flow 37 (Fulfillment — Goodwill Shirt) |
| Table | Table 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 Party | Apliiq (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_sizestored in theUserstable (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_urljustification.
Post-conditions
- If
Active == Yes AND Claimed < Limitat processing time:Claimedis 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'sActiveis set toNo, and a Slack alert is sent to#ops-marketing(Ref: BR_10.1.8.1 items 1 and 3). - If
Active == Noat 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
| Field | Value |
|---|---|
| Method | POST |
| Path | /bps/send-goodwill-merch |
| Auth | Admin 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)
POST /bps/send-goodwill-merchis called by the Support team. Pre-check: reject with an error if a row already exists inperk_claimedfor(user_id, "Goodwill_Shirt")— 1-per-user rule- The Middleware performs an atomic conditional update on PostgreSQL
Perks_Registryper Ref: BR_10.1.8.1 forLevel = "Support"andPerk = "T-Shirt" - [If the increment succeeds] → the Middleware fires a webhook containing the trader's full name, shipping address and
shirt_sizeto Zapier (Flow 37), which calls the Apliiq API to place an order, then inserts a row(user_id, "Goodwill_Shirt")intoperk_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_sizemapped from theUserstable via the static size-mapping dictionary (Ref: UC_8.5 §BR_8.5.2. - Recipient: Trader Name/Address from the SQL DB.
- The Middleware sends the
Perk_Unlocked_Generalevent 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 anew_levelfield since this perk is not tied to any level. - Audit: write a record to
BPS_Audit_Logper CR-21 §21.5, withAction Type = SEND_GOODWILL_MERCH.
2.3 Exceptional Flow — Flow 37
- [If the increment at step 2 fails because
Limitwas 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_claimedfor this flow
- System behavior: return HTTP 403 Forbidden with message
- [If a row already exists in
perk_claimedfor(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_Registryis touched for this attempt.
- 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
- [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
Claimedcounter onPerks_Registryback by 1; create a Freshdesk ticket for Operations withuser_id,admin_id,action_type(SEND_GOODWILL_MERCH, per CR-21 §21.5), andtimestamp; do not mark the perk as claimed (noperk_claimedinsert); do not show any error to the user beyond the standard request-failed response.
- System behavior: retry the Apliiq call up to 3 times. If all 3 attempts fail: decrement the
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.
-
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.
-
Execution order (increment-first): incrementing PostgreSQL
Perks_Registryalways runs BEFORE the dependent actions (Apliiq order, Klaviyo event). -
Limit Reached Alert: if the increment causes
Claimed = Limit(i.e.Activehas just flipped toNo), the Middleware sends a Slack alert to#ops-marketing(C0BQX3AV679):Flow Message Flow 37 "Goodwill Merch Inventory Depleted. Support can no longer issue free shirts."
Changelog
| Date | Version | Updated item | Before | After | Notes |
|---|---|---|---|---|---|
| 2026-10-21 | v1 | Split out from UC_8.5 | Flow 37 content embedded in UC_8.5 (§4, part of BR_8.5.1) | Standalone UC_10.1.8 | Content preserved as reviewed; cross-refs to UC_8.5 added where Flow 37 shares mechanics with Flow 25/26 |
| 2026-10-29 | v2 | Enforced 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 handling | See updated §Pre-conditions, §Overview Table row, §2.2, §2.3 | Client QnA 2026-10-29 |
| 2026-11-02 | v3 | Replaced 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 value | See updated §2.1, §2.2 step 5, §2.3 | CR-21 (common_rules.md), raised from CHR question review 2026-11-02 |