@openreceive/node 0.4.10 → 0.4.11
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/dist/{chunk-5LIZZ7XG.js → chunk-7WTXLEDH.js} +133 -134
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +32 -28
- package/dist/index.js +8 -2
- package/package.json +2 -2
- package/skills/integrate-openreceive/references/btcpay.md +6 -4
- package/skills/integrate-openreceive/references/django.md +5 -3
- package/skills/integrate-openreceive/references/fastapi.md +6 -4
- package/skills/integrate-openreceive/references/fastify.md +6 -4
- package/skills/integrate-openreceive/references/laravel.md +5 -3
- package/skills/integrate-openreceive/references/next.md +6 -4
- package/skills/integrate-openreceive/references/node.md +6 -4
- package/skills/integrate-openreceive/references/php.md +5 -3
- package/skills/integrate-openreceive/references/rails.md +5 -3
- package/skills/integrate-openreceive/references/woocommerce.md +3 -1
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
isErrorCode,
|
|
4
4
|
isRetryableErrorCode,
|
|
5
|
-
OpenReceiveError
|
|
5
|
+
OpenReceiveError,
|
|
6
|
+
publicErrorBody
|
|
6
7
|
} from "@openreceive/core";
|
|
7
8
|
var WalletPreflightError = class extends Error {
|
|
8
9
|
code;
|
|
@@ -69,7 +70,8 @@ var OPENRECEIVE_ERROR_MESSAGES = {
|
|
|
69
70
|
CONFLICT: "NWC wallet service reported a conflicting request."
|
|
70
71
|
};
|
|
71
72
|
function normalizeNwcWalletError(error) {
|
|
72
|
-
if (error instanceof OpenReceiveError)
|
|
73
|
+
if (error instanceof OpenReceiveError)
|
|
74
|
+
return new OpenReceiveError(publicErrorBody(error.toJSON()));
|
|
73
75
|
const records = collectErrorRecords(error);
|
|
74
76
|
const code = knownErrorCode(error) ?? errorCodeFromRecords(records) ?? (typeof error === "string" ? normalizeNwcErrorCode(error) : void 0) ?? "OTHER";
|
|
75
77
|
const message = errorMessageFromRecords(records, error, code);
|
|
@@ -83,7 +85,7 @@ function normalizeNwcWalletError(error) {
|
|
|
83
85
|
...requestId === void 0 ? {} : { request_id: requestId },
|
|
84
86
|
...details === void 0 ? {} : { details }
|
|
85
87
|
};
|
|
86
|
-
return new OpenReceiveError(body
|
|
88
|
+
return new OpenReceiveError(publicErrorBody(body));
|
|
87
89
|
}
|
|
88
90
|
function knownErrorCode(error) {
|
|
89
91
|
if (error instanceof ReceiveCheckoutValidationError) {
|
|
@@ -309,7 +311,7 @@ function normalizeMakeInvoiceResult(rawResult) {
|
|
|
309
311
|
const expiresAt = parseOptionalInteger(result.expires_at ?? result.expiresAt, "expires_at");
|
|
310
312
|
return {
|
|
311
313
|
invoice,
|
|
312
|
-
payment_hash: paymentHash,
|
|
314
|
+
payment_hash: paymentHash.toLowerCase(),
|
|
313
315
|
amount_msats: amountMsats,
|
|
314
316
|
...createdAt === void 0 ? {} : { created_at: createdAt },
|
|
315
317
|
...expiresAt === void 0 ? {} : { expires_at: expiresAt }
|
|
@@ -331,6 +333,10 @@ function normalizeListTransactionsResult(rawResult) {
|
|
|
331
333
|
const transactions = [];
|
|
332
334
|
let skippedRows = 0;
|
|
333
335
|
for (const rawTransaction of rawTransactions) {
|
|
336
|
+
if (typeof rawTransaction !== "object" || rawTransaction === null || Array.isArray(rawTransaction)) {
|
|
337
|
+
skippedRows += 1;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
334
340
|
try {
|
|
335
341
|
transactions.push(normalizeNwcTransaction(rawTransaction));
|
|
336
342
|
} catch {
|
|
@@ -354,7 +360,7 @@ function normalizeNwcTransaction(rawTransaction) {
|
|
|
354
360
|
if (!/^[0-9a-fA-F]{64}$/.test(paymentHash)) {
|
|
355
361
|
throw new TypeError("payment_hash must be 64 hexadecimal characters");
|
|
356
362
|
}
|
|
357
|
-
normalized.payment_hash = paymentHash;
|
|
363
|
+
normalized.payment_hash = paymentHash.toLowerCase();
|
|
358
364
|
}
|
|
359
365
|
if (result.amount_msats !== void 0 || result.amount !== void 0) {
|
|
360
366
|
normalized.amount_msats = toBigInt(result.amount_msats ?? result.amount, "amount_msats");
|
|
@@ -394,7 +400,7 @@ function normalizeNwcNotification(rawNotification) {
|
|
|
394
400
|
}
|
|
395
401
|
}
|
|
396
402
|
const rawHash = payload.payment_hash ?? payload.paymentHash ?? record.payment_hash ?? record.paymentHash;
|
|
397
|
-
const paymentHash = typeof rawHash === "string" && rawHash.length > 0 ? rawHash : transaction?.payment_hash;
|
|
403
|
+
const paymentHash = typeof rawHash === "string" && rawHash.length > 0 ? rawHash.toLowerCase() : transaction?.payment_hash;
|
|
398
404
|
return {
|
|
399
405
|
type,
|
|
400
406
|
...paymentHash === void 0 ? {} : { payment_hash: paymentHash },
|
|
@@ -485,15 +491,85 @@ function unwrapNwcResult(value) {
|
|
|
485
491
|
import { createRequire } from "module";
|
|
486
492
|
import { pathToFileURL } from "url";
|
|
487
493
|
import { recordOrEmpty as recordOrEmpty2 } from "@openreceive/core";
|
|
494
|
+
|
|
495
|
+
// src/nwc/history-request.ts
|
|
496
|
+
async function historyRequest(client, params, signal) {
|
|
497
|
+
signal.throwIfAborted();
|
|
498
|
+
const content = await client.encrypt(
|
|
499
|
+
client.walletPubkey,
|
|
500
|
+
JSON.stringify({ method: "list_transactions", params })
|
|
501
|
+
);
|
|
502
|
+
signal.throwIfAborted();
|
|
503
|
+
const event = await client.signEvent({
|
|
504
|
+
kind: 23194,
|
|
505
|
+
created_at: Math.floor(Date.now() / 1e3),
|
|
506
|
+
tags: [
|
|
507
|
+
["p", client.walletPubkey],
|
|
508
|
+
["v", client.encryptionType === "nip44_v2" ? "1.0" : "0.0"],
|
|
509
|
+
["encryption", client.encryptionType]
|
|
510
|
+
],
|
|
511
|
+
content
|
|
512
|
+
});
|
|
513
|
+
signal.throwIfAborted();
|
|
514
|
+
return new Promise((resolve, reject) => {
|
|
515
|
+
const controller = new AbortController();
|
|
516
|
+
let done = false;
|
|
517
|
+
let subscription;
|
|
518
|
+
const finish = (error, result) => {
|
|
519
|
+
if (done) return;
|
|
520
|
+
done = true;
|
|
521
|
+
signal.removeEventListener("abort", abort);
|
|
522
|
+
controller.abort();
|
|
523
|
+
subscription?.close();
|
|
524
|
+
if (error === void 0) resolve(result);
|
|
525
|
+
else reject(error);
|
|
526
|
+
};
|
|
527
|
+
const abort = () => finish(signal.reason ?? new Error("Wallet scan cancelled."));
|
|
528
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
529
|
+
if (signal.aborted) {
|
|
530
|
+
abort();
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
try {
|
|
534
|
+
subscription = client.pool.subscribe(
|
|
535
|
+
client.relayUrls,
|
|
536
|
+
{ kinds: [23195], authors: [client.walletPubkey], "#e": [event.id] },
|
|
537
|
+
{
|
|
538
|
+
abort: controller.signal,
|
|
539
|
+
onevent: async (reply) => {
|
|
540
|
+
if (done || reply.pubkey !== client.walletPubkey || !reply.tags.some((tag) => tag[0] === "e" && tag[1] === event.id))
|
|
541
|
+
return;
|
|
542
|
+
try {
|
|
543
|
+
const body = JSON.parse(await client.decrypt(client.walletPubkey, reply.content));
|
|
544
|
+
if (done || signal.aborted) return;
|
|
545
|
+
if (body.error !== void 0 && body.error !== null) finish(body.error);
|
|
546
|
+
else finish(void 0, body.result);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
finish(error);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
);
|
|
553
|
+
if (done) {
|
|
554
|
+
subscription.close();
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
void Promise.any(
|
|
558
|
+
client.pool.publish(client.relayUrls, event, { abort: controller.signal })
|
|
559
|
+
).catch((error) => finish(error));
|
|
560
|
+
} catch (error) {
|
|
561
|
+
finish(error);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/nwc/transport.ts
|
|
488
567
|
var require2 = createRequire(import.meta.url);
|
|
489
|
-
async function callRequiredMethod(client, names, request) {
|
|
568
|
+
async function callRequiredMethod(client, names, request, options) {
|
|
490
569
|
for (const name of names) {
|
|
491
570
|
const method = client[name];
|
|
492
571
|
if (typeof method === "function") {
|
|
493
|
-
return await method.call(
|
|
494
|
-
client,
|
|
495
|
-
request
|
|
496
|
-
);
|
|
572
|
+
return await method.call(client, request, options);
|
|
497
573
|
}
|
|
498
574
|
}
|
|
499
575
|
throw new WalletPreflightError(
|
|
@@ -515,9 +591,10 @@ async function createDefaultAlbyNwcClient(connectionString) {
|
|
|
515
591
|
);
|
|
516
592
|
}
|
|
517
593
|
const NWCClientConstructor = Constructor;
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
594
|
+
const client = new NWCClientConstructor({ nostrWalletConnectUrl: connectionString });
|
|
595
|
+
const receiveClient = client;
|
|
596
|
+
receiveClient.listTransactions = (request, options) => historyRequest(client, request, options?.signal ?? AbortSignal.timeout(1e4));
|
|
597
|
+
return receiveClient;
|
|
521
598
|
}
|
|
522
599
|
function ensureNodeWebSocket() {
|
|
523
600
|
if (globalThis.WebSocket !== void 0) return;
|
|
@@ -555,7 +632,6 @@ var AlbyNwcReceiveClient = class {
|
|
|
555
632
|
#clientFactory;
|
|
556
633
|
#preflightSummary;
|
|
557
634
|
#preflightPromise;
|
|
558
|
-
#requirePreflight;
|
|
559
635
|
#logger;
|
|
560
636
|
#allowSpendCapableWallet;
|
|
561
637
|
#spendCapabilityWarningDelayMs;
|
|
@@ -572,7 +648,6 @@ var AlbyNwcReceiveClient = class {
|
|
|
572
648
|
};
|
|
573
649
|
this.#client = options.client;
|
|
574
650
|
this.#clientFactory = options.clientFactory;
|
|
575
|
-
this.#requirePreflight = options.requirePreflight ?? true;
|
|
576
651
|
this.#logger = options.logger;
|
|
577
652
|
this.#allowSpendCapableWallet = options.allowSpendCapableWallet ?? false;
|
|
578
653
|
this.#spendCapabilityWarningDelayMs = options.spendCapabilityWarningDelayMs ?? 0;
|
|
@@ -729,7 +804,7 @@ var AlbyNwcReceiveClient = class {
|
|
|
729
804
|
});
|
|
730
805
|
return result;
|
|
731
806
|
}
|
|
732
|
-
async listTransactions(request) {
|
|
807
|
+
async listTransactions(request, options) {
|
|
733
808
|
await this.ensurePreflight();
|
|
734
809
|
validateListTransactionsRequest(request);
|
|
735
810
|
this.#log(
|
|
@@ -752,7 +827,8 @@ var AlbyNwcReceiveClient = class {
|
|
|
752
827
|
rawResult = await callRequiredMethod(
|
|
753
828
|
await this.getClient(),
|
|
754
829
|
["listTransactions", "list_transactions"],
|
|
755
|
-
toNip47ListTransactionsParams(request)
|
|
830
|
+
toNip47ListTransactionsParams(request),
|
|
831
|
+
options
|
|
756
832
|
);
|
|
757
833
|
} catch (error) {
|
|
758
834
|
const normalized = normalizeNwcWalletError(error);
|
|
@@ -909,7 +985,7 @@ var AlbyNwcReceiveClient = class {
|
|
|
909
985
|
await client?.close?.();
|
|
910
986
|
}
|
|
911
987
|
async ensurePreflight() {
|
|
912
|
-
if (
|
|
988
|
+
if (this.#preflightSummary !== void 0) return;
|
|
913
989
|
this.#preflightPromise ??= this.preflight();
|
|
914
990
|
try {
|
|
915
991
|
await this.#preflightPromise;
|
|
@@ -1740,6 +1816,7 @@ function fixedFloatAvailabilityMessage(reason) {
|
|
|
1740
1816
|
|
|
1741
1817
|
// src/swap/fixedfloat-transport.ts
|
|
1742
1818
|
import { createHmac } from "crypto";
|
|
1819
|
+
import { isRecord } from "@openreceive/core";
|
|
1743
1820
|
var FixedFloatApiError = class _FixedFloatApiError extends Error {
|
|
1744
1821
|
path;
|
|
1745
1822
|
kind;
|
|
@@ -1876,22 +1953,31 @@ var FixedFloatTransport = class {
|
|
|
1876
1953
|
return parsed.data;
|
|
1877
1954
|
}
|
|
1878
1955
|
logApiRequest(path, body = {}) {
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1956
|
+
try {
|
|
1957
|
+
this.apiRequestLogger?.({
|
|
1958
|
+
provider: this.provider,
|
|
1959
|
+
path,
|
|
1960
|
+
has_body: Object.keys(body).length > 0,
|
|
1961
|
+
has_token: body.token != null
|
|
1962
|
+
});
|
|
1963
|
+
} catch {
|
|
1964
|
+
}
|
|
1884
1965
|
}
|
|
1885
1966
|
logApiResponse(input) {
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1967
|
+
const pairCount = isRecord(input.data) ? input.data.pair_count : void 0;
|
|
1968
|
+
try {
|
|
1969
|
+
this.apiResponseLogger?.({
|
|
1970
|
+
provider: this.provider,
|
|
1971
|
+
path: input.path,
|
|
1972
|
+
status: input.status,
|
|
1973
|
+
ok: input.ok,
|
|
1974
|
+
code: typeof input.code === "number" ? input.code : void 0,
|
|
1975
|
+
has_data: input.data != null,
|
|
1976
|
+
items: Array.isArray(input.data) ? input.data.length : void 0,
|
|
1977
|
+
pair_count: typeof pairCount === "number" ? pairCount : void 0
|
|
1978
|
+
});
|
|
1979
|
+
} catch {
|
|
1980
|
+
}
|
|
1895
1981
|
}
|
|
1896
1982
|
};
|
|
1897
1983
|
function formatFixedFloatApiErrorMessage(path, status, msg) {
|
|
@@ -2380,7 +2466,8 @@ function providerIdFromUri(uri) {
|
|
|
2380
2466
|
}
|
|
2381
2467
|
|
|
2382
2468
|
// src/service/logging.ts
|
|
2383
|
-
import {
|
|
2469
|
+
import { isSensitiveLogKey, sanitizeLogValue } from "@openreceive/core";
|
|
2470
|
+
import { isSensitiveLogKey as isSensitiveLogKey2, redactSecrets } from "@openreceive/core";
|
|
2384
2471
|
function emitLog(options, level, event, message, fields = {}) {
|
|
2385
2472
|
emitEvent(options, {
|
|
2386
2473
|
level,
|
|
@@ -2426,113 +2513,25 @@ function sanitizeEvent(entry) {
|
|
|
2426
2513
|
}
|
|
2427
2514
|
return clean;
|
|
2428
2515
|
}
|
|
2429
|
-
function sanitizeLogValue(value) {
|
|
2430
|
-
if (typeof value === "string") return redactSecrets(value);
|
|
2431
|
-
if (Array.isArray(value)) return value.map(sanitizeLogValue);
|
|
2432
|
-
if (typeof value !== "object" || value === null) return value;
|
|
2433
|
-
const clean = {};
|
|
2434
|
-
for (const [key, nested] of Object.entries(value)) {
|
|
2435
|
-
if (isSensitiveLogKey(key)) {
|
|
2436
|
-
clean[key] = "[REDACTED]";
|
|
2437
|
-
} else {
|
|
2438
|
-
clean[key] = sanitizeLogValue(nested);
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
|
-
return clean;
|
|
2442
|
-
}
|
|
2443
|
-
function isSensitiveLogKey(key) {
|
|
2444
|
-
if (/_present$/i.test(key)) return false;
|
|
2445
|
-
return /secret|token|authorization|cookie|nwc|dsn|preimage|invoice|bolt11|swap_?data|(?:private|api)[_-]?key|^key$|api[_-]?sign/i.test(
|
|
2446
|
-
key
|
|
2447
|
-
);
|
|
2448
|
-
}
|
|
2449
|
-
function redactSecrets(value) {
|
|
2450
|
-
return value.replace(/nostr\+walletconnect:[^\s"'`<>]+/g, "[REDACTED_NWC]").replace(/lightning\+swapconnect:[^\s"'`<>]+/g, "[REDACTED_LSC]").replace(/([?&](?:token|secret|key)=)[^&\s"'`<>]+/gi, "$1[REDACTED]");
|
|
2451
|
-
}
|
|
2452
2516
|
function summarizeSwapProviderApiRequest(entry) {
|
|
2453
|
-
|
|
2454
|
-
return compact3({
|
|
2517
|
+
return {
|
|
2455
2518
|
provider: entry.provider,
|
|
2456
2519
|
path: entry.path,
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
to_ccy: optionalLogString(body?.toCcy),
|
|
2461
|
-
amount: optionalLogString(body?.amount) ?? optionalLogNumber(body?.amount)
|
|
2462
|
-
});
|
|
2520
|
+
has_body: entry.has_body,
|
|
2521
|
+
has_token: entry.has_token
|
|
2522
|
+
};
|
|
2463
2523
|
}
|
|
2464
2524
|
function summarizeSwapProviderApiResponse(entry) {
|
|
2465
|
-
|
|
2525
|
+
return {
|
|
2466
2526
|
provider: entry.provider,
|
|
2467
2527
|
path: entry.path,
|
|
2468
2528
|
status: entry.status,
|
|
2469
|
-
ok: entry.ok
|
|
2529
|
+
ok: entry.ok,
|
|
2530
|
+
code: entry.code,
|
|
2531
|
+
has_data: entry.has_data,
|
|
2532
|
+
items: entry.items,
|
|
2533
|
+
pair_count: entry.pair_count
|
|
2470
2534
|
};
|
|
2471
|
-
if (entry.code !== void 0 && entry.code !== null) summary.code = entry.code;
|
|
2472
|
-
const msg = optionalLogString(entry.msg);
|
|
2473
|
-
if (msg !== void 0 && msg !== "OK") summary.msg = msg;
|
|
2474
|
-
const data = entry.data;
|
|
2475
|
-
if (Array.isArray(data)) {
|
|
2476
|
-
summary.items = data.length;
|
|
2477
|
-
return summary;
|
|
2478
|
-
}
|
|
2479
|
-
if (!isRecord(data)) return summary;
|
|
2480
|
-
const pairCount = optionalLogNumber(data.pair_count);
|
|
2481
|
-
if (pairCount !== void 0) {
|
|
2482
|
-
summary.pair_count = pairCount;
|
|
2483
|
-
return summary;
|
|
2484
|
-
}
|
|
2485
|
-
const reference = optionalLogString(data.id);
|
|
2486
|
-
const orderStatus = optionalLogString(data.status);
|
|
2487
|
-
if (reference !== void 0) summary.reference = reference;
|
|
2488
|
-
if (orderStatus !== void 0) summary.order_status = orderStatus;
|
|
2489
|
-
const from = summarizeSwapProviderSide(data.from);
|
|
2490
|
-
const to = summarizeSwapProviderSide(data.to);
|
|
2491
|
-
if (from !== void 0) summary.from = from;
|
|
2492
|
-
if (to !== void 0) summary.to = to;
|
|
2493
|
-
if (isRecord(data.time)) {
|
|
2494
|
-
const left = optionalLogNumber(data.time.left);
|
|
2495
|
-
if (left !== void 0) summary.left = left;
|
|
2496
|
-
}
|
|
2497
|
-
if (isRecord(data.emergency)) {
|
|
2498
|
-
const choice = optionalLogString(data.emergency.choice);
|
|
2499
|
-
if (choice !== void 0 && choice !== "NONE") summary.emergency = choice;
|
|
2500
|
-
const statuses = Array.isArray(data.emergency.status) ? data.emergency.status.filter((item) => typeof item === "string" && item.length > 0).map((item) => item.toUpperCase()) : [];
|
|
2501
|
-
if (statuses.length > 0) summary.emergency_status = statuses.join(",");
|
|
2502
|
-
const repeat = data.emergency.repeat;
|
|
2503
|
-
if (repeat === true || repeat === "1" || repeat === 1) summary.emergency_repeat = true;
|
|
2504
|
-
}
|
|
2505
|
-
if (isRecord(data.from) && isRecord(data.from.tx)) {
|
|
2506
|
-
const received = optionalLogString(data.from.tx.amount);
|
|
2507
|
-
if (received !== void 0) summary.deposit_received = received;
|
|
2508
|
-
}
|
|
2509
|
-
if (isRecord(data.back)) {
|
|
2510
|
-
const refundAmount = optionalLogString(data.back.amount);
|
|
2511
|
-
if (refundAmount !== void 0) summary.refund_amount = refundAmount;
|
|
2512
|
-
}
|
|
2513
|
-
if (reference === void 0) {
|
|
2514
|
-
const fromRecord = isRecord(data.from) ? data.from : void 0;
|
|
2515
|
-
const toRecord = isRecord(data.to) ? data.to : void 0;
|
|
2516
|
-
const fromAmount = optionalLogString(fromRecord?.amount) ?? optionalLogString(data.fromAmount);
|
|
2517
|
-
const toAmount = optionalLogString(toRecord?.amount) ?? optionalLogString(data.toAmount);
|
|
2518
|
-
if (fromAmount !== void 0) summary.from_amount = fromAmount;
|
|
2519
|
-
if (toAmount !== void 0) summary.to_amount = toAmount;
|
|
2520
|
-
}
|
|
2521
|
-
return summary;
|
|
2522
|
-
}
|
|
2523
|
-
function summarizeSwapProviderSide(side) {
|
|
2524
|
-
if (!isRecord(side)) return void 0;
|
|
2525
|
-
const code = optionalLogString(side.code) ?? optionalLogString(side.coin);
|
|
2526
|
-
const amount = optionalLogString(side.amount);
|
|
2527
|
-
if (code === void 0 && amount === void 0) return void 0;
|
|
2528
|
-
if (code !== void 0 && amount !== void 0) return `${code} ${amount}`;
|
|
2529
|
-
return code ?? amount;
|
|
2530
|
-
}
|
|
2531
|
-
function optionalLogString(value) {
|
|
2532
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
2533
|
-
}
|
|
2534
|
-
function optionalLogNumber(value) {
|
|
2535
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2536
2535
|
}
|
|
2537
2536
|
|
|
2538
2537
|
export {
|
|
@@ -2555,7 +2554,7 @@ export {
|
|
|
2555
2554
|
createNwcEndpointLogger,
|
|
2556
2555
|
summarizeReconcilePass,
|
|
2557
2556
|
sanitizeEvent,
|
|
2558
|
-
redactSecrets,
|
|
2559
2557
|
summarizeSwapProviderApiRequest,
|
|
2560
|
-
summarizeSwapProviderApiResponse
|
|
2558
|
+
summarizeSwapProviderApiResponse,
|
|
2559
|
+
redactSecrets
|
|
2561
2560
|
};
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WalletCapabilitySummary, ReceiveNwcClient, NwcTransaction, ParsedNwcConnection, RedactedNwcConnection, MakeInvoiceRequest, MakeInvoiceResult, ListTransactionsRequest, ListTransactionsResult, ErrorCode, ErrorBody, RateQuote, SourcedPriceProvider, SimplePriceFetch, PaidPayment, PaymentCheck, BtcFiatRateMapWithSource, MoneyAmount, CachedPriceFeed } from '@openreceive/core';
|
|
1
|
+
import { WalletCapabilitySummary, ReceiveNwcClient, NwcTransaction, ParsedNwcConnection, RedactedNwcConnection, MakeInvoiceRequest, MakeInvoiceResult, ListTransactionsRequest, ListTransactionsResult, ErrorCode, ErrorBody, RateQuote, SourcedPriceProvider, SimplePriceFetch, PaidPayment, PaymentCheck, PaymentScanWindow, PaymentScanSlice, BtcFiatRateMapWithSource, MoneyAmount, CachedPriceFeed } from '@openreceive/core';
|
|
2
2
|
export { ErrorBody, ErrorCode, NwcTransaction, OpenReceiveError, PaidPayment, PaymentCheck, RateQuote, ReceiveNwcClient } from '@openreceive/core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -12,8 +12,12 @@ interface AlbyNwcCompatibleClient {
|
|
|
12
12
|
getWalletServiceInfo?: () => Promise<unknown>;
|
|
13
13
|
makeInvoice?: (request: Record<string, unknown>) => Promise<unknown>;
|
|
14
14
|
make_invoice?: (request: Record<string, unknown>) => Promise<unknown>;
|
|
15
|
-
listTransactions?: (request: Record<string, unknown
|
|
16
|
-
|
|
15
|
+
listTransactions?: (request: Record<string, unknown>, options?: {
|
|
16
|
+
readonly signal?: AbortSignal;
|
|
17
|
+
}) => Promise<unknown>;
|
|
18
|
+
list_transactions?: (request: Record<string, unknown>, options?: {
|
|
19
|
+
readonly signal?: AbortSignal;
|
|
20
|
+
}) => Promise<unknown>;
|
|
17
21
|
subscribeNotifications?: (callback: (notification: unknown) => void, notificationTypes?: string[]) => unknown;
|
|
18
22
|
close?: () => Promise<void> | void;
|
|
19
23
|
}
|
|
@@ -97,7 +101,6 @@ interface AlbyNwcReceiveClientOptions {
|
|
|
97
101
|
connectionString: string;
|
|
98
102
|
client?: AlbyNwcCompatibleClient;
|
|
99
103
|
clientFactory?: AlbyNwcClientFactory;
|
|
100
|
-
requirePreflight?: boolean;
|
|
101
104
|
logger?: NwcEndpointLogger;
|
|
102
105
|
/**
|
|
103
106
|
* Explicit override: boot even when the connection advertises spend methods
|
|
@@ -128,7 +131,9 @@ declare class AlbyNwcReceiveClient implements ReceiveNwcClient {
|
|
|
128
131
|
constructor(options: AlbyNwcReceiveClientOptions);
|
|
129
132
|
preflight(): Promise<WalletCapabilitySummary>;
|
|
130
133
|
makeInvoice(request: MakeInvoiceRequest): Promise<MakeInvoiceResult>;
|
|
131
|
-
listTransactions(request: ListTransactionsRequest
|
|
134
|
+
listTransactions(request: ListTransactionsRequest, options?: {
|
|
135
|
+
readonly signal?: AbortSignal;
|
|
136
|
+
}): Promise<ListTransactionsResult>;
|
|
132
137
|
/**
|
|
133
138
|
* Opt-in NWC-02 notification subscription, limited to `payment_received`.
|
|
134
139
|
* Notifications are authenticated wallet data: the handler receives the
|
|
@@ -279,32 +284,23 @@ interface SwapOrder {
|
|
|
279
284
|
readonly fee?: SwapFee;
|
|
280
285
|
readonly raw?: unknown;
|
|
281
286
|
}
|
|
282
|
-
/**
|
|
283
|
-
* A single raw provider API response, surfaced for server-side observability.
|
|
284
|
-
* Carries the HTTP status and the parsed `{code, msg, data}` envelope. Emitted
|
|
285
|
-
* through the service's sanitizing log sink, so any nested secret (e.g. a
|
|
286
|
-
* FixedFloat order token) is redacted before it reaches a log line.
|
|
287
|
-
*/
|
|
287
|
+
/** Allowlisted metadata, sanitized before invoking any diagnostic sink. */
|
|
288
288
|
interface SwapProviderApiResponseLog {
|
|
289
289
|
readonly provider: string;
|
|
290
290
|
readonly path: string;
|
|
291
291
|
readonly status: number;
|
|
292
292
|
readonly ok: boolean;
|
|
293
|
-
readonly code
|
|
294
|
-
readonly
|
|
295
|
-
readonly
|
|
293
|
+
readonly code?: number;
|
|
294
|
+
readonly has_data: boolean;
|
|
295
|
+
readonly items?: number;
|
|
296
|
+
readonly pair_count?: number;
|
|
296
297
|
}
|
|
297
|
-
/**
|
|
298
|
-
* A single outbound provider API request, surfaced for server-side observability
|
|
299
|
-
* alongside {@link SwapProviderApiResponseLog}. Carries the request path and body.
|
|
300
|
-
* Emitted through the service's sanitizing log sink, so any secret in the body
|
|
301
|
-
* (e.g. a FixedFloat order token on status/refund calls) is redacted; provider
|
|
302
|
-
* auth headers are never included here.
|
|
303
|
-
*/
|
|
298
|
+
/** Request bodies, credentials, invoices and addresses never enter this hook. */
|
|
304
299
|
interface SwapProviderApiRequestLog {
|
|
305
300
|
readonly provider: string;
|
|
306
301
|
readonly path: string;
|
|
307
|
-
readonly
|
|
302
|
+
readonly has_body: boolean;
|
|
303
|
+
readonly has_token: boolean;
|
|
308
304
|
}
|
|
309
305
|
interface SwapProvider {
|
|
310
306
|
readonly name: string;
|
|
@@ -314,15 +310,13 @@ interface SwapProvider {
|
|
|
314
310
|
attachSwapCache?(cache: TransientSwapCache): void;
|
|
315
311
|
/**
|
|
316
312
|
* Attach a sink for outbound provider API requests, mirroring
|
|
317
|
-
* {@link attachApiResponseLogger}.
|
|
318
|
-
*
|
|
319
|
-
* are redacted. Providers that make no remote calls may omit this.
|
|
313
|
+
* {@link attachApiResponseLogger}. Emit only the allowlisted metadata;
|
|
314
|
+
* diagnostics must not affect payment behavior. Providers without remote calls may omit this.
|
|
320
315
|
*/
|
|
321
316
|
attachApiRequestLogger?(log: (entry: SwapProviderApiRequestLog) => void): void;
|
|
322
317
|
/**
|
|
323
|
-
* Attach a sink for
|
|
324
|
-
*
|
|
325
|
-
* remote calls may omit this.
|
|
318
|
+
* Attach a sink for allowlisted response metadata, never raw envelopes.
|
|
319
|
+
* Providers without remote calls may omit this.
|
|
326
320
|
*/
|
|
327
321
|
attachApiResponseLogger?(log: (entry: SwapProviderApiResponseLog) => void): void;
|
|
328
322
|
/**
|
|
@@ -547,6 +541,8 @@ interface Checkout {
|
|
|
547
541
|
readonly bolt11: string;
|
|
548
542
|
readonly amountMsats: number;
|
|
549
543
|
readonly createdAt: number;
|
|
544
|
+
/** Missing on legacy snapshots; only wallet timestamps narrow history scans. */
|
|
545
|
+
readonly createdAtSource?: "wallet" | "host";
|
|
550
546
|
readonly expiresAt: number;
|
|
551
547
|
readonly fiatQuote: RateQuote | null;
|
|
552
548
|
}
|
|
@@ -705,6 +701,14 @@ interface OpenReceive {
|
|
|
705
701
|
}>;
|
|
706
702
|
createCheckout(input: CreateCheckoutRequest): Promise<Checkout>;
|
|
707
703
|
reconcilePayments(input: ReconcilePaymentsRequest): Promise<readonly PaymentCheck[]>;
|
|
704
|
+
/** Bounded durable history slice used by repository-backed HTTP and workers. */
|
|
705
|
+
scanPaymentSlice(input: {
|
|
706
|
+
window: PaymentScanWindow;
|
|
707
|
+
maxPages?: number;
|
|
708
|
+
deadline?: number;
|
|
709
|
+
signal?: AbortSignal;
|
|
710
|
+
onFinality?: (check: PaymentCheck) => Promise<void>;
|
|
711
|
+
}): Promise<PaymentScanSlice>;
|
|
708
712
|
/**
|
|
709
713
|
* Opt-in NWC-02 notifications: subscribe to wallet `payment_received`
|
|
710
714
|
* notifications. Notifications are authenticated wallet data — a payload
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
summarizeReconcilePass,
|
|
20
20
|
summarizeSwapProviderApiRequest,
|
|
21
21
|
summarizeSwapProviderApiResponse
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-7WTXLEDH.js";
|
|
23
23
|
|
|
24
24
|
// src/index.ts
|
|
25
25
|
import { OpenReceiveError as OpenReceiveError2 } from "@openreceive/core";
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
OpenReceiveError,
|
|
34
34
|
parseNwcUri,
|
|
35
35
|
reconcilePaymentAttempts,
|
|
36
|
+
scanPaymentSlice,
|
|
36
37
|
unixSeconds
|
|
37
38
|
} from "@openreceive/core";
|
|
38
39
|
|
|
@@ -765,6 +766,7 @@ async function createCheckout(context, request) {
|
|
|
765
766
|
bolt11: walletInvoice.invoice,
|
|
766
767
|
amountMsats: toSafeInteger(walletInvoice.amount_msats, "amount_msats"),
|
|
767
768
|
createdAt,
|
|
769
|
+
createdAtSource: walletInvoice.created_at === void 0 ? "host" : "wallet",
|
|
768
770
|
expiresAt,
|
|
769
771
|
fiatQuote: resolved.fiatQuote
|
|
770
772
|
};
|
|
@@ -1184,11 +1186,14 @@ function parsePayInAsset(value) {
|
|
|
1184
1186
|
return value;
|
|
1185
1187
|
}
|
|
1186
1188
|
function parseRefundAddress(value, payInAsset) {
|
|
1189
|
+
if (!isSwapPayInAsset(payInAsset)) {
|
|
1190
|
+
throw serviceError(503, "INTERNAL", "Swap recovery requires a supported pay-in asset/network.");
|
|
1191
|
+
}
|
|
1187
1192
|
const normalized = value.trim();
|
|
1188
1193
|
if (normalized.length === 0 || normalized.length > 300) {
|
|
1189
1194
|
throw serviceError(400, "INVALID_REQUEST", "refundAddress is invalid.");
|
|
1190
1195
|
}
|
|
1191
|
-
if (
|
|
1196
|
+
if (!isValidSwapAddressForPayInAsset(payInAsset, normalized)) {
|
|
1192
1197
|
throw serviceError(
|
|
1193
1198
|
400,
|
|
1194
1199
|
"INVALID_REQUEST",
|
|
@@ -1347,6 +1352,7 @@ async function createOpenReceive(supplied = {}) {
|
|
|
1347
1352
|
};
|
|
1348
1353
|
const service = {
|
|
1349
1354
|
priceCurrencies,
|
|
1355
|
+
scanPaymentSlice: (input) => scanPaymentSlice({ ...input, client, clock }),
|
|
1350
1356
|
prepareCheckout: (input) => prepareCheckout(context, input),
|
|
1351
1357
|
createCheckout: (input) => createCheckout(context, input),
|
|
1352
1358
|
reconcilePayments: async (input) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openreceive/node",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
4
4
|
"description": "Accept Bitcoin Lightning payments in Node.js with your own wallet and optional USDT, USDC, SOL and ETH swaps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@getalby/sdk": "^8.0.3",
|
|
20
|
-
"@openreceive/core": "0.4.
|
|
20
|
+
"@openreceive/core": "0.4.11"
|
|
21
21
|
},
|
|
22
22
|
"bin": {
|
|
23
23
|
"openreceive": "./bin/openreceive.mjs"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenReceive agent directions (BTCPay Server)
|
|
2
2
|
|
|
3
|
-
These directions describe OpenReceive 0.4.
|
|
3
|
+
These directions describe OpenReceive 0.4.11.
|
|
4
4
|
|
|
5
5
|
Connect a BTCPay Server store to a receive-only NWC wallet with the OpenReceive
|
|
6
6
|
plugin, and optionally let payers pay BTCPay invoices with USDT, USDC, ETH or
|
|
@@ -119,6 +119,8 @@ enough; drop the `.md` for the same page a person would read.
|
|
|
119
119
|
Questions, or a problem with the plugin itself:
|
|
120
120
|
https://openreceive.org/contact
|
|
121
121
|
|
|
122
|
+
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
123
|
+
|
|
122
124
|
---
|
|
123
125
|
|
|
124
126
|
## The quickstart, in full
|
|
@@ -157,9 +159,9 @@ invoices, checkout, webhooks and Greenfield API are the host.
|
|
|
157
159
|
|
|
158
160
|
In BTCPay, open **Server Settings → Plugins**, search the plugin directory
|
|
159
161
|
for **OpenReceive**, click **Install**, and restart BTCPay when prompted.
|
|
160
|
-
BTCPay creates the plugin's
|
|
161
|
-
`BTCPayServer.Plugins.OpenReceive`) in its own
|
|
162
|
-
else is created.
|
|
162
|
+
BTCPay creates the plugin's two tables (`openreceive_invoices` and
|
|
163
|
+
`openreceive_swaps`, schema `BTCPayServer.Plugins.OpenReceive`) in its own
|
|
164
|
+
Postgres at startup; nothing else is created.
|
|
163
165
|
|
|
164
166
|
To build the plugin from source instead, follow
|
|
165
167
|
[the .NET workspace README](https://github.com/OpenReceive/openreceive/blob/master/packages/dotnet/README.md).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenReceive agent directions (Django)
|
|
2
2
|
|
|
3
|
-
These directions describe OpenReceive 0.4.
|
|
3
|
+
These directions describe OpenReceive 0.4.11.
|
|
4
4
|
|
|
5
5
|
Add OpenReceive to a Django project — the app you are already working in. You
|
|
6
6
|
do not need a copy of the OpenReceive source: the Python package is on PyPI
|
|
@@ -130,7 +130,7 @@ itself, and they hold for every integration.
|
|
|
130
130
|
a placeholder that allows everything (`manage.py check` warns
|
|
131
131
|
`openreceive.W002` while it is set) — replace it with this app's real
|
|
132
132
|
ownership check, same as `on_paid`.
|
|
133
|
-
- `on_paid` must be idempotent.
|
|
133
|
+
- `on_paid` must be idempotent. Its database fulfillment commits once per `reference` — your order
|
|
134
134
|
id, one per thing you fulfill, created before checkout, kept across retries,
|
|
135
135
|
never reused. A fresh id per page load lets one order be paid twice.
|
|
136
136
|
- Receive-only NWC is required; a spend-capable code fails closed at boot unless
|
|
@@ -307,6 +307,8 @@ enough; drop the `.md` for the same page a person would read.
|
|
|
307
307
|
Questions, or a problem with the library itself:
|
|
308
308
|
https://openreceive.org/contact
|
|
309
309
|
|
|
310
|
+
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
311
|
+
|
|
310
312
|
---
|
|
311
313
|
|
|
312
314
|
## The quickstart, in full
|
|
@@ -480,7 +482,7 @@ The host class needs three things: authorization, the trusted price, and
|
|
|
480
482
|
fulfillment. All three receive the `reference` — a string you choose, and the
|
|
481
483
|
fulfillment identity: your order id, one per thing you fulfill, created before
|
|
482
484
|
checkout, kept across retries, never reused. OpenReceive never looks inside
|
|
483
|
-
it, but `on_paid`
|
|
485
|
+
it, but `on_paid` commits fulfillment once per reference, a new checkout under a reference
|
|
484
486
|
that already settled is refused with 409, and a fresh id per page load lets
|
|
485
487
|
one order be paid twice.
|
|
486
488
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenReceive agent directions (FastAPI)
|
|
2
2
|
|
|
3
|
-
These directions describe OpenReceive 0.4.
|
|
3
|
+
These directions describe OpenReceive 0.4.11.
|
|
4
4
|
|
|
5
5
|
Add OpenReceive to a FastAPI application — the app you are already working in.
|
|
6
6
|
You do not need a copy of the OpenReceive source: the engine is on PyPI
|
|
@@ -116,7 +116,7 @@ itself, and they hold for every integration.
|
|
|
116
116
|
- `authorize` runs on every request, and the `resource` it receives is a CLAIM
|
|
117
117
|
the payer made, not proof. Read the Starlette request's session, cookie or
|
|
118
118
|
auth dependency; never trust a body field.
|
|
119
|
-
- `on_paid` must be idempotent.
|
|
119
|
+
- `on_paid` must be idempotent. Its database fulfillment commits once per `reference` — your order id, one
|
|
120
120
|
per thing you fulfill, created before checkout, kept across retries, never
|
|
121
121
|
reused. A fresh id per page load lets one order be paid twice.
|
|
122
122
|
- Receive-only NWC is required; a spend-capable code fails closed at boot unless
|
|
@@ -301,6 +301,8 @@ enough; drop the `.md` for the same page a person would read.
|
|
|
301
301
|
Questions, or a problem with the library itself:
|
|
302
302
|
https://openreceive.org/contact
|
|
303
303
|
|
|
304
|
+
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
305
|
+
|
|
304
306
|
---
|
|
305
307
|
|
|
306
308
|
## The quickstart, in full
|
|
@@ -475,7 +477,7 @@ the `reference`. OpenReceive never prices from payer input.
|
|
|
475
477
|
The `reference` is a string you choose, and it is the fulfillment identity:
|
|
476
478
|
your order id — one per thing you fulfill, created before checkout, kept
|
|
477
479
|
across retries, never reused. OpenReceive never looks inside it, but `on_paid`
|
|
478
|
-
|
|
480
|
+
commits fulfillment once per reference, a new checkout under a reference that already
|
|
479
481
|
settled is refused with 409, and a fresh id per page load lets one order be
|
|
480
482
|
paid twice.
|
|
481
483
|
|
|
@@ -524,7 +526,7 @@ Content-Security-Policy has a strict `img-src`, allow `data:`
|
|
|
524
526
|
([Provider registry](https://openreceive.org/guides/provider-registry.md#assets)).
|
|
525
527
|
|
|
526
528
|
That is the whole loop: your server owns the price and the order, the payer gets
|
|
527
|
-
an invoice, and `onPaid` runs
|
|
529
|
+
an invoice, and `onPaid` runs inside the settlement transaction. Rolled-back transactions may retry the callback; use a host outbox for external delivery.
|
|
528
530
|
|
|
529
531
|
A page without a bundler renders the same checkout as a custom element:
|
|
530
532
|
`<openreceive-checkout reference="…" prefix="/openreceive">` from
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenReceive agent directions (Fastify)
|
|
2
2
|
|
|
3
|
-
These directions describe OpenReceive 0.4.
|
|
3
|
+
These directions describe OpenReceive 0.4.11.
|
|
4
4
|
|
|
5
5
|
Add OpenReceive to a Fastify application — the app you are already working in.
|
|
6
6
|
You do not need a copy of the OpenReceive source: the packages are on npm, and
|
|
@@ -107,7 +107,7 @@ itself, and they hold for every integration.
|
|
|
107
107
|
payer-supplied amounts.
|
|
108
108
|
- `authorize` runs on every request, and the `resource` it receives is a CLAIM
|
|
109
109
|
the payer made, not proof. Read a framework session; never trust a body field.
|
|
110
|
-
- `onPaid` must be idempotent.
|
|
110
|
+
- `onPaid` must be idempotent. Its database fulfillment commits once per `reference` — your order id, one
|
|
111
111
|
per thing you fulfill, created before checkout, kept across retries, never
|
|
112
112
|
reused. A fresh id per page load lets one order be paid twice.
|
|
113
113
|
- Receive-only NWC is required; a spend-capable code fails closed at boot unless
|
|
@@ -284,6 +284,8 @@ enough; drop the `.md` for the same page a person would read.
|
|
|
284
284
|
Questions, or a problem with the library itself:
|
|
285
285
|
https://openreceive.org/contact
|
|
286
286
|
|
|
287
|
+
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
288
|
+
|
|
287
289
|
---
|
|
288
290
|
|
|
289
291
|
## The quickstart, in full
|
|
@@ -477,7 +479,7 @@ the `reference`. OpenReceive never prices from payer input.
|
|
|
477
479
|
The `reference` is a string you choose, and it is the fulfillment identity:
|
|
478
480
|
your order id — one per thing you fulfill, created before checkout, kept
|
|
479
481
|
across retries, never reused. OpenReceive never looks inside it, but `onPaid`
|
|
480
|
-
|
|
482
|
+
commits fulfillment once per reference, a new checkout under a reference that already
|
|
481
483
|
settled is refused with 409, and a fresh id per page load lets one order be
|
|
482
484
|
paid twice.
|
|
483
485
|
|
|
@@ -526,7 +528,7 @@ Content-Security-Policy has a strict `img-src`, allow `data:`
|
|
|
526
528
|
([Provider registry](https://openreceive.org/guides/provider-registry.md#assets)).
|
|
527
529
|
|
|
528
530
|
That is the whole loop: your server owns the price and the order, the payer gets
|
|
529
|
-
an invoice, and `onPaid` runs
|
|
531
|
+
an invoice, and `onPaid` runs inside the settlement transaction. Rolled-back transactions may retry the callback; use a host outbox for external delivery.
|
|
530
532
|
|
|
531
533
|
A runnable illustration of this boundary — not a template to copy models from —
|
|
532
534
|
is Buy a Button
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenReceive agent directions (Laravel)
|
|
2
2
|
|
|
3
|
-
These directions describe OpenReceive 0.4.
|
|
3
|
+
These directions describe OpenReceive 0.4.11.
|
|
4
4
|
|
|
5
5
|
Add OpenReceive to a Laravel application — the app you are already working in.
|
|
6
6
|
You do not need a copy of the OpenReceive source: the package is on Packagist
|
|
@@ -121,7 +121,7 @@ itself, and they hold for every integration.
|
|
|
121
121
|
scaffolds `use AllowAllAuthorize;`, a placeholder trait that allows
|
|
122
122
|
everything (the engine warns at boot while it is there) — replace it with
|
|
123
123
|
this app's real ownership check, same as `onPaid`.
|
|
124
|
-
- `onPaid` must be idempotent.
|
|
124
|
+
- `onPaid` must be idempotent. Its database fulfillment commits once per `reference` — your order
|
|
125
125
|
id, one per thing you fulfill, created before checkout, kept across retries,
|
|
126
126
|
never reused. A fresh id per page load lets one order be paid twice.
|
|
127
127
|
- Receive-only NWC is required; a spend-capable code fails closed at boot unless
|
|
@@ -291,6 +291,8 @@ enough; drop the `.md` for the same page a person would read.
|
|
|
291
291
|
Questions, or a problem with the library itself:
|
|
292
292
|
https://openreceive.org/contact
|
|
293
293
|
|
|
294
|
+
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
295
|
+
|
|
294
296
|
---
|
|
295
297
|
|
|
296
298
|
## The quickstart, in full
|
|
@@ -456,7 +458,7 @@ variable as set/unset only.
|
|
|
456
458
|
price, and fulfillment. All three receive the `reference` — a string you
|
|
457
459
|
choose, and the fulfillment identity: your order id, one per thing you
|
|
458
460
|
fulfill, created before checkout, kept across retries, never reused.
|
|
459
|
-
OpenReceive never looks inside it, but `onPaid`
|
|
461
|
+
OpenReceive never looks inside it, but `onPaid` commits fulfillment once per reference, a new
|
|
460
462
|
checkout under a reference that already settled is refused with 409, and a
|
|
461
463
|
fresh id per page load lets one order be paid twice.
|
|
462
464
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenReceive agent directions (Next.js)
|
|
2
2
|
|
|
3
|
-
These directions describe OpenReceive 0.4.
|
|
3
|
+
These directions describe OpenReceive 0.4.11.
|
|
4
4
|
|
|
5
5
|
Add OpenReceive to a Next.js App Router application — the app you are already
|
|
6
6
|
working in. You do not need a copy of the OpenReceive source: the packages are
|
|
@@ -109,7 +109,7 @@ itself, and they hold for every integration.
|
|
|
109
109
|
payer-supplied amounts.
|
|
110
110
|
- `authorize` runs on every request, and the `resource` it receives is a CLAIM
|
|
111
111
|
the payer made, not proof. Read a framework session; never trust a body field.
|
|
112
|
-
- `onPaid` must be idempotent.
|
|
112
|
+
- `onPaid` must be idempotent. Its database fulfillment commits once per `reference` — your order id, one
|
|
113
113
|
per thing you fulfill, created before checkout, kept across retries, never
|
|
114
114
|
reused. A fresh id per page load lets one order be paid twice.
|
|
115
115
|
- Receive-only NWC is required; a spend-capable code fails closed at boot unless
|
|
@@ -290,6 +290,8 @@ enough; drop the `.md` for the same page a person would read.
|
|
|
290
290
|
Questions, or a problem with the library itself:
|
|
291
291
|
https://openreceive.org/contact
|
|
292
292
|
|
|
293
|
+
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
294
|
+
|
|
293
295
|
---
|
|
294
296
|
|
|
295
297
|
## The quickstart, in full
|
|
@@ -496,7 +498,7 @@ the `reference`. OpenReceive never prices from payer input.
|
|
|
496
498
|
The `reference` is a string you choose, and it is the fulfillment identity:
|
|
497
499
|
your order id — one per thing you fulfill, created before checkout, kept
|
|
498
500
|
across retries, never reused. OpenReceive never looks inside it, but `onPaid`
|
|
499
|
-
|
|
501
|
+
commits fulfillment once per reference, a new checkout under a reference that already
|
|
500
502
|
settled is refused with 409, and a fresh id per page load lets one order be
|
|
501
503
|
paid twice.
|
|
502
504
|
|
|
@@ -576,7 +578,7 @@ Content-Security-Policy has a strict `img-src`, allow `data:`
|
|
|
576
578
|
([Provider registry](https://openreceive.org/guides/provider-registry.md#assets)).
|
|
577
579
|
|
|
578
580
|
That is the whole loop: your server owns the price and the order, the payer gets
|
|
579
|
-
an invoice, and `onPaid` runs
|
|
581
|
+
an invoice, and `onPaid` runs inside the settlement transaction. Rolled-back transactions may retry the callback; use a host outbox for external delivery.
|
|
580
582
|
|
|
581
583
|
A runnable illustration of this boundary — not a template to copy models from —
|
|
582
584
|
is Buy a Button
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenReceive agent directions (Node.js)
|
|
2
2
|
|
|
3
|
-
These directions describe OpenReceive 0.4.
|
|
3
|
+
These directions describe OpenReceive 0.4.11.
|
|
4
4
|
|
|
5
5
|
Add OpenReceive to a Node application — the app you are already working in. You
|
|
6
6
|
do not need a copy of the OpenReceive source: the packages are on npm, and the
|
|
@@ -104,7 +104,7 @@ itself, and they hold for every integration.
|
|
|
104
104
|
payer-supplied amounts.
|
|
105
105
|
- `authorize` runs on every request, and the `resource` it receives is a CLAIM
|
|
106
106
|
the payer made, not proof. Read a framework session; never trust a body field.
|
|
107
|
-
- `onPaid` must be idempotent.
|
|
107
|
+
- `onPaid` must be idempotent. Its database fulfillment commits once per `reference` — your order id, one
|
|
108
108
|
per thing you fulfill, created before checkout, kept across retries, never
|
|
109
109
|
reused. A fresh id per page load lets one order be paid twice.
|
|
110
110
|
- Receive-only NWC is required; a spend-capable code fails closed at boot unless
|
|
@@ -276,6 +276,8 @@ enough; drop the `.md` for the same page a person would read.
|
|
|
276
276
|
Questions, or a problem with the library itself:
|
|
277
277
|
https://openreceive.org/contact
|
|
278
278
|
|
|
279
|
+
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
280
|
+
|
|
279
281
|
---
|
|
280
282
|
|
|
281
283
|
## The quickstart, in full
|
|
@@ -454,7 +456,7 @@ the `reference`. OpenReceive never prices from payer input.
|
|
|
454
456
|
The `reference` is a string you choose, and it is the fulfillment identity:
|
|
455
457
|
your order id — one per thing you fulfill, created before checkout, kept
|
|
456
458
|
across retries, never reused. OpenReceive never looks inside it, but `onPaid`
|
|
457
|
-
|
|
459
|
+
commits fulfillment once per reference, a new checkout under a reference that already
|
|
458
460
|
settled is refused with 409, and a fresh id per page load lets one order be
|
|
459
461
|
paid twice.
|
|
460
462
|
|
|
@@ -503,7 +505,7 @@ Content-Security-Policy has a strict `img-src`, allow `data:`
|
|
|
503
505
|
([Provider registry](https://openreceive.org/guides/provider-registry.md#assets)).
|
|
504
506
|
|
|
505
507
|
That is the whole loop: your server owns the price and the order, the payer gets
|
|
506
|
-
an invoice, and `onPaid` runs
|
|
508
|
+
an invoice, and `onPaid` runs inside the settlement transaction. Rolled-back transactions may retry the callback; use a host outbox for external delivery.
|
|
507
509
|
|
|
508
510
|
A runnable illustration of this boundary — not a template to copy models from —
|
|
509
511
|
is Buy a Button
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenReceive agent directions (PHP)
|
|
2
2
|
|
|
3
|
-
These directions describe OpenReceive 0.4.
|
|
3
|
+
These directions describe OpenReceive 0.4.11.
|
|
4
4
|
|
|
5
5
|
Add OpenReceive to a PHP application — the app you are already working in. You
|
|
6
6
|
do not need a copy of the OpenReceive source: the engine is on Packagist
|
|
@@ -125,7 +125,7 @@ itself, and they hold for every integration.
|
|
|
125
125
|
is a placeholder that allows everything (the engine warns at boot while a
|
|
126
126
|
host uses it) — replace it with this app's real ownership check, same as
|
|
127
127
|
`onPaid`'s `Hosts\LoggingOnPaid`.
|
|
128
|
-
- `onPaid` must be idempotent.
|
|
128
|
+
- `onPaid` must be idempotent. Its database fulfillment commits once per `reference` — your order id, one
|
|
129
129
|
per thing you fulfill, created before checkout, kept across retries, never
|
|
130
130
|
reused. A fresh id per page load lets one order be paid twice.
|
|
131
131
|
- Receive-only NWC is required; a spend-capable code fails closed at boot unless
|
|
@@ -301,6 +301,8 @@ enough; drop the `.md` for the same page a person would read.
|
|
|
301
301
|
Questions, or a problem with the library itself:
|
|
302
302
|
https://openreceive.org/contact
|
|
303
303
|
|
|
304
|
+
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
305
|
+
|
|
304
306
|
---
|
|
305
307
|
|
|
306
308
|
## The quickstart, in full
|
|
@@ -512,7 +514,7 @@ prices with exact decimal math, and returns the order id the page will pass as
|
|
|
512
514
|
the `reference`. OpenReceive never prices from payer input. The `reference` is
|
|
513
515
|
a string you choose, and it is the fulfillment identity: your order id — one
|
|
514
516
|
per thing you fulfill, created before checkout, kept across retries, never
|
|
515
|
-
reused. `onPaid`
|
|
517
|
+
reused. `onPaid` commits fulfillment once per reference, a new checkout under a reference
|
|
516
518
|
that already settled is refused with 409, and a fresh id per page load lets
|
|
517
519
|
one order be paid twice.
|
|
518
520
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenReceive agent directions (Rails)
|
|
2
2
|
|
|
3
|
-
These directions describe OpenReceive 0.4.
|
|
3
|
+
These directions describe OpenReceive 0.4.11.
|
|
4
4
|
|
|
5
5
|
Add OpenReceive to a Rails application — the app you are already working in. You
|
|
6
6
|
do not need a copy of the OpenReceive source: the gem is on RubyGems, the
|
|
@@ -116,7 +116,7 @@ itself, and they hold for every integration.
|
|
|
116
116
|
body field. The generator installs `OpenReceive::ALLOW_ALL_AUTHORIZE`, a
|
|
117
117
|
placeholder that allows everything (the engine warns at boot while it is
|
|
118
118
|
set) — replace it with this app's real ownership check, same as `on_paid`.
|
|
119
|
-
- `config.on_paid` must be idempotent.
|
|
119
|
+
- `config.on_paid` must be idempotent. Its database fulfillment commits once per `reference` — your order
|
|
120
120
|
id, one per thing you fulfill, created before checkout, kept across retries,
|
|
121
121
|
never reused. A fresh id per page load lets one order be paid twice.
|
|
122
122
|
- Receive-only NWC is required; a spend-capable code fails closed at boot unless
|
|
@@ -285,6 +285,8 @@ enough; drop the `.md` for the same page a person would read.
|
|
|
285
285
|
Questions, or a problem with the library itself:
|
|
286
286
|
https://openreceive.org/contact
|
|
287
287
|
|
|
288
|
+
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
289
|
+
|
|
288
290
|
---
|
|
289
291
|
|
|
290
292
|
## The quickstart, in full
|
|
@@ -432,7 +434,7 @@ The initializer needs three things: authorization, the trusted price, and
|
|
|
432
434
|
fulfillment. All three receive the `reference` — a string you choose, and the
|
|
433
435
|
fulfillment identity: your order id, one per thing you fulfill, created before
|
|
434
436
|
checkout, kept across retries, never reused. OpenReceive never looks inside
|
|
435
|
-
it, but `on_paid`
|
|
437
|
+
it, but `on_paid` commits fulfillment once per reference, a new checkout under a reference
|
|
436
438
|
that already settled is refused with 409, and a fresh id per page load lets
|
|
437
439
|
one order be paid twice.
|
|
438
440
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenReceive agent directions (WordPress + WooCommerce)
|
|
2
2
|
|
|
3
|
-
These directions describe OpenReceive 0.4.
|
|
3
|
+
These directions describe OpenReceive 0.4.11.
|
|
4
4
|
|
|
5
5
|
Install and configure the OpenReceive gateway in the existing WooCommerce
|
|
6
6
|
store. Preserve its theme, checkout, customer accounts, order model and prices.
|
|
@@ -73,6 +73,8 @@ flows; a receive-only NWC wallet cannot send payments.
|
|
|
73
73
|
- [Agent Directions: BTCPay Server](https://openreceive.org/guides/agent-directions-btcpay.md)
|
|
74
74
|
- [WordPress + WooCommerce Quickstart](https://openreceive.org/guides/quickstart-woocommerce.md)
|
|
75
75
|
|
|
76
|
+
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
77
|
+
|
|
76
78
|
---
|
|
77
79
|
|
|
78
80
|
## The quickstart, in full
|