mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
remove temp files
This commit is contained in:
-131
@@ -1,131 +0,0 @@
|
||||
## Payments Revamp Plan
|
||||
|
||||
### 1. Background & Review Feedback
|
||||
|
||||
- PR [#10573](https://github.com/appwrite/appwrite/pull/10573) review (eldadfux) requires moving subscription logic into a dedicated `v1/payments` service, aligned with post-1.7 module architecture and REST plural conventions.
|
||||
- Avoid extending `/v1/account` and prevent Stripe vendor lock-in; design must allow alternate providers ("UltraPayments" mention) and attach subscriptions to both users and teams.
|
||||
- Internal usage tracking is still required for quota enforcement while syncing with external billing systems.
|
||||
|
||||
### 2. Current Draft Gaps (auth-subscriptions branch)
|
||||
|
||||
- Controllers: `app/controllers/api/account.php` and `api/auth-plans.php` house payments logic, diverging from modular HTTP action classes (`src/Appwrite/Platform/Modules/*/Http/...`).
|
||||
- Data Coupling: `app/config/collections/platform.php` and `collections/common.php` now embed Stripe-specific columns (`authStripeSecretKey`, `stripeCustomerId`, etc.), making migration to other providers costly.
|
||||
- Service Registry: `app/config/services.php` declares an `authPlans` controller entry rather than using a module-driven service (contrast with `functions`/`sites` entries that leave `controller` empty and rely on modules).
|
||||
- User Records: Subscription status fields live directly on `users`, preventing team sharing and complicating multi-actor ownership.
|
||||
- Tests & Specs: No module-style route tests or worker coverage; OpenAPI specs patched manually via controller paths.
|
||||
|
||||
### 3. Architectural Principles to Mirror
|
||||
|
||||
- **Module Composition:** Each platform module provides `Module.php` registering `Services/Http.php` (and optional `Services/Workers.php`) (e.g., `src/Appwrite/Platform/Modules/Functions` and `Sites`). Classes extend `Utopia\Platform\Service`, where HTTP service constructors call `addAction()` for each route class (`Create`, `Get`, `XList`, etc.).
|
||||
- **Route Classes:** HTTP actions extend `Appwrite\Platform\Modules\Compute\Base` (where applicable), use the `HTTP` trait, describe metadata (scopes, events, SDK information), inject dependencies, and implement an `action()` method (see `Functions/Http/Functions/Create.php` and `Sites/Http/Deployments/Template/Create.php`).
|
||||
- **Platform Bootstrap:** `src/Appwrite/Platform/Appwrite.php` registers modules. New services must follow this pattern so the factory loads them automatically.
|
||||
- **Collections:** Core metadata uses JSON blobs for provider-specific config (e.g., `projects` collection attributes `services`, `auths`, `smtp` with `json` filter) instead of expanding columns per provider. Use this precedent when storing payments integrations.
|
||||
- **Testing:** Route behavior is covered via module-specific tests under `tests/e2e/Services` and unit tests for validators. Payment routes must follow same structure.
|
||||
|
||||
### 4. Implementation Roadmap
|
||||
|
||||
#### Phase A – Module Bootstrapping
|
||||
|
||||
- Create `src/Appwrite/Platform/Modules/Payments/Module.php` mirroring `Functions\Module`/`Sites\Module`, registering an HTTP service and (if needed) worker service(s).
|
||||
- Add `src/Appwrite/Platform/Modules/Payments/Services/Http.php`; register route action classes (Plans, Subscriptions, Usage, Configuration). Maintain naming parity: `Plans\Create.php`, `Plans\Update.php`, `Plans\XList.php`, etc.
|
||||
- Wire module in `src/Appwrite/Platform/Appwrite.php` via `$this->addModule(new Payments\Module());`.
|
||||
- Update DI autoload if required (Composer PSR-4 should already include `src/Appwrite`).
|
||||
|
||||
#### Phase B – REST Surface Definition
|
||||
|
||||
- Design plural endpoints under `/v1/payments/...` (e.g., `/v1/payments/plans`, `/v1/payments/subscriptions`, `/v1/payments/subscriptions/{subscriptionId}`, `/v1/payments/providers`).
|
||||
- Each route should expose scope labels (new `payments.*` scopes) and SDK metadata similar to `Functions` routes. Provide responses using dedicated models (plan, subscription, usage event) under `src/Appwrite/Utopia/Response/Model`.
|
||||
- Implement both project-admin endpoints (plan management) and authenticated actor endpoints (user/team subscription management). Accept `actorType` and `actorId` to allow teams or users.
|
||||
- Remove or deprecate `/v1/account/subscription*` routes, replacing them with thin adapters or erroring with migration notice once clients are updated.
|
||||
|
||||
#### Phase C – Scope, Permissions & Service Registry
|
||||
|
||||
- Update `app/config/scopes.php` to introduce `payments.read`, `payments.write`, `payments.subscribe`, and console equivalents. Ensure console/admin scopes map to UI flows.
|
||||
- Amend `app/config/services.php` to add a `payments` entry with empty `controller` (module-managed), similar to `functions` and `sites`, and expose docs metadata when available.
|
||||
- Ensure new scopes appear in e2e scope tests (`tests/e2e/Scopes/`) and update SDK generation metadata if necessary.
|
||||
|
||||
#### Phase D – Data Model Restructuring
|
||||
|
||||
- Introduce `app/config/collections/payments.php` (or reuse existing file with new namespace) describing:
|
||||
- `payments_plans`: plan metadata, generic fields (name, price(s), billing cycle, features) plus `providers` JSON mapping provider IDs/handles.
|
||||
- `payments_features`: reusable feature definitions with type (`boolean`, `metered`), description, and provider metadata (if applicable).
|
||||
- `payments_plan_features`: join table storing feature assignments per plan with tiering/usage caps; provider-specific IDs live inside a `providers` JSON object.
|
||||
- `payments_subscriptions`: subscription records linking `projectId`, `actorType` (`user`/`team`), `actorInternalId`, `planId`, `status`, `lifecycle timestamps`, `quota progress`, and `providers` JSON (customer IDs, subscription IDs, invoice sync state).
|
||||
- `payments_usage_events`: internal usage ledger capturing increments, reconciliation status, and optional provider receipt IDs.
|
||||
- Remove Stripe-specific attributes from `app/config/collections/platform.php` and `common.php`; replace with:
|
||||
- `projects.payments` JSON attribute storing provider configuration (`{"providers": {"stripe": {...}}}`) with `json` and `encrypt` filters for sensitive values.
|
||||
- Users/teams should no longer carry plan IDs or Stripe identifiers; instead, join via `payments_subscriptions`.
|
||||
- Provide migration helpers to seed existing data into new collections (see Phase G).
|
||||
|
||||
#### Phase E – Provider Abstraction Layer
|
||||
|
||||
- Define `src/Appwrite/Payments/Provider/ProviderAdapterInterface` encapsulating operations used today (`ensureProduct`, `ensurePrice`, `ensureMeter`, `createCheckoutSession`, `createPortalSession`, `switchPlan`, `cancel`, `reportUsage`, etc.).
|
||||
- Move `src/Appwrite/Auth/Subscription/StripeService.php` into `src/Appwrite/Payments/Provider/StripeAdapter.php`, implementing the new interface. Update namespaces and dependency injection accordingly.
|
||||
- Introduce a provider registry/factory (e.g., `src/Appwrite/Payments/Provider/Registry.php`) to resolve adapters based on project configuration (`payments.providers` JSON). Support multiple providers by iterating adapters when needed.
|
||||
- Relocate validator/exception classes under `src/Appwrite/Payments/` namespace (`Validator/StripeKey.php` → `Payments/Validator/StripeKey.php`) and replace Stripe-specific names with provider-neutral naming where possible.
|
||||
|
||||
#### Phase F – Business Logic Implementation
|
||||
|
||||
- Plan CRUD: Route classes should validate payloads, persist records in `payments_plans`, and delegate provider provisioning via the adapter interface (creating products/prices/meters for configured providers).
|
||||
- Feature assignment: Manage assignments in `payments_plan_features`, including meter provisioning and tiered pricing via provider adapters.
|
||||
- Subscription lifecycle: Implement routes for creating/updating/canceling subscriptions. Logic should:
|
||||
- Resolve actor (user/team) and existing subscription record.
|
||||
- Ensure plan compatibility and usage caps.
|
||||
- Call provider adapter to create/update external subscription IDs.
|
||||
- Persist status, period boundaries, and cancellation flags in `payments_subscriptions`.
|
||||
- Usage enforcement: Provide endpoints/services to increment usage (`payments_usage_events`), compute current consumption, and determine cap breaches synchronously for feature gating. Expose summary endpoints to clients.
|
||||
- Configuration flows: Rebuild project-level configuration endpoints (`/v1/projects/:projectId/payments/providers`) under the module, enabling/disabling providers and storing credentials in `projects.payments` JSON. Ensure `StripeAdapter` initialization replicates existing `initializeAccount()` behavior.
|
||||
|
||||
#### Phase G – Background Processing & Workers
|
||||
|
||||
- If asynchronous tasks are required (e.g., syncing usage to providers, handling webhooks), create `src/Appwrite/Platform/Modules/Payments/Services/Workers.php` and worker action classes (pattern from `Functions/Services/Workers.php`).
|
||||
- Hook into existing queues (`queueForEvents`, `queueForStatsUsage`, etc.) where appropriate to emit events/audits similar to other services.
|
||||
- Evaluate need for cron tasks to reconcile usage or handle grace periods.
|
||||
|
||||
#### Phase H – Migration & Backward Compatibility
|
||||
|
||||
- Author migration script (CLI task under `src/Appwrite/Platform/Tasks` or a dedicated maintenance script) to:
|
||||
- Read existing `auth_plans`, `auth_plan_features`, and user subscription fields.
|
||||
- Populate new `payments_*` collections and JSON config fields.
|
||||
- Update user/team documents to remove deprecated attributes.
|
||||
- Provide database migration documentation and fallback strategy (e.g., how to disable payments before upgrade).
|
||||
- For installations already using the draft branch, document manual steps to transfer customer data.
|
||||
|
||||
#### Phase I – Specification & SDK Updates
|
||||
|
||||
- Regenerate OpenAPI/Swagger specs via existing tooling (`bin/specs` / `src/Appwrite/Platform/Tasks/Specs.php`) after new routes are in place. Confirm console/client/server specs include `payments` namespace.
|
||||
- Update SDK code generation inputs and ensure new endpoints appear in client/server SDKs with correct grouping.
|
||||
- Adjust console UI integrations to consume the new endpoints and models (plan lists, subscription management, usage dashboards).
|
||||
|
||||
#### Phase J – Testing Strategy
|
||||
|
||||
- **Unit Tests:**
|
||||
- Adapter tests mocking Stripe client interactions.
|
||||
- Validators under `tests/unit/Payments/Validator/*`.
|
||||
- **Module Route Tests:**
|
||||
- Add new classes in `tests/e2e/Services/Payments/` covering admin plan management, actor subscription flows, and usage queries for both users and teams.
|
||||
- Include negative cases (disabled provider, cap exceeded, missing permissions).
|
||||
- **Integration Tests:**
|
||||
- Mock provider webhook handling and ensure idempotency.
|
||||
- Test migrations by seeding legacy data and verifying conversion results.
|
||||
- Update benchmark/security CI workflows if the new module introduces additional containers or environment variables.
|
||||
|
||||
#### Phase K – Documentation & Developer Experience
|
||||
|
||||
- Draft developer docs (`/docs/services/payments.md`) describing configuration, APIs, and migration steps.
|
||||
- Update console copy and CLI help text to match new terminology (plans, features, subscriptions, usage events).
|
||||
- Provide sample scripts for common tasks (e.g., creating plans via CLI/SDK).
|
||||
|
||||
### 5. Open Questions / Follow-Up Items
|
||||
|
||||
- **Multi-provider Support:** Should multiple providers operate simultaneously per project or is it a single active provider? Decide how `providers` JSON is structured (array vs keyed map).
|
||||
- **Team Billing Semantics:** Clarify how team-owned subscriptions map to team members—do all members share quotas automatically? Need UX decisions in console.
|
||||
- **Usage Reconciliation:** Define authoritative source when provider usage differs from internal tracking; plan for dispute resolution.
|
||||
- **Webhook Processing:** Determine whether payments module handles Stripe webhooks or leverages existing generic webhook processor; adjust architecture accordingly.
|
||||
- **Legacy Clients:** Agree on deprecation timeline for `/v1/account/subscription*` endpoints and provide compatibility shims if required.
|
||||
|
||||
### 6. Next Steps
|
||||
|
||||
- Validate this plan with stakeholders (product + engineering) to confirm scope.
|
||||
- Sequence implementation into incremental PRs (module scaffolding → data model → provider abstraction → endpoint migration → migrations/tests).
|
||||
- Reserve time for console integration and documentation updates before GA release.
|
||||
@@ -1,306 +0,0 @@
|
||||
## Payments Revamp Detailed Blueprint
|
||||
|
||||
### Executive Summary
|
||||
|
||||
- Replace draft `/v1/account/subscription*` endpoints with a dedicated `/v1/payments` service implemented as an Appwrite platform module, satisfying Eldad’s review.
|
||||
- Deliver provider-agnostic APIs and data model so multiple payment vendors can plug in without breaking clients.
|
||||
- Introduce robust internal usage controls and enable subscriptions for both users and teams, matching multi-tenant requirements.
|
||||
|
||||
### 1. Service Architecture
|
||||
|
||||
1. **Module Registration**
|
||||
|
||||
- Add `Appwrite\Platform\Modules\Payments\Module` mirroring existing modules (`Functions`, `Sites`).
|
||||
- Register in `Appwrite\Platform\Appwrite::__construct` via `$this->addModule(new Payments\Module());`.
|
||||
- Provide `Services/Http.php` and optional `Services/Workers.php` classes inheriting `Utopia\Platform\Service`.
|
||||
- Use registrar pattern if route count grows (similar to `Databases\Services\Registry`).
|
||||
|
||||
2. **Route Classes (per Endpoint)**
|
||||
|
||||
- Location: `src/Appwrite/Platform/Modules/Payments/Http/...`.
|
||||
- Each class extends `Appwrite\Platform\Modules\Compute\Base` or a lighter abstract if no compute needs, uses `use HTTP;`, configures metadata (`setHttpMethod`, `setHttpPath`, `desc`, `groups`, `label`, `param`).
|
||||
- Inject dependencies via `->inject()` matching established pattern (response, db, queue, project, user/team documents, adapter registry).
|
||||
- Return models defined in `src/Appwrite/Utopia/Response/Model` (new models for payments entities).
|
||||
|
||||
3. **SDK & Metadata Integration**
|
||||
- Add `->label('sdk', new Method(...))` for each route to define namespace/group (`payments`) and auth types.
|
||||
- Define new permission scopes (`payments.read`, `payments.write`, `payments.subscribe`) and map in `app/config/scopes.php` for client, server, console contexts.
|
||||
- Ensure each action sets `resourceType`, `event`, `audits.*` labels where relevant to integrate with events/auditing infrastructure.
|
||||
|
||||
### 2. API Surface
|
||||
|
||||
Below is the proposed REST contract (subject to iterative refinement). All endpoints live under `/v1/payments` unless noted.
|
||||
|
||||
| Endpoint | Method | Description | Auth scopes | Notes |
|
||||
| --------------------------------------------------- | ------ | ----------------------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------- |
|
||||
| `/v1/payments/plans` | POST | Create plan | `payments.write` (project admin) | Accepts plan metadata, pricing options, feature definitions. Returns plan model. |
|
||||
| `/v1/payments/plans` | GET | List plans | `payments.read` | Supports queries/filtering; returns paginated list. |
|
||||
| `/v1/payments/plans/:planId` | GET | Get single plan | `payments.read` | |
|
||||
| `/v1/payments/plans/:planId` | PUT | Update plan | `payments.write` | Provider-specific updates routed through adapters while preserving API stability. |
|
||||
| `/v1/payments/plans/:planId` | DELETE | Archive/deactivate plan | `payments.write` | Soft-delete toggles `active=false`. |
|
||||
| `/v1/payments/plans/:planId/features` | POST | Assign/Reconfigure features | `payments.write` | Handles metered tiers, usage caps. |
|
||||
| `/v1/payments/plans/:planId/features` | GET | List assigned features | `payments.read` | |
|
||||
| `/v1/payments/plans/:planId/features/:featureId` | DELETE | Remove feature | `payments.write` | |
|
||||
| `/v1/payments/features` | POST | Create reusable feature definition | `payments.write` | Feature types: `boolean`, `metered`, potential future types. |
|
||||
| `/v1/payments/features` | GET | List features | `payments.read` | |
|
||||
| `/v1/payments/features/:featureId` | PUT | Update feature | `payments.write` | |
|
||||
| `/v1/payments/features/:featureId` | DELETE | Archive feature | `payments.write` | |
|
||||
| `/v1/payments/providers` | GET | Get project payments configuration | `payments.read` (admin) | Returns provider statuses, capabilities. |
|
||||
| `/v1/payments/providers` | PUT | Configure providers | `payments.write` (admin) | Accepts provider credentials/options; triggers adapter bootstrap (webhooks, products). |
|
||||
| `/v1/payments/providers/:providerId/actions/test` | POST | Validate provider credentials | `payments.write` | Optional sanity check endpoint per provider. |
|
||||
| `/v1/payments/subscriptions` | POST | Create subscription | `payments.subscribe` (actor) | Body contains `actorType` (`user`/`team`), `actorId`, `planId`, optional payment options. |
|
||||
| `/v1/payments/subscriptions` | GET | List subscriptions | `payments.read` | Filters: actor, plan, status, provider. |
|
||||
| `/v1/payments/subscriptions/:subscriptionId` | GET | Retrieve subscription | `payments.read` | Validates access (actor membership, admin). |
|
||||
| `/v1/payments/subscriptions/:subscriptionId` | PATCH | Update subscription (plan switch, cancel) | `payments.subscribe` or `payments.write` | Handles upgrade/downgrade, cancellation, metadata updates. |
|
||||
| `/v1/payments/subscriptions/:subscriptionId/cancel` | POST | Explicit cancel endpoint | `payments.subscribe` | Sets cancel at period end or immediate cancel. |
|
||||
| `/v1/payments/subscriptions/:subscriptionId/resume` | POST | Resume cancelled subscription | `payments.subscribe` | |
|
||||
| `/v1/payments/subscriptions/:subscriptionId/usage` | GET | Get detailed usage summary | `payments.read` | Combines internal events with provider sync status. |
|
||||
| `/v1/payments/subscriptions/:subscriptionId/usage` | POST | Report usage event | `payments.subscribe` (server key) | For services to emit usage increments (e.g., rate limiting). Supports batching. |
|
||||
| `/v1/payments/usage/events` | GET | Query raw usage events | `payments.read` | For analytics/monitoring. |
|
||||
| `/v1/payments/usage/reconcile` | POST | Trigger reconciliation with provider | `payments.write` | Optional admin action to force sync. |
|
||||
| `/v1/payments/webhooks/:providerId` | POST | Provider webhook endpoint (internal) | Auth via signature | Each provider adapter registers handler. Routed outside public docs. |
|
||||
|
||||
**Compatibility:**
|
||||
|
||||
- Terminate legacy `/v1/account/subscription*` routes after transitional period. Provide compatibility wrappers that call `payments` module while returning same payload shape to avoid immediate SDK breakage.
|
||||
|
||||
### 3. Provider Plug-in System
|
||||
|
||||
1. **Core Concepts**
|
||||
|
||||
- Interface: `src/Appwrite/Payments/Provider/Adapter.php` (or `ProviderAdapterInterface`) defines actions consumed by module.
|
||||
- Responsibilities: product/price lifecycle, customer management, subscription lifecycle, metered usage reporting, invoice retrieval, webhook verification, credential validation.
|
||||
|
||||
2. **Adapter Interface Sketch**
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
interface Adapter
|
||||
{
|
||||
public function getIdentifier(): string; // e.g., "stripe"
|
||||
|
||||
public function configure(array $config, Document $project): ProviderState;
|
||||
|
||||
public function ensurePlan(array $planData, ProviderState $state): ProviderPlanRef;
|
||||
|
||||
public function updatePlan(array $planData, ProviderPlanRef $reference, ProviderState $state): ProviderPlanRef;
|
||||
|
||||
public function deletePlan(ProviderPlanRef $reference, ProviderState $state): void;
|
||||
|
||||
public function ensureFeature(array $featureData, ProviderPlanRef $plan, ProviderState $state): ProviderFeatureRef;
|
||||
|
||||
public function ensureSubscription(Document $actor, array $subscriptionData, ProviderState $state): ProviderSubscriptionRef;
|
||||
|
||||
public function updateSubscription(ProviderSubscriptionRef $subscription, array $changes, ProviderState $state): ProviderSubscriptionRef;
|
||||
|
||||
public function cancelSubscription(ProviderSubscriptionRef $subscription, bool $atPeriodEnd, ProviderState $state): ProviderSubscriptionRef;
|
||||
|
||||
public function resumeSubscription(ProviderSubscriptionRef $subscription, ProviderState $state): ProviderSubscriptionRef;
|
||||
|
||||
public function createCheckoutSession(Document $actor, array $planContext, ProviderState $state, array $options = []): ProviderCheckoutSession;
|
||||
|
||||
public function createPortalSession(Document $actor, ProviderState $state, array $options = []): ProviderPortalSession;
|
||||
|
||||
public function reportUsage(ProviderSubscriptionRef $subscription, string $featureId, int $quantity, \DateTimeInterface $timestamp, ProviderState $state): void;
|
||||
|
||||
public function syncUsage(ProviderSubscriptionRef $subscription, ProviderState $state): ProviderUsageReport;
|
||||
|
||||
public function handleWebhook(array $payload, ProviderState $state): ProviderWebhookResult;
|
||||
|
||||
public function testConnection(array $config): ProviderTestResult;
|
||||
}
|
||||
```
|
||||
|
||||
- `ProviderState`, `ProviderPlanRef`, `ProviderFeatureRef`, `ProviderSubscriptionRef`, etc., are small value objects storing provider IDs and metadata. They help hide vendor specifics from the rest of the system.
|
||||
- `Document` references represent Appwrite entities (project, user, team) fetched prior to calling adapter.
|
||||
|
||||
3. **Registry & Resolution**
|
||||
|
||||
- Introduce `src/Appwrite/Payments/Provider/Registry.php` to map provider identifiers (e.g., `stripe`, `ultra`) to adapter instances. Similar to `Messaging` using adapter classes.
|
||||
- Registry loads adapters lazily and caches per project if necessary.
|
||||
- Module obtains provider configuration from `project` document (see data model) and uses registry to instantiate adapters with project-specific credentials.
|
||||
|
||||
4. **Project Configuration Structure**
|
||||
|
||||
```json
|
||||
{
|
||||
"providers": {
|
||||
"stripe": {
|
||||
"secretKey": "...",
|
||||
"publishableKey": "...",
|
||||
"webhookSecret": "...",
|
||||
"webhookEndpointId": "...",
|
||||
"currency": "usd",
|
||||
"defaultTaxRates": [],
|
||||
"capabilities": {
|
||||
"meteredBilling": true,
|
||||
"checkout": true
|
||||
}
|
||||
},
|
||||
"ultra": {
|
||||
"apiKey": "...",
|
||||
"region": "us-east",
|
||||
"capabilities": { ... }
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"currency": "usd",
|
||||
"trialDays": 14
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Stored under `projects.payments` attribute (JSON, with `encrypt` filter for secrets). Project update endpoints manage serialization.
|
||||
|
||||
5. **Adapter Lifecycle**
|
||||
|
||||
- **Configure**: Called when admin enables provider. Adapter sets up webhook endpoints or necessary products where required. Returns sanitized state (IDs, statuses).
|
||||
- **Plan Management**: Core module persists generic plan data; adapter ensures provider artifacts (product IDs, price IDs) exist and stores references in plan document.
|
||||
- **Subscription Flow**: Module constructs canonical subscription request, adapter handles vendor-specific operations (creating customers, scheduling invoices), returns provider subscription key stored alongside Appwrite subscription record.
|
||||
- **Usage Reporting**: Module records usage in internal ledger immediately; adapter may asynchronously forward to provider (e.g., Stripe metered billing). Worker jobs or sync endpoints ensure eventual consistency.
|
||||
- **Webhooks**: Module registers provider-specific webhook endpoints; incoming webhooks use registry to locate appropriate adapter and update `payments_subscriptions` or usage data.
|
||||
|
||||
6. **Error Handling**
|
||||
- Create `src/Appwrite/Payments/Exception/PaymentException.php` with standardized codes (e.g., `PROVIDER_AUTH_FAILED`, `PLAN_CONFLICT`, `USAGE_REPORT_FAILED`).
|
||||
- Adapter implementations throw provider-specific exceptions mapped to `PaymentException` with actionable messages.
|
||||
|
||||
### 4. Database Schema
|
||||
|
||||
Create new configuration file `app/config/collections/payments.php`. Proposed collections:
|
||||
|
||||
1. **`payments_plans`**
|
||||
|
||||
- `projectId`, `projectInternalId` (key indexes).
|
||||
- `planId` (custom ID exposed to clients).
|
||||
- `name`, `description`, `pricing` array (support multiple billing intervals/currencies), `isDefault`, `isFree`, `status`, `migrationVersion`.
|
||||
- `providers` JSON: map provider identifier -> { productId, priceIds, metadata }.
|
||||
- `features` summary JSON for quick lookup.
|
||||
- Indexes: unique (`projectId`, `planId`), key on `status`, fulltext `search` combining name/description.
|
||||
|
||||
2. **`payments_features`**
|
||||
|
||||
- Definition list available for assignment to plans.
|
||||
- Attributes: `featureId`, `name`, `type`, `description`, `defaultIncludedUnits`, `defaultTiers`, etc.
|
||||
- `providers` JSON storing mapping to external meter IDs or feature codes.
|
||||
- Indexes: unique (`projectId`, `featureId`), key on `type`.
|
||||
|
||||
3. **`payments_plan_features`**
|
||||
|
||||
- Join table linking plans to feature configs.
|
||||
- Attributes: `planId`, `featureId`, `type`, `enabled`, `currency`, `interval`, `includedUnits`, `tiersMode`, `tiers`, `usageCap`, `overagePrice`, `providers` JSON (price/meter references), `metadata` JSON.
|
||||
- Indexes: unique (`projectId`, `planId`, `featureId`), key on `type`, `active` flag.
|
||||
|
||||
4. **`payments_subscriptions`**
|
||||
|
||||
- Tracks active/past subscriptions.
|
||||
- Attributes: `subscriptionId` (Appwrite ID), `projectId`, `actorType` (`user`/`team`), `actorId`, `actorInternalId`, `planId`, `status`, `trialEndsAt`, `currentPeriodStart`, `currentPeriodEnd`, `cancelAtPeriodEnd`, `canceledAt`, `providerStatus`, `providers` JSON (customerId, subscriptionId, invoiceId, lastSyncedAt), `usageSummary` JSON, `tags` array, `search` field.
|
||||
- Indexes: key on (`projectId`, `actorType`, `actorId`), key on `status`, key on `planId`, fulltext search.
|
||||
|
||||
5. **`payments_usage_events`**
|
||||
|
||||
- Internal usage ledger.
|
||||
- Attributes: `projectId`, `subscriptionId`, `actorType`, `actorId`, `planId`, `featureId`, `quantity`, `timestamp`, `providerSyncState`, `providerEventId`, `metadata`.
|
||||
- Indexes: key on (`projectId`, `subscriptionId`, `featureId`, `timestamp`), key on `providerSyncState`.
|
||||
|
||||
6. **`payments_provider_logs`** (optional) – store provider interactions/webhook payloads for debugging.
|
||||
|
||||
**Migration Strategy**
|
||||
|
||||
- Update `app/config/collections.php` to load `payments.php` similar to other modules.
|
||||
|
||||
### 5. Business Logic Flow
|
||||
|
||||
1. **Plan Creation**
|
||||
|
||||
- Validate request (IDs, pricing structure) using new validators (`Payments\Validator\PlanPayload`).
|
||||
- Persist plan document in `payments_plans` (without provider references initially).
|
||||
- For each enabled provider, call adapter->ensurePlan. Merge returned references into `providers` JSON and persist.
|
||||
- Emit events/audits (`payments.plans.[planId].create`).
|
||||
|
||||
2. **Feature Assignment**
|
||||
|
||||
- Validate feature definitions exist and types are allowed.
|
||||
- Persist in `payments_plan_features`, call adapter->ensureFeature where required (e.g., create metered price). Update plan document summary.
|
||||
|
||||
3. **Subscription Lifecycle**
|
||||
|
||||
- `POST /subscriptions`: Determine actor document (fetch user or team). If team, ensure membership + permissions.
|
||||
- Check existing active subscription; depending on config, allow multiple or enforce single.
|
||||
- For free plans: create internal subscription record without provider interaction.
|
||||
- For paid plans: adapter->ensureSubscription (create customer if needed, attach payment method, start subscription). Store provider subscription ID and status.
|
||||
- Update `payments_subscriptions` with internal status (`active`, `trialing`, `past_due`, etc.).
|
||||
- Trigger usage initialization jobs and optionally send welcome events.
|
||||
|
||||
4. **Usage Tracking**
|
||||
|
||||
- Services emit usage via `POST /subscriptions/:id/usage` (server key or internal worker). Each entry stored in `payments_usage_events` and aggregated for quick reads.
|
||||
- Cron/worker picks unsynced events, groups by provider, calls adapter->reportUsage or other relevant API.
|
||||
- `GET /subscriptions/:id/usage` returns aggregated totals (internal vs provider synced) plus detail entries.
|
||||
|
||||
5. **Provider Webhooks**
|
||||
|
||||
- Webhook endpoint verifies signature (adapter->handleWebhook). Update subscription statuses (e.g., `past_due`, `canceled`), record invoice IDs, mark usage sync states.
|
||||
- Fire events to notify console or automation flows.
|
||||
|
||||
6. **Project Configuration**
|
||||
- `PUT /payments/providers`: Accept provider configs, run validations (fields required per provider). Adapter->configure may create webhook endpoints, check API keys. Persist sanitized config in `project.payments` JSON and update `projects` document.
|
||||
- Provide GET endpoint returning provider status for console (enabled, last sync, warnings).
|
||||
|
||||
### 6. Security & Permissions
|
||||
|
||||
- Scope mapping:
|
||||
- `payments.read`: Admins or service keys retrieving plan/subscription data.
|
||||
- `payments.write`: Admin actions to manage plans, providers, migrations.
|
||||
- `payments.subscribe`: Actors initiating subscription changes (users/teams). Distinguish between user session vs team owner in access control.
|
||||
- Authorization logic uses existing role helpers (e.g., `Role::user`, `Role::team`). Ensure team endpoints check membership + `owner` or `billing` roles.
|
||||
- Sensitive provider credentials stored encrypted; route responses never expose secrets.
|
||||
- Webhook endpoints use provider-specific secret validation; avoid exposing under public documentation.
|
||||
|
||||
### 7. Testing & QA
|
||||
|
||||
- **Unit Tests:**
|
||||
- Provider adapter contract (mock HTTP clients) verifying plan creation, subscription lifecycle, error propagation.
|
||||
- Validators for plan payloads, provider configs.
|
||||
- **E2E Tests:**
|
||||
- Add `tests/e2e/Services/Payments/` with scenarios: plan CRUD, subscription flow (user + team), usage reporting, provider configuration failure.
|
||||
- Use mock provider adapter for deterministic responses.
|
||||
- **Integration Tests:**
|
||||
- Worker tests simulating usage reconciliation.
|
||||
- Migration tests verifying data transformation from legacy schemas.
|
||||
- **Benchmarks:**
|
||||
- Evaluate overhead of usage events and plan operations under load to adjust indexes.
|
||||
|
||||
### 8. Migration & Rollout
|
||||
|
||||
1. **Phase Gate**
|
||||
|
||||
- Ship module behind feature flag (`project.payments.enabled`). Default disabled until admin configures provider.
|
||||
- Provide CLI to enable module per project post-migration.
|
||||
|
||||
2. **Data Migration**
|
||||
|
||||
- CLI script reads existing `auth_*` collections and populates new schema.
|
||||
- Migrate user-level `planId`, `stripeCustomerId`, etc., into `payments_subscriptions` records with `actorType=user`.
|
||||
- Generate default team subscriptions if teams had equivalent data (if none, start blank).
|
||||
|
||||
3. **Documentation & Console Update**
|
||||
- Update console to consume `/v1/payments` endpoints (plan management UI, subscription views, usage dashboards).
|
||||
- Provide developer docs describing multi-actor support, provider configuration, usage APIs, error handling.
|
||||
|
||||
### 9. Open Issues & Decisions Needed
|
||||
|
||||
- **Team billing semantics**: define allowed team roles for subscription management, and how consumption is aggregated across team members.
|
||||
- **Multiple providers simultaneously**: Determine whether a project can use multiple providers concurrently (failover vs multi-market). Data model supports multiple by design; confirm product direction.
|
||||
- **Self-hosted provider**: Consider built-in “Manual” provider for on-prem setups wanting internal invoicing without external gateway.
|
||||
- **Plan versioning**: Decide if plan updates create new versions or mutate in place; consider locking for existing subscriptions.
|
||||
- **Grace periods**: Align with Stripe-style grace or immediate suspension rules; ensure configurable per provider/project.
|
||||
|
||||
### 10. Next Steps
|
||||
|
||||
1. Validate blueprint with stakeholders (engineering leads, product).
|
||||
2. Break implementation into milestones: module skeleton → data schema → provider abstraction → endpoints → migrations/tests.
|
||||
3. Allocate time for console integration and documentation once backend stabilizes.
|
||||
-350
@@ -1,350 +0,0 @@
|
||||
# Payments Revamp Stepbook
|
||||
|
||||
This stepbook sequences the end-to-end implementation of the payments revamp. Each step is agent-friendly, with explicit prerequisites, work instructions, deliverables, and verification guidance. Follow the steps in order unless a dependency explicitly states otherwise.
|
||||
|
||||
## Legend
|
||||
|
||||
- **Prereqs**: Conditions or artifacts required before tackling the step.
|
||||
- **Core Tasks**: Ordered instructions for human/AI agents.
|
||||
- **Outputs**: Expected tangible results (code, docs, configs).
|
||||
- **Verification**: Commands or checks that confirm completion.
|
||||
- **Handoff**: Follow-up steps or consumers of the outputs.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 – Module Scaffolding
|
||||
|
||||
- **Prereqs**
|
||||
- Workspace prep complete.
|
||||
- **Core Tasks**
|
||||
1. Create module namespace: `src/Appwrite/Platform/Modules/Payments/`.
|
||||
2. Add `Module.php` mirroring structure of `Functions\Module`.
|
||||
3. Create `Services/Http.php` (and placeholder `Services/Workers.php` if background work planned) extending `Utopia\Platform\Service`.
|
||||
4. Register module within `src/Appwrite/Platform/Appwrite.php` constructor via `$this->addModule(new Payments\Module());`.
|
||||
5. Ensure Composer autoload already covers `src/Appwrite` (confirm `composer.json` PSR-4 mapping). If gaps exist, patch accordingly.
|
||||
- **Outputs**
|
||||
- Module skeleton files committed with minimal placeholder logic.
|
||||
- **Verification**
|
||||
- `composer dump-autoload` executes without errors.
|
||||
- `vendor/bin/phpunit --filter Payments` (should report zero tests but no failures).
|
||||
- `php -l` on new files passes.
|
||||
- **Handoff**
|
||||
- Step 4 uses module skeleton to register HTTP actions.
|
||||
|
||||
## Step 4 – HTTP Route Class Framework
|
||||
|
||||
- **Prereqs**
|
||||
- Module skeleton registered; app boots without fatal errors.
|
||||
- **Core Tasks**
|
||||
1. Design folder structure under `src/Appwrite/Platform/Modules/Payments/Http/` matching endpoint domains (e.g., `Plans`, `Features`, `PlanFeatures`, `Subscriptions`, `Usage`, `Providers`, `Webhooks`).
|
||||
2. For each endpoint group, create base action classes extending `Appwrite\Platform\Modules\Compute\Base` (or appropriate lightweight variant) and `use HTTP` trait.
|
||||
3. In `Services/Http.php`, call `addAction(new Plans\Create())`, etc., for every planned route.
|
||||
4. Stub methods: set `setName`, `setHttpMethod`, `setHttpPath`, `setDescription`, `setGroup`, placeholder `->param` definitions.
|
||||
5. Add TODO comments referencing Step 8/9 tasks for actual business logic.
|
||||
- **Outputs**
|
||||
- Skeleton route classes with metadata scaffolding.
|
||||
- **Verification**
|
||||
- `php -l` on all HTTP files.
|
||||
- Application boot (via `php -d detect_unicode=0 app/init.php`) logs no missing class errors.
|
||||
- **Handoff**
|
||||
- Step 5 introduces scope/SDK metadata consumed by these classes.
|
||||
|
||||
## Step 5 – Scope & SDK Metadata Wiring
|
||||
|
||||
- **Prereqs**
|
||||
- Route classes stubbed.
|
||||
- **Core Tasks**
|
||||
1. Update `app/config/scopes.php` to define `payments.read`, `payments.write`, `payments.subscribe` (client/server/console mappings per blueprint).
|
||||
2. Add console/admin scope entries and ensure scope descriptions reflect usage.
|
||||
3. In each route class, register SDK metadata via `$this->label('sdk', new Method(...))` specifying group `payments`, auth types, and method name.
|
||||
4. Annotate routes with `->label('scope', 'payments.read')` etc.; include `resourceType`, `event`, and `audits.*` labels where applicable.
|
||||
5. Update scope tests under `tests/e2e/Scopes` (add cases ensuring new scopes exist and map to correct roles).
|
||||
6. Regenerate scope documentation if automation expects (`bin/specs scopes`).
|
||||
- **Outputs**
|
||||
- Scopes defined and linked; routes enriched with metadata.
|
||||
- **Verification**
|
||||
- `vendor/bin/phpunit tests/e2e/Scopes` passes (or targeted test command).
|
||||
- `bin/specs` (or relevant generator) succeeds without missing scope errors.
|
||||
- **Handoff**
|
||||
- Step 6 relies on new scopes for permission configuration.
|
||||
|
||||
## Step 6 – Data Model Definition
|
||||
|
||||
- **Prereqs**
|
||||
- Scope wiring complete; DBA consulted for index strategy (if required).
|
||||
- **Core Tasks**
|
||||
1. Create `app/config/collections/payments.php` describing all new collections:
|
||||
- `payments_plans`
|
||||
- `payments_features`
|
||||
- `payments_plan_features`
|
||||
- `payments_subscriptions`
|
||||
- `payments_usage_events`
|
||||
- optional `payments_provider_logs`
|
||||
2. For each collection, define attributes (types, filters, required flags) exactly as blueprint specifies (IDs, JSON fields, indexes, encryption flags).
|
||||
3. Update `app/config/collections.php` to include the new config file.
|
||||
4. Remove/flag legacy Stripe-specific attributes from `app/config/collections/platform.php` and `app/config/collections/common.php`; add `projects.payments` JSON attribute with `encrypt` filter for secrets.
|
||||
5. Draft migration notes describing how new collections replace old fields (feeds into Step 11).
|
||||
- **Outputs**
|
||||
- Payments collections configuration file with indexes and attribute metadata.
|
||||
- Legacy collection definitions cleaned up.
|
||||
- **Verification**
|
||||
- Run `php -d detect_unicode=0 app/init.php` to ensure config loads.
|
||||
- `vendor/bin/phpunit tests/unit/Database/Collections` (or relevant) passes.
|
||||
- **Handoff**
|
||||
- Step 7 implements provider abstractions referencing new schema.
|
||||
|
||||
## Step 7 – Provider Abstraction Layer
|
||||
|
||||
- **Prereqs**
|
||||
- Data model defined; requirements for provider interactions clarified.
|
||||
- **Core Tasks**
|
||||
1. Create namespace `src/Appwrite/Payments/Provider/`.
|
||||
2. Implement `Adapter` interface (or `ProviderAdapterInterface`) as per blueprint signature (plan management, subscriptions, usage, webhooks, testing).
|
||||
3. Define supporting value objects (`ProviderState`, `ProviderPlanRef`, `ProviderFeatureRef`, `ProviderSubscriptionRef`, `ProviderCheckoutSession`, `ProviderPortalSession`, `ProviderUsageReport`, `ProviderWebhookResult`, `ProviderTestResult`).
|
||||
4. Build `Registry` class to resolve adapters by identifier, instantiate with project-specific config, and cache where appropriate.
|
||||
5. Migrate existing Stripe logic (`src/Appwrite/Auth/Subscription/StripeService.php`) into `Payments\Provider\StripeAdapter` implementing interface.
|
||||
6. Relocate validators and exceptions into `src/Appwrite/Payments/Validator` and `src/Appwrite/Payments/Exception\PaymentException.php`.
|
||||
7. Ensure adapter error handling maps provider-specific issues to standardized `PaymentException` codes (`PROVIDER_AUTH_FAILED`, `PLAN_CONFLICT`, etc.).
|
||||
- **Outputs**
|
||||
- Provider interface and registry.
|
||||
- Stripe adapter aligned with new abstractions.
|
||||
- **Verification**
|
||||
- Unit tests for registry and adapter contract (`tests/unit/Payments/Provider/`) passing.
|
||||
- Static analysis (`vendor/bin/phpstan analyse src/Appwrite/Payments`).
|
||||
- **Handoff**
|
||||
- Step 8 integrates registry into route actions.
|
||||
|
||||
## Step 8 – Business Logic Integration (Plans & Features)
|
||||
|
||||
- **Prereqs**
|
||||
- Provider layer functional; data model available.
|
||||
- **Core Tasks**
|
||||
1. Implement `Plans` routes (`Create`, `Get`, `List`, `Update`, `Delete`) to validate payloads (`Payments\Validator\PlanPayload`), persist documents in `payments_plans`, and invoke provider adapters for provisioning.
|
||||
2. Build `Features` routes for reusable feature definitions, including create/update/delete flows with provider interactions (`ensureFeature`).
|
||||
3. Implement `PlanFeatures` routes (`Assign`, `List`, `Remove`) to link plans and features, manage tiered pricing, and update plan summaries.
|
||||
4. Ensure events/audits triggered (`payments.plans.{planId}.create`, etc.).
|
||||
5. Write unit tests for validators and repository interactions; add e2e tests covering positive/negative plan scenarios.
|
||||
- **Outputs**
|
||||
- Fully functioning plan and feature endpoints with provider provisioning.
|
||||
- **Verification**
|
||||
- `vendor/bin/phpunit tests/unit/Payments/Plans` and `tests/e2e/Services/Payments/Plans`.
|
||||
- Manual API smoke tests via `docs/scripts` or HTTP client verifying CRUD operations.
|
||||
- **Handoff**
|
||||
- Step 9 builds subscription lifecycle using same abstractions.
|
||||
|
||||
## Step 9 – Business Logic Integration (Subscriptions & Usage)
|
||||
|
||||
- **Prereqs**
|
||||
- Plans/features endpoints operational; provider adapter tested.
|
||||
- **Core Tasks**
|
||||
1. Implement subscription routes:
|
||||
- `POST /v1/payments/subscriptions`
|
||||
- `GET /v1/payments/subscriptions`
|
||||
- `GET /v1/payments/subscriptions/:subscriptionId`
|
||||
- `PATCH /v1/payments/subscriptions/:subscriptionId`
|
||||
- `POST /v1/payments/subscriptions/:subscriptionId/cancel`
|
||||
- `POST /v1/payments/subscriptions/:subscriptionId/resume`
|
||||
2. Logic should:
|
||||
- Resolve actor documents (user/team) with permission checks.
|
||||
- Handle free vs paid plan flows (internal-only vs provider interactions).
|
||||
- Persist subscription state in `payments_subscriptions` with lifecycle timestamps.
|
||||
- Trigger events/audits and queue initialization jobs.
|
||||
3. Implement usage endpoints:
|
||||
- `GET /v1/payments/subscriptions/:subscriptionId/usage`
|
||||
- `POST /v1/payments/subscriptions/:subscriptionId/usage`
|
||||
- `GET /v1/payments/usage/events`
|
||||
- `POST /v1/payments/usage/reconcile`
|
||||
4. Wire usage reporting to adapters (`reportUsage`, `syncUsage`) and internal ledger (`payments_usage_events`).
|
||||
5. Add permission checks for `payments.subscribe` vs `payments.write`; ensure team role enforcement (`owner`, `billing`).
|
||||
6. Unit + e2e tests covering actor scenarios, cancellations, resumes, usage reporting, reconciliation.
|
||||
- **Outputs**
|
||||
- Subscription and usage endpoints with provider interoperability.
|
||||
- **Verification**
|
||||
- `vendor/bin/phpunit tests/unit/Payments/Subscriptions` & `Usage` suites.
|
||||
- `tests/e2e/Services/Payments/Subscriptions` scenarios.
|
||||
- Manual smoke test (create plan, assign feature, create subscription, report usage, reconcile).
|
||||
- **Handoff**
|
||||
- Step 10 handles provider configuration endpoints and webhooks.
|
||||
|
||||
## Step 10 – Provider Configuration & Webhooks
|
||||
|
||||
- **Prereqs**
|
||||
- Adapter registry in place; subscription flows implemented.
|
||||
- **Core Tasks**
|
||||
1. Build provider management routes:
|
||||
- `GET /v1/payments/providers`
|
||||
- `PUT /v1/payments/providers`
|
||||
- `POST /v1/payments/providers/:providerId/actions/test`
|
||||
2. Persist provider configuration in `projects.payments` JSON (encrypted fields) with validation per provider.
|
||||
3. Implement webhook handler `POST /v1/payments/webhooks/:providerId` with signature validation delegating to adapter `handleWebhook`.
|
||||
4. Ensure webhook updates subscription status, invoices, usage sync states, and emits relevant events.
|
||||
5. Add unit tests for configuration validators and webhook processing; e2e tests simulating webhook payloads (mock provider).
|
||||
- **Outputs**
|
||||
- Provider configuration endpoints and webhook infrastructure.
|
||||
- **Verification**
|
||||
- `vendor/bin/phpunit tests/unit/Payments/Providers`.
|
||||
- `tests/e2e/Services/Payments/Providers` including webhook scenarios.
|
||||
- **Handoff**
|
||||
- Step 11 introduces background workers for async tasks.
|
||||
|
||||
## Step 11 – Background Workers & Queues
|
||||
|
||||
- **Prereqs**
|
||||
- Provider webhooks implemented; need for async operations confirmed.
|
||||
- **Core Tasks**
|
||||
1. If required, implement `Services/Workers.php` under payments module registering worker actions (usage sync, webhook replay, reconciliation, notifications).
|
||||
2. Connect workers to relevant queues (`queueForEvents`, `queueForStatsUsage`, etc.).
|
||||
3. Implement worker handlers for:
|
||||
- Processing unsynced usage events in batches.
|
||||
- Retrying failed provider interactions.
|
||||
- Emitting audit logs/notifications.
|
||||
4. Add configuration toggles/cron definitions if periodic jobs required.
|
||||
5. Write worker unit/integration tests (simulate queue payloads, ensure idempotency) and update CI to execute them.
|
||||
- **Outputs**
|
||||
- Worker services handling async tasks with tests.
|
||||
- **Verification**
|
||||
- `vendor/bin/phpunit tests/unit/Payments/Workers`.
|
||||
- Manual queue dry-run via `php bin/queue --capture payments-usage-sync` (or equivalent) shows successful execution.
|
||||
- **Handoff**
|
||||
- Step 12 manages migrations and data backfill.
|
||||
|
||||
## Step 12 – Data Migration & Compatibility Layer
|
||||
|
||||
- **Prereqs**
|
||||
- Core functionality ready; legacy schema inventoried.
|
||||
- **Core Tasks**
|
||||
1. Develop migration CLI (e.g., `src/Appwrite/Platform/Tasks/MigratePayments.php`) to read legacy `auth_*` collections and user fields, populate new `payments_*` collections.
|
||||
2. Handle user-level Stripe data conversion into subscriptions (`actorType=user`) and generate team records as needed.
|
||||
3. Provide dry-run mode producing migration report.
|
||||
4. Implement compatibility wrappers for legacy `/v1/account/subscription*` endpoints (if transitional period required) calling new module while preserving response shape.
|
||||
5. Document manual fallback steps (disable module, rollback) and update upgrade scripts (`bin/upgrade`, `bin/migrate`).
|
||||
- **Outputs**
|
||||
- Migration CLI + documentation.
|
||||
- Optional compatibility endpoints bridging legacy clients.
|
||||
- **Verification**
|
||||
- `php bin/migrate payments --dry-run` (or equivalent) generates expected report.
|
||||
- Integration test seeding legacy data and validating converted records.
|
||||
- **Handoff**
|
||||
- Step 13 regenerates specifications and SDKs.
|
||||
|
||||
## Step 13 – Specification & SDK Updates
|
||||
|
||||
- **Prereqs**
|
||||
- API routes finalized; migration plan approved.
|
||||
- **Core Tasks**
|
||||
1. Update OpenAPI spec generation tooling (`bin/specs`, `src/Appwrite/Platform/Tasks/Specs.php`) to include `/v1/payments` endpoints with accurate schemas/models.
|
||||
2. Create new response models under `src/Appwrite/Utopia/Response/Model` (Plan, PlanList, Feature, Subscription, UsageEvent, etc.) and ensure routes reference them.
|
||||
3. Regenerate SDKs (`bin/sdks generate all`) and verify new payments namespace appears across client/server SDKs.
|
||||
4. Audit console web client to ensure TypeScript models integrate new endpoints (update `public/sdk-console` if required).
|
||||
- **Outputs**
|
||||
- Updated specs, generated SDK artifacts, and console types.
|
||||
- **Verification**
|
||||
- `git diff` shows updated spec JSON + SDK source.
|
||||
- Run sample SDK script invoking payments endpoints successfully (smoke test).
|
||||
- **Handoff**
|
||||
- Step 14 focuses on testing strategy.
|
||||
|
||||
## Step 14 – Testing & Quality Assurance
|
||||
|
||||
- **Prereqs**
|
||||
- Functional code complete; tests implemented alongside earlier steps.
|
||||
- **Core Tasks**
|
||||
1. Ensure unit, e2e, integration, and worker tests cover:
|
||||
- Plan/feature CRUD
|
||||
- Subscription lifecycle (user/team)
|
||||
- Usage reporting & reconciliation
|
||||
- Provider configuration + webhooks
|
||||
- Migration scripts
|
||||
2. Add benchmark tests if usage events need performance validation.
|
||||
3. Execute full test matrix: `vendor/bin/phpunit`, `npm run test` (if console updated), `bin/bench` (if available).
|
||||
4. Collect coverage report and ensure thresholds met (update `phpunit.xml` if needed).
|
||||
5. Document manual QA scenarios (console UI walk-through, webhook replay).
|
||||
- **Outputs**
|
||||
- Comprehensive passing test suite and QA checklist.
|
||||
- **Verification**
|
||||
- CI pipeline green across all stages.
|
||||
- QA sign-off recorded in tracker.
|
||||
- **Handoff**
|
||||
- Step 15 handles documentation and developer experience.
|
||||
|
||||
## Step 15 – Documentation & Developer Experience
|
||||
|
||||
- **Prereqs**
|
||||
- Testing complete; product sign-off on functionality.
|
||||
- **Core Tasks**
|
||||
1. Author `docs/services/payments.md` covering configuration, endpoints, usage reporting, migration notes.
|
||||
2. Update console documentation and copy (billing UI, error messages).
|
||||
3. Refresh developer tutorials (CLI scripts for plan creation, sample usage reporting).
|
||||
4. Update CHANGELOG/RELEASE notes summarizing payments revamp.
|
||||
5. Provide sample automation scripts in `docs/examples` demonstrating typical flows.
|
||||
- **Outputs**
|
||||
- Updated documentation stack.
|
||||
- **Verification**
|
||||
- Docs build (if applicable) passes (`npm run docs:build` or similar).
|
||||
- Technical writers/product review completed.
|
||||
- **Handoff**
|
||||
- Step 16 manages rollout and feature flagging.
|
||||
|
||||
## Step 16 – Rollout & Feature Flag Management
|
||||
|
||||
- **Prereqs**
|
||||
- Documentation ready; QA sign-off.
|
||||
- **Core Tasks**
|
||||
1. Implement feature flag gating (`project.payments.enabled`) with default disabled; ensure configuration endpoint toggles it.
|
||||
2. Prepare rollout plan (beta projects, staged enablement, monitoring dashboards).
|
||||
3. Update CLI tooling or admin UI to toggle flag per project post-migration.
|
||||
4. Coordinate with support to craft customer communication, fallback procedures.
|
||||
5. Monitor metrics (errors, webhook failures, subscription churn) during rollout.
|
||||
- **Outputs**
|
||||
- Feature flag controls, rollout schedule, monitoring plan.
|
||||
- **Verification**
|
||||
- Dry-run enabling flag on staging succeeds end-to-end.
|
||||
- Monitoring alerts configured with thresholds.
|
||||
- **Handoff**
|
||||
- Step 17 covers legacy deprecation.
|
||||
|
||||
## Step 17 – Legacy Endpoint Deprecation
|
||||
|
||||
- **Prereqs**
|
||||
- Rollout underway; compatibility shims deployed.
|
||||
- **Core Tasks**
|
||||
1. Announce deprecation timeline for `/v1/account/subscription*` endpoints.
|
||||
2. Implement logging/metrics to detect lingering usage of legacy endpoints.
|
||||
3. Provide migration guidance to SDK consumers (release notes, upgrade guides).
|
||||
4. Schedule removal window; when ready, retire compatibility shims and delete legacy controllers/config.
|
||||
5. Update specs/SDKs to remove legacy endpoints once EOL date passes.
|
||||
- **Outputs**
|
||||
- Legacy endpoints officially deprecated and removed per schedule.
|
||||
- **Verification**
|
||||
- No production traffic to legacy routes (dashboards confirm zero hits).
|
||||
- Codebase free of legacy controllers/paths.
|
||||
- **Handoff**
|
||||
- Step 18 finalizes project closure.
|
||||
|
||||
## Step 18 – Project Closure & Retrospective
|
||||
|
||||
- **Prereqs**
|
||||
- Legacy endpoints removed; new payments module stable in production.
|
||||
- **Core Tasks**
|
||||
1. Conduct retrospective documenting successes, challenges, follow-up actions.
|
||||
2. Archive decision log updates, migration scripts outcomes, and open issues resolved/deferred.
|
||||
3. Confirm tracker tasks closed and post-rollout monitoring handed to operations.
|
||||
4. Celebrate with team acknowledgement (optional but recommended!).
|
||||
- **Outputs**
|
||||
- Retrospective report, cleaned tracker, knowledge captured.
|
||||
- **Verification**
|
||||
- All project artifacts stored in agreed knowledge base.
|
||||
- Stakeholders acknowledge completion.
|
||||
- **Handoff**
|
||||
- None; project concluded.
|
||||
|
||||
---
|
||||
|
||||
### Supporting References
|
||||
|
||||
- `REVAMP_PLAN_DETAILED.md` – Deep blueprint for architecture, API, adapters, data model.
|
||||
- `REVAMP_PLAN.md` – High-level roadmap and phase breakdown.
|
||||
- Existing module patterns: `src/Appwrite/Platform/Modules/Functions`, `Sites`.
|
||||
- Provider integration precedents: `src/Appwrite/Messaging/Provider`, `src/Appwrite/Auth/Subscription/StripeService.php` (legacy).
|
||||
|
||||
Use this stepbook as the authoritative sequencing for agents and humans collaborating on the payments revamp. Update the document if scope adjustments or new decisions arise.
|
||||
-370
@@ -1,370 +0,0 @@
|
||||
## Payments Revamp Test Plan (Step-by-step, with sample bodies)
|
||||
|
||||
Variables used below:
|
||||
|
||||
- BASE: https://your-appwrite-endpoint
|
||||
- PROJECT_ID: your project ID
|
||||
- API_KEY: Admin API key with payments.write/read/subscribe
|
||||
- USER_ID: existing user ID (payer for user subscriptions and team payer)
|
||||
- TEAM_ID: existing team ID (for team subscriptions)
|
||||
|
||||
Common headers (Admin key):
|
||||
|
||||
```
|
||||
-H "X-Appwrite-Project: PROJECT_ID" \
|
||||
-H "X-Appwrite-Key: API_KEY" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
### 1) Configure provider (Stripe) and enable payments
|
||||
|
||||
Request:
|
||||
|
||||
```bash
|
||||
curl -X PUT "$BASE/v1/payments/providers" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" \
|
||||
-H "X-Appwrite-Key: API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"config": {
|
||||
"enabled": true,
|
||||
"providers": {
|
||||
"stripe": {
|
||||
"secretKey": "sk_test_xxx",
|
||||
"publishableKey": "pk_test_xxx"
|
||||
}
|
||||
}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
Verify (secrets masked):
|
||||
|
||||
```bash
|
||||
curl -X GET "$BASE/v1/payments/providers" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" \
|
||||
-H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
Optional: test credentials
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/providers/stripe/actions/test" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" \
|
||||
-H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
### 2) Create feature definitions
|
||||
|
||||
2a. Boolean feature (e.g., builds):
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/features" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"featureId": "builds",
|
||||
"name": "Builds",
|
||||
"type": "boolean",
|
||||
"description": "Ability to run builds"
|
||||
}'
|
||||
```
|
||||
|
||||
2b. Metered feature (e.g., requests):
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/features" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"featureId": "requests",
|
||||
"name": "Requests",
|
||||
"type": "metered",
|
||||
"description": "Metered API requests"
|
||||
}'
|
||||
```
|
||||
|
||||
List features:
|
||||
|
||||
```bash
|
||||
curl -X GET "$BASE/v1/payments/features" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
### 3) Create a plan with pricing
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/plans" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"planId": "pro",
|
||||
"name": "Pro",
|
||||
"description": "Pro plan",
|
||||
"isDefault": false,
|
||||
"isFree": false,
|
||||
"pricing": [
|
||||
{ "amount": 1999, "currency": "usd", "interval": "month" },
|
||||
{ "amount": 19900, "currency": "usd", "interval": "year" }
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
Get plan:
|
||||
|
||||
```bash
|
||||
curl -X GET "$BASE/v1/payments/plans/pro" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
### 4) Assign features to the plan
|
||||
|
||||
4a. Assign boolean feature (type inferred from feature):
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/plans/pro/features" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"featureId": "builds",
|
||||
"currency": "usd",
|
||||
"interval": "month",
|
||||
"includedUnits": 0
|
||||
}'
|
||||
```
|
||||
|
||||
4b. Assign metered feature (type inferred from feature):
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/plans/pro/features" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"featureId": "requests",
|
||||
"currency": "usd",
|
||||
"interval": "month",
|
||||
"includedUnits": 10000
|
||||
}'
|
||||
```
|
||||
|
||||
4c. Tiered (graduated) pricing (note):
|
||||
|
||||
- The current route accepts the documented fields: `featureId`, `currency`, `interval`, `includedUnits`. Type is inferred from the feature definition.
|
||||
- Keys like `tiersMode`, `tiers`, `usageCap`, `overagePrice` are not read by the current implementation and will be ignored if sent.
|
||||
- Below is a future-facing example body for reference only (not applied by current code):
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/plans/pro/features" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"featureId": "requests",
|
||||
"type": "metered",
|
||||
"currency": "usd",
|
||||
"interval": "month",
|
||||
"includedUnits": 10000,
|
||||
"tiersMode": "graduated",
|
||||
"tiers": [
|
||||
{ "up_to": 10000, "unit_amount": 0 },
|
||||
{ "up_to": 100000, "unit_amount": 10 },
|
||||
{ "up_to": "inf", "unit_amount": 8 }
|
||||
],
|
||||
"usageCap": null,
|
||||
"overagePrice": null
|
||||
}'
|
||||
```
|
||||
|
||||
List plan features:
|
||||
|
||||
```bash
|
||||
curl -X GET "$BASE/v1/payments/plans/pro/features" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
### 5) Create subscriptions
|
||||
|
||||
5a. User subscription:
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/subscriptions" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"actorType": "user",
|
||||
"actorId": "USER_ID",
|
||||
"planId": "pro"
|
||||
}'
|
||||
```
|
||||
|
||||
5b. Team subscription (payer must be team member with owner/billing):
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/subscriptions" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"actorType": "team",
|
||||
"actorId": "TEAM_ID",
|
||||
"planId": "pro",
|
||||
"payerUserId": "USER_ID"
|
||||
}'
|
||||
```
|
||||
|
||||
List subscriptions (optional filters):
|
||||
|
||||
```bash
|
||||
curl -X GET "$BASE/v1/payments/subscriptions?actorType=user&actorId=USER_ID" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
Get subscription by ID:
|
||||
|
||||
```bash
|
||||
curl -X GET "$BASE/v1/payments/subscriptions/SUBSCRIPTION_ID" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
### 6) Update, cancel, resume
|
||||
|
||||
6a. Update plan (switch):
|
||||
|
||||
```bash
|
||||
curl -X PATCH "$BASE/v1/payments/subscriptions/SUBSCRIPTION_ID" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{ "planId": "pro" }'
|
||||
```
|
||||
|
||||
6b. Cancel at period end (explicit cancel endpoint):
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/subscriptions/SUBSCRIPTION_ID/cancel" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
Alternative using PATCH (sets cancelAtPeriodEnd):
|
||||
|
||||
```bash
|
||||
curl -X PATCH "$BASE/v1/payments/subscriptions/SUBSCRIPTION_ID" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{ "cancelAtPeriodEnd": true }'
|
||||
```
|
||||
|
||||
6c. Resume:
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/subscriptions/SUBSCRIPTION_ID/resume" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
### 7) Report usage (metered features)
|
||||
|
||||
Emit usage events (e.g., requests):
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/subscriptions/SUBSCRIPTION_ID/usage" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"featureId": "requests",
|
||||
"quantity": 2500
|
||||
}'
|
||||
```
|
||||
|
||||
Get usage summary:
|
||||
|
||||
```bash
|
||||
curl -X GET "$BASE/v1/payments/subscriptions/SUBSCRIPTION_ID/usage" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
List raw usage events:
|
||||
|
||||
```bash
|
||||
curl -X GET "$BASE/v1/payments/usage/events?subscriptionId=SUBSCRIPTION_ID&featureId=requests" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
Reconcile (aggregate summary + optional provider sync):
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/usage/reconcile" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
### 7.1) Optional: Update plan and feature definitions
|
||||
|
||||
Update plan metadata/pricing (only send fields you want to change):
|
||||
|
||||
```bash
|
||||
curl -X PUT "$BASE/v1/payments/plans/pro" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Pro Updated",
|
||||
"description": "Updated desc",
|
||||
"isDefault": false,
|
||||
"isFree": false,
|
||||
"pricing": [ { "amount": 2499, "currency": "usd", "interval": "month" } ]
|
||||
}'
|
||||
```
|
||||
|
||||
Delete plan:
|
||||
|
||||
```bash
|
||||
curl -X DELETE "$BASE/v1/payments/plans/pro" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
Update feature:
|
||||
|
||||
```bash
|
||||
curl -X PUT "$BASE/v1/payments/features/requests" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{ "name": "Requests (updated)", "description": "Updated" }'
|
||||
```
|
||||
|
||||
Delete feature:
|
||||
|
||||
```bash
|
||||
curl -X DELETE "$BASE/v1/payments/features/requests" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY"
|
||||
```
|
||||
|
||||
### 8) Webhooks (Stripe)
|
||||
|
||||
Endpoint:
|
||||
|
||||
```
|
||||
POST $BASE/v1/payments/webhooks/stripe/PROJECT_ID
|
||||
Headers:
|
||||
Content-Type: application/json
|
||||
Stripe-Signature: t=timestamp,v1=signature
|
||||
Body: (Stripe event payload, e.g., customer.subscription.updated)
|
||||
```
|
||||
|
||||
Example minimal payload (adjust to Stripe test fixtures):
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/webhooks/stripe/PROJECT_ID" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Stripe-Signature: t=1700000000,v1=REPLACE_WITH_VALID_SIGNATURE" \
|
||||
-d '{
|
||||
"type": "customer.subscription.updated",
|
||||
"data": { "object": { "id": "sub_123", "status": "active", "current_period_start": 1700000000, "current_period_end": 1702592000 } }
|
||||
}'
|
||||
```
|
||||
|
||||
Note: Use the `webhookSecret` returned in providers state to compute a valid Stripe signature for real tests.
|
||||
|
||||
### 9) Feature flag toggle (block writes when disabled)
|
||||
|
||||
Disable payments and verify write routes return 403:
|
||||
|
||||
```bash
|
||||
curl -X PUT "$BASE/v1/payments/providers" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{ "enabled": false, "providers": {} }'
|
||||
```
|
||||
|
||||
Then try, for example, creating a plan (should fail with 403):
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/v1/payments/plans" \
|
||||
-H "X-Appwrite-Project: PROJECT_ID" -H "X-Appwrite-Key: API_KEY" -H "Content-Type: application/json" \
|
||||
-d '{ "planId": "test", "name": "Test", "pricing": [] }'
|
||||
```
|
||||
|
||||
### 10) (Optional) Worker & scheduler
|
||||
|
||||
- Start usage sync worker: `php app/worker.php payments-usage-sync`
|
||||
- Periodic scheduler (enqueue usage sync for active projects): `php app/cli.php schedule-payments-usage`
|
||||
Reference in New Issue
Block a user