@siglume/direct-request-payment 0.4.23 → 0.4.25
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/CHANGELOG.md +38 -0
- package/README.md +23 -12
- package/bin/siglume-sdrp.mjs +244 -9
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/docs/announcement-ja.md +7 -3
- package/docs/api-reference.md +5 -3
- package/docs/merchant-quickstart.md +6 -4
- package/docs/pricing.md +1 -1
- package/docs/quickstart-10-minutes.md +91 -58
- package/docs/sandbox.md +23 -5
- package/docs/troubleshooting.md +12 -8
- package/examples/hosted-checkout-python/pyproject.toml +1 -1
- package/package.json +14 -2
- package/templates/express/siglume-order-store.sql.ts +272 -54
- package/templates/express/siglume-sdrp-routes.ts +43 -9
- package/templates/fastapi/README.md +32 -3
- package/templates/fastapi/siglume_order_store_example.py +15 -0
- package/templates/fastapi/siglume_order_store_sqlalchemy.py +238 -53
- package/templates/fastapi/siglume_order_store_sqlalchemy_async.py +496 -0
- package/templates/fastapi/siglume_sdrp_routes.py +31 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.25 - 2026-06-20
|
|
4
|
+
|
|
5
|
+
- Added a real PostgreSQL/MySQL ORM matrix for the Express SQL order store,
|
|
6
|
+
covering Prisma + PostgreSQL, TypeORM + PostgreSQL, Sequelize + MySQL,
|
|
7
|
+
Drizzle + PostgreSQL, and Drizzle + MySQL.
|
|
8
|
+
- The matrix verifies concurrent Hosted Checkout starts create one session,
|
|
9
|
+
expired checkout attempts create a new attempt, webhook handler failures stay
|
|
10
|
+
retryable, duplicate webhook events stay idempotent, and paid status reaches
|
|
11
|
+
the merchant order table.
|
|
12
|
+
- Hardened the TypeORM and Drizzle SQL executors so ORM-specific affected-row
|
|
13
|
+
and row-return shapes are normalized before checkout/webhook safety decisions.
|
|
14
|
+
- Added PostgreSQL and MySQL service-backed CI coverage, and made npm release
|
|
15
|
+
publish only after the ORM matrix passes.
|
|
16
|
+
|
|
17
|
+
## 0.4.24 - 2026-06-20
|
|
18
|
+
|
|
19
|
+
- Split CLI checks into `preflight` for pre-mount setup checks and `verify` for
|
|
20
|
+
full Hosted Checkout plus signed webhook delivery verification, so the
|
|
21
|
+
10-minute guide no longer asks users to verify a webhook route before it
|
|
22
|
+
exists.
|
|
23
|
+
- Made merchant account status fail-closed in readiness checks; only `active`
|
|
24
|
+
and `ready` pass.
|
|
25
|
+
- Reworked Express and FastAPI checkout attempts to support attempt generations,
|
|
26
|
+
expiry/failure recovery, and one active checkout attempt per order enforced by
|
|
27
|
+
a database unique key.
|
|
28
|
+
- Added Express E2E coverage for 50 concurrent checkout starts creating exactly
|
|
29
|
+
one Hosted Checkout session, new attempt creation after expiry, and stale
|
|
30
|
+
non-transactional webhook `processing` recovery.
|
|
31
|
+
- Added FastAPI SQLAlchemy expiry retry coverage and made the adapter configurable
|
|
32
|
+
for existing product order table/column names.
|
|
33
|
+
- Added a FastAPI `AsyncSession` SQLAlchemy adapter and E2E coverage for async
|
|
34
|
+
checkout concurrency, webhook idempotency, and expired-session retry.
|
|
35
|
+
- Marked readiness probe webhooks so generated routes ignore them instead of
|
|
36
|
+
writing manual-review records.
|
|
37
|
+
- Updated sandbox checkout to follow the returned success redirect after
|
|
38
|
+
confirmation and expose metered summary responses with seller-borne Micro /
|
|
39
|
+
Nano accounting fields.
|
|
40
|
+
|
|
3
41
|
## 0.4.23 - 2026-06-20
|
|
4
42
|
|
|
5
43
|
- Made the local SDRP sandbox reject invalid checkout input early, including
|
package/README.md
CHANGED
|
@@ -86,10 +86,11 @@ CLI-first:
|
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
88
|
npm install @siglume/direct-request-payment
|
|
89
|
-
npx siglume-sdrp sandbox --webhook-url http://localhost:3000/payments/webhooks/siglume
|
|
90
|
-
npx siglume-check readiness --sandbox
|
|
91
|
-
npx siglume-check readiness
|
|
92
89
|
npx siglume-sdrp init express --target src/siglume
|
|
90
|
+
# mount the routes, start your app, then:
|
|
91
|
+
npx siglume-sdrp sandbox --webhook-url http://localhost:3000/payments/webhooks/siglume
|
|
92
|
+
npx siglume-check verify --sandbox
|
|
93
|
+
npx siglume-check verify
|
|
93
94
|
```
|
|
94
95
|
|
|
95
96
|
or:
|
|
@@ -97,14 +98,22 @@ or:
|
|
|
97
98
|
```bash
|
|
98
99
|
pip install siglume-direct-request-payment
|
|
99
100
|
siglume-sdrp init fastapi --target app/siglume
|
|
101
|
+
# mount the routes, start your app, then use the npm sandbox for local checkout:
|
|
102
|
+
npx siglume-sdrp sandbox --webhook-url http://localhost:3000/payments/webhooks/siglume
|
|
103
|
+
siglume-check verify --sandbox
|
|
100
104
|
```
|
|
101
105
|
|
|
102
106
|
The sandbox command starts a local Siglume-compatible API that creates fake
|
|
103
107
|
checkout sessions and sends signed webhooks to your product. It never charges a
|
|
104
|
-
wallet; see [SDRP Sandbox](./docs/sandbox.md).
|
|
105
|
-
account, billing, origin, webhook, and Hosted
|
|
106
|
-
|
|
107
|
-
delivery
|
|
108
|
+
wallet; see [SDRP Sandbox](./docs/sandbox.md). `siglume-check preflight`
|
|
109
|
+
checks account, billing, origin, webhook subscription metadata, and Hosted
|
|
110
|
+
Checkout availability before route mounting. `siglume-check verify` additionally
|
|
111
|
+
requires a signed webhook test delivery, so run it only after your webhook route
|
|
112
|
+
is mounted and your app is running.
|
|
113
|
+
|
|
114
|
+
The FastAPI templates include both sync `Session` and async `AsyncSession`
|
|
115
|
+
SQLAlchemy adapters. The sandbox also exposes metered summary endpoints so you
|
|
116
|
+
can verify Micro / Nano seller-borne fee fields before using live credentials.
|
|
108
117
|
|
|
109
118
|
Before implementation, confirm Hosted Checkout readiness in
|
|
110
119
|
[Troubleshooting](./docs/troubleshooting.md#hosted-checkout-readiness). For
|
|
@@ -125,7 +134,7 @@ fulfilling orders.
|
|
|
125
134
|
|
|
126
135
|
| Use case | Recommended path | 10-minute integration path? | Production work still required |
|
|
127
136
|
| --- | --- | --- | --- |
|
|
128
|
-
| EC one-time Standard payment | Hosted Checkout | Yes, with `siglume-
|
|
137
|
+
| EC one-time Standard payment | Hosted Checkout | Yes, with `siglume-sdrp init`, sandbox, and `siglume-check verify` | Product DB adapter, refund/support process, monitoring |
|
|
129
138
|
| Game consumables | Hosted Checkout or agent/API | Conditional | Idempotent entitlement grants, disconnect recovery, Micro / Nano settlement reconciliation and past-due handling |
|
|
130
139
|
| Paid API / AtoA | Direct API or Siglume marketplace tool | Conditional | Request idempotency, buyer auth context, reconciliation |
|
|
131
140
|
| SaaS subscription | Recurring challenge plus raw API | No | Renewal, cancellation, failed renewal, plan-change lifecycle |
|
|
@@ -139,7 +148,7 @@ case `createCheckoutSession(...)` / `getCheckoutSession(...)` raises
|
|
|
139
148
|
`HostedCheckoutNotAvailableError` instead of exposing the raw rollout 404/409.
|
|
140
149
|
Keep the signed `direct_payment.confirmed` webhook as the durable signal, and
|
|
141
150
|
inspect its settlement machine fields before marking any order paid.
|
|
142
|
-
|
|
151
|
+
Run preflight before route mounting and verify after your webhook is live; see
|
|
143
152
|
[Hosted Checkout readiness](./docs/troubleshooting.md#hosted-checkout-readiness).
|
|
144
153
|
|
|
145
154
|
Hosted Checkout is a Siglume-hosted page that turns a "Pay with Siglume" button
|
|
@@ -265,9 +274,11 @@ transaction. Micro / Nano settlement batches are aggregated on-chain after the
|
|
|
265
274
|
weekly or monthly close, or earlier when the fixed amount threshold is reached.
|
|
266
275
|
|
|
267
276
|
Micro Payment and Nano Payment are not separate products you opt into; they are
|
|
268
|
-
amount bands Siglume applies on your behalf.
|
|
269
|
-
|
|
270
|
-
|
|
277
|
+
amount bands Siglume applies on your behalf. Payment initiation is the same
|
|
278
|
+
across amount bands. Fulfillment, revenue recognition, reconciliation, past-due
|
|
279
|
+
handling, and terminal write-off handling differ for Micro / Nano. The full fee
|
|
280
|
+
table and the exact weekly / monthly settlement schedule plus early threshold
|
|
281
|
+
settlement rule are in
|
|
271
282
|
[docs/pricing.md](./docs/pricing.md).
|
|
272
283
|
Provider revenue in the Micro and Nano bands is not settled revenue until the
|
|
273
284
|
aggregated on-chain settlement succeeds. Siglume keeps outstanding failed
|
package/bin/siglume-sdrp.mjs
CHANGED
|
@@ -27,8 +27,14 @@ async function main() {
|
|
|
27
27
|
printHelp();
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
|
-
if (command === "readiness" || command === "doctor") {
|
|
31
|
-
await readiness(parseArgs(args));
|
|
30
|
+
if (command === "readiness" || command === "verify" || command === "doctor") {
|
|
31
|
+
await readiness(parseArgs(args), { requireProbe: true, label: command === "verify" ? "verify" : "readiness" });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (command === "preflight") {
|
|
35
|
+
const options = parseArgs(args);
|
|
36
|
+
options.probe = false;
|
|
37
|
+
await readiness(options, { requireProbe: false, label: "preflight" });
|
|
32
38
|
return;
|
|
33
39
|
}
|
|
34
40
|
if (command === "sandbox") {
|
|
@@ -46,7 +52,9 @@ function printHelp() {
|
|
|
46
52
|
console.log(`Siglume SDRP integration CLI
|
|
47
53
|
|
|
48
54
|
Usage:
|
|
55
|
+
siglume-check preflight --merchant <key> --origin <https://shop.example> --webhook-url <https://api.example/siglume/webhook>
|
|
49
56
|
siglume-check readiness --merchant <key> --origin <https://shop.example> --webhook-url <https://api.example/siglume/webhook>
|
|
57
|
+
siglume-check verify --merchant <key> --origin <https://shop.example> --webhook-url <https://api.example/siglume/webhook>
|
|
50
58
|
siglume-sdrp sandbox --webhook-url <http://localhost:3000/payments/webhooks/siglume>
|
|
51
59
|
siglume-sdrp init express --target src/siglume
|
|
52
60
|
siglume-sdrp init fastapi --target app/siglume
|
|
@@ -60,7 +68,7 @@ Readiness options:
|
|
|
60
68
|
--base-url <url> Siglume API base URL. Defaults to SIGLUME_API_BASE or production.
|
|
61
69
|
--sandbox Use the local sandbox default API base (http://127.0.0.1:8787/v1).
|
|
62
70
|
--no-api Validate local config only; do not call Siglume.
|
|
63
|
-
--no-probe Partial API check only; readiness will not be reported as ready.
|
|
71
|
+
--no-probe Partial API check only; readiness/verify will not be reported as ready. preflight sets this automatically.
|
|
64
72
|
--json Print machine-readable JSON.
|
|
65
73
|
|
|
66
74
|
Sandbox options:
|
|
@@ -101,7 +109,7 @@ function parseArgs(args) {
|
|
|
101
109
|
return out;
|
|
102
110
|
}
|
|
103
111
|
|
|
104
|
-
async function readiness(options) {
|
|
112
|
+
async function readiness(options, mode = { requireProbe: true, label: "readiness" }) {
|
|
105
113
|
const checks = [];
|
|
106
114
|
const sandboxMode = Boolean(options.sandbox) || String(process.env.SIGLUME_ENV || "").toLowerCase() === "sandbox";
|
|
107
115
|
const merchant = options.merchant || process.env.SIGLUME_DIRECT_PAYMENT_MERCHANT || "";
|
|
@@ -133,7 +141,7 @@ async function readiness(options) {
|
|
|
133
141
|
check(checks, "merchant_exists", Boolean(account.merchant), "Run merchant setup before checkout.");
|
|
134
142
|
check(checks, "billing_mandate", Boolean(account.billing_mandate_id), "Complete the merchant billing mandate wallet approval.");
|
|
135
143
|
check(checks, "billing_status_active", activeLike(account.billing_status), `Billing status is ${account.billing_status || "unknown"}; it must be active before accepting payments.`);
|
|
136
|
-
|
|
144
|
+
check(checks, "merchant_status_active", merchantStatusAllowed(account.status), `Merchant status is ${account.status || "unknown"}; it must be active or ready before accepting payments.`);
|
|
137
145
|
} catch (error) {
|
|
138
146
|
check(checks, "merchant_api", false, apiErrorMessage(error, "Could not read the merchant account."));
|
|
139
147
|
}
|
|
@@ -162,8 +170,10 @@ async function readiness(options) {
|
|
|
162
170
|
}
|
|
163
171
|
}
|
|
164
172
|
|
|
165
|
-
if (!options.probe && !hasFailures(checks)) {
|
|
173
|
+
if (!options.probe && mode.requireProbe && !hasFailures(checks)) {
|
|
166
174
|
check(checks, "hosted_checkout_probe", false, "--no-probe skips Hosted Checkout and webhook delivery probes. Remove --no-probe for readiness.");
|
|
175
|
+
} else if (!options.probe && !mode.requireProbe && !hasFailures(checks)) {
|
|
176
|
+
check(checks, "delivery_probe_skipped", true, "preflight only; run siglume-check verify after mounting and starting the webhook route.");
|
|
167
177
|
}
|
|
168
178
|
|
|
169
179
|
if (options.probe && !hasFailures(checks)) {
|
|
@@ -205,7 +215,11 @@ async function readiness(options) {
|
|
|
205
215
|
if (ok && !options.api) {
|
|
206
216
|
console.log("Local config checks passed. API, Hosted Checkout, and webhook delivery readiness were not verified.");
|
|
207
217
|
} else {
|
|
208
|
-
|
|
218
|
+
if (ok && !options.probe && !mode.requireProbe) {
|
|
219
|
+
console.log(`Preflight passed (${sandboxMode ? "sandbox" : "live"}). Mount the routes, start your app, then run siglume-check verify.`);
|
|
220
|
+
} else {
|
|
221
|
+
console.log(ok ? `Ready for 10-minute SDRP integration (${sandboxMode ? "sandbox" : "live"}).` : `Not ready. Fix the FAIL items before ${mode.label === "preflight" ? "mounting checkout" : "opening checkout"}.`);
|
|
222
|
+
}
|
|
209
223
|
}
|
|
210
224
|
}
|
|
211
225
|
if (!ok) {
|
|
@@ -233,7 +247,7 @@ async function init(args) {
|
|
|
233
247
|
}
|
|
234
248
|
await copyDir(from, to, Boolean(parsed.force));
|
|
235
249
|
console.log(`Copied ${framework} SDRP integration files to ${to}`);
|
|
236
|
-
console.log("Wire the exported router into your app, then run siglume-check
|
|
250
|
+
console.log("Wire the exported router into your app, start it, then run siglume-check verify before opening checkout.");
|
|
237
251
|
}
|
|
238
252
|
|
|
239
253
|
async function sandbox(options) {
|
|
@@ -260,6 +274,7 @@ async function sandbox(options) {
|
|
|
260
274
|
subscriptionId: "whsub_sandbox_local",
|
|
261
275
|
sessions: new Map(),
|
|
262
276
|
deliveries: [],
|
|
277
|
+
meteredUsageEvents: [],
|
|
263
278
|
};
|
|
264
279
|
|
|
265
280
|
const server = createServer(async (req, res) => {
|
|
@@ -294,7 +309,7 @@ async function sandbox(options) {
|
|
|
294
309
|
console.log(`SHOP_PUBLIC_ORIGIN=${origin}`);
|
|
295
310
|
console.log(`SHOP_WEBHOOK_URL=${webhookUrl}`);
|
|
296
311
|
console.log("");
|
|
297
|
-
console.log(`Then run: siglume-check
|
|
312
|
+
console.log(`Then run after your app is running: siglume-check verify --sandbox`);
|
|
298
313
|
}
|
|
299
314
|
}
|
|
300
315
|
|
|
@@ -417,6 +432,35 @@ async function handleSandboxRequest(req, res, state, port) {
|
|
|
417
432
|
return;
|
|
418
433
|
}
|
|
419
434
|
|
|
435
|
+
if (req.method === "GET" && url.pathname === "/v1/sdrp/metered/my-summary") {
|
|
436
|
+
sendEnvelope(res, 200, sandboxBuyerMeteredSummary(state, url));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (req.method === "GET" && url.pathname === "/v1/sdrp/metered/provider/summary") {
|
|
441
|
+
sendEnvelope(res, 200, sandboxProviderMeteredSummary(state, url));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (req.method === "GET" && (
|
|
446
|
+
url.pathname === "/v1/sdrp/metered/my-usage-events"
|
|
447
|
+
|| url.pathname === "/v1/sdrp/metered/provider/usage-events"
|
|
448
|
+
)) {
|
|
449
|
+
sendEnvelope(res, 200, {
|
|
450
|
+
items: filterSandboxMeteredUsage(state, url),
|
|
451
|
+
next_cursor: null,
|
|
452
|
+
});
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (req.method === "GET" && url.pathname === "/v1/sdrp/metered/provider/settlement-batches") {
|
|
457
|
+
sendEnvelope(res, 200, {
|
|
458
|
+
items: sandboxSettlementBatches(state, url),
|
|
459
|
+
next_cursor: null,
|
|
460
|
+
});
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
420
464
|
if (req.method === "GET" && url.pathname.startsWith("/pay/")) {
|
|
421
465
|
const sessionId = decodeURIComponent(url.pathname.split("/").pop() || "");
|
|
422
466
|
const session = state.sessions.get(sessionId);
|
|
@@ -444,6 +488,10 @@ async function handleSandboxRequest(req, res, state, port) {
|
|
|
444
488
|
session.requirement_id = `dpr_sandbox_${sessionId}`;
|
|
445
489
|
const event = sandboxPaymentConfirmedEvent(session);
|
|
446
490
|
session.confirmation_event = event;
|
|
491
|
+
const usageEvent = sandboxMeteredUsageEvent(session, event);
|
|
492
|
+
if (usageEvent) {
|
|
493
|
+
state.meteredUsageEvents.unshift(usageEvent);
|
|
494
|
+
}
|
|
447
495
|
await deliverSandboxWebhook(state, event);
|
|
448
496
|
sendEnvelope(res, 200, sandboxConfirmResponse(session, event));
|
|
449
497
|
return;
|
|
@@ -487,6 +535,7 @@ function sandboxEvent({ event_type, data }) {
|
|
|
487
535
|
function sandboxPaymentConfirmedEvent(session) {
|
|
488
536
|
const pricingBand = classifySandboxAmount(session.currency, Number(session.amount_minor));
|
|
489
537
|
const metered = pricingBand === "micro" || pricingBand === "nano";
|
|
538
|
+
const accounting = metered ? sandboxMeteredAccounting(session, pricingBand) : null;
|
|
490
539
|
return sandboxEvent({
|
|
491
540
|
event_type: "direct_payment.confirmed",
|
|
492
541
|
data: {
|
|
@@ -503,10 +552,188 @@ function sandboxPaymentConfirmedEvent(session) {
|
|
|
503
552
|
settlement_status: metered ? "pending_settlement" : "settled",
|
|
504
553
|
chain_receipt_id: metered ? undefined : `chain_sandbox_${session.session_id}`,
|
|
505
554
|
environment: "sandbox",
|
|
555
|
+
...(accounting ? {
|
|
556
|
+
provider_gross_amount_minor: accounting.provider_gross_amount_minor,
|
|
557
|
+
provider_usage_amount_minor: accounting.provider_usage_amount_minor,
|
|
558
|
+
protocol_fee_minor: accounting.protocol_fee_minor,
|
|
559
|
+
provider_receivable_minor: accounting.provider_receivable_minor,
|
|
560
|
+
gross_buyer_debit_minor: accounting.gross_buyer_debit_minor,
|
|
561
|
+
buyer_debit_minor: accounting.buyer_debit_minor,
|
|
562
|
+
rounding_delta_minor: accounting.rounding_delta_minor,
|
|
563
|
+
settlement_threshold_minor: accounting.settlement_threshold_minor,
|
|
564
|
+
} : {}),
|
|
506
565
|
},
|
|
507
566
|
});
|
|
508
567
|
}
|
|
509
568
|
|
|
569
|
+
function sandboxMeteredUsageEvent(session, event) {
|
|
570
|
+
const pricingBand = classifySandboxAmount(session.currency, Number(session.amount_minor));
|
|
571
|
+
if (pricingBand !== "micro" && pricingBand !== "nano") return null;
|
|
572
|
+
const accounting = sandboxMeteredAccounting(session, pricingBand);
|
|
573
|
+
return {
|
|
574
|
+
metered_usage_id: `mu_sandbox_${session.session_id}`,
|
|
575
|
+
created_at: new Date().toISOString(),
|
|
576
|
+
plan_type: pricingBand,
|
|
577
|
+
pricing_band: pricingBand,
|
|
578
|
+
settlement_cadence: pricingBand === "micro" ? "weekly" : "monthly",
|
|
579
|
+
period_start: new Date().toISOString(),
|
|
580
|
+
period_end: null,
|
|
581
|
+
listing_id: session.metadata_jsonb?.listing_id || "sandbox_listing",
|
|
582
|
+
capability_key: session.metadata_jsonb?.capability_key || "sandbox_checkout",
|
|
583
|
+
operation_key: session.metadata_jsonb?.operation_key || "checkout.confirm",
|
|
584
|
+
currency: session.currency,
|
|
585
|
+
token_symbol: session.token_symbol,
|
|
586
|
+
status: "open",
|
|
587
|
+
settlement_batch_id: null,
|
|
588
|
+
buyer_period_ref: `buyer_sandbox:${session.merchant}:${session.token_symbol}:${pricingBand}`,
|
|
589
|
+
requirement_id: session.requirement_id,
|
|
590
|
+
challenge_hash: session.challenge_hash,
|
|
591
|
+
event_id: event.id,
|
|
592
|
+
...accounting,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function sandboxMeteredAccounting(session, pricingBand) {
|
|
597
|
+
const currency = String(session.currency || "").toUpperCase();
|
|
598
|
+
const providerGrossTenths = Number(session.amount_minor) * 10;
|
|
599
|
+
const protocolFeeTenths = sandboxProtocolFeeTenths(currency, pricingBand);
|
|
600
|
+
const providerReceivableTenths = providerGrossTenths - protocolFeeTenths;
|
|
601
|
+
return {
|
|
602
|
+
provider_gross_amount_minor: formatTenths(providerGrossTenths),
|
|
603
|
+
provider_usage_amount_minor: formatTenths(providerGrossTenths),
|
|
604
|
+
protocol_fee_minor: formatTenths(protocolFeeTenths),
|
|
605
|
+
provider_receivable_minor: formatTenths(providerReceivableTenths),
|
|
606
|
+
gross_buyer_debit_minor: formatTenths(providerGrossTenths),
|
|
607
|
+
buyer_debit_minor: formatTenths(providerGrossTenths),
|
|
608
|
+
rounding_delta_minor: "0",
|
|
609
|
+
settlement_threshold_minor: "10000",
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function sandboxProtocolFeeTenths(currency, pricingBand) {
|
|
614
|
+
if (pricingBand === "micro") return currency === "USD" ? 10 : 20;
|
|
615
|
+
return currency === "USD" ? 1 : 2;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function sandboxBuyerMeteredSummary(state, url) {
|
|
619
|
+
const events = filterSandboxMeteredUsage(state, url);
|
|
620
|
+
const batches = sandboxSettlementBatches(state, url);
|
|
621
|
+
return {
|
|
622
|
+
role: "buyer",
|
|
623
|
+
open_periods: sandboxOpenPeriods(events),
|
|
624
|
+
settlement_batches: batches,
|
|
625
|
+
past_due_blocks: [],
|
|
626
|
+
balance_sufficiency: { sufficient: true },
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function sandboxProviderMeteredSummary(state, url) {
|
|
631
|
+
const events = filterSandboxMeteredUsage(state, url);
|
|
632
|
+
const totals = sandboxProviderTotals(events);
|
|
633
|
+
return {
|
|
634
|
+
role: "provider",
|
|
635
|
+
timezone: "UTC",
|
|
636
|
+
filters: Object.fromEntries(url.searchParams.entries()),
|
|
637
|
+
open_periods: sandboxOpenPeriods(events),
|
|
638
|
+
periods: sandboxSettlementBatches(state, url),
|
|
639
|
+
totals,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function filterSandboxMeteredUsage(state, url) {
|
|
644
|
+
const planType = url.searchParams.get("plan_type");
|
|
645
|
+
const tokenSymbol = url.searchParams.get("token_symbol");
|
|
646
|
+
const status = url.searchParams.get("status");
|
|
647
|
+
return state.meteredUsageEvents.filter((event) => {
|
|
648
|
+
if (planType && event.plan_type !== planType) return false;
|
|
649
|
+
if (tokenSymbol && event.token_symbol !== tokenSymbol) return false;
|
|
650
|
+
if (status && event.status !== status) return false;
|
|
651
|
+
return true;
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function sandboxOpenPeriods(events) {
|
|
656
|
+
return Object.values(groupSandboxMeteredEvents(events)).map((group) => {
|
|
657
|
+
const grossTenths = sumTenths(group.events, "provider_gross_amount_minor");
|
|
658
|
+
const protocolFeeTenths = sumTenths(group.events, "protocol_fee_minor");
|
|
659
|
+
const receivableTenths = sumTenths(group.events, "provider_receivable_minor");
|
|
660
|
+
const buyerDebitTenths = sumTenths(group.events, "buyer_debit_minor");
|
|
661
|
+
const thresholdTenths = 10000 * 10;
|
|
662
|
+
const thresholdReached = grossTenths >= thresholdTenths;
|
|
663
|
+
return {
|
|
664
|
+
plan_type: group.plan_type,
|
|
665
|
+
settlement_cadence: group.plan_type === "micro" ? "weekly" : "monthly",
|
|
666
|
+
currency: group.currency,
|
|
667
|
+
token_symbol: group.token_symbol,
|
|
668
|
+
period_start: group.events[group.events.length - 1]?.created_at ?? null,
|
|
669
|
+
period_end: null,
|
|
670
|
+
close_at: null,
|
|
671
|
+
settlement_trigger: thresholdReached ? "amount_threshold" : null,
|
|
672
|
+
settlement_threshold_minor: "10000",
|
|
673
|
+
threshold_reached_at: thresholdReached ? group.events[0]?.created_at ?? null : null,
|
|
674
|
+
provider_gross_amount_minor: formatTenths(grossTenths),
|
|
675
|
+
provider_usage_amount_minor: formatTenths(grossTenths),
|
|
676
|
+
protocol_fee_minor: formatTenths(protocolFeeTenths),
|
|
677
|
+
provider_receivable_minor: formatTenths(receivableTenths),
|
|
678
|
+
buyer_debit_minor: formatTenths(buyerDebitTenths),
|
|
679
|
+
total_unsettled_exposure_minor: formatTenths(grossTenths),
|
|
680
|
+
};
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function sandboxSettlementBatches(state, url) {
|
|
685
|
+
const events = filterSandboxMeteredUsage(state, url);
|
|
686
|
+
return sandboxOpenPeriods(events)
|
|
687
|
+
.filter((period) => period.settlement_trigger === "amount_threshold")
|
|
688
|
+
.map((period) => ({
|
|
689
|
+
settlement_batch_id: `batch_sandbox_${period.plan_type}_${period.currency}_${period.token_symbol}`,
|
|
690
|
+
status: "notice_pending",
|
|
691
|
+
notice_status: "pending",
|
|
692
|
+
not_before_attempt_at: new Date(Date.now() + 72 * 60 * 60 * 1000).toISOString(),
|
|
693
|
+
expected_scheduled_debit_at: new Date(Date.now() + 72 * 60 * 60 * 1000).toISOString(),
|
|
694
|
+
...period,
|
|
695
|
+
}));
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function sandboxProviderTotals(events) {
|
|
699
|
+
return {
|
|
700
|
+
settled_provider_receivable_minor: "0",
|
|
701
|
+
unsettled_provider_receivable_minor: formatTenths(sumTenths(events, "provider_receivable_minor")),
|
|
702
|
+
past_due_provider_receivable_minor: "0",
|
|
703
|
+
terminal_provider_receivable_minor: "0",
|
|
704
|
+
uncollectible_provider_receivable_minor: "0",
|
|
705
|
+
written_off_provider_receivable_minor: "0",
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function groupSandboxMeteredEvents(events) {
|
|
710
|
+
const groups = {};
|
|
711
|
+
for (const event of events) {
|
|
712
|
+
const key = `${event.plan_type}:${event.currency}:${event.token_symbol}`;
|
|
713
|
+
groups[key] ||= {
|
|
714
|
+
plan_type: event.plan_type,
|
|
715
|
+
currency: event.currency,
|
|
716
|
+
token_symbol: event.token_symbol,
|
|
717
|
+
events: [],
|
|
718
|
+
};
|
|
719
|
+
groups[key].events.push(event);
|
|
720
|
+
}
|
|
721
|
+
return groups;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function sumTenths(items, field) {
|
|
725
|
+
return items.reduce((total, item) => total + parseTenths(item[field]), 0);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function parseTenths(value) {
|
|
729
|
+
return Math.round(Number(value || 0) * 10);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function formatTenths(value) {
|
|
733
|
+
if (value % 10 === 0) return String(value / 10);
|
|
734
|
+
return (value / 10).toFixed(1);
|
|
735
|
+
}
|
|
736
|
+
|
|
510
737
|
function sandboxConfirmResponse(session, event) {
|
|
511
738
|
return {
|
|
512
739
|
status: "paid",
|
|
@@ -567,6 +794,9 @@ function sandboxCheckoutHtml(session) {
|
|
|
567
794
|
const body = await response.json();
|
|
568
795
|
document.getElementById("status").textContent = body.data?.status || "failed";
|
|
569
796
|
document.getElementById("output").textContent = JSON.stringify(body, null, 2);
|
|
797
|
+
if (body.data?.redirect_url) {
|
|
798
|
+
window.setTimeout(() => window.location.assign(body.data.redirect_url), 400);
|
|
799
|
+
}
|
|
570
800
|
});
|
|
571
801
|
</script>
|
|
572
802
|
</body>`;
|
|
@@ -781,6 +1011,10 @@ function activeLike(value) {
|
|
|
781
1011
|
return /^(active|ready|current|ok|enabled|paid|complete|completed)$/i.test(String(value || ""));
|
|
782
1012
|
}
|
|
783
1013
|
|
|
1014
|
+
function merchantStatusAllowed(value) {
|
|
1015
|
+
return /^(active|ready)$/i.test(String(value || ""));
|
|
1016
|
+
}
|
|
1017
|
+
|
|
784
1018
|
function includesEventType(eventTypes, eventType) {
|
|
785
1019
|
if (!Array.isArray(eventTypes) || eventTypes.length === 0) return true;
|
|
786
1020
|
return eventTypes.map((item) => String(item)).includes(eventType);
|
|
@@ -812,6 +1046,7 @@ async function checkWebhookDeliveryProbe(checks, merchantClient, { merchant, sub
|
|
|
812
1046
|
subscription_ids: [id],
|
|
813
1047
|
data: {
|
|
814
1048
|
mode: "readiness_probe",
|
|
1049
|
+
readiness_probe: true,
|
|
815
1050
|
merchant,
|
|
816
1051
|
direct_payment_requirement_id: `dpr_readiness_${Date.now()}`,
|
|
817
1052
|
requirement_id: `dpr_readiness_${Date.now()}`,
|
package/dist/index.cjs
CHANGED
|
@@ -85,7 +85,7 @@ var DIRECT_REQUEST_PAYMENT_RECEIPT_KIND = "sdrp_direct_payment";
|
|
|
85
85
|
var DIRECT_REQUEST_PAYMENT_ALLOWANCE_RECEIPT_KIND = "sdrp_direct_payment_allowance";
|
|
86
86
|
var DIRECT_REQUEST_PAYMENT_REFERENCE_TYPE = "sdrp_direct_payment_requirement";
|
|
87
87
|
var DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
88
|
-
var DIRECT_REQUEST_PAYMENT_SDK_VERSION = "0.4.
|
|
88
|
+
var DIRECT_REQUEST_PAYMENT_SDK_VERSION = "0.4.25";
|
|
89
89
|
var DIRECT_REQUEST_PAYMENT_STANDARD_SETTLED_STATUS = "settled";
|
|
90
90
|
var DIRECT_REQUEST_PAYMENT_METERED_ACCEPTED_STATUS = "pending_settlement";
|
|
91
91
|
var DIRECT_REQUEST_PAYMENT_STANDARD_FINALITY = "per_payment_onchain";
|