---
feature: Business model, packages, commission
slug: subscriptions
surface: trainer
consumer: flutter
version: 1.0.0
status: ready
brd_version: "v1.0 (11-8-2026) + additional requirements"
brd_ids: [TRR-005, TRR-006, TRR-007, TRR-030]
business_rules: [BR-FIN-03, BR-ENR-05]
decisions: [DEC-23, TD-15, TD-25]
schema_tables: [trainer_plans, trainer_subscriptions, payments]
related_docs: [course-create.md, ../admin/trainer-finance.md]
last_updated: 2026-11-15
---

# Business model, packages, commission — Trainer App Integration Guide

> **Base URL:** `{HOST}/api/v1/trainer` · Shared rules: [API_CONVENTIONS.md](../API_CONVENTIONS.md)

## Quick reference

| | |
|---|---|
| Version | 1.0.0 (2026-11-15) — first release; app action: build the business-model screens |
| Status | ready · BRD TRR-005..007, TRR-030 · DEC-23 (early renewal / model switching stay out) · payments run on the `fake` gateway (TD-15) |
| Auth | Trainer Bearer token, account `active`, independent trainers only (an affiliated trainer gets 422). |

| Method | Path | Route name | Purpose |
|--------|------|------------|---------|
| `GET` | `/subscription-plans` | `trainer.subscription.plans` | Active packages + current commission rate |
| `GET` | `/subscription` | `trainer.subscription.current` | Active model, allowance, `can_submit_courses` |
| `POST` | `/subscription/purchase` | `trainer.subscription.purchase` | Buy a package |
| `POST` | `/subscription/commission` | `trainer.subscription.commission` | Accept the commission model |

**Statuses:** `pending_payment` · `active` · `expired` · `payment_failed` · `replaced`. Models: `subscription`, `commission`.
**Error codes:** `BUSINESS_RULE_VIOLATED` (422: affiliated trainer, commission model active, plan unavailable) · `IDEMPOTENCY_CONFLICT` · `INVALID_STATE_TRANSITION`.
**Open decisions:** DEC-23 — early renewal and switching between models while one is active stay out until the client confirms.

## 1. Business summary

After approval an independent trainer picks a business model: a monthly or annual package (price, duration, course allowance set by the admin) or the commission model at the admin's current rate. A package is **not active until the payment succeeds**. Plan terms and the price are snapshotted on the subscription, so later plan edits affect new subscriptions only (BR-ENR-05); the commission rate is copied at acceptance and never recomputed (BR-FIN-03). Renewing replaces the current package and the remainder is not carried over (TRR-030). When a package ends, the trainer cannot submit new courses (see [course-create.md](course-create.md)) but existing courses are untouched. An affiliated trainer has no plan.

## 2. Endpoints

### 2.1 `GET /subscription-plans`, `GET /subscription`

```json
{"status":"success","message":"","data":{"plans":[{"id":"a33aebf1-141a-4de3-9256-6041a1998419","name":"شهري","description":null,"features":[],"billing_cycle":{"value":"monthly","label":"شهري"},"price":"199.00","duration_days":30,"course_limit":5}],"commission":{"rate":"0.00"}}}
```

```json
{"status":"success","message":"","data":{"subscription":{"id":"a33aebf1-1b04-43bf-9e70-61f3d6891037","model":{"value":"subscription","label":"اشتراك"},"status":{"value":"active","label":"نشط"},"plan":{"id":"a33aebf1-141a-4de3-9256-6041a1998419","name":"شهري","billing_cycle":"monthly","duration_days":30},"price":"199.00","commission_rate":null,"course_limit":5,"courses_used":0,"courses_remaining":5,"starts_at":"2026-11-15T09:23:38+00:00","ends_at":"2026-12-15T09:23:38+00:00","is_expired":false},"can_submit_courses":true,"blocked_reason":null}}
```

`data.subscription` is `null` with `can_submit_courses: false` and a `blocked_reason` when there is no usable model.

### 2.2 `POST /subscription/purchase`

`{ "plan_id": "<uuid>", "idempotency_key": "<8–60 chars: letters, digits, : _ ->" }`. Send the same key again after a timeout or double tap: the original subscription is returned, nothing is charged twice. With the fake gateway a key ending `:paid` succeeds, `:failed` fails, anything else stays `pending_payment`.

```json
{"status":"success","message":"Subscription request processed.","data":{"id":"a33aebf1-1b04-43bf-9e70-61f3d6891037","model":{"value":"subscription","label":"اشتراك"},"status":{"value":"active","label":"نشط"},"plan":{"id":"a33aebf1-141a-4de3-9256-6041a1998419","name":"شهري","billing_cycle":"monthly","duration_days":30},"price":"199.00","commission_rate":null,"course_limit":5,"courses_used":0,"courses_remaining":5,"starts_at":"2026-11-15T09:23:38+00:00","ends_at":"2026-12-15T09:23:38+00:00","is_expired":false}}
```

### 2.3 `POST /subscription/commission`

Activates the commission model at the current rate; repeating it returns the active one. A trainer with an active package gets 422.

## 3. Business rules enforced

| Rule | Behaviour |
|------|-----------|
| TRR-006 | No activation before payment; failure leaves the trainer without the package. |
| TRR-030 | Renewal replaces the current package; expiry is derived from `ends_at`, so it holds without a scheduler (the stored status is corrected on the next read). |
| DEC-23 | A commission trainer cannot buy a package; a package holder cannot accept commission. |
| BR-ENR-05 / BR-FIN-03 | Plan terms and commission rate are snapshotted. |

## 4. Known limitations

- Payment goes through the fake gateway; the real gateway and its callback route are a later task (TD-15). A callback for a plan payment is applied by `ConfirmTrainerPlanPayment`.
- The admin plan CRUD and subscription list (ADM-036, ADM-037) are not built; plans come from seeders for now.

## Version history

| Version | Date | Type | Summary | App action required |
|---------|------|------|---------|---------------------|
| 1.0.0 | 2026-11-15 | Initial | Plans list, purchase with idempotency, commission model, current subscription. | Build the screens above. |

## Changelog

### 1.0.0 — 2026-11-15
- **Added:** `subscription-plans`, `subscription`, `subscription/purchase`, `subscription/commission`.
- **Breaking:** No
- **App action required:** Build the screens above.
- **BRD / decisions:** TRR-005..007, TRR-030, DEC-23, TD-25
