@ekzs/cli 0.2.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.
Files changed (113) hide show
  1. package/README.md +148 -0
  2. package/dist/commands/agent.d.ts +31 -0
  3. package/dist/commands/agent.d.ts.map +1 -0
  4. package/dist/commands/agent.js +55 -0
  5. package/dist/commands/ask.d.ts +20 -0
  6. package/dist/commands/ask.d.ts.map +1 -0
  7. package/dist/commands/ask.js +154 -0
  8. package/dist/commands/doctor.d.ts +3 -0
  9. package/dist/commands/doctor.d.ts.map +1 -0
  10. package/dist/commands/doctor.js +44 -0
  11. package/dist/commands/health.d.ts +2 -0
  12. package/dist/commands/health.d.ts.map +1 -0
  13. package/dist/commands/health.js +28 -0
  14. package/dist/commands/local-agent.d.ts +19 -0
  15. package/dist/commands/local-agent.d.ts.map +1 -0
  16. package/dist/commands/local-agent.js +450 -0
  17. package/dist/commands/scan.d.ts +11 -0
  18. package/dist/commands/scan.d.ts.map +1 -0
  19. package/dist/commands/scan.js +119 -0
  20. package/dist/commands/webhook.d.ts +10 -0
  21. package/dist/commands/webhook.d.ts.map +1 -0
  22. package/dist/commands/webhook.js +42 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +185 -0
  26. package/dist/lib/banner.d.ts +10 -0
  27. package/dist/lib/banner.d.ts.map +1 -0
  28. package/dist/lib/banner.js +26 -0
  29. package/dist/lib/commands-i18n.d.ts +20 -0
  30. package/dist/lib/commands-i18n.d.ts.map +1 -0
  31. package/dist/lib/commands-i18n.js +157 -0
  32. package/dist/lib/composer-model.d.ts +10 -0
  33. package/dist/lib/composer-model.d.ts.map +1 -0
  34. package/dist/lib/composer-model.js +15 -0
  35. package/dist/lib/context.d.ts +12 -0
  36. package/dist/lib/context.d.ts.map +1 -0
  37. package/dist/lib/context.js +56 -0
  38. package/dist/lib/doctor-quiet.d.ts +11 -0
  39. package/dist/lib/doctor-quiet.d.ts.map +1 -0
  40. package/dist/lib/doctor-quiet.js +39 -0
  41. package/dist/lib/env.d.ts +18 -0
  42. package/dist/lib/env.d.ts.map +1 -0
  43. package/dist/lib/env.js +66 -0
  44. package/dist/lib/help.d.ts +10 -0
  45. package/dist/lib/help.d.ts.map +1 -0
  46. package/dist/lib/help.js +140 -0
  47. package/dist/lib/locale.d.ts +38 -0
  48. package/dist/lib/locale.d.ts.map +1 -0
  49. package/dist/lib/locale.js +189 -0
  50. package/dist/lib/mode.d.ts +11 -0
  51. package/dist/lib/mode.d.ts.map +1 -0
  52. package/dist/lib/mode.js +29 -0
  53. package/dist/lib/output.d.ts +7 -0
  54. package/dist/lib/output.d.ts.map +1 -0
  55. package/dist/lib/output.js +18 -0
  56. package/dist/lib/preferences.d.ts +9 -0
  57. package/dist/lib/preferences.d.ts.map +1 -0
  58. package/dist/lib/preferences.js +35 -0
  59. package/dist/lib/redact.d.ts +3 -0
  60. package/dist/lib/redact.d.ts.map +1 -0
  61. package/dist/lib/redact.js +32 -0
  62. package/dist/lib/scan-quiet.d.ts +4 -0
  63. package/dist/lib/scan-quiet.d.ts.map +1 -0
  64. package/dist/lib/scan-quiet.js +4 -0
  65. package/dist/lib/scope.d.ts +5 -0
  66. package/dist/lib/scope.d.ts.map +1 -0
  67. package/dist/lib/scope.js +61 -0
  68. package/dist/lib/session.d.ts +31 -0
  69. package/dist/lib/session.d.ts.map +1 -0
  70. package/dist/lib/session.js +101 -0
  71. package/dist/lib/shell.d.ts +18 -0
  72. package/dist/lib/shell.d.ts.map +1 -0
  73. package/dist/lib/shell.js +214 -0
  74. package/dist/lib/skill.d.ts +3 -0
  75. package/dist/lib/skill.d.ts.map +1 -0
  76. package/dist/lib/skill.js +2 -0
  77. package/dist/lib/skills.d.ts +16 -0
  78. package/dist/lib/skills.d.ts.map +1 -0
  79. package/dist/lib/skills.js +199 -0
  80. package/dist/lib/theme.d.ts +23 -0
  81. package/dist/lib/theme.d.ts.map +1 -0
  82. package/dist/lib/theme.js +40 -0
  83. package/dist/lib/ui/ascii-art.d.ts +10 -0
  84. package/dist/lib/ui/ascii-art.d.ts.map +1 -0
  85. package/dist/lib/ui/ascii-art.js +55 -0
  86. package/dist/lib/ui/layout.d.ts +19 -0
  87. package/dist/lib/ui/layout.d.ts.map +1 -0
  88. package/dist/lib/ui/layout.js +46 -0
  89. package/dist/lib/ui/logo.d.ts +3 -0
  90. package/dist/lib/ui/logo.d.ts.map +1 -0
  91. package/dist/lib/ui/logo.js +8 -0
  92. package/dist/lib/ui/prompt.d.ts +6 -0
  93. package/dist/lib/ui/prompt.d.ts.map +1 -0
  94. package/dist/lib/ui/prompt.js +75 -0
  95. package/dist/lib/ui/splash.d.ts +15 -0
  96. package/dist/lib/ui/splash.d.ts.map +1 -0
  97. package/dist/lib/ui/splash.js +121 -0
  98. package/package.json +48 -0
  99. package/skills/ekz-connect/SKILL.md +99 -0
  100. package/skills/ekz-data-layer-design/SKILL.md +199 -0
  101. package/skills/ekz-data-mongo/SKILL.md +341 -0
  102. package/skills/ekz-data-mysql/SKILL.md +245 -0
  103. package/skills/ekz-data-postgres/SKILL.md +257 -0
  104. package/skills/ekz-data-sqlite/SKILL.md +261 -0
  105. package/skills/ekz-ekwanza-provider-adapter/SKILL.md +91 -0
  106. package/skills/ekz-integration-playbook/SKILL.md +122 -0
  107. package/skills/ekz-one-time-product-payments/SKILL.md +91 -0
  108. package/skills/ekz-overage-billing/SKILL.md +68 -0
  109. package/skills/ekz-payment-core-architecture/SKILL.md +121 -0
  110. package/skills/ekz-sdk-cli/SKILL.md +82 -0
  111. package/skills/ekz-subscription-billing/SKILL.md +120 -0
  112. package/skills/ekz-ticket-invite-selling/SKILL.md +64 -0
  113. package/skills/ekz-webhook-normalization/SKILL.md +88 -0
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: ekz-payment-core-architecture
3
+ description: >-
4
+ Provider-agnostic payment architecture: PaymentRequest, PaymentAttempt,
5
+ ProviderTransaction, WebhookEvent, LedgerEntry, FulfillmentAction,
6
+ Entitlement, BillingCycle, UsageRecord, state machines, idempotency, and
7
+ provider boundaries.
8
+ ---
9
+
10
+ # Payment Core Architecture
11
+
12
+ Use this skill before adding any payment flow. It teaches the reusable payment domain that sits above e-Kwanza or any other provider.
13
+
14
+ ## Core idea
15
+
16
+ Treat e-Kwanza as a one-time payment rail. Build a clean local payment domain on top of it.
17
+
18
+ Products, invite/ticket sales, subscriptions, and overage all compose the same primitives:
19
+
20
+ ```text
21
+ business object -> PaymentRequest -> PaymentAttempt -> ProviderTransaction
22
+ -> WebhookEvent -> LedgerEntry -> FulfillmentAction/Entitlement
23
+ ```
24
+
25
+ ## Primitives
26
+
27
+ `PaymentRequest`
28
+
29
+ The business claim to collect money. It belongs to an order, ticket reservation, subscription cycle, invoice, or overage charge.
30
+
31
+ `PaymentAttempt`
32
+
33
+ One concrete try to collect a request through a provider and rail. A request may have multiple attempts.
34
+
35
+ `ProviderTransaction`
36
+
37
+ The provider's identifiers, raw response, operation code, reference, QR payload, expiration, and correlation IDs.
38
+
39
+ `WebhookEvent`
40
+
41
+ Raw callback plus normalized status, event ID, dedupe status, processing status, and error details.
42
+
43
+ `LedgerEntry`
44
+
45
+ Append-only internal financial/audit movement: requested, authorized, paid, failed, expired, settled, credited, adjusted.
46
+
47
+ `FulfillmentAction`
48
+
49
+ The domain side effect to run after payment succeeds: ship order, issue ticket, extend entitlement, settle overage.
50
+
51
+ `Entitlement`
52
+
53
+ The access or ownership created by payment: plan access, quota, license, ticket ownership, invite validity.
54
+
55
+ `BillingCycle`
56
+
57
+ A local recurring period with start/end, due date, grace window, retry state, and charge request.
58
+
59
+ `UsageRecord`
60
+
61
+ An idempotent metered event that can be aggregated into overage charges.
62
+
63
+ ## State machines
64
+
65
+ Keep provider states separate from local business states.
66
+
67
+ ```text
68
+ PaymentRequest: pending -> paid | failed | expired | canceled
69
+ PaymentAttempt: created -> provider_pending -> succeeded | failed | expired
70
+ WebhookEvent: received -> processing -> processed | ignored | failed
71
+ FulfillmentAction: pending -> completed | failed | compensated
72
+ Entitlement: inactive -> trialing | active | grace | past_due | suspended | canceled
73
+ BillingCycle: open -> invoiced -> paid | past_due | closed
74
+ ```
75
+
76
+ Use the target app's existing enum names if they already exist. Preserve the concepts, not the spelling.
77
+
78
+ ## Provider boundary
79
+
80
+ Business code should not call e-Kwanza directly. Put the provider behind a small adapter:
81
+
82
+ ```typescript
83
+ type CreatePaymentAttemptInput = {
84
+ amountAoa: number;
85
+ rail: "gpo" | "emis_ref" | "ticket";
86
+ correlationId: string;
87
+ phoneNumber?: string;
88
+ metadata?: Record<string, unknown>;
89
+ };
90
+
91
+ type PaymentProvider = {
92
+ createAttempt(input: CreatePaymentAttemptInput): Promise<ProviderTransaction>;
93
+ normalizeWebhook(body: unknown, headers: Headers): Promise<NormalizedPaymentEvent>;
94
+ };
95
+ ```
96
+
97
+ Domain handlers consume local `PaymentRequest` and normalized events, not raw provider payloads.
98
+
99
+ ## Invariants
100
+
101
+ - Payment collection is not business fulfillment.
102
+ - Compute amount server-side from the domain object.
103
+ - Persist the local request/attempt before creating or exposing provider instructions.
104
+ - Store all provider identifiers returned by the provider.
105
+ - Webhook processing must be idempotent.
106
+ - A duplicate paid event must not duplicate fulfillment, ticket issuance, entitlement extension, or usage settlement.
107
+ - Raw provider statuses must be normalized before domain logic sees them.
108
+ - Ledger entries should be append-only where possible.
109
+ - Retrying payment should create or update a payment attempt, not duplicate the business request.
110
+
111
+ ## Test contracts
112
+
113
+ Every payment flow should have tests for:
114
+
115
+ - paid webhook performs exactly one domain side effect
116
+ - duplicate paid webhook is harmless
117
+ - failed/expired webhook leaves a recoverable state
118
+ - amount cannot be changed from the client
119
+ - provider callback without a known local payment is stored or rejected safely
120
+ - retry creates a new attempt or intentional update without double fulfillment
121
+
@@ -0,0 +1,82 @@
1
+ ---
2
+ name: ekz-sdk-cli
3
+ description: >-
4
+ @ekzs/connect TypeScript SDK and @ekzs/cli — healthCheck, createPayment, webhooks,
5
+ ekz doctor, scan, fix, agent commands.
6
+ ---
7
+
8
+ # @ekzs/connect SDK + CLI
9
+
10
+ **Agent limit:** in-scope for edits. Unrelated code is read-only context only.
11
+
12
+ ## SDK install
13
+
14
+ ```bash
15
+ npm install @ekzs/connect
16
+ # monorepo: file:packages/sdk
17
+ ```
18
+
19
+ ```typescript
20
+ import { EkzConnect, configFromEnv, normalizeWebhookPayload, verifyTicketSignature } from "@ekzs/connect";
21
+
22
+ const client = new EkzConnect(configFromEnv()!);
23
+ await client.healthCheck();
24
+ await client.createPayment("gpo", { amountAoa, merchantTransactionId, phoneNumber });
25
+ ```
26
+
27
+ Rails: `gpo` | `emis_ref` | `ticket`
28
+
29
+ Model env: `EKZ_AGENT_MODEL=composer-2.5-fast` → `composer-2.5` + `fast: true`
30
+
31
+ ## CLI commands
32
+
33
+ | Command | Purpose |
34
+ |---------|---------|
35
+ | `ekz doctor` | Validate `.env`, live OAuth (free) |
36
+ | `ekz scan` | Common integration mistakes (free) |
37
+ | `ekz webhook <url>` | POST sample payload (free) |
38
+ | `ekz agent` | Local coding agent, edits repo (CURSOR_API_KEY) |
39
+ | `ekz fix` | Auto-audit + fix integration |
40
+ | `ekz ask` | Remote Q&A only (EKZ_AGENT_API_KEY) |
41
+
42
+ Modes: `--mode agent` (default) | `--mode plan` | `--mode ask`. Shorthand: `--plan` = plan mode.
43
+ In the REPL: `/help`, `/mode agent|plan|ask`, `/doctor`, `/scan`, `/lang`, `/save`, `/resume`.
44
+
45
+ ```bash
46
+ export CURSOR_API_KEY=crsr_...
47
+ ekz # interactive REPL (agent mode)
48
+ ekz --mode plan # plan before edits
49
+ ekz --mode ask # remote Q&A, no edits
50
+ ekz fix # full integration pass
51
+ ekz agent --resume # continue .ekz/session.json
52
+ ```
53
+
54
+ ### Windows / PowerShell
55
+
56
+ Run the CLI with `.\ekz`, `npx ekz`, or global `ekz` after `npm run setup:cli`.
57
+
58
+ When the **local agent** runs shell commands, Ekz detects the terminal at launch (Cursor settings → Git Bash → Windows PowerShell default) and injects matching rules into every turn.
59
+
60
+ - Use `curl.exe -s "http://localhost:3000/..."` — not bare `curl` (PowerShell alias).
61
+ - Prefer `ekz doctor`, `ekz health`, `ekz webhook <url>` over manual HTTP curls.
62
+ - Avoid bash-only pipes and `/dev/null` unless invoking Git Bash explicitly.
63
+
64
+ ## Agent skills
65
+
66
+ Bundled in `packages/cli/skills/`. Cursor IDE: `.cursor/skills/` (same content).
67
+
68
+ Orchestrator: **ekz-connect** — routes to sub-skills by problem type.
69
+
70
+ ## Package layout
71
+
72
+ ```
73
+ packages/sdk/ @ekzs/connect
74
+ packages/cli/ @ekzs/cli
75
+ packages/cli/skills/
76
+ ```
77
+
78
+ ## Server agent API
79
+
80
+ `POST /api/developers/agent` — remote ask with redacted doctor context (Composer 2.5 Fast).
81
+
82
+ Local file edits require **`ekz agent`**, not the browser demo panel.
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: ekz-subscription-billing
3
+ description: >-
4
+ Product-agnostic subscription billing over one-time payment rails: local
5
+ billing cycles, recurring payment requests, grace periods, retries, dunning,
6
+ entitlement extension, suspension, and internal reference patterns.
7
+ ---
8
+
9
+ # Subscription Billing
10
+
11
+ Use this skill for recurring access, memberships, SaaS plans, renewals, trials, grace periods, dunning, and entitlement extension.
12
+
13
+ ## Core idea
14
+
15
+ e-Kwanza has no native subscription API. A subscription is a local billing lifecycle that creates one-time `PaymentRequest`s when cycles are due.
16
+
17
+ The internal reference implementation proves this pattern, but its plans, table names, routes, and UI are examples only.
18
+
19
+ ## Pattern
20
+
21
+ ```text
22
+ SubscriptionAccount -> BillingCycle -> PaymentRequest -> PaymentAttempt
23
+ -> paid webhook -> extend Entitlement
24
+ -> failed/expired -> grace/past_due/suspend
25
+ ```
26
+
27
+ ## Concepts
28
+
29
+ - Subscription account: user, team, tenant, merchant, organization.
30
+ - Plan/product: app-owned catalog and pricing.
31
+ - Billing cycle: period start/end, due date, grace deadline, retry state.
32
+ - Billing request: the one-time charge for a specific cycle.
33
+ - Payment attempt: one e-Kwanza provider attempt for that request.
34
+ - Entitlement: access granted by trial, paid cycle, grace, or cancellation policy.
35
+ - Scheduler: job/cron/queue that issues due requests and expires old ones.
36
+
37
+ ## Lifecycle
38
+
39
+ 1. Select subscriptions whose trial has ended or whose next cycle is due.
40
+ 2. Create or reuse the cycle's billing request.
41
+ 3. Create a one-time provider attempt through the provider adapter.
42
+ 4. Move the subscription into grace or pending-payment state according to policy.
43
+ 5. On normalized `paid`, mark request paid, advance next due date, and extend entitlement.
44
+ 6. On normalized `failed`, retry or move to `past_due`.
45
+ 7. On normalized `expired` after grace, suspend or cancel entitlement.
46
+
47
+ ## Minimal persistence
48
+
49
+ Use existing models where possible. Add only missing concepts:
50
+
51
+ ```text
52
+ subscription/account:
53
+ plan_or_product_id
54
+ status
55
+ interval
56
+ trial_ends_at
57
+ next_due_at
58
+ grace_ends_at
59
+ entitlement_state
60
+ preferred_payment_rail
61
+ payer_phone
62
+
63
+ billing_cycle/request:
64
+ subscriber_id
65
+ cycle_anchor
66
+ period_start
67
+ period_end
68
+ due_at
69
+ amount_aoa
70
+ status
71
+ payment_request_id
72
+ metadata
73
+ ```
74
+
75
+ Names are illustrative. Preserve the target app's vocabulary.
76
+
77
+ ## Invariants
78
+
79
+ - Do not hardcode internal reference-app plan slugs, prices, or intervals.
80
+ - Compute price server-side from the app's catalog.
81
+ - Use a unique key per subscriber and billing cycle.
82
+ - Payment method changes for a pending cycle should update/retry intentionally, not create duplicate cycle requests.
83
+ - Entitlement changes happen from normalized payment events, not raw provider payloads.
84
+ - Trials do not issue charges until policy says the first paid cycle is due.
85
+ - Grace, retry, dunning, and suspension are app policy, not e-Kwanza behavior.
86
+ - Subscription renewal and overage billing can share payment primitives but should remain separate domain flows.
87
+
88
+ ## Scheduler
89
+
90
+ Use the target app's normal background mechanism:
91
+
92
+ - cron endpoint
93
+ - queue worker
94
+ - scheduled function
95
+ - database job
96
+ - admin/manual retry command
97
+
98
+ The scheduler should issue due billing requests, send reminders, expire old pending requests, retry according to policy, and transition accounts through grace, past_due, suspended, or canceled.
99
+
100
+ ## Internal case study
101
+
102
+ The internal subscription reference maps the generic pattern like this:
103
+
104
+ - Subscriber: tenant.
105
+ - Billing request: `billing_payment_requests`.
106
+ - Provider label: `ekwanza_subscription`.
107
+ - Scheduler: internal e-Kwanza billing cycle route.
108
+ - Webhook branch: shared e-Kwanza URL routed to billing after public payments.
109
+ - Cycle behavior: local due dates, grace, reminders, paid/failed/canceled transitions.
110
+
111
+ Use it to understand lifecycle structure. Do not copy its schema or plan catalog unless you are working inside that app.
112
+
113
+ ## Tests
114
+
115
+ - first paid cycle extends entitlement
116
+ - duplicate paid webhook does not extend twice
117
+ - failed payment enters retry or past_due policy
118
+ - expired request after grace suspends/cancels correctly
119
+ - plan change does not create duplicate cycle requests
120
+ - scheduler skips active trial accounts until due
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: ekz-ticket-invite-selling
3
+ description: >-
4
+ Ticket and invite selling architecture: reservation windows, inventory
5
+ locking, invite ownership, QR/code issuance, release on expiry, paid-only
6
+ issuance, and duplicate-webhook safety.
7
+ ---
8
+
9
+ # Ticket / Invite Selling
10
+
11
+ Use this skill for selling event tickets, invitation passes, access codes, QR tickets, memberships with invite tokens, or limited-capacity entry.
12
+
13
+ Do not confuse this with the e-Kwanza `ticket` rail. The e-Kwanza Ticket API is a provider payment rail. This skill is about the business object being sold.
14
+
15
+ ## Pattern
16
+
17
+ ```text
18
+ TicketReservation -> PaymentRequest -> PaymentAttempt
19
+ -> paid webhook -> IssueTicket/Invite
20
+ -> failed/expired/timeout -> ReleaseReservation
21
+ ```
22
+
23
+ ## Concepts
24
+
25
+ - Inventory: total capacity, available count, per-type capacity, per-user limits.
26
+ - Reservation: temporary hold with expiration.
27
+ - Payment request: the reservation's charge.
28
+ - Issued ticket/invite: final owned asset after payment.
29
+ - QR/code: app-generated access credential, not proof of payment by itself.
30
+ - Check-in/redemption: separate from payment collection.
31
+
32
+ ## Reservation rules
33
+
34
+ - Create a reservation before taking payment when inventory is scarce.
35
+ - Give the reservation a short expiration window.
36
+ - Do not issue the final ticket/invite until payment is paid.
37
+ - Release reservation on failed, expired, canceled, or timeout.
38
+ - A retry may reuse a valid reservation or create a new one by explicit policy.
39
+
40
+ ## Issuance rules
41
+
42
+ Issue exactly once after a normalized paid event:
43
+
44
+ ```text
45
+ paid payment -> lock reservation -> create issued ticket/invite -> mark fulfilled
46
+ ```
47
+
48
+ The issued ticket/invite should store owner, event/product, status, QR/code hash, issuance time, and redemption state.
49
+
50
+ ## QR and invite codes
51
+
52
+ - Generate QR/invite credentials locally.
53
+ - Store hashes or signed tokens according to the app's security model.
54
+ - Do not treat provider QR/reference data as the final event ticket unless the product intentionally uses it as the access credential.
55
+
56
+ ## Tests
57
+
58
+ - reservation expires and stock is released
59
+ - paid event issues exactly one ticket/invite
60
+ - duplicate paid webhook does not issue duplicates
61
+ - failed payment releases reservation
62
+ - sold-out inventory blocks new reservations
63
+ - QR/check-in cannot be used before issuance
64
+
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: ekz-webhook-normalization
3
+ description: >-
4
+ Webhook normalization architecture: parse GPO, EMIS reference, and Ticket
5
+ callbacks into shared internal events, dedupe, persist, and route to product,
6
+ ticket, subscription, or overage handlers.
7
+ ---
8
+
9
+ # Webhook Normalization
10
+
11
+ Webhook code should normalize provider callbacks before touching business logic.
12
+
13
+ ## Pipeline
14
+
15
+ ```text
16
+ raw provider callback
17
+ -> verify provider authenticity
18
+ -> normalize status and identifiers
19
+ -> persist WebhookEvent idempotently
20
+ -> resolve local PaymentAttempt/PaymentRequest
21
+ -> route to domain handler
22
+ -> mark event processed or failed
23
+ ```
24
+
25
+ ## Normalized event
26
+
27
+ ```typescript
28
+ type NormalizedPaymentEvent = {
29
+ provider: "ekwanza";
30
+ rail: "gpo" | "emis_ref" | "ticket" | "unknown";
31
+ status: "pending" | "paid" | "failed" | "expired";
32
+ rawStatus: number | string | null;
33
+ eventId: string;
34
+ merchantTransactionId?: string;
35
+ providerOperationId?: string;
36
+ operationCode?: string;
37
+ code?: string;
38
+ occurredAt: string;
39
+ raw: unknown;
40
+ };
41
+ ```
42
+
43
+ ## Status mapping
44
+
45
+ | Provider value | Internal status |
46
+ |----------------|-----------------|
47
+ | `0` | `pending` |
48
+ | `1` | `paid` |
49
+ | `3` | `expired` |
50
+ | `4`, `5` | `failed` |
51
+
52
+ Normalize string numbers before comparison.
53
+
54
+ ## Idempotency
55
+
56
+ Durably register the event before side effects. Event IDs should combine stable provider identifiers and status:
57
+
58
+ ```text
59
+ ekwanza:{merchantTransactionId}:{status}
60
+ ekwanza-ticket:{operationCode}:{code}:{status}
61
+ ```
62
+
63
+ A duplicate event must not double-fulfill, double-issue, double-extend, or double-settle anything.
64
+
65
+ ## Routing
66
+
67
+ Route normalized events by local payment ownership:
68
+
69
+ - product/order/invoice payment -> product fulfillment handler
70
+ - ticket/invite reservation -> ticket issuance/release handler
71
+ - subscription cycle request -> subscription entitlement handler
72
+ - overage charge -> usage settlement handler
73
+
74
+ The webhook route itself should not contain all business behavior.
75
+
76
+ ## Ticket HMAC
77
+
78
+ Ticket-like callbacks must verify `x-signature` using the provider adapter. Production behavior must fail closed.
79
+
80
+ ## Tests
81
+
82
+ - GPO paid normalizes to paid
83
+ - Ticket paid normalizes to paid after HMAC verification
84
+ - string status `"1"` and numeric status `1` are equivalent
85
+ - duplicate event is recorded once and side effects run once
86
+ - unknown local payment is handled safely
87
+ - invalid Ticket signature does not reach domain handlers
88
+