@siglume/direct-request-payment 0.4.18 → 0.4.19

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 CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.19 - 2026-06-20
4
+
5
+ Make the 10-minute integration path a real product-integration path instead of
6
+ a separate demo.
7
+
8
+ - Added npm and PyPI CLI bins `siglume-sdrp` and `siglume-check`.
9
+ - Added `siglume-check readiness` to validate merchant token, merchant key,
10
+ HTTPS origin/webhook configuration, Standard-band probe amount, merchant
11
+ account/billing readiness, and Hosted Checkout availability through an unpaid
12
+ checkout-session probe.
13
+ - Added `siglume-sdrp init express` for npm and `siglume-sdrp init fastapi` for
14
+ npm/PyPI to copy framework-specific checkout/webhook route files into an
15
+ existing product.
16
+ - Added Express and FastAPI integration templates with order-store adapter
17
+ interfaces so teams can wire SDRP into their real order database instead of
18
+ starting from an isolated sample app.
19
+ - Reframed the 10-minute guide around existing-product integration and moved
20
+ the readiness check before any coding.
21
+
3
22
  ## 0.4.18 - 2026-06-19
4
23
 
5
24
  Developer-onboarding cleanup for the v0.4.17 public review.
package/README.md CHANGED
@@ -80,11 +80,25 @@ card-style "instant" checkout for first-time buyers.
80
80
 
81
81
  ## Fast Path
82
82
 
83
- If your merchant account, Hosted Checkout enablement, billing mandate, HTTPS
84
- webhook URL, and buyer test wallet are already ready, use
85
- [10-Minute First Test Payment](./docs/quickstart-10-minutes.md) to connect one
86
- Standard Payment test. That page is intentionally scoped to a first test
87
- payment, not a production launch.
83
+ Use [10-Minute Product Integration](./docs/quickstart-10-minutes.md) to add
84
+ Hosted Checkout routes to an existing Express or FastAPI product. The path is
85
+ CLI-first:
86
+
87
+ ```bash
88
+ npm install @siglume/direct-request-payment
89
+ npx siglume-check readiness
90
+ npx siglume-sdrp init express --target src/siglume
91
+ ```
92
+
93
+ or:
94
+
95
+ ```bash
96
+ pip install siglume-direct-request-payment
97
+ siglume-sdrp init fastapi --target app/siglume
98
+ ```
99
+
100
+ The readiness command checks account, billing, origin, webhook, and Hosted
101
+ Checkout availability before you write checkout code.
88
102
 
89
103
  Before implementation, confirm Hosted Checkout readiness in
90
104
  [Troubleshooting](./docs/troubleshooting.md#hosted-checkout-readiness). For
@@ -103,9 +117,9 @@ fulfilling orders.
103
117
 
104
118
  ## Use-Case Fit
105
119
 
106
- | Use case | Recommended path | 10-minute demo? | Production work still required |
120
+ | Use case | Recommended path | 10-minute integration path? | Production work still required |
107
121
  | --- | --- | --- | --- |
108
- | EC one-time Standard payment | Hosted Checkout | Yes, if prerequisites are ready | Durable order DB, webhook dedupe, refund/support process, monitoring |
122
+ | EC one-time Standard payment | Hosted Checkout | Yes, with `siglume-check readiness` and `siglume-sdrp init` | Refund/support process and monitoring |
109
123
  | Game consumables | Hosted Checkout or agent/API | Conditional | Idempotent entitlement grants, disconnect recovery, Micro / Nano unsettled-risk handling |
110
124
  | Paid API / AtoA | Direct API or Siglume marketplace tool | Conditional | Request idempotency, buyer auth context, reconciliation |
111
125
  | SaaS subscription | Recurring challenge plus raw API | No | Renewal, cancellation, failed renewal, plan-change lifecycle |
@@ -707,7 +721,7 @@ handling and support escalation.
707
721
  ## Documentation
708
722
 
709
723
  - [Merchant quickstart](./docs/merchant-quickstart.md)
710
- - [10-minute first test payment](./docs/quickstart-10-minutes.md)
724
+ - [10-minute product integration](./docs/quickstart-10-minutes.md)
711
725
  - [Payment lifecycle](./docs/payment-lifecycle.md)
712
726
  - [Troubleshooting](./docs/troubleshooting.md)
713
727
  - [API reference](./docs/api-reference.md)
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync } from "node:fs";
4
+ import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ import {
9
+ DirectRequestPaymentMerchantClient,
10
+ HostedCheckoutNotAvailableError,
11
+ SiglumeApiError,
12
+ } from "../dist/index.js";
13
+
14
+ const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
15
+
16
+ main().catch((error) => {
17
+ console.error(`siglume-sdrp: ${error instanceof Error ? error.message : String(error)}`);
18
+ process.exitCode = 1;
19
+ });
20
+
21
+ async function main() {
22
+ loadDotEnv();
23
+ const [command = "help", ...args] = process.argv.slice(2);
24
+ if (command === "help" || command === "--help" || command === "-h") {
25
+ printHelp();
26
+ return;
27
+ }
28
+ if (command === "readiness" || command === "doctor") {
29
+ await readiness(parseArgs(args));
30
+ return;
31
+ }
32
+ if (command === "init") {
33
+ await init(args);
34
+ return;
35
+ }
36
+ throw new Error(`Unknown command: ${command}`);
37
+ }
38
+
39
+ function printHelp() {
40
+ console.log(`Siglume SDRP integration CLI
41
+
42
+ Usage:
43
+ siglume-check readiness --merchant <key> --origin <https://shop.example> --webhook-url <https://api.example/siglume/webhook>
44
+ siglume-sdrp init express --target src/siglume
45
+ siglume-sdrp init fastapi --target app/siglume
46
+
47
+ Readiness options:
48
+ --merchant <key> Merchant key. Defaults to SIGLUME_DIRECT_PAYMENT_MERCHANT.
49
+ --origin <origin> Public shop origin. Defaults to SHOP_PUBLIC_ORIGIN.
50
+ --webhook-url <url> Public webhook URL. Defaults to SHOP_WEBHOOK_URL.
51
+ --currency <JPY|USD> Probe currency. Defaults to SIGLUME_DIRECT_PAYMENT_TEST_CURRENCY or JPY.
52
+ --amount-minor <amount> Standard-band probe amount. Defaults to 501 for JPY, 301 for USD.
53
+ --base-url <url> Siglume API base URL. Defaults to SIGLUME_API_BASE or production.
54
+ --no-api Validate local config only; do not call Siglume.
55
+ --no-probe Call getMerchant only; do not create an unpaid checkout session.
56
+ --json Print machine-readable JSON.
57
+ `);
58
+ }
59
+
60
+ function parseArgs(args) {
61
+ const out = { api: true, probe: true, json: false };
62
+ for (let i = 0; i < args.length; i += 1) {
63
+ const arg = args[i];
64
+ if (arg === "--no-api") {
65
+ out.api = false;
66
+ } else if (arg === "--no-probe") {
67
+ out.probe = false;
68
+ } else if (arg === "--json") {
69
+ out.json = true;
70
+ } else if (arg === "--force") {
71
+ out.force = true;
72
+ } else if (arg.startsWith("--")) {
73
+ const key = arg.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
74
+ const value = args[i + 1];
75
+ if (!value || value.startsWith("--")) {
76
+ throw new Error(`${arg} requires a value.`);
77
+ }
78
+ out[key] = value;
79
+ i += 1;
80
+ } else {
81
+ throw new Error(`Unexpected argument: ${arg}`);
82
+ }
83
+ }
84
+ return out;
85
+ }
86
+
87
+ async function readiness(options) {
88
+ const checks = [];
89
+ const merchant = options.merchant || process.env.SIGLUME_DIRECT_PAYMENT_MERCHANT || "";
90
+ const origin = options.origin || process.env.SHOP_PUBLIC_ORIGIN || "";
91
+ const webhookUrl = options.webhookUrl || process.env.SHOP_WEBHOOK_URL || "";
92
+ const token = process.env.SIGLUME_MERCHANT_AUTH_TOKEN || process.env.SIGLUME_AUTH_TOKEN || "";
93
+ const currency = normalizeCurrency(options.currency || process.env.SIGLUME_DIRECT_PAYMENT_TEST_CURRENCY || "JPY");
94
+ const amountMinor = Number(options.amountMinor || process.env.SIGLUME_DIRECT_PAYMENT_TEST_AMOUNT_MINOR || (currency === "USD" ? 301 : 501));
95
+
96
+ check(checks, "merchant_key", Boolean(merchant), "Set SIGLUME_DIRECT_PAYMENT_MERCHANT or pass --merchant.");
97
+ check(checks, "merchant_token", Boolean(token) && !token.startsWith("cli_"), "Set SIGLUME_MERCHANT_AUTH_TOKEN to a merchant Siglume bearer token, not a cli_ key.");
98
+ check(checks, "shop_origin", isHttpsOrigin(origin), "Set SHOP_PUBLIC_ORIGIN to an https origin, for example https://www.example.com.");
99
+ check(checks, "webhook_url", isHttpsUrl(webhookUrl), "Set SHOP_WEBHOOK_URL to a public https webhook URL.");
100
+ check(checks, "standard_probe_amount", isStandardAmount(currency, amountMinor), "Use a Standard-band probe amount: JPY 501+ or USD 301+ minor units.");
101
+
102
+ if (options.api && !hasFailures(checks)) {
103
+ const merchantClient = new DirectRequestPaymentMerchantClient({
104
+ auth_token: token,
105
+ base_url: options.baseUrl || process.env.SIGLUME_API_BASE,
106
+ });
107
+ try {
108
+ const merchantResponse = await merchantClient.getMerchant(merchant);
109
+ const account = merchantResponse.merchant_account || {};
110
+ check(checks, "merchant_exists", Boolean(account.merchant), "Run merchant setup before checkout.");
111
+ check(checks, "billing_mandate", Boolean(account.billing_mandate_id) || activeLike(account.billing_status), "Complete the merchant billing mandate wallet approval.");
112
+ warnIf(checks, "merchant_status", account.status && !activeLike(account.status), `Merchant status is ${account.status}; confirm it is allowed to accept payments.`);
113
+ warnIf(checks, "billing_status", account.billing_status && !activeLike(account.billing_status), `Billing status is ${account.billing_status}; confirm it is active before accepting payments.`);
114
+ } catch (error) {
115
+ check(checks, "merchant_api", false, apiErrorMessage(error, "Could not read the merchant account."));
116
+ }
117
+
118
+ if (options.probe && !hasFailures(checks)) {
119
+ try {
120
+ const session = await merchantClient.createCheckoutSession({
121
+ merchant,
122
+ amount_minor: amountMinor,
123
+ currency,
124
+ nonce: `sdrp-readiness-${Date.now()}`,
125
+ success_url: `${origin}/siglume-readiness/success`,
126
+ cancel_url: `${origin}/siglume-readiness/cancel`,
127
+ metadata: { source: "siglume-sdrp-readiness" },
128
+ });
129
+ check(checks, "hosted_checkout", Boolean(session.checkout_url && session.challenge_hash), "Hosted Checkout did not return a checkout_url.");
130
+ } catch (error) {
131
+ const message = error instanceof HostedCheckoutNotAvailableError
132
+ ? "Hosted Checkout is not enabled for this merchant account. Ask Siglume to enable it before coding the human checkout path."
133
+ : apiErrorMessage(error, "Hosted Checkout probe failed. Check checkout_allowed_origins, currency, amount, and billing mandate.");
134
+ check(checks, "hosted_checkout", false, message);
135
+ }
136
+ }
137
+ }
138
+
139
+ const ok = !hasFailures(checks);
140
+ if (options.json) {
141
+ console.log(JSON.stringify({ ok, checks }, null, 2));
142
+ } else {
143
+ for (const item of checks) {
144
+ const mark = item.status === "pass" ? "OK" : item.status === "warn" ? "WARN" : "FAIL";
145
+ console.log(`${mark} ${item.name}: ${item.message}`);
146
+ }
147
+ console.log(ok ? "Ready for 10-minute SDRP integration." : "Not ready. Fix the FAIL items before coding checkout.");
148
+ }
149
+ if (!ok) {
150
+ process.exitCode = 1;
151
+ }
152
+ }
153
+
154
+ async function init(args) {
155
+ const framework = args[0];
156
+ const parsed = parseArgs(args.slice(1));
157
+ const target = parsed.target;
158
+ if (!["express", "fastapi"].includes(framework)) {
159
+ throw new Error("init requires framework: express or fastapi.");
160
+ }
161
+ if (!target) {
162
+ throw new Error("init requires --target <directory>.");
163
+ }
164
+ const from = join(rootDir, "templates", framework);
165
+ const to = resolve(process.cwd(), target);
166
+ await copyDir(from, to, Boolean(parsed.force));
167
+ console.log(`Copied ${framework} SDRP integration files to ${to}`);
168
+ console.log("Wire the exported router into your app, then run siglume-check readiness before opening checkout.");
169
+ }
170
+
171
+ async function copyDir(from, to, force) {
172
+ await mkdir(to, { recursive: true });
173
+ for (const entry of await readdir(from)) {
174
+ const src = join(from, entry);
175
+ const dst = join(to, entry);
176
+ const info = await stat(src);
177
+ if (info.isDirectory()) {
178
+ await copyDir(src, dst, force);
179
+ } else {
180
+ if (!force && await exists(dst)) {
181
+ throw new Error(`${dst} already exists. Re-run with --force to overwrite.`);
182
+ }
183
+ await mkdir(dirname(dst), { recursive: true });
184
+ await writeFile(dst, await readFile(src));
185
+ }
186
+ }
187
+ }
188
+
189
+ async function exists(path) {
190
+ try {
191
+ await stat(path);
192
+ return true;
193
+ } catch {
194
+ return false;
195
+ }
196
+ }
197
+
198
+ function loadDotEnv() {
199
+ try {
200
+ const text = readFileSync(resolve(process.cwd(), ".env"), "utf8");
201
+ for (const line of text.split(/\r?\n/)) {
202
+ const trimmed = line.trim();
203
+ if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) continue;
204
+ const [key, ...rest] = trimmed.split("=");
205
+ if (key && !process.env[key]) {
206
+ process.env[key] = rest.join("=").replace(/^["']|["']$/g, "");
207
+ }
208
+ }
209
+ } catch {
210
+ // .env is optional.
211
+ }
212
+ }
213
+
214
+ function check(checks, name, passed, message) {
215
+ checks.push({ name, status: passed ? "pass" : "fail", message: passed ? "ready" : message });
216
+ }
217
+
218
+ function warnIf(checks, name, condition, message) {
219
+ if (condition) {
220
+ checks.push({ name, status: "warn", message });
221
+ }
222
+ }
223
+
224
+ function hasFailures(checks) {
225
+ return checks.some((item) => item.status === "fail");
226
+ }
227
+
228
+ function isHttpsOrigin(value) {
229
+ try {
230
+ const url = new URL(value);
231
+ return url.protocol === "https:" && url.origin === value.replace(/\/$/, "");
232
+ } catch {
233
+ return false;
234
+ }
235
+ }
236
+
237
+ function isHttpsUrl(value) {
238
+ try {
239
+ const url = new URL(value);
240
+ return url.protocol === "https:";
241
+ } catch {
242
+ return false;
243
+ }
244
+ }
245
+
246
+ function normalizeCurrency(value) {
247
+ const currency = String(value || "").toUpperCase();
248
+ if (currency !== "JPY" && currency !== "USD") {
249
+ throw new Error("--currency must be JPY or USD.");
250
+ }
251
+ return currency;
252
+ }
253
+
254
+ function isStandardAmount(currency, amountMinor) {
255
+ return Number.isSafeInteger(amountMinor) && amountMinor >= (currency === "USD" ? 301 : 501);
256
+ }
257
+
258
+ function activeLike(value) {
259
+ return /^(active|ready|current|ok|enabled|paid|complete|completed)$/i.test(String(value || ""));
260
+ }
261
+
262
+ function apiErrorMessage(error, fallback) {
263
+ if (error instanceof SiglumeApiError) {
264
+ return `${fallback} ${error.code} (${error.status}).`;
265
+ }
266
+ return fallback;
267
+ }
package/dist/index.cjs CHANGED
@@ -83,7 +83,7 @@ var DIRECT_REQUEST_PAYMENT_RECEIPT_KIND = "sdrp_direct_payment";
83
83
  var DIRECT_REQUEST_PAYMENT_ALLOWANCE_RECEIPT_KIND = "sdrp_direct_payment_allowance";
84
84
  var DIRECT_REQUEST_PAYMENT_REFERENCE_TYPE = "sdrp_direct_payment_requirement";
85
85
  var DEFAULT_WEBHOOK_TOLERANCE_SECONDS = 300;
86
- var DIRECT_REQUEST_PAYMENT_SDK_VERSION = "0.4.18";
86
+ var DIRECT_REQUEST_PAYMENT_SDK_VERSION = "0.4.19";
87
87
  var DIRECT_REQUEST_PAYMENT_STANDARD_SETTLED_STATUS = "settled";
88
88
  var DIRECT_REQUEST_PAYMENT_METERED_ACCEPTED_STATUS = "pending_settlement";
89
89
  var DIRECT_REQUEST_PAYMENT_STANDARD_FINALITY = "per_payment_onchain";