Skip to content

feat: add Paykit subscriptions - #685

Merged
ovitrif merged 22 commits into
masterfrom
codex/paykit-subscriptions
Sep 9, 2026
Merged

feat: add Paykit subscriptions#685
ovitrif merged 22 commits into
masterfrom
codex/paykit-subscriptions

Conversation

@ben-kaufman

@ben-kaufman ben-kaufman commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Description

Adds the payer side of Paykit subscriptions, building on the merged payment-proof work in #683.

  • Receives private subscription proposals and lets the payer review and confirm them.
  • Discloses the first billing period’s end date/time and full-period charge on Review, including short initial intervals.
  • Requires an immediately due first period to be paid before showing the subscribed success state; definitely failed payments remain due and can be retried, while uncertain submissions stay protected until their outcome is known.
  • Turns each unpaid billing period into the existing incoming payment-request flow, including automatic presentation, the bell queue, and the Payments tab.
  • Schedules local due-payment notifications and opens the corresponding request when the notification is selected.
  • Adds the Figma-aligned Subscriptions and Payments tabs, subscription detail/status views, cancellation, empty states, and fixed/open-ended summaries.
  • Shows a normalized Monthly Cost across all active subscriptions, independent of whether the current periods are already paid.
  • Associates payment proofs with their exact billing period so recurring periods remain independent while retaining private-only resolution and fresh Private Payment List requirements.
  • Uses the shared monetary text component for primary and secondary request amounts, with visible currency symbols in either display mode.
  • Keeps subscribe-and-pay inside one continuously presented sheet, including initial-payment failure and retry, without the modal dismissal/reappearance gap.
  • Retains the settled payment rail in completed request and subscription history so Lightning feedback is purple and on-chain feedback is orange, including success confetti.

This PR intentionally covers payer-side receipt only. Subscription creation, Discover, automatic payment without confirmation, and renewal are not included because they are outside this MVP or are not currently exposed by Paykit.

Dependencies:

Linked Issues/Tasks

Screenshot / Video

Final iOS and cross-platform proof recordings were completed locally; they are not attached to the PR.

QA Notes

Manual Tests

  • Receive an immediate-start private subscription proposal and verify review → payment → success stays in one continuously presented sheet.
  • Exercise initial-payment failure and retry and verify the sheet stays presented while the first period remains due.
  • Complete Lightning and on-chain subscription payments and verify purple/orange success confetti and matching completed-payment icons.
  • Receive a future-start proposal, trigger its due notification, and open the exact billing-period payment.
  • Dismiss and reopen a due request from the bell queue and Payments tab, then pay it with fresh private payment details.
  • Review active, expired, fixed-term, and open-ended subscription details and cancel an active subscription.

Automated Checks

  • 143 focused currency/formatting/confirmation/request/proof tests passed after the latest UI fixes, including the automatic/manual funding confirmation policy, on-chain pre-broadcast failure classification, and started-proof cleanup
  • Rendered and inspected both changed request amount headers in Bitcoin-primary and fiat-primary modes using the app’s dark appearance. Both units and symbols are visible; temporary rendering probes were removed.
  • E2E regtest Debug build for an arm64 iOS simulator
  • Exact build installed and cold-launched on an iOS simulator
  • SwiftFormat validation for all 13 previously changed Swift files and both files in the latest amount-header fix
  • Localization validation
  • git diff --check

Known limitation

Full recovery of orphaned Lightning submissions is tracked in LDK Node #116. A missing payment-history record is not proof of failure, so unknown started attempts remain protected and may block subscription cancellation. The latest submission-error fixes and first-period disclosure are covered by automated regressions. The earlier recordings/manual checks predate these review fixes; no new crash-injection E2E or UI recording was performed for this update.

Latest review follow-up (559e3008): an automatic first Lightning payment that changes to savings or hardware during warnings, authentication, or request preparation returns to the existing fee-details screen for a fresh swipe. The broader BitkitTests run was stopped because the existing address-type integration tests received HTTP 404 from the Blocktank regtest funding endpoint. The focused suite passed; the live timing reproduction was not rerun for this update.

@greptile-apps

This comment has been minimized.

Comment thread Bitkit/Services/PaykitPaymentProofService.swift
Comment thread Bitkit/Services/PaykitPaymentProofService.swift Outdated
@ben-kaufman
ben-kaufman force-pushed the codex/paykit-payment-proofs branch from fee8601 to 01c4164 Compare August 28, 2026 14:13
@ben-kaufman
ben-kaufman force-pushed the codex/paykit-subscriptions branch from a8d71d0 to 059eb83 Compare August 31, 2026 13:10
@ben-kaufman
ben-kaufman force-pushed the codex/paykit-payment-proofs branch from 01c4164 to bcf1d8a Compare August 31, 2026 14:25
@ben-kaufman
ben-kaufman force-pushed the codex/paykit-subscriptions branch from 059eb83 to 69a0746 Compare August 31, 2026 14:25
@ben-kaufman
ben-kaufman force-pushed the codex/paykit-subscriptions branch from 69a0746 to 3a1bcd2 Compare August 31, 2026 18:22
@ben-kaufman

This comment was marked as resolved.

@ben-kaufman
ben-kaufman force-pushed the codex/paykit-payment-proofs branch from c3f2d8c to 281b865 Compare August 31, 2026 18:30
@ben-kaufman
ben-kaufman force-pushed the codex/paykit-subscriptions branch from 3a1bcd2 to cf4cea2 Compare August 31, 2026 18:30
@ben-kaufman

This comment was marked as resolved.

@ben-kaufman ben-kaufman mentioned this pull request Aug 31, 2026
2 tasks
ovitrif

This comment was marked as resolved.

@ben-kaufman
ben-kaufman requested a review from ovitrif September 1, 2026 03:20
@ben-kaufman
ben-kaufman force-pushed the codex/paykit-payment-proofs branch from 281b865 to e66771b Compare September 1, 2026 12:44
@ben-kaufman
ben-kaufman force-pushed the codex/paykit-subscriptions branch 3 times, most recently from d68a602 to c274b0a Compare September 1, 2026 16:38
@ben-kaufman

This comment was marked as resolved.

ovitrif

This comment was marked as resolved.

@ben-kaufman
ben-kaufman force-pushed the codex/paykit-subscriptions branch from c274b0a to e641682 Compare September 1, 2026 17:18
@ben-kaufman

This comment was marked as resolved.

@ben-kaufman
ben-kaufman requested a review from ovitrif September 1, 2026 17:18
ovitrif

This comment was marked as resolved.

@ben-kaufman
ben-kaufman requested a review from ovitrif September 1, 2026 18:45
ovitrif

This comment was marked as resolved.

Base automatically changed from codex/paykit-payment-proofs to master September 2, 2026 10:57
ovitrif

This comment was marked as resolved.

@ben-kaufman
ben-kaufman requested a review from ovitrif September 6, 2026 15:18
jvsena42

This comment was marked as resolved.

@ben-kaufman
ben-kaufman requested a review from jvsena42 September 7, 2026 17:26
jvsena42

This comment was marked as resolved.

@ben-kaufman
ben-kaufman requested a review from jvsena42 September 7, 2026 18:11
jvsena42

This comment was marked as resolved.

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QA Notes

Ran the six Manual Tests on an iPhone 17 simulator (iOS 26.5) paired with a second iPhone 17 simulator as the Paykit counterparty, both on regtest, from a build of this head.

  • Immediate-start proposal stays in one continuously presented sheet from review through payment to the subscribed state.
  • Initial-payment failure keeps the sheet presented with the first period still due, and Retry Payment re-enters the flow in place.
  • Lightning and on-chain subscription payments both settle with the matching purple and orange success treatment.
  • A future-start proposal correctly takes nothing on acceptance, schedules its due notifications on the billing boundaries, and tapping the delivered Subscription Payment Due notification opens exactly that billing period's payment, which then completes.
  • A due request dismissed from the failure sheet reopens from both the bell queue and the Payments tab and pays with fresh private payment details.
  • Active, expired, fixed-term and open-ended subscription details all render, and cancelling an active subscription works.

Both findings from the previous review are fixed. The uncertain on-chain branch no longer passes a Paykit request id as a payment hash, so the pending screen stops polling for an id that could never match, and the recipient row without a saved contact now renders the truncated key once instead of twice.

Approve.


Reviewed by Claude Code (claude-opus-5 high) via gh-pr-review-loop skill

ovitrif

This comment was marked as resolved.

@ovitrif ovitrif added this to the 2.6.0 milestone Sep 8, 2026

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: one accessibility-identifier nit from a cross-platform run of this branch against bitkit-android#codex/paykit-subscriptions-android on a simulator + emulator side by side.

Everything else lined up: drawer entry, Overview/Payments tabs and both empty states are identical to Android, and cross-platform Lightning payments succeeded in both directions (Android → iOS 1,000 sats, iOS → Android 500 sats) through the Send confirm / Receive QR changes in this PR.

Comment thread Bitkit/Views/Subscriptions/SubscriptionsView.swift

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QA Notes

Ran all six Manual Tests on an iPhone 17 simulator (iOS 26.5) paired with a second iPhone 17 simulator as the Paykit counterparty, both on regtest, from a build of this head.

  • Immediate-start proposal stays in one continuously presented sheet from review through payment to the subscribed state.
  • Initial-payment failure keeps the sheet presented with the first period still due, and Retry Payment re-enters the flow in place.
  • Lightning and on-chain subscription payments settle with purple and orange confetti respectively, and the completed-payment icons match.
  • A future-start proposal takes nothing on acceptance, and tapping its delivered due notification opens exactly that billing period, which then pays.
  • A dismissed due request reopens from both the bell queue and the Payments tab and pays with freshly published private payment details.
  • Active, expired, fixed-term and open-ended subscription details all render correctly, and cancelling an active subscription works.

The currency change in this head shows up as intended: the payment request and subscription screens now render the secondary unit above the primary amount with both symbols, for example $ 3.33 above ₿ 5 000 on the subscription review sheet and $ 1.00 above - ₿ 1500 on the payment request detail.

Approve.

Test 1: Receive an immediate-start private subscription proposal and verify review → payment → success stays in one… (passed)
Screenshot
Test 2: Exercise initial-payment failure and retry and verify the sheet stays presented while the first period… (passed)
Screenshot
Test 3: Complete Lightning and on-chain subscription payments and verify purple/orange success confetti and matching… (passed)
Screenshot
Test 4: Receive a future-start proposal, trigger its due notification, and open the exact billing-period payment. (passed)
Screenshot
Test 5: Dismiss and reopen a due request from the bell queue and Payments tab, then pay it with fresh private payment… (passed)
Screenshot
Test 6: Review active, expired, fixed-term, and open-ended subscription details and cancel an active subscription. (passed)
Screenshot

Coverage

Total: 67%

  • Journeys: 100% - All six Manual Tests in the PR body were driven on two simulators against the local regtest stack.
  • Unit tests: 0% - This head's currency-display change carries no accompanying unit test.
  • QA: 100% - Six of six Manual Tests passed on a binary proven to be this head.


Reviewed by Claude Code (claude-opus-5 high) via gh-pr-review-loop skill

ovitrif

This comment was marked as resolved.

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QA Notes

Ran all six Manual Tests on an iPhone 17 simulator (iOS 26.5) paired with a second iPhone 17 simulator as the Paykit counterparty, both on regtest, from a build of this head.

  1. passed: an immediate-start proposal stays in one continuously presented sheet, the header changing in place from Review & Subscribe to Subscription to Bitcoin Sent.
Test 1: Receive an immediate-start private subscription proposal and verify review → payment → success stays in one… (passed)
Screenshot
  1. passed: the initial-payment failure keeps the sheet presented with the first period still due, and Retry Payment re-enters the flow in place.
Test 2: Exercise initial-payment failure and retry and verify the sheet stays presented while the first period… (passed)
Screenshot
  1. passed: the Lightning payment settles with purple confetti and a purple completed-payment icon, the on-chain one with orange.
Test 3: Complete Lightning and on-chain subscription payments and verify purple/orange success confetti and matching… (passed)
Screenshot
  1. passed: a future-start proposal shows Swipe To Subscribe without & Pay and charges nothing, and opening its delivered due notification lands on exactly that billing period.
Test 4: Receive a future-start proposal, trigger its due notification, and open the exact billing-period payment. (passed)
Screenshot
  1. passed: a dismissed request reopens from both the bell queue and the Payments tab and pays with freshly published private payment details.
Test 5: Dismiss and reopen a due request from the bell queue and Payments tab, then pay it with fresh private payment… (passed)
Screenshot
  1. passed: active, expired, fixed-term and open-ended details all render correctly, and cancelling an active subscription propagates to the payee.
Test 6: Review active, expired, fixed-term, and open-ended subscription details and cancel an active subscription. (passed)
Screenshot

The container identifiers this head adds behave as intended: .accessibilityElement(children: .contain) keeps the children individually addressable, so the bell sheet still exposes PaymentRequestsSeeAll, the per-request rows and their Dismiss and Pay actions as separate elements. The earlier currency work is intact too, with the secondary unit above the primary amount and both symbols rendered.

Approve.

Coverage

Total: 100%

  • Journeys: 100% - All six Manual Tests were driven on two simulators against the local regtest stack.
  • Unit tests: 100% - The merged master work carries its own tests and this delta authors no production code.
  • QA: 100% - Six of six Manual Tests passed on a binary proven to be this head.


Reviewed by Claude Code (claude-opus-5 high) via gh-pr-review-loop skill

@ovitrif
ovitrif enabled auto-merge September 9, 2026 07:27

@ovitrif ovitrif left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I concur with the bot approval

@ovitrif
ovitrif merged commit 692930a into master Sep 9, 2026
11 checks passed
@ovitrif
ovitrif deleted the codex/paykit-subscriptions branch September 9, 2026 08:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants