@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.
- package/README.md +148 -0
- package/dist/commands/agent.d.ts +31 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +55 -0
- package/dist/commands/ask.d.ts +20 -0
- package/dist/commands/ask.d.ts.map +1 -0
- package/dist/commands/ask.js +154 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +44 -0
- package/dist/commands/health.d.ts +2 -0
- package/dist/commands/health.d.ts.map +1 -0
- package/dist/commands/health.js +28 -0
- package/dist/commands/local-agent.d.ts +19 -0
- package/dist/commands/local-agent.d.ts.map +1 -0
- package/dist/commands/local-agent.js +450 -0
- package/dist/commands/scan.d.ts +11 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +119 -0
- package/dist/commands/webhook.d.ts +10 -0
- package/dist/commands/webhook.d.ts.map +1 -0
- package/dist/commands/webhook.js +42 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +185 -0
- package/dist/lib/banner.d.ts +10 -0
- package/dist/lib/banner.d.ts.map +1 -0
- package/dist/lib/banner.js +26 -0
- package/dist/lib/commands-i18n.d.ts +20 -0
- package/dist/lib/commands-i18n.d.ts.map +1 -0
- package/dist/lib/commands-i18n.js +157 -0
- package/dist/lib/composer-model.d.ts +10 -0
- package/dist/lib/composer-model.d.ts.map +1 -0
- package/dist/lib/composer-model.js +15 -0
- package/dist/lib/context.d.ts +12 -0
- package/dist/lib/context.d.ts.map +1 -0
- package/dist/lib/context.js +56 -0
- package/dist/lib/doctor-quiet.d.ts +11 -0
- package/dist/lib/doctor-quiet.d.ts.map +1 -0
- package/dist/lib/doctor-quiet.js +39 -0
- package/dist/lib/env.d.ts +18 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +66 -0
- package/dist/lib/help.d.ts +10 -0
- package/dist/lib/help.d.ts.map +1 -0
- package/dist/lib/help.js +140 -0
- package/dist/lib/locale.d.ts +38 -0
- package/dist/lib/locale.d.ts.map +1 -0
- package/dist/lib/locale.js +189 -0
- package/dist/lib/mode.d.ts +11 -0
- package/dist/lib/mode.d.ts.map +1 -0
- package/dist/lib/mode.js +29 -0
- package/dist/lib/output.d.ts +7 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +18 -0
- package/dist/lib/preferences.d.ts +9 -0
- package/dist/lib/preferences.d.ts.map +1 -0
- package/dist/lib/preferences.js +35 -0
- package/dist/lib/redact.d.ts +3 -0
- package/dist/lib/redact.d.ts.map +1 -0
- package/dist/lib/redact.js +32 -0
- package/dist/lib/scan-quiet.d.ts +4 -0
- package/dist/lib/scan-quiet.d.ts.map +1 -0
- package/dist/lib/scan-quiet.js +4 -0
- package/dist/lib/scope.d.ts +5 -0
- package/dist/lib/scope.d.ts.map +1 -0
- package/dist/lib/scope.js +61 -0
- package/dist/lib/session.d.ts +31 -0
- package/dist/lib/session.d.ts.map +1 -0
- package/dist/lib/session.js +101 -0
- package/dist/lib/shell.d.ts +18 -0
- package/dist/lib/shell.d.ts.map +1 -0
- package/dist/lib/shell.js +214 -0
- package/dist/lib/skill.d.ts +3 -0
- package/dist/lib/skill.d.ts.map +1 -0
- package/dist/lib/skill.js +2 -0
- package/dist/lib/skills.d.ts +16 -0
- package/dist/lib/skills.d.ts.map +1 -0
- package/dist/lib/skills.js +199 -0
- package/dist/lib/theme.d.ts +23 -0
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/lib/theme.js +40 -0
- package/dist/lib/ui/ascii-art.d.ts +10 -0
- package/dist/lib/ui/ascii-art.d.ts.map +1 -0
- package/dist/lib/ui/ascii-art.js +55 -0
- package/dist/lib/ui/layout.d.ts +19 -0
- package/dist/lib/ui/layout.d.ts.map +1 -0
- package/dist/lib/ui/layout.js +46 -0
- package/dist/lib/ui/logo.d.ts +3 -0
- package/dist/lib/ui/logo.d.ts.map +1 -0
- package/dist/lib/ui/logo.js +8 -0
- package/dist/lib/ui/prompt.d.ts +6 -0
- package/dist/lib/ui/prompt.d.ts.map +1 -0
- package/dist/lib/ui/prompt.js +75 -0
- package/dist/lib/ui/splash.d.ts +15 -0
- package/dist/lib/ui/splash.d.ts.map +1 -0
- package/dist/lib/ui/splash.js +121 -0
- package/package.json +48 -0
- package/skills/ekz-connect/SKILL.md +99 -0
- package/skills/ekz-data-layer-design/SKILL.md +199 -0
- package/skills/ekz-data-mongo/SKILL.md +341 -0
- package/skills/ekz-data-mysql/SKILL.md +245 -0
- package/skills/ekz-data-postgres/SKILL.md +257 -0
- package/skills/ekz-data-sqlite/SKILL.md +261 -0
- package/skills/ekz-ekwanza-provider-adapter/SKILL.md +91 -0
- package/skills/ekz-integration-playbook/SKILL.md +122 -0
- package/skills/ekz-one-time-product-payments/SKILL.md +91 -0
- package/skills/ekz-overage-billing/SKILL.md +68 -0
- package/skills/ekz-payment-core-architecture/SKILL.md +121 -0
- package/skills/ekz-sdk-cli/SKILL.md +82 -0
- package/skills/ekz-subscription-billing/SKILL.md +120 -0
- package/skills/ekz-ticket-invite-selling/SKILL.md +64 -0
- 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
|
+
|