Unified saved payment methods (NWC + Revolut) with automatic renewal#160
Merged
Conversation
Adds Revolut merchant-initiated (off-session) automatic renewals alongside the existing Lightning/NWC path. When a customer completes a Revolut checkout while auto-renewal is enabled, the card is saved (savePaymentMethodFor=merchant) and its opaque token references + non-sensitive card metadata (brand/last4/expiry) are captured on webhook completion into a new provider-agnostic user_payment_method table. The worker then charges the default saved method off-session when a subscription is expiring. - DB: user_payment_method table (provider-agnostic, default/enabled/expiry) + model, mysql + mock impls, migration - Capture: RevolutPaymentHandler fetches the merchant-capable saved method + card details from the customer endpoint, dedupes, first method becomes default - Charge: SubscriptionHandler::auto_renew_via_revolut selects the default enabled, non-expired method and charges via charge_subscription - Worker: Revolut fallback branch after NWC in handle_subscription_state (behind the revolut feature) - API: read-only revolut_payment_method_saved on GET /api/v1/account - Bumps payments-rs to include the Revolut saved-method fixes - examples/revolut_sandbox.rs + revolut_widget.html: sandbox test harness - ignored integration test validating the off-session charge end-to-end Only opaque provider token references are stored (encrypted at rest); no card PAN/CVV ever touches the DB. Fixes #159
…selection
Reworks saved payment methods into a single user-selectable model:
- NWC (Nostr Wallet Connect) is now a payment method too. Existing
users.nwc_connection_string values are migrated into user_payment_method
(provider='nwc') and the column is dropped.
- external_customer_id is nullable (NWC has none).
- Auto-renewal now charges the user's DEFAULT enabled method, dispatched by
provider (NWC Lightning wallet or Revolut card off-session). The worker's
separate NWC/Revolut branches are replaced by a single auto_renew() call.
- New user-facing payment-methods API:
- GET /api/v1/payment-methods list (masked; no tokens/NWC strings)
- POST /api/v1/payment-methods add an NWC connection (validated)
- PATCH /api/v1/payment-methods/{id} set default / enable-disable
- DELETE /api/v1/payment-methods/{id} remove
- Removes nwc_connection_string and revolut_payment_method_saved from the
account object; NWC is managed via the payment-methods API. Admin has_nwc is
computed via a subquery.
Adds offline unit tests (default selection, provider dispatch, mock
FiatPaymentService charge, NWC/card CRUD) plus UserPaymentMethod::is_expired.
Adds a nullable name column to user_payment_method so users can label multiple methods (e.g. 'Personal Visa', 'Backup wallet'). Exposed on GET /api/v1/payment-methods, settable via POST (name?) and PATCH (name). Refs #85
Contributor
Author
|
Also added an optional user-defined name on saved payment methods so users can distinguish multiple methods (e.g. "Personal Visa", "Backup wallet"). Refs #85 (payment method config). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #159
Summary
Reworks automatic renewal around a single, user-selectable saved payment methods model, and adds Revolut off-session (merchant-initiated) card renewals.
NWC and Revolut are now both modelled as payment methods in one provider-agnostic table, so a user can keep several methods (e.g. a Lightning wallet and a card) and choose which is the default. Auto-renewal charges the default method, dispatched by provider: NWC pays a Lightning invoice; Revolut charges the saved card off-session.
Changes
user_payment_methodtable (provider-agnostic;is_default/enabled;card_brand/card_last_four/exp_month/exp_year; nullableexternal_customer_id). Migration backfills existingusers.nwc_connection_stringinto the table (providernwc) and drops the column. Adminhas_nwcis now a computed subquery.payments/revolut.rs): on webhook completion, fetches the merchant-capable saved method + card details from the Revolut customer endpoint, dedupes, first method becomes default.subscription/mod.rs):auto_renew(sub_id)picks the user's default enabled, non-expired method and dispatches toauto_renew_via_nwc/auto_renew_via_revolut. Worker's separate branches collapsed into one.GET /api/v1/payment-methods— list (masked: no tokens / NWC strings)POST /api/v1/payment-methods— add an NWC connection (validated forpay_invoice)PATCH /api/v1/payment-methods/{id}— set default / enable-disableDELETE /api/v1/payment-methods/{id}— removenwc_connection_string(and the interimrevolut_payment_method_saved) removed from the account object; NWC is managed via the payment-methods API now.f4456be(nestedcustomer.id,get_customer_payment_methods, saved card details, off-session charge).Compliance
Only opaque provider token references are stored (encrypted at rest) plus PCI-safe card metadata (brand/last4/expiry). No PAN/CVV. GDPR token scrub on account deletion is a tracked follow-up (#159).
Testing
FiatPaymentService, NWC/card CRUD,is_expired.#[ignore]) against the Revolut sandbox: widget save →get_customer_payment_methods→ off-session merchant-initiated charge → completed, captured on the saved card.revolut) and unit tests pass; clippy clean.Frontend
Companion: LNVPS/web#16 adds
savePaymentMethodFor: 'merchant'to the card widget. Follow-up needed in web: move the NWC settings UI from the account form to the new payment-methods API (the accountnwc_connection_stringfield is gone).