Subscription module: add gateway capability flow and UX fixes
This commit is contained in:
166
docs/SUBSCRIPTION_GATEWAY_CAPABILITIES.md
Normal file
166
docs/SUBSCRIPTION_GATEWAY_CAPABILITIES.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# Subscription Gateway Capabilities
|
||||
|
||||
> How WooNooW decides which payment gateways can auto-debit subscription
|
||||
> renewals, and how merchants can override that decision.
|
||||
|
||||
## Why this exists
|
||||
|
||||
Before this system, a subscription renewal would attempt to call
|
||||
`$gateway->process_subscription_renewal_payment($order, $subscription)` if
|
||||
the gateway *happened* to implement that method. That had three problems:
|
||||
|
||||
1. **Capability was invisible** — the merchant had no way to see, declare,
|
||||
or override which gateways supported subscription auto-renew.
|
||||
2. **The default was unsafe** — a gateway without the method silently fell
|
||||
through to manual payment. The system "worked," but the merchant
|
||||
believed auto-debit was happening and customers were surprised when
|
||||
they had to log in and pay manually.
|
||||
3. **No override was possible** — a merchant running a custom Stripe
|
||||
wrapper that *does* support auto-debit could not declare it, and a
|
||||
merchant using stock Stripe could not opt out.
|
||||
|
||||
## What it is now
|
||||
|
||||
A **per-gateway capability table** that the merchant (or a WooNooW
|
||||
defaults policy) controls explicitly. The system consults the table at
|
||||
renewal time and decides whether to attempt auto-debit or fall through
|
||||
to manual. PHP method existence alone is no longer authoritative.
|
||||
|
||||
### Storage
|
||||
|
||||
```
|
||||
wp_option('woonoow_gateway_subscription_capabilities', [
|
||||
'<gateway_id>' => [ 'subscription_auto_renew' => bool ],
|
||||
...
|
||||
])
|
||||
```
|
||||
|
||||
### Decision flow
|
||||
|
||||
For a renewal where the subscription's stored `payment_method` is
|
||||
`<gateway_id>`:
|
||||
|
||||
1. If the site-level `force_manual_renewal` setting is on, fall through
|
||||
to manual. (Kill switch — see below.)
|
||||
2. Look up `<gateway_id>` in the merged capability map (defaults <
|
||||
stored overrides < `woonoow_gateway_subscription_capabilities` filter).
|
||||
3. If the lookup returns `subscription_auto_renew = true`, attempt
|
||||
auto-debit via the gateway's `process_subscription_renewal_payment`
|
||||
method. On success, run `handle_renewal_success`. On failure, fall
|
||||
through to manual and notify the customer.
|
||||
4. If the lookup is missing or false, skip auto-debit entirely, create a
|
||||
manual renewal order, and send the `renewal_payment_due` email.
|
||||
|
||||
The decision is made by
|
||||
`WooNooW\Modules\Subscription\GatewayCapabilities::should_attempt_auto_renew($gateway_id)`.
|
||||
|
||||
## Built-in defaults
|
||||
|
||||
| Gateway ID | Default auto-renew | Why |
|
||||
|---------------------|--------------------|-----|
|
||||
| `paypal` | true | PayPal Reference Transactions supports recurring |
|
||||
| `stripe` | true | With a WooNooW Stripe adapter implementing the contract |
|
||||
| `stripe_cc` | true | Alias for stripe credit card |
|
||||
| `stripe_sepa` | true | SEPA Direct Debit supports recurring |
|
||||
| `dodo` | true | Dodo Payments supports recurring subscriptions |
|
||||
| `tripay` | false | VA/QRIS/e-wallet — no recurring |
|
||||
| `midtrans` | false | VA/QRIS/e-wallet — no recurring |
|
||||
| `xendit` | false | Indonesian credit card requires customer re-auth (BI/PCI-DSS) |
|
||||
| `doku` | false | Indonesian manual-only |
|
||||
| `duitku` | false | Indonesian manual-only |
|
||||
| `cheque`, `bacs`, `cod` | false | Offline / no auto-debit |
|
||||
| **any unknown** | false | Safe default |
|
||||
|
||||
The default for any unknown gateway is `false`. A merchant who has a
|
||||
custom adapter for an unknown gateway can flip the toggle in the admin
|
||||
UI (Settings → Modules → Subscription → Gateway Auto-Renew Capabilities).
|
||||
|
||||
## Why per-gateway, not site-level "billing mode"
|
||||
|
||||
A site-level "manual vs auto" toggle asks the merchant to understand a
|
||||
concept that does not exist in their head. The merchant thinks in
|
||||
**payment gateways**. A checkbox next to each gateway in the admin is
|
||||
data the merchant already knows.
|
||||
|
||||
Additionally:
|
||||
|
||||
- Different merchants use different gateways. A site-level toggle forces
|
||||
a single behavior even when the merchant runs two gateways (one
|
||||
auto-capable, one not) for different products.
|
||||
- The capability is a property of the **integration**, not of the
|
||||
**store**. The merchant did not choose "manual mode" — they chose
|
||||
Tripay, and Tripay is a manual gateway.
|
||||
- The capability can change as WooNooW ships new adapters. A
|
||||
per-gateway table updates as adapters ship.
|
||||
|
||||
## Site-level kill switch
|
||||
|
||||
There is one site-level override:
|
||||
|
||||
- `force_manual_renewal` (default **off**) — when on, all renewals are
|
||||
manual regardless of the per-gateway capability table. Useful as a
|
||||
kill switch during an incident or regulatory change.
|
||||
|
||||
This lives in the standard module settings form (Settings → Modules →
|
||||
Subscription) and is not on the gateway capability matrix screen.
|
||||
|
||||
## Admin UI
|
||||
|
||||
`Settings → Modules → Subscription` now has two sections:
|
||||
|
||||
1. **Configuration** — the standard 12-field schema (button text,
|
||||
pause/cancel permissions, retry policy, kill switch, etc.). Driven
|
||||
by the existing `SubscriptionSettings` schema.
|
||||
2. **Gateway Auto-Renew Capabilities** — one row per WooCommerce
|
||||
payment gateway with a per-gateway toggle. Built dynamically from
|
||||
`WC()->payment_gateways()`. The merchant can flip a gateway on or
|
||||
off, and the change is persisted via
|
||||
`POST /woonoow/v1/subscriptions/gateway-capabilities`.
|
||||
|
||||
When the kill switch is on, every row shows a "Forced manual" badge and
|
||||
the per-gateway toggles are disabled.
|
||||
|
||||
## Customer messaging
|
||||
|
||||
The order-pay response (`/checkout/order/{id}`) and the subscription
|
||||
detail response both include `gateway_supports_auto_renew`. The
|
||||
customer-spa OrderPay page renders a different callout for manual
|
||||
gateways (amber) versus auto-renew gateways (blue):
|
||||
|
||||
- **Auto-renew:** "Your subscription will renew automatically on the
|
||||
date shown below."
|
||||
- **Manual:** "Your saved payment method cannot be charged
|
||||
automatically for this gateway, so please complete the payment to
|
||||
continue your subscription."
|
||||
|
||||
This ensures the customer is never promised auto-debit that the system
|
||||
will not deliver.
|
||||
|
||||
## Extending the table (for gateway adapter authors)
|
||||
|
||||
A gateway adapter or third-party plugin can extend the capability
|
||||
table at boot time via the
|
||||
`woonoow_gateway_subscription_capabilities` filter:
|
||||
|
||||
```php
|
||||
add_filter('woonoow_gateway_subscription_capabilities', function ($caps) {
|
||||
$caps['my_custom_stripe'] = ['subscription_auto_renew' => true];
|
||||
return $caps;
|
||||
});
|
||||
```
|
||||
|
||||
The adapter is then responsible for implementing
|
||||
`process_subscription_renewal_payment(WC_Order $order, $subscription)`
|
||||
on its gateway class. If the method does not exist, the capability
|
||||
declaration is meaningless — the renewal will fall through to manual.
|
||||
|
||||
## Migration / no migration
|
||||
|
||||
This is a behavioral improvement, not a schema change. Existing
|
||||
subscriptions keep their `payment_method` value. The capability table
|
||||
is consulted at renewal time, not retroactively.
|
||||
|
||||
If a merchant upgrades WooNooW and previously relied on PHP method
|
||||
existence alone, the renewal will continue to work — but the merchant
|
||||
will now see the capability matrix and can confirm or override each
|
||||
gateway.
|
||||
Reference in New Issue
Block a user