@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 +19 -0
- package/README.md +22 -8
- package/bin/siglume-sdrp.mjs +267 -0
- 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/merchant-quickstart.md +5 -6
- package/docs/pricing.md +1 -1
- package/docs/quickstart-10-minutes.md +98 -129
- package/docs/troubleshooting.md +8 -0
- package/examples/hosted-checkout-python/pyproject.toml +1 -1
- package/package.json +9 -1
- package/templates/express/README.md +22 -0
- package/templates/express/siglume-order-store.example.ts +53 -0
- package/templates/express/siglume-sdrp-routes.ts +157 -0
- package/templates/fastapi/README.md +22 -0
- package/templates/fastapi/siglume_order_store_example.py +54 -0
- package/templates/fastapi/siglume_sdrp_routes.py +107 -0
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
120
|
+
| Use case | Recommended path | 10-minute integration path? | Production work still required |
|
|
107
121
|
| --- | --- | --- | --- |
|
|
108
|
-
| EC one-time Standard payment | Hosted Checkout | Yes,
|
|
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
|
|
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.
|
|
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";
|