Skip to content

feat: add Paykit subscriptions - #685

Open
ben-kaufman wants to merge 20 commits into
masterfrom
codex/paykit-subscriptions
Open

feat: add Paykit subscriptions#685
ben-kaufman wants to merge 20 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
@jvsena42

jvsena42 commented Sep 4, 2026

Copy link
Copy Markdown
Member

@ben-kaufman conflicts

@ben-kaufman
ben-kaufman force-pushed the codex/paykit-subscriptions branch from 4656cef to 9136de4 Compare September 4, 2026 16:32
jvsena42

This comment was marked as resolved.

This comment was marked as resolved.

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[bot]

This comment was marked as resolved.

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.

.padding(.horizontal, 16)
.background(Color.black)
.navigationBarHidden(true)
.accessibilityIdentifier("SubscriptionsScreen")

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: this container identifier isn't paired with .accessibilityElement(children: .contain), so it masks its children's identifiers.

Confirmed on a booted simulator — every element on the Subscriptions screen reports SubscriptionsScreen, so Tab-overview, Tab-payments, NavigationBack and HeaderMenu are all unaddressable. Android exposes all four on the same screen, and SegmentedControl.swift:71 does set the tab identifiers here; they just don't survive the container.

AGENTS.md asks for the pairing ("PAIR a container identifier with .accessibilityElement(children: .contain) so it is queryable"), and the rest of the codebase follows it (HardwareWalletsSettingsScreen.swift:42, BTCPayConnectionSheet.swift:100, ElectrumSettingsScreen.swift:46).

Same missing pairing, if you're touching these anyway: PaymentRequestsView.swift:230,334,531 and CreatePaymentRequestView.swift:105.

@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 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.

manual approve for merge

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