@pooriaarab/foxpay 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pooria Arab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,373 @@
1
- # Temporary Holding Version
1
+ # foxpay
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <p align="center">Agent payments with spend caps and approval for each payment.</p>
4
+
5
+ <p align="center">
6
+ <a href="https://github.com/pooriaarab/foxpay/actions"><img src="https://github.com/pooriaarab/foxpay/actions/workflows/ci.yml/badge.svg" alt="CI"/></a>
7
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="License MIT"/></a>
8
+ </p>
9
+
10
+ foxpay lets an AI agent ask to pay, and lets a human approve each payment.
11
+ The agent writes an intent: the merchant, the amount, the currency, and the
12
+ reason. foxpay reads the real amount itself, from the merchant page or from
13
+ the `402 Payment Required` answer. Then it asks [foxgate](https://github.com/pooriaarab/foxgate),
14
+ which checks the spend cap and waits for a human. After one approval, foxpay
15
+ pays one time with a card from [foxvault](https://github.com/pooriaarab/foxvault)
16
+ or with an x402 payment on Base Sepolia. Each payment gets a receipt.
17
+
18
+ No real money moves in this repo. The tests and the demo use test cards, a
19
+ fake card provider, the Base Sepolia testnet, and a local fake facilitator.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ npm i @pooriaarab/foxpay
25
+ ```
26
+
27
+ The npm package is `@pooriaarab/foxpay`: npm refuses the plain name `foxpay` as too similar to an existing package (fox-pay).
28
+
29
+ ## Example
30
+
31
+ This example pays a fake x402 API in Node. It runs as written.
32
+
33
+ ```js
34
+ import { createFoxgate, memoryStore } from "foxgate";
35
+ import { createVault } from "foxvault";
36
+ import { PAY_TOOL, createFoxpay, payTools, x402 } from "@pooriaarab/foxpay";
37
+ import { fakeX402Api } from "@pooriaarab/foxpay/testing";
38
+
39
+ // A Base Sepolia test key and recipient. The fake API stands in for a paid API.
40
+ const WALLET_KEY = "0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318";
41
+ const PAY_TO = "0x209693Bc6afc0C5328bA36FaF03C514EF312287C";
42
+
43
+ const store = memoryStore();
44
+ const { gate, host } = createFoxgate({ tools: payTools(), store });
45
+ // A cap of 0.05 test USDC. foxgate counts test USDC as XTS, in atomic units.
46
+ await host.addGrant({ scope: "pay", domains: ["api.example"], tools: [PAY_TOOL], spendCap: { value: 50_000, currency: "XTS" } });
47
+
48
+ const vault = createVault();
49
+ await vault.initialize();
50
+ await vault.set("vault:wallet", WALLET_KEY, { domains: ["api.example"] });
51
+
52
+ const api = fakeX402Api({ host: "api.example", payTo: PAY_TO, price: 10_000, body: "sunny" });
53
+ const method = x402({ vault, wallet: "vault:wallet", payTo: { "api.example": PAY_TO }, fetch: api.fetch });
54
+ const pay = createFoxpay({ gate, store, methods: { x402: method } });
55
+
56
+ const asked = await pay.request({
57
+ merchant: "api.example",
58
+ amount: 10_000, // 0.01 test USDC
59
+ currency: "USDC",
60
+ reason: "Weather data, one call",
61
+ method: "x402",
62
+ idempotencyKey: "call-0001",
63
+ target: { url: "https://api.example/weather" },
64
+ });
65
+ console.log(asked.status); // ask
66
+
67
+ const [request] = await host.pending();
68
+ console.log(request.text); // the exact action that the human approves
69
+
70
+ const done = await pay.complete(asked.id, await host.approve(asked.requestId));
71
+ console.log(done.status, done.body); // paid sunny
72
+ const again = await pay.request({ merchant: "api.example", amount: 10_000, currency: "USDC", reason: "Weather data, one call", method: "x402", idempotencyKey: "call-0001", target: { url: "https://api.example/weather" } });
73
+ console.log(again.status, api.settled.length); // paid 1
74
+ ```
75
+
76
+ ## Use cases
77
+
78
+ | Who | What they build | How foxpay helps |
79
+ |---|---|---|
80
+ | A browser agent author (for example foxmate) | An agent that buys things for the user in a budget | The planner proposes a payment. foxpay reads the total from the checkout page, foxgate refuses anything over the cap, and the user approves the exact amount before the card is filled. |
81
+ | A developer of AI tools that call paid APIs | A tool that pays for each API call with x402 | Each `402` answer becomes one intent. foxpay checks the network, the token, and the recipient, then signs one payment with a key that stays in foxvault. |
82
+ | A person with SaaS subscriptions | An agent that renews tools within a monthly limit | The host adds a foxgate grant with a `spendCap` and an `expiresAt` at the end of the month, then adds a new grant for the next month. foxpay refuses a renewal that passes the cap. |
83
+ | A caregiver or a parent | Purchases that a family member or an assistant starts | The person who holds the budget approves each payment in the popup. A virtual card provider can make a single-use card with the approved limit for the one merchant. |
84
+ | A QA engineer | Automated tests of checkout flows on staging | Run real checkouts with test cards and the fake card provider. A retry with the same idempotency key never submits a second order. |
85
+ | An MCP server author | A `pay` tool that an outside agent can call | The server calls `request` and returns the waiting request to the client. Only `complete` with a human's token pays. |
86
+
87
+ ## How it works
88
+
89
+ ```mermaid
90
+ flowchart TD
91
+ P[Planner intent: merchant, amount, currency, reason, method] --> V{Valid intent?}
92
+ V -- no --> R1[refused: bad-intent]
93
+ V -- yes --> Q[Method quote: the host reads the amount and the payee]
94
+ Q -- different amount or currency --> R2[refused: amount-mismatch or currency-mismatch]
95
+ Q --> G{foxgate check: pay action}
96
+ G -- over the cap, wrong currency, no grant --> R3[refused: spend-cap, currency, no-grant]
97
+ G -- ask --> H[Human sees merchant, amount, currency, reason, payee]
98
+ H -- reject --> R4[refused: rejected]
99
+ H -- approve: token --> C{Method check: same page, same total, vault open?}
100
+ C -- no --> R5[refused before the token is used]
101
+ C -- yes --> T{foxgate redeem: one time}
102
+ T --> M[Method pays: card fill or x402]
103
+ M --> RC[Receipt: paid, submitted, unsettled, or failed]
104
+ RC --> E[onEvent: foxtrail-compatible entry]
105
+ ```
106
+
107
+ 1. `request` checks the intent. The idempotency key binds it: a retry with
108
+ the same key gets the same request, or the same receipt after payment.
109
+ 2. The method makes a quote on the host side. Card fill reads the total from
110
+ the top document of the merchant tab. x402 reads the `PAYMENT-REQUIRED`
111
+ header. When the planner amount or currency is different, foxpay refuses.
112
+ It never converts currencies.
113
+ 3. foxpay builds a foxgate `pay` action from the quote. For x402, the action
114
+ also names the URL and the `402` resource URL in `details`. The `amount` function
115
+ that `payTools()` registers reads the quoted amount, so the planner cannot
116
+ set it. foxgate refuses an amount over the cap before it asks anyone.
117
+ 4. The human approves the exact action. foxgate signs a one-time token.
118
+ 5. `complete` runs the method check first: the same page, the same total,
119
+ and an open vault. Then foxgate uses the token and counts the spend.
120
+ 6. The method pays one time. An intent that was paying when the app stopped
121
+ is never paid again by foxpay. Its outcome is `outcome-unknown`.
122
+
123
+ ```mermaid
124
+ sequenceDiagram
125
+ participant A as Agent
126
+ participant F as foxpay
127
+ participant S as Paid API
128
+ participant G as foxgate
129
+ participant U as Human
130
+ participant V as foxvault
131
+ A->>F: request(intent with target.url)
132
+ F->>S: GET url
133
+ S-->>F: 402, PAYMENT-REQUIRED (Base Sepolia, USDC, payTo, amount)
134
+ F->>F: Check network, asset, recipient, token domain, amount
135
+ F->>G: check(pay action)
136
+ G-->>U: Approval request
137
+ U->>G: Approve
138
+ G-->>F: One-time token
139
+ F->>G: redeem(token)
140
+ F->>V: use(wallet handle): sign EIP-3009 authorization, new 32-byte nonce
141
+ F->>S: GET url, PAYMENT-SIGNATURE (one time)
142
+ S-->>F: 200, PAYMENT-RESPONSE (success, transaction)
143
+ F-->>A: Receipt paid, or unsettled when there is no settlement
144
+ ```
145
+
146
+ Every failure mode has a test: see [docs/failure-modes.md](docs/failure-modes.md).
147
+
148
+ ### The x402 signature
149
+
150
+ foxpay signs EIP-3009 `TransferWithAuthorization` with EIP-712, for the x402
151
+ `exact` scheme on EVM. [`src/eip712.ts`](src/eip712.ts) has about 120 lines
152
+ for this one struct. It uses [@noble/curves](https://github.com/paulmillr/noble-curves)
153
+ and [@noble/hashes](https://github.com/paulmillr/noble-hashes) (MIT,
154
+ audited). The signature domain is `USDC`, version `2`, chain 84532, and the
155
+ Base Sepolia USDC contract. The tests compare the digest and the signature
156
+ with [viem](https://viem.sh), which is a dev dependency only. The official
157
+ `@x402/evm` client (Apache-2.0) depends on viem and zod. foxpay needs one
158
+ struct, so it keeps the bundle small and the signing code short enough to read.
159
+
160
+ ## API
161
+
162
+ This package is a library only. It has no CLI and no MCP server. A payment
163
+ needs the approval state in foxgate and the secrets in foxvault, and both live
164
+ in one process. A command that starts and stops cannot keep them.
165
+
166
+ ### `createFoxpay(options)`
167
+
168
+ | Option | What it does |
169
+ |---|---|
170
+ | `gate` | A foxgate gate made with `createFoxgate({ tools: payTools() })`. Add `[FILL_TOOL]: "fill"` for card fill. |
171
+ | `methods` | `{ name: PayMethod }`, for example `{ card: cardFill(...), x402: x402(...) }`. |
172
+ | `store` | Where intents and receipts live. Use the same store as foxgate, for example `storageAreaStore(browser.storage.local)`. |
173
+ | `onEvent` | `(event) => void \| Promise<void>`. Gets `{ actor: "foxpay", kind, data }`, which fits foxtrail `log.append`. If it throws before a payment, the payment stops with `hook-failed`. |
174
+ | `now` | The clock, in ms. Default: `Date.now`. |
175
+
176
+ | Method | What it does |
177
+ |---|---|
178
+ | `request(intent)` | Checks and quotes the intent and asks foxgate. Returns `{ status: "ask", id, requestId }`, `{ status: "refused", reason, message }`, or a final result when the intent was already paid or the grant needs no approval. |
179
+ | `complete(id, token)` | Pays an approved intent with the foxgate token. Returns `{ status, id, receipt, body? }` or a refusal. Later calls give the same receipt. |
180
+ | `intents()` | Every stored intent with its status. |
181
+ | `receipts()` | Every receipt, oldest first. |
182
+
183
+ An intent is `{ merchant, amount, currency, reason, method, idempotencyKey, target? }`.
184
+ `amount` is whole minor units: cents for USD, atomic units (6 decimals) for
185
+ USDC. `reason` has 1 to 200 characters. `idempotencyKey` has 8 to 128.
186
+ `target` is `{ tabId }` for card fill and `{ url }` for x402.
187
+
188
+ A receipt holds the merchant, the amount, the currency, the reason, the
189
+ method, the payee, the status, the request id, the time, and a `proof`. The
190
+ proof holds only public data: the last 4 card digits, the card handle or the
191
+ provider card id, the page after submit, or the payer, the nonce, the network,
192
+ and the transaction hash.
193
+
194
+ Statuses: `paid` (x402 with a settlement), `submitted` (card checkout
195
+ submitted), `unsettled` (no settlement proof; money may have moved), and
196
+ `failed` (foxpay knows that nothing was paid).
197
+
198
+ Refusal reasons: `bad-intent`, `key-reused`, `amount-mismatch`,
199
+ `currency-mismatch`, `in-progress`, `outcome-unknown`, `not-found`,
200
+ `hook-failed`, `storage-error`, `method-error`, every foxgate deny reason
201
+ (for example `spend-cap`, `currency`, `rejected`, `token-used`), and the
202
+ method reasons below.
203
+
204
+ ### `cardFill(options)`
205
+
206
+ | Option | What it does |
207
+ |---|---|
208
+ | `vault`, `browser` | A foxvault vault (with a gate that has a `fill` grant for the merchant) and the WebExtension `browser` object. foxpay pins every fill, also the foxvault one, to the `documentId` that it checked. Needs a foxvault with the `documentId` fill option. |
209
+ | `card` | `{ number, details }`: the handle of the card number and the handle of `"MM/YY CVC"`. |
210
+ | `provider` | Or a `VirtualCardProvider` with `createCard({ intentId, merchant, amount })` that returns `{ id, number, exp, cvc }`, and an optional `cancelCard(id)` that foxpay calls when the payment fails before submit. |
211
+ | `fields` | `{ number, exp, cvc }`: CSS selectors in the checkout page. |
212
+ | `total`, `submit` | The selector of the total text and of the pay button. |
213
+ | `currency` | The merchant currency. foxpay never reads it from the page. |
214
+ | `decimals` | Default: 2. |
215
+ | `allowHttp` | Allow plain `http:` pages. For local tests only. |
216
+ | `waitMs` | How long to wait for the page after submit. Default: 10 seconds. |
217
+
218
+ Reasons: `bad-target`, `no-tab`, `http`, `merchant-mismatch`, `page-changed`,
219
+ `amount-changed`, `no-total`, `no-card`, `domain`, `locked`,
220
+ `provider-error`, `not-found`, and every foxvault fill reason. When a step
221
+ fails after the number fill, foxpay clears the card fields first. When the
222
+ submit script starts but its result is lost, the status is `unsettled` with
223
+ `submit-unknown`, because the order may be placed.
224
+
225
+ ### `x402(options)`
226
+
227
+ | Option | What it does |
228
+ |---|---|
229
+ | `vault`, `wallet` | The vault and the handle of a Base Sepolia test wallet key (0x and 64 hex digits). |
230
+ | `payTo` | `{ host: address }`: the recipient that each merchant must name. |
231
+ | `fetch` | Default: the global `fetch`. |
232
+ | `confirm` | `(settlement) => Promise<boolean>`. Check the transaction yourself, for example on an RPC node. When it says no, the receipt is `unsettled`. |
233
+ | `maxTimeoutSeconds` | The longest time a signed authorization stays valid. Default: 300. |
234
+
235
+ Reasons before the payment: `bad-target`, `merchant-mismatch`,
236
+ `no-recipient`, `no-wallet`, `not-402`, `bad-requirements`,
237
+ `requirements-mismatch`, `amount-changed`, `locked`, and `sign-error`
238
+ (status `failed`). foxpay does not follow redirects.
239
+
240
+ After foxpay sends the signed payload, the payee holds a valid authorization
241
+ until `validBefore`. So x402 never returns `failed` after that point. The
242
+ status is `paid` with a settlement, or else `unsettled` with a reason:
243
+ `no-response`, `read-error`, `no-settlement`, `not-confirmed`,
244
+ `http-<status>`, or the payee's `errorReason`. A payee refusal is not proof
245
+ that no money moved. Check an `unsettled` payment on the chain before you
246
+ pay again.
247
+
248
+ ### Write your own method
249
+
250
+ A `PayMethod` has `quote(intent)`, an optional `check(context)`, and
251
+ `pay(context)`.
252
+
253
+ - `quote` reads the amount and the payee on the host side. Throw
254
+ `PayRefusal` to refuse. Nothing has been approved yet.
255
+ - `check` runs before foxgate uses the token. Return a reason, for example
256
+ `amount-changed` or `locked`, to stop with no spend.
257
+ - `pay` returns `{ status, reason?, proof?, body? }`. Return `failed` only
258
+ when you know that no money moved. When you do not know, return
259
+ `unsettled`.
260
+ - From `pay`, throw `PayRefusal` only before any money can move. foxpay
261
+ records it as `failed` with its reason. foxpay records any other error
262
+ from `pay` as `unsettled` with `method-error`, because it can come after
263
+ money moved. The error message is not kept.
264
+
265
+
266
+
267
+ | Export | What it does |
268
+ |---|---|
269
+ | `payTools()`, `PAY_TOOL` | The foxgate tool registry entry `foxpay.pay`, with an amount function that reads the quoted amount. |
270
+ | `GATE_CURRENCY` | `{ USDC: "XTS" }`. foxgate takes 3-letter codes, so test USDC counts as XTS, the ISO 4217 code for tests. |
271
+ | `PayRefusal` | Throw it from your own `PayMethod` to refuse with a reason. See "Write your own method". |
272
+ | `BASE_SEPOLIA` | The network, chain id, USDC contract, and token domain that x402 accepts. |
273
+ | `encodeHeader`, `decodeHeader` | Base64 JSON for the x402 headers. |
274
+ | `signAuthorization`, `recoverAuthorizer`, `authorizationDigest`, `addressOf`, `checksumAddress` | The EIP-712 and EIP-3009 helpers. |
275
+ | `foxpay/testing`: `fakeX402Api(options)` | A fake paid API with a local fake facilitator. It checks the signature and refuses a used nonce. For tests and demos only. |
276
+
277
+ ### Demo extension
278
+
279
+ `extension/` is a demo for Firefox 153+. The popup shows the spend caps and
280
+ what is left, the payments that wait for approval with their exact action
281
+ JSON, and the receipts. A simulated agent pays for a
282
+ [foxbench](https://github.com/pooriaarab/foxbench) Trailhead Supply checkout
283
+ with a test card, or calls a local fake x402 API.
284
+
285
+ ```bash
286
+ pnpm install
287
+ pnpm e2e # builds, loads the demo in Firefox, and runs the E2E checks
288
+ pnpm build:ext # builds dist-ext/; load it from about:debugging
289
+ ```
290
+
291
+ `pnpm e2e` writes `artifacts/e2e-<date>.json`. It checks a $26.00 order under
292
+ a $100 cap with one approval (the foxbench server sees the order and card
293
+ ending 4242), a $500 order that is refused before any fill, and an x402 call
294
+ that pays one time while a replay is refused.
295
+
296
+ ## Firefox APIs used
297
+
298
+ | API | MDN | Why |
299
+ |---|---|---|
300
+ | `webNavigation.getFrame` | [getFrame](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webNavigation/getFrame) | Read the URL and the `documentId` of the top document of the checkout tab. |
301
+ | `scripting.executeScript` with `documentIds` (Firefox 153+) | [executeScript](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/executeScript) | Read the total, fill the expiry and the CVC, and submit, in that exact document only. |
302
+ | `storage.local` | [storage.local](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/local) | Keep intents, receipts, foxgate state, and foxvault ciphertexts. |
303
+ | IndexedDB (through foxvault) | [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) | Keep the non-extractable vault key. |
304
+ | `fetch` with host permissions | [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) | Call the paid API and read the x402 headers. |
305
+ | `crypto.getRandomValues`, `crypto.randomUUID` | [getRandomValues](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues) | Make the x402 nonce and the virtual card handles. |
306
+ | `btoa`, `atob`, `TextEncoder`, `TextDecoder` | [btoa](https://developer.mozilla.org/en-US/docs/Web/API/Window/btoa) | Encode the x402 headers as base64 JSON. |
307
+ | `publicSuffix.getDomain` (Firefox 153+) | [publicSuffix](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/publicSuffix) | foxgate and foxvault refuse `*.` patterns on a public suffix. Demo only. |
308
+ | `tabs.query` | [tabs.query](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/query) | Find the checkout tab of the merchant. Demo only. |
309
+ | `runtime.sendMessage`, `runtime.onMessage` | [runtime](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime) | The popup talks to the background page. Demo only. |
310
+ | `action` (`default_popup`) | [action](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/action) | The toolbar button that opens the approval popup. Demo only. |
311
+
312
+ ## Limits
313
+
314
+ - x402 pays on Base Sepolia only. The network and the USDC contract are
315
+ fixed in the code. There is no mainnet option.
316
+ - foxpay was tested with its own fake facilitator only. It follows the x402
317
+ version 2 HTTP headers, but no test ran against the public x402.org
318
+ facilitator or a real chain.
319
+ - There is no real virtual card provider yet. `VirtualCardProvider` is the
320
+ interface for one, such as Stripe Issuing or Privacy.com. The tests use a
321
+ fake provider.
322
+ - Card fill works in the top document only. A card form inside an iframe,
323
+ for example a hosted payment field, cannot be filled.
324
+ - A card payment ends as `submitted`. foxpay sees the page after submit, not
325
+ the charge. The total is what the merchant page shows.
326
+ - Each merchant needs its own selectors for the card fields, the total, and
327
+ the pay button.
328
+ - foxvault values need 8 characters or more, so the expiry and the CVC are
329
+ one secret, `"MM/YY CVC"`. foxpay fills them through `vault.use` with its
330
+ own bundled function, pinned to the same document. `redact` finds that
331
+ pair, not the CVC alone.
332
+ - foxgate counts the spend when the token is used. A payment that fails
333
+ after that still counts against the cap. Before the token, foxpay checks
334
+ the page total or fetches the `402` again, so a changed price is refused
335
+ with no spend.
336
+ - `outcome-unknown` needs a person. foxpay does not check with the merchant
337
+ or the chain if an interrupted payment went through.
338
+ - Run one foxpay object, one foxgate gate, and one vault for each store, in
339
+ the background page. Two objects on the same store can race.
340
+ - Legal and compliance: foxpay is not a payment processor, a wallet service,
341
+ or financial advice. A real card number in a browser extension can bring
342
+ your app into the scope of PCI DSS and your card issuer's terms. Paying
343
+ with real stablecoins can bring money transmission and sanctions rules.
344
+ Get legal advice before you use real money.
345
+
346
+ ## Part of the fox primitives
347
+
348
+ ```mermaid
349
+ flowchart LR
350
+ foxkit[foxkit] -- template --> foxpay[foxpay]
351
+ foxgate[foxgate] --> foxpay
352
+ foxvault[foxvault] --> foxpay
353
+ foxgate --> foxvault
354
+ foxpay -. onEvent hook .-> foxtrail[foxtrail]
355
+ foxbench[foxbench] -. demo shop .-> foxpay
356
+ foxpay --> foxmate[foxmate]
357
+ click foxkit "https://github.com/pooriaarab/foxkit"
358
+ click foxgate "https://github.com/pooriaarab/foxgate"
359
+ click foxvault "https://github.com/pooriaarab/foxvault"
360
+ click foxpay "https://github.com/pooriaarab/foxpay"
361
+ click foxtrail "https://github.com/pooriaarab/foxtrail"
362
+ click foxbench "https://github.com/pooriaarab/foxbench"
363
+ click foxmate "https://github.com/pooriaarab/foxmate"
364
+ ```
365
+
366
+ foxpay depends on foxgate for approvals and caps, and on foxvault for the
367
+ card and the wallet key. foxtrail and foxbench are dev dependencies only:
368
+ the tests write events to a foxtrail log, and the E2E test uses the foxbench
369
+ shop.
370
+
371
+ ## License
372
+
373
+ [MIT](LICENSE)
package/dist/card.d.ts ADDED
@@ -0,0 +1,80 @@
1
+ import type { Vault } from "foxvault";
2
+ import { type PayMethod } from "./intent.js";
3
+ /** The parts of the WebExtension `browser` object that card fill uses. Pass the same object to foxvault. */
4
+ export interface CardBrowser {
5
+ webNavigation: {
6
+ getFrame(details: {
7
+ tabId: number;
8
+ frameId: number;
9
+ }): Promise<{
10
+ url: string;
11
+ documentId?: string;
12
+ } | null | undefined>;
13
+ };
14
+ scripting: {
15
+ executeScript(injection: {
16
+ target: {
17
+ tabId: number;
18
+ documentIds: string[];
19
+ };
20
+ func: (...args: never[]) => unknown;
21
+ args: unknown[];
22
+ }): Promise<{
23
+ result?: unknown;
24
+ }[]>;
25
+ };
26
+ }
27
+ /** A card issuer that makes a single-use card for one merchant and one limit, such as Stripe Issuing. */
28
+ export interface VirtualCardProvider {
29
+ createCard(request: {
30
+ intentId: string;
31
+ merchant: string;
32
+ amount: {
33
+ value: number;
34
+ currency: string;
35
+ };
36
+ }): Promise<{
37
+ id: string;
38
+ number: string;
39
+ exp: string;
40
+ cvc: string;
41
+ }>;
42
+ /** Close a card that foxpay made but did not use, for example after a failed fill (C15). */
43
+ cancelCard?(id: string): Promise<void>;
44
+ }
45
+ export interface CardFillOptions {
46
+ /** A foxvault vault with a gate that has a fill grant for the merchants, and the same `browser`. */
47
+ vault: Vault;
48
+ browser: CardBrowser;
49
+ /** A stored card: the handle of the number, and the handle of `"MM/YY CVC"`. */
50
+ card?: {
51
+ number: string;
52
+ details: string;
53
+ };
54
+ /** Or a provider that makes a new card for each payment. */
55
+ provider?: VirtualCardProvider;
56
+ /** CSS selectors in the merchant checkout page. */
57
+ fields: {
58
+ number: string;
59
+ exp: string;
60
+ cvc: string;
61
+ };
62
+ /** The element that holds the total, for example `Total $26.00`. */
63
+ total: string;
64
+ submit: string;
65
+ /** The currency of the merchant. foxpay never reads it from the page. */
66
+ currency: string;
67
+ /** Digits after the decimal point. Default: 2. */
68
+ decimals?: number;
69
+ /** Allow plain http pages. For local tests only. */
70
+ allowHttp?: boolean;
71
+ /** How long to wait for the page after submit. Default: 10 seconds. */
72
+ waitMs?: number;
73
+ }
74
+ export declare function readTotal(selector: string, host: string): string | null;
75
+ export declare function fillDetails(expSelector: string, cvcSelector: string, exp: string, cvc: string, host: string): string;
76
+ export declare function submitCheckout(submitSelector: string, totalSelector: string, totalText: string, host: string): string;
77
+ export declare function clearFields(selectors: string[], host: string): string;
78
+ /** Whole minor units from a total such as `Total $26.00`, or undefined when the text has not exactly one amount. */
79
+ export declare function parseTotal(text: string | null, decimals?: number): number | undefined;
80
+ export declare function cardFill(options: CardFillOptions): PayMethod;