@openreceive/react 0.4.10 → 0.4.12
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/index.js +79 -42
- package/package.json +3 -3
- package/skills/integrate-openreceive/SKILL.md +4 -3
- package/skills/integrate-openreceive/references/btcpay.md +67 -36
- package/skills/integrate-openreceive/references/django.md +291 -244
- package/skills/integrate-openreceive/references/fastapi.md +206 -155
- package/skills/integrate-openreceive/references/fastify.md +200 -147
- package/skills/integrate-openreceive/references/laravel.md +294 -234
- package/skills/integrate-openreceive/references/next.md +210 -162
- package/skills/integrate-openreceive/references/node.md +188 -138
- package/skills/integrate-openreceive/references/php.md +248 -193
- package/skills/integrate-openreceive/references/rails.md +262 -218
- package/skills/integrate-openreceive/references/woocommerce.md +68 -51
package/dist/index.js
CHANGED
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
mergeAttemptIntoCheckout,
|
|
7
7
|
mergeAttemptIntoSnapshot,
|
|
8
8
|
OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES as OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES3,
|
|
9
|
-
OPENRECEIVE_STYLE_ROOT_ATTRIBUTE as OPENRECEIVE_STYLE_ROOT_ATTRIBUTE2,
|
|
10
9
|
OPENRECEIVE_DEFAULT_PREFIX,
|
|
10
|
+
OPENRECEIVE_STYLE_ROOT_ATTRIBUTE as OPENRECEIVE_STYLE_ROOT_ATTRIBUTE2,
|
|
11
11
|
orClasses as orClasses7,
|
|
12
12
|
prepareCheckout,
|
|
13
13
|
requestCheckout,
|
|
@@ -30,7 +30,8 @@ function useCheckoutSession(options) {
|
|
|
30
30
|
sessionRef.current = createCheckoutSession({
|
|
31
31
|
snapshot: () => optionsRef.current.snapshot(),
|
|
32
32
|
reference: () => optionsRef.current.reference(),
|
|
33
|
-
|
|
33
|
+
prefix: () => optionsRef.current.prefix?.(),
|
|
34
|
+
requestCheckout: (reference, signal) => optionsRef.current.requestCheckout?.(reference, signal),
|
|
34
35
|
onSnapshot: (snapshot) => optionsRef.current.onSnapshot?.(snapshot),
|
|
35
36
|
// The inner `swap` is always present because the session is built once,
|
|
36
37
|
// on the first render, and the host may only gain swap options later.
|
|
@@ -57,7 +58,10 @@ function useCheckoutSession(options) {
|
|
|
57
58
|
onChange: rerender
|
|
58
59
|
});
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
+
const session = sessionRef.current;
|
|
62
|
+
session.syncIdentity();
|
|
63
|
+
React.useEffect(() => () => session.dispose(), [session]);
|
|
64
|
+
return session;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
// src/components.ts
|
|
@@ -712,15 +716,15 @@ function TransactionDetailCopyButton(props) {
|
|
|
712
716
|
}
|
|
713
717
|
|
|
714
718
|
// src/use-checkout.ts
|
|
715
|
-
import * as React6 from "react";
|
|
716
719
|
import {
|
|
717
720
|
copyInvoice as copyInvoiceHelper2,
|
|
718
721
|
createCheckoutController,
|
|
719
722
|
createCheckoutState,
|
|
720
723
|
createCheckoutStatusModel as createCheckoutStatusModel2,
|
|
721
|
-
|
|
722
|
-
|
|
724
|
+
deriveStatus,
|
|
725
|
+
openWallet as openWalletHelper2
|
|
723
726
|
} from "@openreceive/browser/headless";
|
|
727
|
+
import * as React6 from "react";
|
|
724
728
|
function useCheckout(options) {
|
|
725
729
|
const checkout = options.checkout;
|
|
726
730
|
const [copied, showCopied] = useTransientValue(false);
|
|
@@ -857,8 +861,9 @@ function useCheckout(options) {
|
|
|
857
861
|
}, [logContext, state.invoice, options.open, options.logger]);
|
|
858
862
|
const reloadState = React6.useCallback(async () => {
|
|
859
863
|
try {
|
|
860
|
-
const
|
|
861
|
-
|
|
864
|
+
const controller = controllerRef.current;
|
|
865
|
+
const next = await controller?.reloadState();
|
|
866
|
+
if (controllerRef.current === controller && next !== void 0) setState(next);
|
|
862
867
|
} catch (error) {
|
|
863
868
|
onErrorRef.current?.(error);
|
|
864
869
|
throw error;
|
|
@@ -878,10 +883,11 @@ function useCheckout(options) {
|
|
|
878
883
|
}, []);
|
|
879
884
|
const stageSwapRefund = React6.useCallback(
|
|
880
885
|
async (refund) => {
|
|
886
|
+
const controller = swapRefundController();
|
|
881
887
|
try {
|
|
882
|
-
return await
|
|
888
|
+
return await controller.stageSwapRefund(refund);
|
|
883
889
|
} catch (error) {
|
|
884
|
-
onErrorRef.current?.(error);
|
|
890
|
+
if (controllerRef.current === controller) onErrorRef.current?.(error);
|
|
885
891
|
throw error;
|
|
886
892
|
}
|
|
887
893
|
},
|
|
@@ -889,10 +895,11 @@ function useCheckout(options) {
|
|
|
889
895
|
);
|
|
890
896
|
const confirmSwapRefund = React6.useCallback(
|
|
891
897
|
async (refund) => {
|
|
898
|
+
const controller = swapRefundController();
|
|
892
899
|
try {
|
|
893
|
-
return await
|
|
900
|
+
return await controller.confirmSwapRefund(refund);
|
|
894
901
|
} catch (error) {
|
|
895
|
-
onErrorRef.current?.(error);
|
|
902
|
+
if (controllerRef.current === controller) onErrorRef.current?.(error);
|
|
896
903
|
throw error;
|
|
897
904
|
}
|
|
898
905
|
},
|
|
@@ -1861,24 +1868,20 @@ function renderKeepOrderNote(options) {
|
|
|
1861
1868
|
)
|
|
1862
1869
|
);
|
|
1863
1870
|
}
|
|
1864
|
-
var REFUND_ADDRESS_DRAFT_LIMIT = 8;
|
|
1865
|
-
var refundAddressDraftByAttempt = /* @__PURE__ */ new Map();
|
|
1866
|
-
function setRefundAddressDraft(attemptId, value) {
|
|
1867
|
-
refundAddressDraftByAttempt.delete(attemptId);
|
|
1868
|
-
refundAddressDraftByAttempt.set(attemptId, value);
|
|
1869
|
-
while (refundAddressDraftByAttempt.size > REFUND_ADDRESS_DRAFT_LIMIT) {
|
|
1870
|
-
const oldest = refundAddressDraftByAttempt.keys().next().value;
|
|
1871
|
-
if (oldest === void 0) break;
|
|
1872
|
-
refundAddressDraftByAttempt.delete(oldest);
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
1871
|
function SwapRefundForm(props) {
|
|
1876
|
-
const [refundAddress, setRefundAddress] = React8.useState(
|
|
1877
|
-
|
|
1872
|
+
const [refundAddress, setRefundAddress] = React8.useState("");
|
|
1873
|
+
const [draftAttemptId, setDraftAttemptId] = React8.useState(props.attemptId);
|
|
1874
|
+
if (draftAttemptId !== props.attemptId) {
|
|
1875
|
+
setDraftAttemptId(props.attemptId);
|
|
1876
|
+
setRefundAddress("");
|
|
1877
|
+
}
|
|
1878
|
+
const generation = React8.useRef(0);
|
|
1879
|
+
React8.useEffect(
|
|
1880
|
+
() => () => {
|
|
1881
|
+
generation.current += 1;
|
|
1882
|
+
},
|
|
1883
|
+
[props.attemptId]
|
|
1878
1884
|
);
|
|
1879
|
-
React8.useEffect(() => {
|
|
1880
|
-
setRefundAddress(refundAddressDraftByAttempt.get(props.attemptId) ?? "");
|
|
1881
|
-
}, [props.attemptId]);
|
|
1882
1885
|
const [submitting, setSubmitting] = React8.useState(false);
|
|
1883
1886
|
const [showAddressError, setShowAddressError] = React8.useState(false);
|
|
1884
1887
|
const address = refundAddress.trim();
|
|
@@ -1898,7 +1901,12 @@ function SwapRefundForm(props) {
|
|
|
1898
1901
|
return;
|
|
1899
1902
|
}
|
|
1900
1903
|
setSubmitting(true);
|
|
1901
|
-
|
|
1904
|
+
const captured = generation.current;
|
|
1905
|
+
void props.onRefund(props.attemptId, address, confirm).catch((error) => {
|
|
1906
|
+
if (captured === generation.current) props.onError?.(error);
|
|
1907
|
+
}).finally(() => {
|
|
1908
|
+
if (captured === generation.current) setSubmitting(false);
|
|
1909
|
+
});
|
|
1902
1910
|
}
|
|
1903
1911
|
},
|
|
1904
1912
|
// The form says what it is and why it is here before it asks for anything:
|
|
@@ -1934,7 +1942,6 @@ function SwapRefundForm(props) {
|
|
|
1934
1942
|
className: showError ? orClasses5.swapRefundInputInvalid : orClasses5.swapRefundInput,
|
|
1935
1943
|
onChange: (event) => {
|
|
1936
1944
|
const value = event.currentTarget.value;
|
|
1937
|
-
setRefundAddressDraft(props.attemptId, value);
|
|
1938
1945
|
setRefundAddress(value);
|
|
1939
1946
|
},
|
|
1940
1947
|
onBlur: () => {
|
|
@@ -1998,13 +2005,21 @@ function SwapPayloadQRCode(props) {
|
|
|
1998
2005
|
|
|
1999
2006
|
// src/wizard.ts
|
|
2000
2007
|
function PaymentWizard(props) {
|
|
2008
|
+
return React9.createElement(PaymentWizardSession, {
|
|
2009
|
+
...props,
|
|
2010
|
+
key: JSON.stringify([props.checkout?.reference, props.prefix?.replace(/\/+$/, "")])
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
function PaymentWizardSession(props) {
|
|
2001
2014
|
const [selection, setSelection] = React9.useState(
|
|
2002
2015
|
() => createPaymentWizardController().getSelection()
|
|
2003
2016
|
);
|
|
2004
2017
|
const [activeTutorial, setActiveTutorial] = React9.useState(null);
|
|
2005
2018
|
const [startedSwapInvoice, setStartedSwapInvoice] = React9.useState(null);
|
|
2006
2019
|
const [dismissedSwapInvoiceId, setDismissedSwapInvoiceId] = React9.useState(null);
|
|
2007
|
-
const [selectedSwapAsset, setSelectedSwapAsset] = React9.useState(
|
|
2020
|
+
const [selectedSwapAsset, setSelectedSwapAsset] = React9.useState(
|
|
2021
|
+
() => selectCurrentSwapInvoice(props.checkout)?.swap?.pay_in_asset ?? null
|
|
2022
|
+
);
|
|
2008
2023
|
const [selectedSwapAssetByGroup, setSelectedSwapAssetByGroup] = React9.useState({});
|
|
2009
2024
|
const [selectedPickerKey, setSelectedPickerKey] = React9.useState(null);
|
|
2010
2025
|
const autoSwapAttemptedRef = React9.useRef(/* @__PURE__ */ new Set());
|
|
@@ -2014,6 +2029,7 @@ function PaymentWizard(props) {
|
|
|
2014
2029
|
const session = useCheckoutSession({
|
|
2015
2030
|
snapshot: () => checkout,
|
|
2016
2031
|
reference: () => reference,
|
|
2032
|
+
prefix: () => props.prefix,
|
|
2017
2033
|
swap: {
|
|
2018
2034
|
selection: {
|
|
2019
2035
|
started: () => startedSwapInvoice ?? void 0,
|
|
@@ -2076,6 +2092,7 @@ function PaymentWizard(props) {
|
|
|
2076
2092
|
if (prefix === void 0 || reference === void 0 || fetcher === void 0) {
|
|
2077
2093
|
return;
|
|
2078
2094
|
}
|
|
2095
|
+
const action = session.capture();
|
|
2079
2096
|
try {
|
|
2080
2097
|
const invoice = swapRefund !== void 0 ? await (confirm ? swapRefund.confirmSwapRefund : swapRefund.stageSwapRefund).call(
|
|
2081
2098
|
swapRefund,
|
|
@@ -2093,10 +2110,11 @@ function PaymentWizard(props) {
|
|
|
2093
2110
|
confirm,
|
|
2094
2111
|
...props.logger === void 0 ? {} : { logger: props.logger }
|
|
2095
2112
|
});
|
|
2113
|
+
if (!action.isCurrent()) return;
|
|
2096
2114
|
setStartedSwapInvoice(invoice);
|
|
2097
2115
|
setDismissedSwapInvoiceId(null);
|
|
2098
2116
|
} catch (error) {
|
|
2099
|
-
props.onError?.(error);
|
|
2117
|
+
if (action.isCurrent()) props.onError?.(error);
|
|
2100
2118
|
}
|
|
2101
2119
|
},
|
|
2102
2120
|
[
|
|
@@ -2108,7 +2126,8 @@ function PaymentWizard(props) {
|
|
|
2108
2126
|
props.logger,
|
|
2109
2127
|
swapRefund,
|
|
2110
2128
|
startedSwapInvoice,
|
|
2111
|
-
checkout?.invoices
|
|
2129
|
+
checkout?.invoices,
|
|
2130
|
+
session
|
|
2112
2131
|
]
|
|
2113
2132
|
);
|
|
2114
2133
|
const updateWizardSelection = React9.useCallback(
|
|
@@ -2148,7 +2167,7 @@ function PaymentWizard(props) {
|
|
|
2148
2167
|
stickySwapInvoiceRef.current = void 0;
|
|
2149
2168
|
}
|
|
2150
2169
|
const activeSwapForAsset = selectedSwapAsset === null ? void 0 : currentSwapInvoice !== void 0 && currentSwapInvoice.swap?.pay_in_asset === selectedSwapAsset ? currentSwapInvoice : stickySwapInvoiceRef.current?.swap?.pay_in_asset === selectedSwapAsset ? stickySwapInvoiceRef.current : void 0;
|
|
2151
|
-
const resumeAdoptedRef = React9.useRef(
|
|
2170
|
+
const resumeAdoptedRef = React9.useRef(selectedSwapAsset !== null);
|
|
2152
2171
|
React9.useEffect(() => {
|
|
2153
2172
|
if (resumeAdoptedRef.current) return;
|
|
2154
2173
|
if (selectedSwapAsset !== null) {
|
|
@@ -2917,11 +2936,21 @@ function Checkout(props) {
|
|
|
2917
2936
|
if (checkout !== void 0) {
|
|
2918
2937
|
return React10.createElement(CheckoutSnapshotMode, {
|
|
2919
2938
|
...props,
|
|
2939
|
+
key: JSON.stringify([
|
|
2940
|
+
checkout.reference,
|
|
2941
|
+
(props.prefix ?? OPENRECEIVE_DEFAULT_PREFIX).replace(/\/+$/, "")
|
|
2942
|
+
]),
|
|
2920
2943
|
checkout,
|
|
2921
2944
|
prefix: props.prefix ?? OPENRECEIVE_DEFAULT_PREFIX
|
|
2922
2945
|
});
|
|
2923
2946
|
}
|
|
2924
|
-
return React10.createElement(CheckoutCreate,
|
|
2947
|
+
return React10.createElement(CheckoutCreate, {
|
|
2948
|
+
...props,
|
|
2949
|
+
key: JSON.stringify([
|
|
2950
|
+
props.reference,
|
|
2951
|
+
(props.prefix ?? OPENRECEIVE_DEFAULT_PREFIX).replace(/\/+$/, "")
|
|
2952
|
+
])
|
|
2953
|
+
});
|
|
2925
2954
|
}
|
|
2926
2955
|
function CheckoutSnapshotMode(props) {
|
|
2927
2956
|
const { checkout } = props;
|
|
@@ -2976,8 +3005,10 @@ function CheckoutCreate(props) {
|
|
|
2976
3005
|
const session = useCheckoutSession({
|
|
2977
3006
|
snapshot: () => createdCheckoutRef.current,
|
|
2978
3007
|
reference: () => reference,
|
|
2979
|
-
|
|
3008
|
+
prefix: () => resolvedPrefix,
|
|
3009
|
+
requestCheckout: (id, signal) => requestCheckout({
|
|
2980
3010
|
prefix: resolvedPrefix,
|
|
3011
|
+
signal,
|
|
2981
3012
|
reference: id,
|
|
2982
3013
|
...csrfHeader === void 0 ? {} : { csrfHeader },
|
|
2983
3014
|
...metadataRef.current === void 0 ? {} : { metadata: metadataRef.current },
|
|
@@ -2998,14 +3029,17 @@ function CheckoutCreate(props) {
|
|
|
2998
3029
|
}, [syncUrl, reference, resumePathPrefix, routeReference]);
|
|
2999
3030
|
React10.useEffect(() => {
|
|
3000
3031
|
let cancelled = false;
|
|
3032
|
+
const action = session.capture();
|
|
3001
3033
|
setCreated({ status: "pending" });
|
|
3002
3034
|
prepareCheckout({
|
|
3003
3035
|
prefix: resolvedPrefix,
|
|
3036
|
+
signal: action.signal,
|
|
3004
3037
|
reference,
|
|
3005
3038
|
...csrfHeader === void 0 ? {} : { csrfHeader },
|
|
3006
3039
|
...createFetchRef.current === void 0 ? {} : { fetch: createFetchRef.current }
|
|
3007
3040
|
}).then(
|
|
3008
|
-
(checkout) => resumePaymentHash === void 0 ? checkout : resumeSwapAttempt({
|
|
3041
|
+
(checkout) => cancelled || !action.isCurrent() || resumePaymentHash === void 0 ? checkout : resumeSwapAttempt({
|
|
3042
|
+
signal: action.signal,
|
|
3009
3043
|
fetch: createFetchRef.current ?? globalThis.fetch,
|
|
3010
3044
|
prefix: resolvedPrefix,
|
|
3011
3045
|
...csrfHeader === void 0 ? {} : { csrfHeader },
|
|
@@ -3014,9 +3048,9 @@ function CheckoutCreate(props) {
|
|
|
3014
3048
|
snapshot: checkout
|
|
3015
3049
|
})
|
|
3016
3050
|
).then((checkout) => {
|
|
3017
|
-
if (!cancelled) setCreated({ status: "ready", checkout });
|
|
3051
|
+
if (!cancelled && action.isCurrent()) setCreated({ status: "ready", checkout });
|
|
3018
3052
|
}).catch((error) => {
|
|
3019
|
-
if (cancelled) return;
|
|
3053
|
+
if (cancelled || !action.isCurrent()) return;
|
|
3020
3054
|
onErrorRef.current?.(error);
|
|
3021
3055
|
setCreated({
|
|
3022
3056
|
status: "error",
|
|
@@ -3025,6 +3059,7 @@ function CheckoutCreate(props) {
|
|
|
3025
3059
|
});
|
|
3026
3060
|
return () => {
|
|
3027
3061
|
cancelled = true;
|
|
3062
|
+
session.reset();
|
|
3028
3063
|
};
|
|
3029
3064
|
}, [reference, resolvedPrefix, csrfHeader, resumePaymentHash, attempt]);
|
|
3030
3065
|
const onSwapStarted = React10.useCallback(
|
|
@@ -3176,7 +3211,9 @@ function CheckoutView(props) {
|
|
|
3176
3211
|
});
|
|
3177
3212
|
const stampsTheme = !theme.fromScope;
|
|
3178
3213
|
const ownsTheme = themeToggle && !theme.fromScope && lockedTheme === void 0;
|
|
3179
|
-
const [swapFocused, setSwapFocused] = React10.useState(
|
|
3214
|
+
const [swapFocused, setSwapFocused] = React10.useState(
|
|
3215
|
+
() => checkout.invoices.some((invoice) => invoice.rail === "swap" && invoice.swap !== void 0)
|
|
3216
|
+
);
|
|
3180
3217
|
const [lightningFocused, setLightningFocused] = React10.useState(false);
|
|
3181
3218
|
const QRCodeComponent = components?.QRCode ?? QRCode;
|
|
3182
3219
|
const InvoiceSummaryComponent = components?.InvoiceSummary ?? InvoiceSummary;
|
|
@@ -3188,7 +3225,7 @@ function CheckoutView(props) {
|
|
|
3188
3225
|
const expired = checkoutModel.status === "expired";
|
|
3189
3226
|
const settled = checkoutModel.status === "settled";
|
|
3190
3227
|
const showLightning = !!checkoutModel.invoice && (!paymentWizard || lightningFocused) && !swapFocused && !expired;
|
|
3191
|
-
const hideLightning = !
|
|
3228
|
+
const hideLightning = !settled && (swapFocused || !showLightning && !expired);
|
|
3192
3229
|
const showSummaryMeta = checkoutModel.status === "settled" || checkoutModel.status === "expired";
|
|
3193
3230
|
const fiatCurrency = checkoutModel.fiat_quote?.fiat?.currency;
|
|
3194
3231
|
const decodeInvoiceHref = createLightningInvoiceDecodeUrl2(checkoutModel.invoice, decodeLinkUrl);
|
|
@@ -3397,7 +3434,7 @@ function CheckoutView(props) {
|
|
|
3397
3434
|
)
|
|
3398
3435
|
)
|
|
3399
3436
|
),
|
|
3400
|
-
paymentWizard && !settled && (!expired || swapFocused) ? React10.createElement(PaymentWizard, {
|
|
3437
|
+
paymentWizard && !settled && (!expired || swapFocused || checkoutModel.checkout?.invoices.some((invoice) => invoice.swap !== void 0)) ? React10.createElement(PaymentWizard, {
|
|
3401
3438
|
key: "wizard",
|
|
3402
3439
|
// Only pass invoice when it's a real bolt11 (non-empty, non-deferred).
|
|
3403
3440
|
invoice: checkoutModel.invoice || void 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openreceive/react",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.12",
|
|
4
4
|
"description": "React components and hooks for Bitcoin Lightning checkout and optional USDT, USDC, SOL and ETH swaps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"main": "./dist/index.js",
|
|
20
20
|
"types": "./dist/index.d.ts",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@openreceive/browser": "0.4.
|
|
23
|
-
"@openreceive/core": "0.4.
|
|
22
|
+
"@openreceive/browser": "0.4.12",
|
|
23
|
+
"@openreceive/core": "0.4.12"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"react": ">=18.0.0"
|
|
@@ -34,9 +34,10 @@ code** (`NWC_URI`).
|
|
|
34
34
|
- BTCPay Server: [references/btcpay.md](references/btcpay.md) — a plugin,
|
|
35
35
|
configured in BTCPay's store UI or Greenfield API; no application code,
|
|
36
36
|
no npm packages, no gem. The rest of this file is about the library.
|
|
37
|
-
3. Follow its **Step 0** first:
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
3. Follow its **Step 0** first: before writing code or searching the machine,
|
|
38
|
+
ask the user for the receive-only NWC code (then the swap URI), one question
|
|
39
|
+
per message, and store each pasted code in the project's env file yourself.
|
|
40
|
+
Never print the value; never invent a placeholder.
|
|
40
41
|
|
|
41
42
|
Install, per adapter — Express: `npm install @openreceive/express @openreceive/react`;
|
|
42
43
|
Fastify: `npm install @openreceive/fastify @openreceive/react`; Next.js:
|
|
@@ -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.12.
|
|
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
|
|
@@ -35,10 +35,12 @@ refund path on the same checkout screen.
|
|
|
35
35
|
1. Confirm the BTCPay Server version is 2.4.4 or later (Server Settings →
|
|
36
36
|
About, or `GET /api/v1/server/info`). The plugin declares that minimum and
|
|
37
37
|
BTCPay refuses to load it below.
|
|
38
|
-
2. Check whether the plugin is installed (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
2. Check whether the plugin is installed (the Plugins menu — the plug icon in
|
|
39
|
+
the top-right corner — under Installed Plugins, or the store navigation
|
|
40
|
+
shows an "OpenReceive" entry). If not, install it from the BTCPay plugin
|
|
41
|
+
directory (the same Plugins menu → Plugin Directory, search "openreceive",
|
|
42
|
+
then Install and Restart now), as the quickstart says; do not invent an
|
|
43
|
+
installer command.
|
|
42
44
|
3. Check whether the store already has an OpenReceive connection:
|
|
43
45
|
`GET /api/v1/stores/{storeId}/openreceive/settings` returns
|
|
44
46
|
`lightningNodeIsOpenReceive`. If true, the wallet step is done — go to
|
|
@@ -119,6 +121,8 @@ enough; drop the `.md` for the same page a person would read.
|
|
|
119
121
|
Questions, or a problem with the plugin itself:
|
|
120
122
|
https://openreceive.org/contact
|
|
121
123
|
|
|
124
|
+
- https://openreceive.org/guides/payment-safety-upgrade.md — coordinated upgrades and reviewed repair of existing attempts
|
|
125
|
+
|
|
122
126
|
---
|
|
123
127
|
|
|
124
128
|
## The quickstart, in full
|
|
@@ -131,15 +135,15 @@ passes. The page it comes from is https://openreceive.org/guides/quickstart-btcp
|
|
|
131
135
|
Requires BTCPay Server ≥ 2.4.4.
|
|
132
136
|
|
|
133
137
|
The OpenReceive plugin makes a receive-only NWC wallet the Lightning node of a
|
|
134
|
-
BTCPay store. BTCPay
|
|
135
|
-
payments
|
|
136
|
-
BTCPay invoice with USDT, USDC, ETH or SOL through a Lightning Swap Connect
|
|
137
|
-
provider
|
|
138
|
+
BTCPay store. BTCPay creates every Lightning invoice in that wallet. It records
|
|
139
|
+
payments the same way it records any other payment. You can also let payers pay
|
|
140
|
+
a BTCPay invoice with USDT, USDC, ETH or SOL through a Lightning Swap Connect
|
|
141
|
+
provider. The swap pays into the same wallet. The store's internal node is
|
|
138
142
|
never used.
|
|
139
143
|
|
|
140
144
|
This is not the Node or Rails library. There are no hooks, no
|
|
141
|
-
`openreceive_payments` table and no OpenReceive HTTP routes
|
|
142
|
-
invoices, checkout, webhooks and Greenfield API
|
|
145
|
+
`openreceive_payments` table and no OpenReceive HTTP routes. BTCPay's own
|
|
146
|
+
invoices, checkout, webhooks and Greenfield API do that work.
|
|
143
147
|
|
|
144
148
|
### 1. Prerequisites
|
|
145
149
|
|
|
@@ -155,39 +159,66 @@ invoices, checkout, webhooks and Greenfield API are the host.
|
|
|
155
159
|
|
|
156
160
|
### 2. Install the plugin
|
|
157
161
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
Sign in as a **server administrator**. If someone else hosts your server, ask
|
|
163
|
+
them to install the plugin for you.
|
|
164
|
+
|
|
165
|
+
**1. Open the Plugins menu.** It is the plug icon in the top-right corner.
|
|
166
|
+
|
|
167
|
+
**2. Click Plugin Directory.**
|
|
168
|
+
|
|
169
|
+
**3. Search for `openreceive`** and click the **OpenReceive** result.
|
|
170
|
+
|
|
171
|
+
**4. Click Install in BTCPay Server.** Confirm when prompted, then click
|
|
172
|
+
**Restart now** and wait for BTCPay to come back.
|
|
173
|
+
|
|
174
|
+
At startup, BTCPay creates the plugin's two tables in its own Postgres
|
|
175
|
+
database: `openreceive_invoices` and `openreceive_swaps`, in the schema
|
|
176
|
+
`BTCPayServer.Plugins.OpenReceive`. Nothing else is created.
|
|
163
177
|
|
|
164
178
|
To build the plugin from source instead, follow
|
|
165
179
|
[the .NET workspace README](https://github.com/OpenReceive/openreceive/blob/master/packages/dotnet/README.md).
|
|
166
180
|
|
|
167
181
|
### 3. Connect the wallet
|
|
168
182
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
node.
|
|
183
|
+
1. Select your store and open **OpenReceive** in its sidebar, under Wallets.
|
|
184
|
+
2. Paste your receive-only NWC code. To see what the wallet supports first,
|
|
185
|
+
click **Test connection**.
|
|
186
|
+
3. Click **Save NWC Code**.
|
|
187
|
+
4. To turn swaps on, paste a Lightning Swap Connect code and click **Save swap
|
|
188
|
+
settings**.
|
|
176
189
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
190
|
+
The page then shows **Wallet connected**. If you set up a provider, it also
|
|
191
|
+
shows **Swaps on**. There is nothing else to configure. You never open BTCPay's
|
|
192
|
+
Lightning node screen, and the plugin never reads the internal node.
|
|
193
|
+
|
|
194
|
+
Screenshots for each of those steps, and for creating a first test invoice,
|
|
195
|
+
are in the plugin's
|
|
196
|
+
[README](https://github.com/OpenReceive/openreceive/blob/master/packages/dotnet/BTCPayServer.Plugins.OpenReceive/README.md).
|
|
197
|
+
|
|
198
|
+
The plugin refuses to save a code whose wallet advertises a spend method such
|
|
199
|
+
as `pay_invoice`. Create a receive-only code instead. If your wallet cannot
|
|
200
|
+
make one, there is an override, but using it is a deliberate choice and the
|
|
201
|
+
plugin logs it.
|
|
202
|
+
|
|
203
|
+
Turning swaps on raises the store's invoice expiration to 60 minutes if it is
|
|
204
|
+
shorter. A swap needs the invoice to stay open for at least 45 minutes.
|
|
182
205
|
|
|
183
206
|
### 4. Check it
|
|
184
207
|
|
|
185
|
-
**Run a health check** on the OpenReceive page runs every
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
208
|
+
Click **Run a health check** on the OpenReceive page. It runs every check
|
|
209
|
+
right there:
|
|
210
|
+
|
|
211
|
+
- the connection
|
|
212
|
+
- the wallet preflight
|
|
213
|
+
- payment notifications
|
|
214
|
+
- the last wallet scan
|
|
215
|
+
- the swap provider and its assets
|
|
216
|
+
- the invoice expiration
|
|
217
|
+
- swaps that need a human
|
|
218
|
+
|
|
219
|
+
Each failing check comes with a link to the fix.
|
|
189
220
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
`nostr+walletconnect://` string in BTCPay's Lightning node screen.
|
|
221
|
+
The [BTCPay plugin reference](https://openreceive.org/guides/btcpay-reference.md) lists every setting,
|
|
222
|
+
Greenfield route, swap state, log event and check. It also lists what the
|
|
223
|
+
plugin does not support by design: every send-side feature, top-up invoices,
|
|
224
|
+
and a bare `nostr+walletconnect://` string in BTCPay's Lightning node screen.
|