Skip to content

feat: add allowances for automatic paykit payments - #1340

Draft
ovitrif wants to merge 13 commits into
masterfrom
feat/paykit-allowances
Draft

ovitrif wants to merge 13 commits into
masterfrom
feat/paykit-allowances

Conversation

@ovitrif

@ovitrif ovitrif commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Depends on pubky/paykit-rs#161 (open; head 8b893f0, the top of the #158 → #159 → #160 → #161 stack). The Paykit bindings are built from that head and resolved only from a Maven directory beside the checkout, so CI cannot build this branch until #161 is published. The 0.1.0-rc55 coordinates are unchanged on purpose. To build it, publish the Kotlin bindings of that head as com.synonym:paykit-android:0.1.0-rc55 into ../maven (paykit-rs paykit-ffi/bindings/android, publishToMavenLocal with -Dmaven.repo.local pointing there).

iOS port: synonymdev/bitkit-ios#799

This PR adds allowances: a payer sets a per-payment and a monthly limit for a Paykit contact, and that contact's requests within the limits are paid without asking.

Description

  • Adds an Allowances tab to Subscriptions, with an empty state, the list and a Set Allowance sheet with per-payment and monthly USD limits, so a payer can let a contact's requests pay themselves
  • Sends the allowance over the existing private Paykit link and opens a review sheet on the receiver, who accepts or declines; either side can end an allowance from its detail sheet
  • Pays an incoming request within the limits automatically over Lightning: the request is reserved in the SDK's allowance accounting before the send and reported back afterwards, so a kill and relaunch mid-payment never pays twice
  • Leaves a request above the per-payment limit, over the monthly cap, or after an end as an ordinary Payment Request, with a Limit Reached notification for the payer and a Payment Executed notification for an automatic one
  • Tags automatically paid rows "Auto-paid" in the Payments tab and shows the amount paid automatically on the allowance detail
  • Keeps Paykit state written by rc55 readable: the stored blob is probed on first launch and converted between the rc55 and allowance layouts, so an upgrade or a downgrade keeps the profile, contacts and requests
  • Waits for the payee's next private payment list instead of asking the payer when the previous list was used up, and holds automatic payments until the node has a usable channel, because both cases fell back to manual on regtest
  • Lets an explicit E2E homegate URL win on every backend, a dev-only change used to run the branch against a local Pubky homegate

Out of Scope

  • Allowance detail on the payee: "Paid automatically" counts only the payer's own journal, so the payee sees $0.00; SDK follow-up
  • Automatic payments: Lightning only, with no capacity check before choosing bolt11 and no on-chain fallback, so a payer without a usable channel falls back to a manual request
  • Failed automatic send: the request is already accepted, so it falls back to a manual Payment Request and is not retried automatically
  • Paykit dependency: stays on the local build of Expose Allowance accounting to Swift and Kotlin pubky/paykit-rs#161 until it is published

Design

Figma "Bitkit - Experimental New": Allowances empty state, Set Allowance, Allowances list. The receiver's review sheet and the detail sheet have no frame and follow the drawn film. Deliberate differences: the first tab stays "Overview", the list row's right column reads the monthly limit, and the copy typos are fixed.

Preview

Allowances empty Set Allowance Review on the payee Detail
Recording
side-by-side.mp4

QA Notes

Journeys

  • new set-and-accept.xml — the offer opens on the payee by itself and both rows turn Active
  • new auto-pay-under-limit.xml — a $2 request pays itself with only a Payment Executed notification
  • new above-limit-asks.xml — a $20 request arrives as an ordinary Payment Request
  • new monthly-cap-reached.xml — the third $4 request on a $10 cap raises Limit Reached and asks
  • new end-stops-auto-pay.xml — after either side ends it, the next request asks
  • new restart-never-pays-twice.xml — a kill right after the hand-off never leads to a second payment

Manual Tests

  • regression: install the rc55 build, create a profile and a contact, then install this build → profile and contact intact, log reads "Resolved the stored Paykit state layout as 'RC55'" — an rc55 build to install first not in Capabilities

Automated Checks

  • added PaykitAllowanceTest.kt — limits, terms, capacity and month anchoring of the allowance model
  • added PaykitAllowanceExecutorTest.kt — reserve, hand-off, outcome recording and restart recovery of automatic payments
  • added PaykitAllowanceRepoTest.kt — admission decisions: automatic, manual, deferred and limit reached
  • added PrivatePaykitAllowancePaymentTest.kt — resolving the payee's private endpoint for an automatic payment
  • added PaykitSdkStateLayoutTest.kt — conversion of the stored Paykit state between the rc55 and allowance layouts
  • added AllowancesViewModelTest.kt — list, set, review and detail state
  • updated AppViewModelSendFlowTest.kt — the send flow with the allowance repo wired in
  • updated PaykitPaymentProofRepoTest.kt — payment proofs carry the allowance id
  • ran ./gradlew --offline assembleDevDebug testDevDebugUnitTest detekt against the local Paykit build — 2,993 unit tests pass and detekt is clean; CI cannot resolve the Paykit build until Expose Allowance accounting to Swift and Kotlin pubky/paykit-rs#161 is published

@ovitrif ovitrif self-assigned this Sep 24, 2026
@@ -0,0 +1,522 @@
package to.bitkit.repositories

import org.mockito.kotlin.doReturn

This branch has not been deployed

No deployments
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.

2 participants