feat: add Paykit subscriptions - #685
Conversation
This comment has been minimized.
This comment has been minimized.
fee8601 to
01c4164
Compare
a8d71d0 to
059eb83
Compare
01c4164 to
bcf1d8a
Compare
059eb83 to
69a0746
Compare
69a0746 to
3a1bcd2
Compare
This comment was marked as resolved.
This comment was marked as resolved.
c3f2d8c to
281b865
Compare
3a1bcd2 to
cf4cea2
Compare
This comment was marked as resolved.
This comment was marked as resolved.
281b865 to
e66771b
Compare
d68a602 to
c274b0a
Compare
This comment was marked as resolved.
This comment was marked as resolved.
c274b0a to
e641682
Compare
This comment was marked as resolved.
This comment was marked as resolved.
There was a problem hiding this comment.
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 Paymentre-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 Duenotification 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
jvsena42
left a comment
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 Paymentre-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
There was a problem hiding this comment.
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.
- passed: an immediate-start proposal stays in one continuously presented sheet, the header changing in place from
Review & SubscribetoSubscriptiontoBitcoin Sent.
Test 1: Receive an immediate-start private subscription proposal and verify review → payment → success stays in one… (passed)
| Screenshot |
|---|
![]() |
![]() |
![]() |
![]() |
- passed: the initial-payment failure keeps the sheet presented with the first period still due, and
Retry Paymentre-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 |
|---|
![]() |
![]() |
- 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 |
|---|
![]() |
![]() |
![]() |
![]() |
- passed: a future-start proposal shows
Swipe To Subscribewithout& Payand 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 |
|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
- 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 |
|---|
![]() |
![]() |
![]() |
![]() |
- 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
left a comment
There was a problem hiding this comment.
I concur with the bot approval























































Description
Adds the payer side of Paykit subscriptions, building on the merged payment-proof work in #683.
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:
0.1.0-rc46Linked 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
Automated Checks
git diff --checkKnown 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 broaderBitkitTestsrun 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.