# io-payment — V3 migration plan

**Branch:** `v3` (forked from `master` @ v1.0.60, 2026-05-13)
**Target first V3 release:** `v3.0.1`
**Triggered by:** [Project Hermes](https://gitlab.apexl/apexl-internal/hermes) (licence + billing platform) needs a Cygnus V3-compatible payment module with first-class multi-gateway support.

## Why migrate

- V1 codebase targets `apexl/io ^1.0` + PHP `^7.1`. Cygnus V3 era requires `apexl/io ^3.6` + PHP 8.4.
- V1 hardcodes Stripe as the primary gateway (`stripe/stripe-php ^7.0` is a direct composer dep). The existing `providerInterface` + Stripe/PayPal/GoCardless implementations are a great baseline; V3 cleans up the contract and adds Revolut as a first-class adapter.
- V3 needs to act as a building block for Hermes (and potentially TC's eventual commercial billing layer). Hermes's `apexl/io-billing` abstraction (Plan 005 §4g) depends on this.

## Migration scope

### Composer constraints
- `php` `^7.1` → `^8.4`
- `apexl/io` `^1.0.0` → `^3.6`
- `stripe/stripe-php` `^7.0` → `^17.0` (pin to a vendor'd API version per Plan 005 §7)
- Drop `minimum-stability: dev` (V3 era uses tagged releases)
- Switch composer repos from `codestore.sc.vg` to `packages.apexl` once V3 is published (handled at release time, not in this migration).

### Namespace + namespace files
- V1 uses `apexl\Io\modules\payment\*` — keeps as-is for V3 (Cygnus V3 vendor convention is the same path; the leaked-filesystem-path namespace `app\vendor\apexl\io\src\Io\interfaces\...` only appears in `apexl/io` core's `interfaces/` dir and is consumed by all modules).
- All `require ^1.0.0` of other `apexl/io-*` deps must become `^3.x`.
- `composer.json` `type` field should stay `project` for now; revisit at release time.

### PHP 8.4 compatibility sweep
- Replace deprecated `each()` if used.
- Strict typing: add `declare(strict_types=1)` where missing.
- Add return types + parameter types throughout (PHP 8.4 reflects them via `ReflectionType`; some platform features need this).
- Replace any `Casts::DATETIME_STAMP`-without-`array_merge(parent::casts(), ...)` with the V3 merge pattern (vendor bug captured in `framework-bugs.md`).
- Replace `app\module\*` namespaces (if any) with `apexl\Io\modules\*`.
- Run `phpstan` or `phan` at level 6 before tagging v3.0.1.

### Entity modernisation
- Migrate each Entity to the V3 patterns:
  - Add `HasEntityFactory` trait + per-entity Factory class.
  - Implement `getData()` if the entity exposes denormalised / virtual columns.
  - Adopt `apexl/io-search`'s `Indexable` interface if relevant (`Payment` and `Subscription` are likely search-targets).
  - Adopt `apexl/io-permissions`'s `Ownable` interface (so the permissions framework can scope by creator + assignees + custom scopes).
  - Adopt `apexl/io-taxonomy`'s `Tagged` trait where useful (e.g. `payment_status` taxonomy replacing the `varchar` status field; `payment_method` taxonomy across cards / wallets / bank-transfer / sepa).
- Add Feature tests (DB-backed round-trip CRUD) per V3 convention. Suite goal: every entity gets at least one round-trip Feature test.

### Generalise the gateway layer

**Current state (V1):**
- `providerInterface` with 4 methods.
- Implementations: `stripeProvider`, `paypalProvider`, `gocardlessProvider`.
- Coupling: `stripe/stripe-php` is a hard composer dep.

**Target state (V3):**
- `Contract\PaymentGatewayInterface` — cleaner method names + typed params + return DTOs (not arrays).
- `Contract\TokenisableGatewayInterface` (extends base) — for gateways supporting card tokenisation flows.
- `Contract\SubscriptionGatewayInterface` (extends base) — for gateways supporting recurring billing natively.
- `Contract\WebhookHandlerInterface` — gateway-side normalisation of webhook events into a standard `WebhookEvent` DTO.
- Adapters per gateway in `Gateways/<Name>/` subdirectory. Each gateway's SDK becomes a **suggest** in composer.json, not a hard `require`. Consumers `composer require apexl/io-payment apexl/io-payment-stripe` to add the Stripe adapter only.
- New adapter modules to ship alongside io-payment:
  - `apexl/io-payment-stripe` (extracted from V1's `stripeProvider.php`)
  - `apexl/io-payment-paypal` (extracted from V1)
  - `apexl/io-payment-gocardless` (extracted from V1)
  - `apexl/io-payment-revolut` (new, V3.1+)
- Gateway registry: `PaymentGatewayRegistry` hook-collected service — modules register their adapter via `payments.collectGateways` hook. Same pattern as the rest of V3.

### Display module (paymentDisplay → @apexl/icarus-payment)

V1 has `paymentDisplay` with Vue components inside the PHP package. V3 splits these out into a `@apexl/icarus-payment` npm package on `http://npm.apexl/`, following the pattern of the 7 existing `@apexl/icarus-*` packages. Components migrated:

- Card-validation iframe / setup-intent UI
- Saved-payment-method list
- Checkout button / hosted-checkout redirect
- Payment status display
- Billing-history table

(Full inventory to be confirmed during the actual extraction.)

### Webhook handling

V1 has a single `webhookController` that routes events by provider header. V3 generalises:

- Gateway adapters expose a `handleWebhook(Request $req): WebhookEvent` method that returns a normalised event.
- The core controller dispatches the normalised event into the standard `payments.webhookReceived` hook → consumers (subscriptions module, audit log, notifier, etc.) listen.
- Idempotency keyed by `provider + provider_event_id` on `payment_webhook_log` table.
- Signed-payload verification per gateway (Stripe HMAC, PayPal IPN-style, Revolut HMAC).

## Phasing

1. **Scaffold v3 branch + composer constraints + plan doc** ✅ (this commit)
2. **PHP 8.4 sweep** — types, strict_types, casts merge. Single commit. Tests still pass against V1 io-core interfaces (we're not yet bumping io-core dep — that's step 3).
3. **Bump apexl/io to ^3.6** — fix the API breakages (vendor patches, controller signatures, route registration). Probably 1-2 commits.
4. **Generalise gateway layer** — extract Stripe/PayPal/GoCardless into separate `apexl/io-payment-{stripe,paypal,gocardless}` repos (each gets its own v3 branch in the same way). Update io-payment to depend on the interface only.
5. **Add Revolut adapter** — new repo `apexl/io-payment-revolut`. Owner: Hermes Phase 3 or later.
6. **Vue split** — extract `paymentDisplay/` → `@apexl/icarus-payment` npm package.
7. **Tag v3.0.1** — first usable V3 release. Hermes consumes from here.

## Open questions

- **Repo group: Bitbucket or GitLab?** V1 lives on Bitbucket (`N1ghteyes` workspace) hosted via `codestore.sc.vg` Satis. The newer V3 era packages live on GitLab (`apexl-packages` group on `gitlab.apexl`) hosted via `packages.apexl` Satis. **Recommend:** move to GitLab on V3 publication — matches the rest of the new platform layer. Leave V1 on Bitbucket / codestore for legacy GlobalSim compatibility.
- **Adapter packaging:** one repo per gateway, or one repo with optional autoload classmaps per gateway? Recommend **one repo per gateway** — cleaner SDK-deps story (consumer only installs Stripe SDK if they install `apexl/io-payment-stripe`).
- **Subscription gateway features:** Stripe has native subscription products; PayPal has billing-plans; GoCardless has mandates+subscriptions; Revolut Business API has subscription-by-mandate. The `SubscriptionGatewayInterface` needs to map these onto a common surface (period / amount / next-bill-date / status). Probably a separate design pass at io-subscription's V3 migration.
- **Hermes-specific use case:** Hermes routes payments through the consuming product's vendor account (Stripe Connect). io-payment's V3 needs to support both "gateway acting on platform's behalf" (TC, GlobalSim — old pattern) and "gateway acting on vendor's behalf" (Hermes — new pattern). Probably one boolean on the Payment entity (`is_platform_acting`) + a vendor_id pointer when `false`.

## Status

- **2026-05-13:** v3 branch created. Migration plan committed. composer.json bumped to PHP 8.4 / apexl/io ^3.6 constraint. No code changes.
- **2026-05-14:** v3.0.1 minimum-viable port shipped.
  - V1 controllers / services / providers / callbacks / displayModule deleted (Hermes owns its own checkout + webhook code; per-gateway adapters live in separate packages).
  - Restructured to V3 platform-extension layout: `src/{paymentModule.php, Entity/, Contract/, Dto/, db/migrations/}` (was `src/Io/modules/payment/…`). composer autoload psr-4 now maps `apexl\Io\modules\payment\` → `src/`.
  - 7 new V3 contract interfaces in `src/Contract/`: PaymentGateway + HostedCheckout + Subscription + PaymentMethod + Webhook + Refundable + BillingPortal. Adapters opt in via `implements`.
  - 8 new V3 DTOs in `src/Dto/`: GatewayConfig, CheckoutSessionRequest+Result, SubscriptionRequest+Result, PaymentMethodDescriptor, WebhookEvent, RefundResult.
  - 5 modernised entities in `src/Entity/`: Payment, PaymentLog, PaymentToken, PaymentWebhookLog, PaymentProviderRef. PHP 8.4 + strict_types + HasCasts parent merge.
  - Install migration `20260514150000_InstallIoPayment` creates the 5 tables idempotently.
  - composer.json: `"type": "library"` (was `project`); author updated to Apexl Studios.
  - Next: extract Stripe adapter into `apexl/io-payment-stripe`. Then PayPal, GoCardless, Revolut.
  - **Not yet shipped:** UserCredit entity (V1 feature deferred to V3.1+).
