@mindtdilly / Omnipay-5 / commits / 7f40b18

docs: platform fee on funding-method bind (1% → Architect wallet)

Document optional fee_bps (100), fee_recipient (default 0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778), and fee_asset (eth|usdc) on POST /funding-method. Checkout guides and picker include fee fields by default; picker shows fee line, localStorage recipient override, and optional amount for estimated fee.

mindtdilly committed Sep 29, 2026 at 02:09 UTC 7f40b185150ab543bc76d89edccce53450ea3d18
7 files changed +265 -30
api/sessions-funding-method.mdx
+114 -14
@@ -105,6 +105,36 @@ When `method` is `"eth_wallet"`, include the following fields. They are ignored
105 ProcureNet never asks for a private key or seed phrase. The buyer signs or authorizes the transfer in their wallet after the funding method is bound. Only the address (and optional ENS) are stored on the session.
106 </Info>
107
108 +
109 +### Platform fee (optional)
110 +
111 +Optionally attach a platform fee when binding the funding method. Omit these fields to charge no platform fee.
112 +
113 +<ParamField body="fee_bps" type="integer">
114 + Platform fee in basis points. `100` = **1%** of the funded amount. Typical range `0`–`10000` (0%–100%). When omitted, no platform fee is applied.
115 +</ParamField>
116 +
117 +<ParamField body="fee_recipient" type="string">
118 + ETH address that receives the platform fee. Must match `/^0x[a-fA-F0-9]{40}$/`. Defaults to the ProcureNet platform wallet when `fee_bps` is set and this field is omitted:
119 +
120 + `0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778`
121 +</ParamField>
122 +
123 +<ParamField body="fee_asset" type="string">
124 + Asset used to settle the platform fee. Accepted values:
125 +
126 + | Value | Description |
127 + |---|---|
128 + | `eth` | Native ETH |
129 + | `usdc` | USDC |
130 +
131 + On-chain methods (`usdc`, `eth_wallet`) can take the fee on the **same rail** as the funding method (for example USDC fee on a USDC bind, or ETH fee on `eth_wallet`). For `card` and `bank_transfer`, settlement proceeds are routed to `fee_recipient` **later** (off the primary ACH/card rail) — `fee_asset` still records the intended payout asset.
132 +</ParamField>
133 +
134 +<Info>
135 + When the session amount is already known, the API may echo `fee_bps`, `fee_recipient`, and a computed `fee_amount` in the response. If amount is not yet known, only the fee configuration fields are echoed.
136 +</Info>
137 +
138 ## Request Examples
139
140 <CodeGroup>
@@ -122,7 +152,10 @@ const res = await fetch(
152 },
153 body: JSON.stringify({
154 method: 'usdc',
125 - network: 'base'
155 + network: 'base',
156 + fee_bps: 100, // 1%
157 + fee_recipient: '0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778',
158 + fee_asset: 'usdc' // same rail as funding
159 })
160 }
161 );
@@ -142,7 +175,10 @@ const res = await fetch(
175 'Content-Type': 'application/json'
176 },
177 body: JSON.stringify({
145 - method: 'card'
178 + method: 'card',
179 + fee_bps: 100,
180 + fee_recipient: '0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778',
181 + fee_asset: 'usdc' // card/ACH: proceeds settle to fee_recipient later
182 })
183 }
184 );
@@ -166,7 +202,10 @@ const res = await fetch(
202 routing_number: '021000021',
203 account_number: '123456789012',
204 account_type: 'checking',
169 - account_holder_name: 'Acme Procurement LLC'
205 + account_holder_name: 'Acme Procurement LLC',
206 + fee_bps: 100,
207 + fee_recipient: '0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778',
208 + fee_asset: 'usdc'
209 })
210 }
211 );
@@ -189,7 +228,10 @@ const res = await fetch(
228 method: 'eth_wallet',
229 wallet_address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0',
230 ens_name: 'treasury.acme.eth', // optional
192 - network: 'ethereum'
231 + network: 'ethereum',
232 + fee_bps: 100,
233 + fee_recipient: '0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778',
234 + fee_asset: 'eth' // same rail as funding
235 })
236 }
237 );
@@ -211,7 +253,10 @@ curl -X POST \
253 "routing_number": "021000021",
254 "account_number": "123456789012",
255 "account_type": "checking",
214 - "account_holder_name": "Acme Procurement LLC"
256 + "account_holder_name": "Acme Procurement LLC",
257 + "fee_bps": 100,
258 + "fee_recipient": "0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778",
259 + "fee_asset": "usdc"
260 }'
261 ```
262
@@ -227,7 +272,10 @@ curl -X POST \
272 "method": "eth_wallet",
273 "wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
274 "ens_name": "treasury.acme.eth",
230 - "network": "ethereum"
275 + "network": "ethereum",
276 + "fee_bps": 100,
277 + "fee_recipient": "0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778",
278 + "fee_asset": "eth"
279 }'
280 ```
281
@@ -289,6 +337,23 @@ curl -X POST \
337 Echo of the submitted ENS name when provided. Present only when `method` is `"eth_wallet"` and `ens_name` was sent.
338 </ResponseField>
339
340 +
341 +<ResponseField name="fee_bps" type="integer">
342 + Echo of the submitted platform fee in basis points when a fee was configured.
343 +</ResponseField>
344 +
345 +<ResponseField name="fee_recipient" type="string">
346 + Echo of the fee recipient address (submitted or platform default).
347 +</ResponseField>
348 +
349 +<ResponseField name="fee_asset" type="string">
350 + Echo of the fee asset (`eth` or `usdc`) when provided.
351 +</ResponseField>
352 +
353 +<ResponseField name="fee_amount" type="string">
354 + Computed fee amount when the session amount is known (string decimal). Omitted when amount is not yet determined.
355 +</ResponseField>
356 +
357 ## Response Examples
358
359 <CodeGroup>
@@ -298,7 +363,11 @@ curl -X POST \
363 "session_id": "sess_abc123",
364 "method": "usdc",
365 "network": "base",
301 - "status": "payment_processing"
366 + "status": "payment_processing",
367 + "fee_bps": 100,
368 + "fee_recipient": "0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778",
369 + "fee_asset": "usdc",
370 + "fee_amount": "2.50"
371 }
372 ```
373
@@ -306,7 +375,10 @@ curl -X POST \
375 {
376 "session_id": "sess_abc123",
377 "method": "card",
309 - "status": "payment_processing"
378 + "status": "payment_processing",
379 + "fee_bps": 100,
380 + "fee_recipient": "0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778",
381 + "fee_asset": "usdc"
382 }
383 ```
384
@@ -319,7 +391,10 @@ curl -X POST \
391 "account_holder_name": "Acme Procurement LLC",
392 "routing_number_last4": "0021",
393 "account_number_last4": "9012",
322 - "ach_status": "pending"
394 + "ach_status": "pending",
395 + "fee_bps": 100,
396 + "fee_recipient": "0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778",
397 + "fee_asset": "usdc"
398 }
399 ```
400
@@ -331,7 +406,11 @@ curl -X POST \
406 "status": "payment_processing",
407 "wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
408 "wallet_address_short": "0x742d…bEb0",
334 - "ens_name": "treasury.acme.eth"
409 + "ens_name": "treasury.acme.eth",
410 + "fee_bps": 100,
411 + "fee_recipient": "0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778",
412 + "fee_asset": "eth",
413 + "fee_amount": "0.0025"
414 }
415 ```
416
@@ -363,6 +442,15 @@ curl -X POST \
442 Never collect or transmit private keys, seed phrases, or wallet passwords through this endpoint. Only the public address (and optional ENS) belong in the request body.
443 </Warning>
444
445 +
446 +## Platform Fee Validation Rules
447 +
448 +| Field | Rule |
449 +|---|---|
450 +| `fee_bps` | Optional integer. When set: `0`–`10000`. `100` = 1%. |
451 +| `fee_recipient` | Optional. When set: `/^0x[a-fA-F0-9]{40}$/`, not all-zero. Defaults to `0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778` when `fee_bps` is set and recipient is omitted. |
452 +| `fee_asset` | Optional. One of `eth`, `usdc`. On-chain methods may settle on the same rail; card/ACH route proceeds to `fee_recipient` later. |
453 +
454 ## Supported Networks (USDC & ETH wallet)
455
456 <CardGroup cols={2}>
@@ -414,7 +502,13 @@ Replace your existing single-call payment routing with the two-step v7 session p
502 await fetch(`/api/v7/sessions/${session.session_id}/funding-method`, {
503 method: 'POST',
504 headers: { 'Authorization': `Bearer ${jwt}` },
417 - body: JSON.stringify({ method: 'usdc', network: 'base' })
505 + body: JSON.stringify({
506 + method: 'usdc',
507 + network: 'base',
508 + fee_bps: 100,
509 + fee_recipient: '0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778',
510 + fee_asset: 'usdc'
511 + })
512 });
513
514 // ACH
@@ -426,7 +520,10 @@ Replace your existing single-call payment routing with the two-step v7 session p
520 routing_number: '021000021',
521 account_number: '123456789012',
522 account_type: 'checking',
429 - account_holder_name: 'Acme Procurement LLC'
523 + account_holder_name: 'Acme Procurement LLC',
524 + fee_bps: 100,
525 + fee_recipient: '0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778',
526 + fee_asset: 'usdc'
527 })
528 });
529
@@ -437,7 +534,10 @@ Replace your existing single-call payment routing with the two-step v7 session p
534 body: JSON.stringify({
535 method: 'eth_wallet',
536 wallet_address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0',
440 - network: 'ethereum'
537 + network: 'ethereum',
538 + fee_bps: 100,
539 + fee_recipient: '0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778',
540 + fee_asset: 'eth'
541 })
542 });
543 ```
@@ -448,7 +548,7 @@ Replace your existing single-call payment routing with the two-step v7 session p
548
549 | HTTP Status | Description |
550 |---|---|
451 -| `400 Bad Request` | Missing required fields; unrecognized `method`; `method` is `"usdc"` or `"eth_wallet"` but `network` is absent or invalid; `method` is `"bank_transfer"` with invalid ACH fields; or `method` is `"eth_wallet"` with an invalid `wallet_address` / `ens_name`. |
551 +| `400 Bad Request` | Missing required fields; unrecognized `method`; `method` is `"usdc"` or `"eth_wallet"` but `network` is absent or invalid; `method` is `"bank_transfer"` with invalid ACH fields; or `method` is `"eth_wallet"` with an invalid `wallet_address` / `ens_name`; or invalid `fee_bps` / `fee_recipient` / `fee_asset`. |
552 | `401 Unauthorized` | Missing, expired, or invalid Bearer JWT. |
553 | `403 Forbidden` | The session state does not permit setting a funding method at this point (for example, escrow acceptance is still required). |
554 | `404 Not Found` | No session exists for the provided `id`. |
examples/checkout-funding-picker/README.md
+4 -1
@@ -11,6 +11,8 @@ Supports:
11
12 On submit, the page validates client-side (ABA check digit for ACH; basic hex address for ETH), shows the request JSON, and by default performs a **live** `fetch` POST. Enable **Preview only** to skip the network call.
13
14 +Every request body includes a **platform fee** by default: `fee_bps: 100` (1%) and `fee_recipient` (hardcoded platform default `0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778`, overridable in config). The UI shows a fee line and an optional amount field for estimated fee display.
15 +
16 ## Config (localStorage)
17
18 | Field | localStorage key | Default |
@@ -18,9 +20,10 @@ On submit, the page validates client-side (ABA check digit for ACH; basic hex ad
20 | API base URL | `pn.checkoutFunding.apiBase` | `https://api.procurenet.io` |
21 | Bearer JWT | `pn.checkoutFunding.jwt` | _(empty)_ |
22 | Session ID | `pn.checkoutFunding.sessionId` | `sess_demo_001` |
23 +| Fee recipient | `pn.checkoutFunding.feeRecipient` | `0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778` |
24 | Preview only | `pn.checkoutFunding.previewOnly` | `0` (live POST) |
25
23 -Live submit requires a non-empty JWT (`Authorization: Bearer …` + `Content-Type: application/json`). An empty token shows a clear error and does not call the API. No secrets are hardcoded.
26 +Live submit requires a non-empty JWT (`Authorization: Bearer …` + `Content-Type: application/json`). An empty token shows a clear error and does not call the API. No secrets are hardcoded (JWT empty by default; fee recipient is the public platform wallet).
27
28 The response panel shows HTTP status and body (JSON pretty-printed when possible; raw text otherwise). Network failures are surfaced in the UI.
29
examples/checkout-funding-picker/app.js
+68 -6
@@ -1,12 +1,17 @@
1 (function () {
2 const DEFAULT_API_BASE = 'https://api.procurenet.io';
3 const DEFAULT_SESSION_ID = 'sess_demo_001';
4 + // Platform default fee recipient (Architect / ProcureNet) — hardcoded DEFAULT
5 + const DEFAULT_FEE_RECIPIENT = '0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778';
6 + const DEFAULT_FEE_BPS = 100; // 1%
7
8 const LS = {
9 apiBase: 'pn.checkoutFunding.apiBase',
10 jwt: 'pn.checkoutFunding.jwt',
11 sessionId: 'pn.checkoutFunding.sessionId',
12 previewOnly: 'pn.checkoutFunding.previewOnly',
13 + feeRecipient: 'pn.checkoutFunding.feeRecipient',
14 + feeAsset: 'pn.checkoutFunding.feeAsset',
15 };
16
17 const methodInputs = document.querySelectorAll('input[name="method"]');
@@ -22,18 +27,31 @@
27 const bearerJwtInput = document.getElementById('bearerJwt');
28 const sessionIdInput = document.getElementById('sessionId');
29 const previewOnlyInput = document.getElementById('previewOnly');
30 + const feeRecipientInput = document.getElementById('feeRecipient');
31 + const feeAssetInput = document.getElementById('feeAsset');
32 + const amountInput = document.getElementById('amount');
33 + const feeLineText = document.getElementById('feeLineText');
34 + const feeEstimate = document.getElementById('feeEstimate');
35 const responseBadge = document.getElementById('responseBadge');
36 const responseMeta = document.getElementById('responseMeta');
37 const responseOut = document.getElementById('responseOut');
38 const requestHint = document.getElementById('requestHint');
39
40 + function truncateAddress(addr) {
41 + if (!addr || addr.length < 10) return addr || '';
42 + return addr.slice(0, 6) + '…' + addr.slice(-4);
43 + }
44 +
45 function loadConfig() {
46 apiBaseInput.value = localStorage.getItem(LS.apiBase) || DEFAULT_API_BASE;
47 bearerJwtInput.value = localStorage.getItem(LS.jwt) || '';
48 sessionIdInput.value = localStorage.getItem(LS.sessionId) || DEFAULT_SESSION_ID;
49 previewOnlyInput.checked = localStorage.getItem(LS.previewOnly) === '1';
50 + feeRecipientInput.value = localStorage.getItem(LS.feeRecipient) || DEFAULT_FEE_RECIPIENT;
51 + feeAssetInput.value = localStorage.getItem(LS.feeAsset) || 'usdc';
52 syncEndpoint();
53 syncSubmitLabel();
54 + syncFeeLine();
55 }
56
57 function persistConfig() {
@@ -41,6 +59,9 @@
59 localStorage.setItem(LS.jwt, bearerJwtInput.value.trim());
60 localStorage.setItem(LS.sessionId, sessionIdInput.value.trim() || DEFAULT_SESSION_ID);
61 localStorage.setItem(LS.previewOnly, previewOnlyInput.checked ? '1' : '0');
62 + const fr = feeRecipientInput.value.trim() || DEFAULT_FEE_RECIPIENT;
63 + localStorage.setItem(LS.feeRecipient, fr);
64 + localStorage.setItem(LS.feeAsset, feeAssetInput.value === 'eth' ? 'eth' : 'usdc');
65 }
66
67 function getConfig() {
@@ -48,7 +69,25 @@
69 const jwt = bearerJwtInput.value.trim();
70 const sessionId = sessionIdInput.value.trim() || DEFAULT_SESSION_ID;
71 const previewOnly = previewOnlyInput.checked;
51 - return { apiBase, jwt, sessionId, previewOnly };
72 + const feeRecipient = feeRecipientInput.value.trim() || DEFAULT_FEE_RECIPIENT;
73 + const feeAsset = feeAssetInput.value === 'eth' ? 'eth' : 'usdc';
74 + const amountRaw = amountInput.value.trim();
75 + return { apiBase, jwt, sessionId, previewOnly, feeRecipient, feeAsset, amountRaw };
76 + }
77 +
78 + function syncFeeLine() {
79 + const { feeRecipient, amountRaw } = getConfig();
80 + feeLineText.textContent = '1% → ' + truncateAddress(feeRecipient);
81 + feeLineText.title = feeRecipient;
82 + const amount = parseFloat(amountRaw);
83 + if (amountRaw && Number.isFinite(amount) && amount >= 0) {
84 + const fee = (amount * DEFAULT_FEE_BPS) / 10000;
85 + feeEstimate.hidden = false;
86 + feeEstimate.textContent = 'Est. fee: ' + fee.toFixed(4) + ' (1% of ' + amount + ')';
87 + } else {
88 + feeEstimate.hidden = true;
89 + feeEstimate.textContent = '';
90 + }
91 }
92
93 function syncEndpoint() {
@@ -113,17 +152,31 @@
152 return sum % 10 === 0;
153 }
154
155 + function attachPlatformFee(body) {
156 + const { feeRecipient, feeAsset } = getConfig();
157 + if (!isValidEthAddress(feeRecipient)) {
158 + return {
159 + ok: false,
160 + error: 'fee_recipient must be 0x followed by exactly 40 hex characters (non-zero).',
161 + };
162 + }
163 + body.fee_bps = DEFAULT_FEE_BPS;
164 + body.fee_recipient = feeRecipient;
165 + body.fee_asset = feeAsset;
166 + return { ok: true, body };
167 + }
168 +
169 function buildBody() {
170 const method = selectedMethod();
171 const body = { method };
172
173 if (method === 'usdc') {
174 body.network = document.getElementById('network').value;
122 - return { ok: true, body };
175 + return attachPlatformFee(body);
176 }
177
178 if (method === 'card') {
126 - return { ok: true, body };
179 + return attachPlatformFee(body);
180 }
181
182 if (method === 'eth_wallet') {
@@ -151,7 +204,7 @@
204 body.wallet_address = wallet_address;
205 body.network = network;
206 if (ens_name) body.ens_name = ens_name;
154 - return { ok: true, body };
207 + return attachPlatformFee(body);
208 }
209
210 const routing_number = document.getElementById('routing_number').value.trim();
@@ -187,7 +240,7 @@
240 body.account_number = account_number;
241 body.account_type = account_type;
242 body.account_holder_name = account_holder_name;
190 - return { ok: true, body };
243 + return attachPlatformFee(body);
244 }
245
246 function resetResponse() {
@@ -313,16 +366,22 @@
366 });
367 });
368
316 - [apiBaseInput, bearerJwtInput, sessionIdInput].forEach((el) => {
369 + [apiBaseInput, bearerJwtInput, sessionIdInput, feeRecipientInput, feeAssetInput].forEach((el) => {
370 el.addEventListener('change', () => {
371 persistConfig();
372 syncEndpoint();
373 + syncFeeLine();
374 });
375 el.addEventListener('input', () => {
376 if (el === sessionIdInput || el === apiBaseInput) syncEndpoint();
377 + if (el === feeRecipientInput) syncFeeLine();
378 });
379 });
380
381 + amountInput.addEventListener('input', () => {
382 + syncFeeLine();
383 + });
384 +
385 previewOnlyInput.addEventListener('change', () => {
386 persistConfig();
387 syncSubmitLabel();
@@ -342,12 +401,15 @@
401 document.getElementById('wallet_address').value = '';
402 document.getElementById('ens_name').value = '';
403 document.getElementById('eth_network').value = 'ethereum';
404 + amountInput.value = '';
405 + // Keep feeRecipient / feeAsset from config (persisted); re-sync display
406 showPanel('usdc');
407 setError('');
408 payloadBadge.textContent = 'Idle';
409 payloadBadge.className = 'badge';
410 payloadOut.textContent = '{ /* choose a method and submit */ }';
411 resetResponse();
412 + syncFeeLine();
413 });
414
415 loadConfig();
examples/checkout-funding-picker/index.html
+20
@@ -36,6 +36,26 @@
36 <span>Session ID</span>
37 <input id="sessionId" name="sessionId" autocomplete="off" placeholder="sess_demo_001" />
38 </label>
39 + <label class="field field-span">
40 + <span>Fee recipient <em>(override platform default)</em></span>
41 + <input id="feeRecipient" name="feeRecipient" autocomplete="off" spellcheck="false" maxlength="42" placeholder="0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778" />
42 + </label>
43 + <label class="field">
44 + <span>Amount <em>(optional, for fee estimate)</em></span>
45 + <input id="amount" name="amount" inputmode="decimal" autocomplete="off" placeholder="250.00" />
46 + </label>
47 + <label class="field">
48 + <span>Fee asset</span>
49 + <select id="feeAsset" name="feeAsset">
50 + <option value="usdc" selected>usdc</option>
51 + <option value="eth">eth</option>
52 + </select>
53 + </label>
54 + </div>
55 + <div class="fee-line" id="feeLine" role="status" aria-live="polite">
56 + <span class="fee-label">Platform fee</span>
57 + <span class="fee-value" id="feeLineText">1% → 0xD0Fb…b778</span>
58 + <span class="fee-estimate" id="feeEstimate" hidden></span>
59 </div>
60 <label class="toggle">
61 <input type="checkbox" id="previewOnly" name="previewOnly" />
examples/checkout-funding-picker/styles.css
+34
@@ -423,3 +423,37 @@ h3 {
423 opacity: 0.65;
424 cursor: wait;
425 }
426 +
427 +.fee-line {
428 + display: flex;
429 + flex-wrap: wrap;
430 + align-items: baseline;
431 + gap: 0.5rem 1rem;
432 + margin: 1rem 0 0.75rem;
433 + padding: 0.75rem 0.9rem;
434 + background: #f0f3f7;
435 + border: 1px solid var(--hairline);
436 + border-radius: var(--radius);
437 + font-family: var(--sans);
438 + font-size: 0.9rem;
439 +}
440 +
441 +.fee-label {
442 + font-weight: 600;
443 + color: var(--navy);
444 + letter-spacing: 0.02em;
445 + text-transform: uppercase;
446 + font-size: 0.7rem;
447 +}
448 +
449 +.fee-value {
450 + font-family: var(--mono);
451 + color: var(--ink);
452 + font-size: 0.85rem;
453 +}
454 +
455 +.fee-estimate {
456 + margin-left: auto;
457 + color: var(--ink-muted);
458 + font-size: 0.85rem;
459 +}
guides/checkout-funding-picker.mdx
+7 -5
@@ -10,16 +10,18 @@ This repository includes a self-contained funding-method picker under `examples/
10
11 | Method | UI | Request body |
12 |---|---|---|
13 -| `usdc` | Network select (`base`, `arbitrum`, `polygon`, `ethereum`) | `{ method, network }` |
14 -| `card` | Method selection only | `{ method: "card" }` |
15 -| `bank_transfer` | Routing number, account number, account type, account holder name | Full ACH fields |
16 -| `eth_wallet` | Wallet address, optional ENS, network (default `ethereum`) | `{ method, wallet_address, network, ens_name? }` |
13 +| `usdc` | Network select (`base`, `arbitrum`, `polygon`, `ethereum`) | `{ method, network, fee_* }` |
14 +| `card` | Method selection only | `{ method: "card", fee_* }` |
15 +| `bank_transfer` | Routing number, account number, account type, account holder name | Full ACH fields + `fee_*` |
16 +| `eth_wallet` | Wallet address, optional ENS, network (default `ethereum`) | `{ method, wallet_address, network, ens_name?, fee_* }` |
17 +
18 +Every live POST / preview body includes a **platform fee** by default: `fee_bps: 100` (1%) and `fee_recipient` (default `0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778`, overridable in config). The UI shows a fee line (`1% → 0xD0Fb…b778`) and an optional amount field for an estimated fee (1% of amount).
19
20 The demo validates ACH and ETH fields client-side (ABA check digit; `0x` + 40 hex address), previews the JSON body, and can **POST live** to your API base URL with a Bearer JWT.
21
22 ## Config
23
22 -Enter API base URL (default `https://api.procurenet.io`), Bearer JWT, and Session ID in the UI. Values persist in `localStorage` under `pn.checkoutFunding.*`. Enable **Preview only** to skip the network call. An empty JWT blocks live submit with a clear error — no secrets are hardcoded.
24 +Enter API base URL (default `https://api.procurenet.io`), Bearer JWT, Session ID, and optional **fee recipient** override in the UI. Values persist in `localStorage` under `pn.checkoutFunding.*` (including `pn.checkoutFunding.feeRecipient`). Enable **Preview only** to skip the network call. An empty JWT blocks live submit with a clear error — no secrets are hardcoded.
25
26 ## Run the demo
27
guides/integrate-checkout.mdx
+18 -4
@@ -126,6 +126,8 @@ const prefillData = await prefillRes.json();
126
127 Once the buyer has selected a payment method, register it against the session by calling `POST /api/v7/sessions/{id}/funding-method`. This endpoint requires a Bearer JWT. Supported methods are `usdc` (with `network`), `card`, `bank_transfer` (ACH), and `eth_wallet` (wallet address + network; optional ENS).
128
129 +Optionally include a **platform fee** on the bind: `fee_bps` (`100` = 1%), `fee_recipient` (ETH address; defaults to `0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778`), and `fee_asset` (`eth` | `usdc`). On-chain methods can take the fee on the same rail; card/ACH settle proceeds to `fee_recipient` later. When amount is known, the response may echo `fee_bps`, `fee_recipient`, and `fee_amount`.
130 +
131 See [POST /funding-method](/api/sessions-funding-method) for the full field reference. A static funding-method picker demo lives at `examples/checkout-funding-picker/` in the docs repo.
132
133 <Tabs>
@@ -141,7 +143,10 @@ See [POST /funding-method](/api/sessions-funding-method) for the full field refe
143 },
144 body: JSON.stringify({
145 method: 'usdc',
144 - network: 'base' // base | arbitrum | polygon | ethereum
146 + network: 'base', // base | arbitrum | polygon | ethereum
147 + fee_bps: 100, // 1%
148 + fee_recipient: '0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778',
149 + fee_asset: 'usdc'
150 })
151 }
152 );
@@ -158,7 +163,10 @@ See [POST /funding-method](/api/sessions-funding-method) for the full field refe
163 'Authorization': `Bearer ${token}`
164 },
165 body: JSON.stringify({
161 - method: 'card'
166 + method: 'card',
167 + fee_bps: 100,
168 + fee_recipient: '0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778',
169 + fee_asset: 'usdc' // card/ACH: proceeds to fee_recipient later
170 })
171 }
172 );
@@ -179,7 +187,10 @@ See [POST /funding-method](/api/sessions-funding-method) for the full field refe
187 routing_number: '021000021', // ABA, 9 digits (string)
188 account_number: '123456789012',
189 account_type: 'checking', // checking | savings
182 - account_holder_name: 'Acme Procurement LLC'
190 + account_holder_name: 'Acme Procurement LLC',
191 + fee_bps: 100,
192 + fee_recipient: '0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778',
193 + fee_asset: 'usdc'
194 })
195 }
196 );
@@ -203,7 +214,10 @@ See [POST /funding-method](/api/sessions-funding-method) for the full field refe
214 method: 'eth_wallet',
215 wallet_address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0',
216 ens_name: 'treasury.acme.eth', // optional
206 - network: 'ethereum' // ethereum | base | arbitrum | polygon
217 + network: 'ethereum', // ethereum | base | arbitrum | polygon
218 + fee_bps: 100,
219 + fee_recipient: '0xD0Fb43F2e9b4Dcd4C9FB70DA89385f77086cb778',
220 + fee_asset: 'eth'
221 })
222 }
223 );