openreceive 0.4.27 → 0.4.29

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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/lib/openreceive/version.rb +1 -1
  4. data/skills/integrate-openreceive/SKILL.md +18 -1
  5. data/skills/integrate-openreceive/references/astro.md +2 -2
  6. data/skills/integrate-openreceive/references/bolt.md +580 -0
  7. data/skills/integrate-openreceive/references/btcpay.md +1 -1
  8. data/skills/integrate-openreceive/references/django.md +1 -1
  9. data/skills/integrate-openreceive/references/edd.md +407 -0
  10. data/skills/integrate-openreceive/references/fastapi.md +1 -1
  11. data/skills/integrate-openreceive/references/fastify.md +6 -5
  12. data/skills/integrate-openreceive/references/flask.md +1 -1
  13. data/skills/integrate-openreceive/references/fossbilling.md +466 -0
  14. data/skills/integrate-openreceive/references/laravel.md +1 -1
  15. data/skills/integrate-openreceive/references/lovable.md +2 -2
  16. data/skills/integrate-openreceive/references/medusa.md +468 -0
  17. data/skills/integrate-openreceive/references/mobile.md +1 -1
  18. data/skills/integrate-openreceive/references/nestjs.md +3 -3
  19. data/skills/integrate-openreceive/references/next.md +7 -6
  20. data/skills/integrate-openreceive/references/node.md +6 -5
  21. data/skills/integrate-openreceive/references/nuxt.md +2 -2
  22. data/skills/integrate-openreceive/references/paymenter.md +369 -0
  23. data/skills/integrate-openreceive/references/php.md +1 -1
  24. data/skills/integrate-openreceive/references/rails.md +1 -1
  25. data/skills/integrate-openreceive/references/react-router.md +2 -2
  26. data/skills/integrate-openreceive/references/sveltekit.md +2 -2
  27. data/skills/integrate-openreceive/references/tanstack-start.md +2 -2
  28. data/skills/integrate-openreceive/references/woocommerce.md +8 -6
  29. metadata +6 -1
@@ -0,0 +1,468 @@
1
+ This is the full file; follow it from Step 0.
2
+
3
+ # OpenReceive agent directions (Medusa)
4
+
5
+ ```sh
6
+ # READ THIS FIRST: this file is 21 KB and a summary drops required steps. Download it whole:
7
+ curl -fsSL https://openreceive.org/agent-directions/medusa/full.md
8
+ # Skip the download only if you already have all of it: pasted, read from disk or fetched raw.
9
+ ```
10
+
11
+ **Step 0 in brief** (Step 0 below has the details):
12
+
13
+ - Before installing or editing anything, ask the user for a receive-only NWC code. One question per message.
14
+ - Next ask for the LSC code. If the user wants stablecoins, USDT, USDC, ETH, SOL or crypto, do not ask yes/no first.
15
+ - The user only pastes codes into this chat. Never ask them to edit .env or run a command to save one.
16
+ - Write each code into .env yourself, as Step 0 says. Never echo it or put it in a shell command.
17
+ - Do not suggest rotating or revoking a code because it was pasted here.
18
+ - Start the quickstart only once the NWC code is saved, and the LSC code is saved or the user said "Bitcoin only".
19
+
20
+ These directions describe OpenReceive 0.4.29.
21
+
22
+ Add OpenReceive to the Medusa store you are working in: a Medusa server, and
23
+ usually a storefront in its own project (Medusa's Next.js starter or another).
24
+ OpenReceive is a Medusa plugin on npm, `@openreceive/medusa`, with a payment
25
+ provider. The quickstart appended to this file has all the code: do not clone
26
+ the OpenReceive repository, and do not write your own payment provider, routes
27
+ or settlement.
28
+
29
+ The one required credential is a receive-only NWC code (Nostr Wallet Connect):
30
+ a string from the merchant's wallet that can create invoices and read their
31
+ status, and cannot spend. A swap provider (an "LSC" code) optionally lets
32
+ buyers pay with USDT, USDC, ETH or SOL instead; the provider converts the
33
+ payment to BTC over Lightning in the merchant's connected wallet. Available
34
+ assets and networks depend on the provider.
35
+
36
+ Medusa keeps carts, orders, prices and customers. The plugin keeps its payment
37
+ attempts in two tables of Medusa's database and places the order through
38
+ Medusa's own workflow once the payment settles. You write no hooks.
39
+
40
+ ## Step 0 — ask for the two codes, one question at a time
41
+
42
+ Before anything else, check one file: the Medusa server's `.env`.
43
+ `grep -E '^(NWC_URI|LSC_URI_PRIMARY)=.' .env | cut -d= -f1` names the codes it
44
+ sets without printing them. Check with that, here and after each write, not
45
+ by reading the file, and never print the values. A code that is already set is
46
+ not asked for again; if both are set, go to Step 1.
47
+
48
+ Otherwise your next action is a question to the user. Do not install packages,
49
+ edit the store or search anywhere else before asking it. Do not read deploy
50
+ config, Docker containers or process environments looking for a code, and
51
+ never run `printenv`: a new store has neither code yet.
52
+
53
+ The user never edits an environment file. They paste each code
54
+ into this chat; you store it. That is the supported path: do not ask them to
55
+ save it themselves, and do not tell them to revoke or replace a
56
+ code because it was pasted here. Ask one question per message.
57
+
58
+ 1. **First message — the NWC code, and nothing else.**
59
+
60
+ > To receive payments I need a receive-only wallet code. In Rizful: open
61
+ > the menu, tap NWC, choose Receive-only NWC code, and tap Copy
62
+ > (https://openreceive.org/get_a_nwc_code_to_receive_payments). If you would
63
+ > rather run your own wallet, Alby Hub works too: Connections → Add
64
+ > Connection → Read Only. Paste the code here and I will store it.
65
+
66
+ 2. **When they paste it.** If it does not start with `nostr+walletconnect://`,
67
+ ask them to copy the receive-only code again. Otherwise write
68
+ `NWC_URI=<paste>` into the Medusa server's `.env` with your file-editing
69
+ tool. Never write it with a shell command (`echo`, `printf`, a heredoc): the
70
+ command line shows the code. Never put it in the storefront, in a
71
+ `NEXT_PUBLIC_` variable or in a commit. Reply only that it is saved, then
72
+ ask the next question.
73
+ 3. **Second message — swaps.** If the user asked for stablecoins, USDT, USDC,
74
+ ETH, SOL, altcoins or "crypto" (as in "Bitcoin and stablecoin payments"),
75
+ this message IS the walkthrough below: do not skip it, and do not ask yes
76
+ or no first. Otherwise ask whether buyers should also be able to pay with
77
+ USDT, USDC, ETH or SOL, then give the walkthrough. The walkthrough:
78
+
79
+ > Go to https://lightning-swap.com, sign in for API keys, create a key, and
80
+ > copy the whole URI (https://openreceive.org/set_up_swap_provider). Paste
81
+ > it here and I will store it — or say "Bitcoin only" and I will continue
82
+ > without it.
83
+
84
+ Mention FixedFloat only if they already use it.
85
+ 4. **When they paste it.** If it does not start with
86
+ `lightning+swapconnect://`, ask them to copy it again. Otherwise add
87
+ `LSC_URI_PRIMARY=<paste>` to the same `.env` with the file-editing tool.
88
+ Swaps are now on, so keep the route back (the swap non-negotiable below).
89
+
90
+ Medusa loads `.env` itself. Under Docker Compose, give the Medusa service
91
+ `env_file: .env` and recreate it with `docker compose up -d`. Do not report
92
+ setup as complete until the NWC code is saved, and the LSC code is saved or
93
+ the user said "Bitcoin only". Never invent a placeholder code.
94
+
95
+ ## Step 1 — install the plugin in the Medusa server
96
+
97
+ In the Medusa server's project, run `npm install @openreceive/medusa`, then
98
+ register the plugin and its payment provider in `medusa-config.ts` exactly as
99
+ the quickstart's "Install the plugin" shows, keeping every setting already
100
+ there. If the payment module is already configured, add the provider to its
101
+ `providers` list. Run `npx medusa db:migrate`, which creates the plugin's two
102
+ tables, and restart Medusa. Medusa needs Node 22.12 or later for the plugin.
103
+
104
+ Restart Medusa and the storefront the way this project already runs them: in
105
+ their own terminal, under Docker Compose with `docker compose up -d`, or by
106
+ the pid of the process you started. Never `pkill` or `killall` by name: on a
107
+ shared machine that stops other people's servers.
108
+
109
+ ## Step 2 — offer it in the regions and check the wallet
110
+
111
+ In the Medusa server's project:
112
+
113
+ ```sh
114
+ npx medusa exec ./node_modules/@openreceive/medusa/dist/enable-openreceive.js
115
+ npx openreceive doctor
116
+ ```
117
+
118
+ The first adds OpenReceive to every region. `doctor` reads `.env`, reports
119
+ each code as set or unset, and asks the wallet whether it is receive-only. It
120
+ names any failed check and exits nonzero; fix it before going on. If it
121
+ reports spend methods such as `pay_invoice`, remove `NWC_URI` from `.env` and
122
+ ask the user for a receive-only code again. Never set the spend-capable
123
+ override to get past it.
124
+
125
+ ## Step 3 — add the checkout to the storefront
126
+
127
+ For Medusa's Next.js starter, make the quickstart's storefront changes:
128
+ `npm install @openreceive/react`, the route file
129
+ `src/app/api/openreceive/[...path]/route.ts`, the method in
130
+ `src/lib/constants.tsx`, and the `OpenReceivePaymentButton` in the payment
131
+ button. Keep the rest of the storefront as it is. Do not import
132
+ `@openreceive/react/styles.css` from a component or a CSS file: the starter's
133
+ Tailwind build refuses it, which is why the route serves it.
134
+
135
+ For another storefront, follow the quickstart's "Another storefront": the
136
+ checkout at the step where the buyer confirms, with the OpenReceive payment
137
+ session's id as `reference`, and the cart completed when it reports settled.
138
+
139
+ Restart the storefront. Then prove the plugin answers on the Medusa server:
140
+
141
+ ```sh
142
+ curl -s -X POST <medusa url>/store/openreceive/checkouts \
143
+ -H 'content-type: application/json' \
144
+ -H 'x-publishable-api-key: <the storefront publishable key>' \
145
+ -d '{"reference":"payses_check"}'
146
+ ```
147
+
148
+ The answer is OpenReceive's JSON refusal, `"code":"FORBIDDEN"`: no such
149
+ session exists, and the plugin's routes are mounted. The publishable key is
150
+ public; it is the storefront's `NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY`.
151
+
152
+ ## Step 4 — hand over, then stop
153
+
154
+ Setup is done when `doctor` is clean and the curl above answers FORBIDDEN.
155
+ The browser check is the user's: name it in one line of your closing message,
156
+ and do not run it yourself. You cannot pay an invoice: the code is
157
+ receive-only. Do not pay, settle or mark an order paid, and do not look for a
158
+ way to (a wallet control port, a test endpoint, another wallet).
159
+
160
+ Your last message starts "Setup is finished" and has at most five short
161
+ lines: the storefront's address, "add a product to the cart, check out, and
162
+ pick Bitcoin & stablecoins at the payment step: the review step shows the
163
+ checkout", and the methods it offers (Bitcoin, plus USDT, USDC, ETH and SOL
164
+ when the swap code is saved). Do not mention minimums, never say a coin will
165
+ not work, do not list what you changed, and send nothing after it. Do not add
166
+ containers, services, workers or cron jobs: the plugin's job runs inside
167
+ Medusa.
168
+
169
+ ## Non-negotiables
170
+
171
+ - Never print, log or commit a code, never put one in a shell argument, and
172
+ never write one into the storefront, source files or browser code.
173
+ `doctor`'s set/unset is all you report.
174
+ - Do not suggest rotating, revoking or replacing a code because it was pasted
175
+ into this chat; that is the supported path.
176
+ - Work only in this store's projects. Never read or run anything from another
177
+ project or directory on this machine (its `node_modules`, tools or source),
178
+ for any reason. A browser, Playwright and reading the plugin's source are
179
+ not part of setup: when `doctor`, `db:migrate` or the curl fails, report its
180
+ output.
181
+ - Receive-only NWC is required. Never get past the preflight with a code that
182
+ can spend.
183
+ - The plugin owns only its two payment tables. Medusa owns carts, orders,
184
+ prices, customers and email. Do not write your own payment provider,
185
+ routes, subscribers or fulfillment code, and do not change Medusa's own
186
+ packages.
187
+ - IF SWAPS ARE ON, KEEP THE ROUTE BACK. A deposit that arrives short or late
188
+ becomes refundable, and the buyer claims it later from the same checkout.
189
+ Keep the plugin installed while swap payments may still need a refund.
190
+ https://openreceive.org/guides/swap-refunds.md
191
+ - A receive-only wallet cannot send refunds. A refund made in the Medusa admin
192
+ is recorded only; the merchant sends it from their own wallet.
193
+
194
+ ## Further reading
195
+
196
+ - [Medusa Quickstart](https://openreceive.org/guides/quickstart-medusa.md)
197
+ - [Automated Swaps](https://openreceive.org/guides/automated-swaps.md)
198
+ - [Swap Refunds](https://openreceive.org/guides/swap-refunds.md)
199
+ - [Lightning Swap Connect URI](https://openreceive.org/guides/lightning-swap-connect.md)
200
+ - [Security](https://openreceive.org/guides/security.md)
201
+ - [Price Feeds](https://openreceive.org/guides/price-feeds.md)
202
+ - [Payment Safety Upgrade](https://openreceive.org/guides/payment-safety-upgrade.md)
203
+
204
+ ---
205
+
206
+ ## The quickstart, in full
207
+
208
+ Inlined verbatim so this file needs no network access. Steps 0–4 above are the setup and this is their reference: it has the code they name. Where the two differ, the steps win, and its admin screens are only for a store whose Medusa server has no shell.
209
+ The page it comes from is https://openreceive.org/guides/quickstart-medusa.
210
+
211
+ ## Medusa quickstart
212
+
213
+ [Medusa](https://medusajs.com) is an open-source commerce platform on Node.
214
+ The OpenReceive plugin adds a payment provider to it: the buyer pays at
215
+ checkout with Bitcoin over Lightning, straight into your own wallet, and the
216
+ order is placed once the payment settles. With a swap provider, the same
217
+ checkout also takes USDT, USDC, SOL and ETH, which the provider converts to
218
+ BTC over Lightning into the same wallet.
219
+
220
+ Medusa keeps owning carts, orders, prices and customers. The plugin keeps its
221
+ payment attempts in two tables of Medusa's own database, over Medusa's own
222
+ connection. Each Medusa payment session is the reference of its payment. When
223
+ a payment settles, Medusa's own process-payment workflow captures the session
224
+ and completes the cart into an order, whether or not the buyer is still on
225
+ the page.
226
+
227
+ Requirements: Medusa 2 on Node 22.12 or later, and Medusa's PostgreSQL
228
+ database. Medusa must run its scheduled jobs, as a Medusa server does unless
229
+ you split it into a server and a worker; then the worker runs them.
230
+
231
+ ### Install the plugin
232
+
233
+ In the Medusa server's project:
234
+
235
+ ```sh
236
+ npm install @openreceive/medusa
237
+ ```
238
+
239
+ Register the plugin and its payment provider in `medusa-config.ts`, next to
240
+ what is already there:
241
+
242
+ ```ts
243
+ module.exports = defineConfig({
244
+ projectConfig: {
245
+ // …your existing settings
246
+ },
247
+ plugins: [{ resolve: "@openreceive/medusa", options: {} }],
248
+ modules: [
249
+ {
250
+ resolve: "@medusajs/medusa/payment",
251
+ options: {
252
+ providers: [
253
+ { resolve: "@openreceive/medusa/providers/openreceive", id: "openreceive" },
254
+ ],
255
+ },
256
+ },
257
+ ],
258
+ })
259
+ ```
260
+
261
+ If `modules` already lists the payment module with other providers, add this
262
+ provider to its `providers` list instead of a second entry. Then create the
263
+ plugin's two tables and restart Medusa:
264
+
265
+ ```sh
266
+ npx medusa db:migrate
267
+ ```
268
+
269
+ The plugin mounts the checkout's routes under `/store/openreceive`, where
270
+ Medusa checks the publishable API key as on every store route. It adds a job
271
+ that runs every minute, and Medusa's id for the provider is
272
+ `pp_openreceive_openreceive`.
273
+
274
+ ### Configure the wallet
275
+
276
+ Put the codes in the Medusa server's `.env`, never in the storefront:
277
+
278
+ ```sh
279
+ NWC_URI=nostr+walletconnect://…
280
+ LSC_URI_PRIMARY=lightning+swapconnect://…
281
+ ```
282
+
283
+ `NWC_URI` is a receive-only Nostr Wallet Connect code: it can create invoices
284
+ and read payments but cannot spend, and the plugin refuses a code that can
285
+ spend. `LSC_URI_PRIMARY` is optional; leave it out for Bitcoin-only checkout.
286
+ Medusa loads `.env` itself. Restart Medusa after changing it.
287
+
288
+ Then offer the method in your regions: in the admin, open **Settings →
289
+ Regions**, edit each region and add **Openreceive (OPENRECEIVE)** to its
290
+ payment providers. Or run, in the Medusa server's project:
291
+
292
+ ```sh
293
+ npx medusa exec ./node_modules/@openreceive/medusa/dist/enable-openreceive.js
294
+ ```
295
+
296
+ It adds the provider to every region and says which ones it changed. Check the
297
+ codes and the wallet with:
298
+
299
+ ```sh
300
+ npx openreceive doctor
301
+ ```
302
+
303
+ `doctor` reads `.env`, prints each code as set or unset (never its value) and
304
+ asks the wallet whether it is receive-only. It exits nonzero when a check
305
+ fails.
306
+
307
+ ### Add the checkout to the storefront
308
+
309
+ The buyer pays at the storefront's review step, before the order is placed.
310
+ For the [Next.js starter](https://github.com/medusajs/nextjs-starter-medusa),
311
+ install the React checkout:
312
+
313
+ ```sh
314
+ npm install @openreceive/react
315
+ ```
316
+
317
+ Add `src/app/api/openreceive/[...path]/route.ts`. It forwards the checkout's
318
+ calls to the Medusa server with the publishable key, as the starter's own
319
+ server calls do, and serves the checkout's stylesheet as it is, outside the
320
+ starter's Tailwind build:
321
+
322
+ ```ts
323
+ import { readFile } from "node:fs/promises"
324
+ import { createRequire } from "node:module"
325
+ import path from "node:path"
326
+ import { NextRequest } from "next/server"
327
+
328
+ const BACKEND_URL = process.env.MEDUSA_BACKEND_URL ?? "http://localhost:9000"
329
+
330
+ async function stylesheet() {
331
+ const resolve = createRequire(path.join(process.cwd(), "package.json")).resolve
332
+ const css = await readFile(resolve("@openreceive/react/styles.css"))
333
+ return new Response(css, {
334
+ headers: { "content-type": "text/css; charset=utf-8", "cache-control": "public, max-age=3600" },
335
+ })
336
+ }
337
+
338
+ async function forward(request: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
339
+ const { path: segments } = await params
340
+ if (request.method === "GET" && segments.join("/") === "styles.css") return stylesheet()
341
+ const url = new URL(`/store/openreceive/${segments.join("/")}`, BACKEND_URL)
342
+ url.search = request.nextUrl.search
343
+ const response = await fetch(url, {
344
+ method: request.method,
345
+ headers: {
346
+ "content-type": request.headers.get("content-type") ?? "application/json",
347
+ accept: "application/json",
348
+ "x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY ?? "",
349
+ },
350
+ body: request.method === "GET" ? undefined : await request.text(),
351
+ cache: "no-store",
352
+ })
353
+ const headers = new Headers({ "cache-control": "no-store" })
354
+ for (const name of ["content-type", "retry-after", "x-request-id"]) {
355
+ const value = response.headers.get(name)
356
+ if (value) headers.set(name, value)
357
+ }
358
+ return new Response(await response.text(), { status: response.status, headers })
359
+ }
360
+
361
+ export { forward as GET, forward as POST }
362
+ ```
363
+
364
+ In `src/lib/constants.tsx`, name the method and add a test for it:
365
+
366
+ ```tsx
367
+ export const paymentInfoMap = {
368
+ // …the existing providers
369
+ pp_openreceive_openreceive: { title: "Bitcoin & stablecoins", icon: <CreditCard /> },
370
+ }
371
+
372
+ export const isOpenReceive = (providerId?: string) =>
373
+ providerId?.startsWith("pp_openreceive_")
374
+ ```
375
+
376
+ In `src/modules/checkout/components/payment-button/index.tsx`, show the
377
+ checkout for an OpenReceive session and place the order once it settles:
378
+
379
+ ```tsx
380
+ import { isManual, isOpenReceive, isStripeLike } from "@lib/constants"
381
+ import { Checkout } from "@openreceive/react"
382
+
383
+ // in PaymentButton's switch, before the manual-payment case:
384
+ case isOpenReceive(paymentSession?.provider_id) && !!paymentSession:
385
+ return <OpenReceivePaymentButton notReady={notReady} sessionId={paymentSession!.id} />
386
+
387
+ const OpenReceivePaymentButton = ({ notReady, sessionId }: { notReady: boolean; sessionId: string }) => {
388
+ const [errorMessage, setErrorMessage] = useState<string | null>(null)
389
+ if (notReady) return <Button disabled>Place order</Button>
390
+ return (
391
+ <>
392
+ <link rel="stylesheet" href="/api/openreceive/styles.css" precedence="default" />
393
+ <Checkout
394
+ reference={sessionId}
395
+ prefix="/api/openreceive"
396
+ resumable
397
+ onSettled={() => {
398
+ placeOrder().catch((err) => setErrorMessage(err.message))
399
+ }}
400
+ />
401
+ <ErrorMessage error={errorMessage} data-testid="openreceive-payment-error-message" />
402
+ </>
403
+ )
404
+ }
405
+ ```
406
+
407
+ The payment step needs no change: it already starts a payment session for
408
+ the method the buyer picks.
409
+
410
+ #### Another storefront
411
+
412
+ Render the checkout where the buyer confirms the order, with the cart's
413
+ OpenReceive payment session id as its `reference`: React `<Checkout>`, or the
414
+ `<openreceive-checkout>` element from `@openreceive/elements`. When it reports
415
+ the payment settled, complete the cart (`POST /store/carts/{id}/complete`).
416
+ Its calls go to `/store/openreceive` on the Medusa server with the
417
+ publishable key: forward them from your storefront's server as above, or call
418
+ Medusa from the browser with a `fetch` that adds the
419
+ `x-publishable-api-key` header. A browser call needs the storefront's origin
420
+ in Medusa's `STORE_CORS`, as the storefront's own browser calls do; from
421
+ another site than Medusa's, the origin must be listed there exactly, not as a
422
+ pattern.
423
+
424
+ ### How a payment works
425
+
426
+ 1. The buyer picks "Bitcoin & stablecoins" at the payment step, and Medusa
427
+ starts a payment session with OpenReceive for the cart's total.
428
+ 2. The review step shows the checkout. It charges the session's amount in the
429
+ cart's currency, converted to sats at the current BTC price. Whoever holds
430
+ the session's id may pay it and follow it, as whoever holds a cart's id may
431
+ complete it in Medusa's own store API.
432
+ 3. Once the wallet reports the payment, the storefront places the order. Medusa
433
+ refuses to complete the cart before that.
434
+ 4. The order's payment is captured at once, and its data holds the Lightning
435
+ payment hash.
436
+
437
+ If the buyer closes the page after paying, the plugin's job finds the payment
438
+ within a minute or so, and Medusa's process-payment workflow places the order
439
+ anyway. If the cart changes before the buyer pays, Medusa starts a new payment
440
+ session, and the checkout asks for the new total.
441
+
442
+ ### Refunds and removal
443
+
444
+ The receive-only wallet cannot send refunds. A refund made in the Medusa admin
445
+ is recorded on the order, as for Medusa's manual payments, and the plugin logs
446
+ a warning naming the payment: send that refund from your own wallet. A buyer
447
+ whose swap deposit never converted reclaims it from the same checkout while
448
+ the cart is still open. If you turn on swaps, keep that path available; see
449
+ [swap refunds](https://openreceive.org/guides/swap-refunds.md).
450
+
451
+ Removing the plugin keeps its two tables: they are a record of payments
452
+ received.
453
+
454
+ ### Tested, and not tested
455
+
456
+ Tested on 2026-10-16 with Medusa 2.21.2 (Medusa's default starter) and
457
+ PostgreSQL 17 in Docker, the Next.js starter, a test wallet and a test swap
458
+ provider: `db:migrate` with the plugin's tables, the enable script, `doctor`,
459
+ and a buyer paying an order by Lightning and by USDT on Solana in Chromium,
460
+ after which the order is placed with its payment captured. A payment whose
461
+ buyer never placed the order was placed by the plugin's job. An unknown
462
+ session, another provider's session and a request without the publishable
463
+ key were refused, and an admin refund was recorded.
464
+
465
+ Not tested: Medusa split into a server and a worker, Medusa Cloud, storefronts
466
+ other than the Next.js starter, and a storefront calling Medusa from the
467
+ browser on another site. Lightning cannot pull a payment, so OpenReceive saves
468
+ no payment methods.
@@ -17,7 +17,7 @@ curl -fsSL https://openreceive.org/agent-directions/mobile/full.md
17
17
  - The app never decides paid: it asks the server. A link back to the app is navigation only.
18
18
  - Do not suggest rotating or revoking a code because it was pasted here.
19
19
 
20
- These directions describe OpenReceive 0.4.27.
20
+ These directions describe OpenReceive 0.4.29.
21
21
 
22
22
  Add Bitcoin checkout, with optional USDT, USDC, ETH and SOL, to a mobile app:
23
23
  React Native or Expo, Flutter, Swift or Kotlin. OpenReceive has no mobile SDK
@@ -17,7 +17,7 @@ curl -fsSL https://openreceive.org/agent-directions/nestjs/full.md
17
17
  - Do not suggest rotating or revoking a code because it was pasted here.
18
18
  - Start the quickstart only once the NWC code is saved, and the LSC code is saved or the user said "Bitcoin only".
19
19
 
20
- These directions describe OpenReceive 0.4.27.
20
+ These directions describe OpenReceive 0.4.29.
21
21
 
22
22
  Add OpenReceive to a NestJS application — the app you are already working in,
23
23
  on Nest's default Express platform. You do not need a copy of the OpenReceive
@@ -330,7 +330,7 @@ products, and you do not join them.
330
330
  ORM. `reference` arrives as a string: convert it when the app's ids are
331
331
  numbers. Database writes
332
332
  only in the hook; emails, jobs, and pushes after commit. Placeholder style is
333
- the dialect you declared: `?` on sqlite, `$1` on postgres.
333
+ the dialect you declared: `?` on sqlite and mysql, `$1` on postgres.
334
334
 
335
335
  ### If the database is Supabase
336
336
 
@@ -595,7 +595,7 @@ async function bootstrap() {
595
595
  // a reference. The WHERE clause is the lock: a second fulfillment
596
596
  // path of yours (admin action, replayed job) updates zero rows. Use
597
597
  // `query` here, not a repository: it runs in this same transaction.
598
- // `$1` on postgres, `?` on sqlite.
598
+ // `$1` on postgres, `?` on sqlite and mysql.
599
599
  await query(
600
600
  "UPDATE orders SET status = 'paid', paid_at = $1 WHERE id = $2 AND status = 'awaiting_payment'",
601
601
  [paidAt, reference],
@@ -3,7 +3,7 @@ This is the full file; follow it from Step 0.
3
3
  # OpenReceive agent directions (Next.js)
4
4
 
5
5
  ```sh
6
- # READ THIS FIRST: this file is 43 KB and a summary drops required steps. Download it whole:
6
+ # READ THIS FIRST: this file is 44 KB and a summary drops required steps. Download it whole:
7
7
  curl -fsSL https://openreceive.org/agent-directions/next/full.md
8
8
  # Skip the download only if you already have all of it: pasted, read from disk or fetched raw.
9
9
  ```
@@ -17,7 +17,7 @@ curl -fsSL https://openreceive.org/agent-directions/next/full.md
17
17
  - Do not suggest rotating or revoking a code because it was pasted here.
18
18
  - Start the quickstart only once the NWC code is saved, and the LSC code is saved or the user said "Bitcoin only".
19
19
 
20
- These directions describe OpenReceive 0.4.27.
20
+ These directions describe OpenReceive 0.4.29.
21
21
 
22
22
  Add OpenReceive to a Next.js App Router application — the app you are already
23
23
  working in. You do not need a copy of the OpenReceive source: the packages are
@@ -319,7 +319,7 @@ products, and you do not join them.
319
319
  app's equivalent) through the `query` the library hands you, on that same
320
320
  settlement transaction, not a second connection from your ORM. Database writes
321
321
  only in the hook; emails, jobs, and pushes after commit. Placeholder style is
322
- the dialect you declared: `?` on sqlite, `$1` on postgres.
322
+ the dialect you declared: `?` on sqlite and mysql, `$1` on postgres.
323
323
 
324
324
  ### If the database is Supabase
325
325
 
@@ -561,13 +561,14 @@ export const dynamic = "force-dynamic";
561
561
  export const { GET, POST } = openReceiveNextHandlers({
562
562
  wallet: { nwc: process.env.NWC_URI! }, // receive-only NWC code; your app refuses to start otherwise
563
563
  storage: {
564
- db, // pg Pool/Client, node:sqlite, better-sqlite3, or a custom adapter
564
+ db, // pg Pool/Client, mysql2 Pool, node:sqlite, better-sqlite3, or a custom adapter
565
565
  onPaid: async ({ reference, paidAt, query }) => {
566
566
  // Settlement transaction; runs only for the first settled attempt for a
567
567
  // reference. The WHERE clause is the lock: a second fulfillment path of
568
568
  // yours (admin action, replayed job) updates zero rows and does nothing.
569
- // Use `query` here, not your ORM's other connection. `?` on sqlite, `$1`
570
- // on postgres.
569
+ // Use `query` here, not your ORM's other connection. `?` on sqlite and
570
+ // mysql, `$1` on postgres. MySQL has no RETURNING: after the UPDATE,
571
+ // `SELECT ROW_COUNT() AS claimed` tells you the same thing.
571
572
  const claimed = await query(
572
573
  "UPDATE orders SET state = 'paid', paid_at = ? WHERE id = ? AND state = 'awaiting_payment' RETURNING id",
573
574
  [paidAt, reference],
@@ -17,7 +17,7 @@ curl -fsSL https://openreceive.org/agent-directions/node/full.md
17
17
  - Do not suggest rotating or revoking a code because it was pasted here.
18
18
  - Start the quickstart only once the NWC code is saved, and the LSC code is saved or the user said "Bitcoin only".
19
19
 
20
- These directions describe OpenReceive 0.4.27.
20
+ These directions describe OpenReceive 0.4.29.
21
21
 
22
22
  Add OpenReceive to a Node application — the app you are already working in. You
23
23
  do not need a copy of the OpenReceive source: the packages are on npm, and the
@@ -310,7 +310,7 @@ products, and you do not join them.
310
310
  app's equivalent) through the `query` the library hands you, on that same
311
311
  settlement transaction, not a second connection from your ORM. Database writes
312
312
  only in the hook; emails, jobs, and pushes after commit. Placeholder style is
313
- the dialect you declared: `?` on sqlite, `$1` on postgres.
313
+ the dialect you declared: `?` on sqlite and mysql, `$1` on postgres.
314
314
 
315
315
  ### If the database is Supabase
316
316
 
@@ -543,13 +543,14 @@ app.use(express.json());
543
543
  const openreceive = openReceiveExpress({
544
544
  wallet: { nwc: process.env.NWC_URI! }, // receive-only NWC code; your app refuses to start otherwise
545
545
  storage: {
546
- db, // pg Pool/Client, node:sqlite, better-sqlite3, or a custom adapter
546
+ db, // pg Pool/Client, mysql2 Pool, node:sqlite, better-sqlite3, or a custom adapter
547
547
  onPaid: async ({ reference, paidAt, query }) => {
548
548
  // Settlement transaction; runs only for the first settled attempt for a
549
549
  // reference. The WHERE clause is the lock: a second fulfillment path of
550
550
  // yours (admin action, replayed job) updates zero rows and does nothing.
551
- // Use `query` here, not your ORM's other connection. `?` on sqlite, `$1`
552
- // on postgres.
551
+ // Use `query` here, not your ORM's other connection. `?` on sqlite and
552
+ // mysql, `$1` on postgres. MySQL has no RETURNING: after the UPDATE,
553
+ // `SELECT ROW_COUNT() AS claimed` tells you the same thing.
553
554
  const claimed = await query(
554
555
  "UPDATE orders SET state = 'paid', paid_at = ? WHERE id = ? AND state = 'awaiting_payment' RETURNING id",
555
556
  [paidAt, reference],
@@ -17,7 +17,7 @@ curl -fsSL https://openreceive.org/agent-directions/nuxt/full.md
17
17
  - Do not suggest rotating or revoking a code because it was pasted here.
18
18
  - Start the quickstart only once the NWC code is saved, and the LSC code is saved or the user said "Bitcoin only".
19
19
 
20
- These directions describe OpenReceive 0.4.27.
20
+ These directions describe OpenReceive 0.4.29.
21
21
 
22
22
  Add OpenReceive to a Nuxt application — the app you are already working in,
23
23
  on Nitro's `node-server` preset. OpenReceive ships no Nuxt package: the
@@ -325,7 +325,7 @@ products, and you do not join them.
325
325
  app's equivalent) through the `query` the library hands you, on that same
326
326
  settlement transaction, not a second connection from your ORM. Database writes
327
327
  only in the hook; emails, jobs, and pushes after commit. Placeholder style is
328
- the dialect you declared: `?` on sqlite, `$1` on postgres.
328
+ the dialect you declared: `?` on sqlite and mysql, `$1` on postgres.
329
329
 
330
330
  ## If you build your own checkout UI
331
331