@blockonomics/medusa-payment-blockonomics 1.0.0 → 1.0.1

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 (2) hide show
  1. package/README.md +141 -280
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,280 +1,141 @@
1
- # @blockonomics/medusa-payment-blockonomics
2
-
3
- Accept Bitcoin payments in [Medusa](https://medusajs.com) v2 through [Blockonomics](https://www.blockonomics.co). Payments go straight to the merchant's own wallet: Blockonomics generates a fresh address for each checkout and notifies Medusa as the transaction is seen and confirmed on-chain.
4
-
5
- Blockonomics never holds funds. That makes refunds a manual step, covered under [Refunds](#refunds). Read that section before going live.
6
-
7
- ## Requirements
8
-
9
- - Medusa `2.19.0` or later
10
- - A [Blockonomics](https://www.blockonomics.co) account with a store and a wallet (xPub) attached
11
- - A Medusa server reachable from the internet, so Blockonomics can deliver callbacks
12
-
13
- ## Install
14
-
15
- ```bash
16
- npm install @blockonomics/medusa-payment-blockonomics
17
- ```
18
-
19
- ## Configure
20
-
21
- ### 1. Register the plugin and the provider
22
-
23
- Add the plugin to `plugins`, then register the provider in the Payment Module:
24
-
25
- ```ts
26
- // medusa-config.ts
27
- import { defineConfig, loadEnv } from "@medusajs/framework/utils"
28
-
29
- loadEnv(process.env.NODE_ENV || "development", process.cwd())
30
-
31
- module.exports = defineConfig({
32
- // ...
33
- plugins: [
34
- {
35
- resolve: "@blockonomics/medusa-payment-blockonomics",
36
- options: {},
37
- },
38
- ],
39
- modules: [
40
- {
41
- resolve: "@medusajs/medusa/payment",
42
- options: {
43
- providers: [
44
- {
45
- resolve: "@blockonomics/medusa-payment-blockonomics/providers/blockonomics",
46
- id: "blockonomics",
47
- options: {
48
- apiKey: process.env.BLOCKONOMICS_API_KEY,
49
- callbackSecret: process.env.BLOCKONOMICS_CALLBACK_SECRET,
50
- confirmations: 2,
51
- },
52
- },
53
- ],
54
- },
55
- },
56
- ],
57
- })
58
- ```
59
-
60
- Both entries are required. The `plugins` entry loads the callback route; the provider entry loads the payment provider. Without the `plugins` entry, checkout works but callbacks return `404`, and no payment ever completes.
61
-
62
- ```bash
63
- # .env
64
- BLOCKONOMICS_API_KEY=your_api_key
65
- BLOCKONOMICS_CALLBACK_SECRET=MEDUSA_...
66
- ```
67
-
68
- Generate the secret by running this in your Medusa project, after installing the plugin:
69
-
70
- ```bash
71
- npx blockonomics-callback-secret
72
- ```
73
-
74
- It prints a `BLOCKONOMICS_CALLBACK_SECRET=MEDUSA_...` line to add to `.env`. Or, without the plugin installed:
75
-
76
- ```bash
77
- node -e "console.log('MEDUSA_' + require('crypto').randomBytes(32).toString('hex'))"
78
- ```
79
-
80
- The `MEDUSA_` prefix is required; Medusa refuses to start without it. The secret is not issued by Blockonomics.
81
-
82
- ### 2. Set the callback URL in Blockonomics
83
-
84
- In the Blockonomics dashboard, open **Stores**, edit the store, and set its callback URL to:
85
-
86
- ```
87
- https://your-store.com/hooks/blockonomics/blockonomics_blockonomics?secret=YOUR_CALLBACK_SECRET
88
- ```
89
-
90
- Get this exactly right. A wrong URL fails silently: callbacks arrive, are rejected, and orders stay unpaid.
91
-
92
- - The path is `/hooks/blockonomics/blockonomics_{id}`, where `{id}` is the provider's `id` in `medusa-config.ts`. With `id: "blockonomics"` that gives `blockonomics_blockonomics`. If you change the `id`, change the URL.
93
- - `secret` must equal `BLOCKONOMICS_CALLBACK_SECRET`. Callbacks with a missing or different secret are logged and ignored.
94
- - Use `/hooks/blockonomics/...`, not Medusa's `/hooks/payment/...`. Blockonomics sends callbacks as `GET` requests, which Medusa's own endpoint does not accept.
95
-
96
- ### 3. Enable the provider in a region
97
-
98
- In the Medusa admin, go to **Settings → Regions**, edit the region, and add **Blockonomics** (`pp_blockonomics_blockonomics`) to its payment providers.
99
-
100
- ## Options
101
-
102
- | Option | Default | Description |
103
- | --- | --- | --- |
104
- | `apiKey` | - | **Required.** API key of the Blockonomics merchant account. |
105
- | `callbackSecret` | - | **Required.** Secret on the store's callback URL, starting with `MEDUSA_`. Callbacks without it are ignored. |
106
- | `confirmations` | `2` | On-chain confirmations a callback has to report before the payment is taken as settled and the order is marked paid: `0`, `1`, or `2`. See [Choosing confirmations](#choosing-confirmations). |
107
- | `priceLockSeconds` | `600` | How long the quoted BTC amount stays valid. Once it expires with nothing received, the amount is re-quoted at the current rate. Clamped to 300–1800. |
108
- | `underpaymentTolerance` | `0` | Fraction of the expected amount that may be missing and still count as paid, to absorb rounding and wallet fee deductions. `0.01` allows a 1% shortfall. Blockonomics for WooCommerce calls this underpayment slack. |
109
- | `overpaymentTolerance` | `0.05` | Excess above which the payment is flagged with `overpaid: true` for manual review. The payment still settles. |
110
- | `matchCallback` | - | Substring of the store's callback URL. Set it when the Blockonomics account has more than one store, so addresses are generated for the right one. An account with more than one store rejects address generation without it, with error `1032`, `Specify store using match_callback parameter`. |
111
- | `baseUrl` | `https://www.blockonomics.co` | Base URL of the Blockonomics API. Only useful for testing against a stub. |
112
-
113
- ### Choosing confirmations
114
-
115
- | Value | Order marked paid | Risk |
116
- | --- | --- | --- |
117
- | `0` | As soon as the transaction is seen in the mempool | The sender can still double-spend it. Only use for low-value or reversible fulfilment. |
118
- | `1` | After about 10 minutes | Low. |
119
- | `2` | After about 20 minutes | Lowest. Recommended default. |
120
-
121
- Whichever value you pick, the order is placed as soon as the transaction is seen and marked paid once it settles. See [Orders](#orders).
122
-
123
- Unconfirmed transactions that opted into Replace-By-Fee are never treated as paid, even with `confirmations: 0`, because the sender can still replace them.
124
-
125
- ### Offering both instant and secure checkout
126
-
127
- There is no settings screen for provider options, but you can register the provider twice under different ids and choose between them per region in the admin:
128
-
129
- ```ts
130
- // medusa-config.ts
131
- providers: [
132
- {
133
- resolve: "@blockonomics/medusa-payment-blockonomics/providers/blockonomics",
134
- id: "instant",
135
- options: {
136
- apiKey: process.env.BLOCKONOMICS_API_KEY,
137
- callbackSecret: process.env.BLOCKONOMICS_CALLBACK_SECRET,
138
- confirmations: 0,
139
- },
140
- },
141
- {
142
- resolve: "@blockonomics/medusa-payment-blockonomics/providers/blockonomics",
143
- id: "secure",
144
- options: {
145
- apiKey: process.env.BLOCKONOMICS_API_KEY,
146
- callbackSecret: process.env.BLOCKONOMICS_CALLBACK_SECRET,
147
- confirmations: 2,
148
- },
149
- },
150
- ]
151
- ```
152
-
153
- These register as `pp_blockonomics_instant` and `pp_blockonomics_secure`. Each needs its own callback URL, so use a separate Blockonomics store for each, with `matchCallback` set to tell them apart:
154
-
155
- ```
156
- https://your-store.com/hooks/blockonomics/blockonomics_instant?secret=...
157
- https://your-store.com/hooks/blockonomics/blockonomics_secure?secret=...
158
- ```
159
-
160
- ## Payment lifecycle
161
-
162
- The provider follows the payment model of the Blockonomics WooCommerce plugin. Every address handed out for a session is a payment with its own fiat quote, and the callback that reaches `confirmations` settles it:
163
-
164
- | Callback | Payment | Payment session status |
165
- | --- | --- | --- |
166
- | None yet | `new` - the quote can still be refreshed | `pending` |
167
- | Below `confirmations` | `in progress` - address and quote are frozen | `pending_authorization`, order is placed |
168
- | At or above `confirmations`, amount covered | `settled` | `captured`, order is paid |
169
- | At or above `confirmations`, amount short | `settled`, underpaid | `pending_authorization` |
170
-
171
- What a settled payment paid is recorded in satoshis and in fiat, valued at the rate that address was quoted at: a customer who sent 40% of the BTC asked for has paid 40% of the fiat, whatever the rate has done since.
172
-
173
- **Underpayments.** The remainder in fiat is quoted on a fresh address at the current rate the next time the quote is refreshed (see [Re-quoting the amount](#re-quoting-the-amount)). The settled address stays on the session as the record of the partial payment. The order is paid once the last address settles in full. Nothing is refunded automatically.
174
-
175
- **Overpayments.** The payment settles as normal, and Medusa records the order total as captured. What was actually received is shown in the **Bitcoin payment** box on the order page in the admin, flagged **Overpaid** above `overpaymentTolerance`, and logged as a warning. Refund the difference from your wallet.
176
-
177
- A settled address ignores further callbacks. An unconfirmed transaction that opted into Replace-By-Fee only has its transaction id recorded, since the sender can still cancel it.
178
-
179
- ### Orders
180
-
181
- - The order is placed as soon as a transaction is seen. It shows in the admin with payment status **Awaiting**.
182
- - Once the payment settles in full, it changes to **Captured**. Only fulfil **Captured** orders.
183
- - An underpaid order stays **Awaiting** until the remainder settles.
184
- - If a transaction never confirms (e.g. double-spent), the order stays **Awaiting**. Cancel it in the admin.
185
-
186
- ## Storefront
187
-
188
- There is no hosted payment page. The storefront shows the customer where to send Bitcoin, using the payment session's `data`.
189
-
190
- [`examples/checkout-page.js`](https://github.com/blockonomics/medusa-plugin/blob/main/examples/checkout-page.js) is a working payment screen that implements everything in this section, in plain JavaScript with no framework. Start from it and port it to your storefront. [Without a storefront](#without-a-storefront) shows how to run it against a cart.
191
-
192
- The session's `data` carries:
193
-
194
- | Field | Description |
195
- | --- | --- |
196
- | `address` | Bitcoin address to pay. |
197
- | `expected_satoshis` | Amount to send to it, in satoshis. Divide by `1e8` for BTC. |
198
- | `expected_fiat` | The same amount in the cart's currency: the order total less what earlier addresses settled. |
199
- | `fiat_amount` | The order total. |
200
- | `paid_fiat` | Settled so far, over all addresses of the session. `0` until an underpayment settles. |
201
- | `btc_price` | Rate the amount was quoted at. |
202
- | `price_locked_until` | Unix milliseconds until the quote expires. |
203
- | `payment_status` | `0` nothing seen, `1` payment in progress, `2` settled. |
204
- | `confirmations` | Confirmations the latest callback reported. |
205
- | `txid` | Transaction id, once one has been seen. |
206
- | `payments` | Every address of the session with the fields above, oldest first. |
207
-
208
- The flow the Blockonomics WooCommerce plugin uses, and what to build. The function names point to where the example does each step:
209
-
210
- 1. Find the cart's payment session whose `provider_id` starts with `pp_blockonomics` (`findSession`).
211
- 2. Show a QR code of `bitcoin:{address}?amount={btc}`, an "Open in wallet" link to the same URI, and the address and amount as copyable fields (`renderPayment`).
212
- 3. Count down to `price_locked_until`. When it runs out, re-quote through the plugin's store route below and update the amount, rate, and QR code (`startTimer`, `refresh`, `requote`). Do not create a new payment session for this: Medusa replaces the session, which hands the customer a new address.
213
- 4. Open `wss://www.blockonomics.co/payment/{address}`, and reconnect if it drops. The first message means the payment has been seen: switch to a receipt screen with the transaction id. Messages carry `status` (`0` unconfirmed, `1`, `2` confirmed), `value` in satoshis, and `txid` (`connectSocket`, `showReceipt`).
214
- 5. If `value` is short of `expected_satoshis`, show the shortfall in fiat (`expected_fiat * (1 - value / expected_satoshis)`). The remainder can be paid once the underpayment has confirmed: call the re-quote route, and when it comes back with a new `address`, return to the payment screen for it. Show `paid_fiat` and `expected_fiat` as the paid and remaining amounts (`offerRemainder`, the **Pay remaining** handler).
215
- 6. Do not complete the cart from the storefront. The callbacks place the order and mark it paid. Poll the cart until `completed_at` is set, and follow the socket for confirmations (`watchOrder`).
216
- 7. When the page is reloaded after paying, `payment_status` is no longer `0`: show the receipt from the session data instead of the payment screen (the boot block at the end of the page script).
217
-
218
- ### Re-quoting the amount
219
-
220
- ```
221
- POST /store/blockonomics/payment-sessions/{payment_session_id}
222
- x-publishable-api-key: pk_...
223
- ```
224
-
225
- Runs the provider's price refresh on the session, what the WooCommerce plugin does when its checkout page loads:
226
-
227
- - nothing seen yet: the amount is re-quoted at the current rate once the price lock has expired. The address is kept.
228
- - payment in progress: nothing changes; the customer sent what they were quoted.
229
- - settled underpayment: a new address is handed out for the remainder at the current rate. The response carries the new `address`.
230
-
231
- ```json
232
- {
233
- "payment_session": {
234
- "id": "payses_...",
235
- "status": "pending",
236
- "data": { "address": "...", "expected_fiat": 20, "expected_satoshis": 30149, "btc_price": 66338.6, "price_locked_until": 1789701331086, "payment_status": 0 }
237
- }
238
- }
239
- ```
240
-
241
- ## Refunds
242
-
243
- Bitcoin payments are irreversible, and Blockonomics holds no funds, so refunds cannot go through the provider. **Clicking Refund in the Medusa admin throws an error.**
244
-
245
- To refund a customer, send the amount from the wallet that received the payment, and record the refund outside of Medusa.
246
-
247
- ## Testing
248
-
249
- Blockonomics has a test mode that fires real callbacks without moving funds.
250
-
251
- 1. Expose your local server with a tunnel such as ngrok, and set the store's callback URL to the tunnel address.
252
- 2. In the Blockonomics dashboard, enable **Testmode** on the store.
253
- 3. Place an order in your storefront and choose Blockonomics.
254
- 4. On the [Test Bench](https://www.blockonomics.co/dashboard#/test-bench), send the quoted amount from the **Test Bitcoin Wallet**.
255
-
256
- Callbacks arrive with status `0` immediately, `1` after about 5 minutes, and `2` after about 10. The order appears in the admin as **Awaiting** after status `0`, and turns **Captured** at `confirmations`.
257
-
258
- Checkout failing with `Blockonomics API responded with 400 for /api/new_address` and error `1032` means the account has more than one store and [`matchCallback`](#options) is unset. Set it to a substring of this store's callback URL that no other store in the account shares.
259
-
260
- ### Without a storefront
261
-
262
- The payment screen can be exercised before a storefront exists. `examples/create-cart.js` builds a cart and a Blockonomics payment session over the Store API, and `examples/checkout-page.js` serves the payment screen for it on port 8000.
263
-
264
- ```bash
265
- export MEDUSA_URL=http://localhost:9000
266
- export ADMIN_EMAIL=you@example.com
267
- export ADMIN_PASSWORD=...
268
-
269
- node examples/create-cart.js # prints a cart id
270
- node examples/checkout-page.js <cart_id> # http://localhost:8000
271
- ```
272
-
273
- The payment screen calls the Store API from the browser, so `http://localhost:8000` has to be in the server's `storeCors`. A local Medusa allows it by default. Point `MEDUSA_URL` at a deployed store and you have to add that origin there yourself.
274
-
275
- Both need Node 18 or later and have no dependencies. They sign in as an admin only to read a publishable key; everything after that goes through the Store API.
276
-
277
-
278
- ## License
279
-
280
- MIT
1
+ # @blockonomics/medusa-payment-blockonomics
2
+
3
+ Accept Bitcoin in [Medusa](https://medusajs.com) v2 through [Blockonomics](https://www.blockonomics.co). Payments go straight to your own wallet: each checkout gets a fresh address, and Blockonomics notifies Medusa as the transaction confirms.
4
+
5
+ Blockonomics never holds funds, so refunds are manual. See [Refunds](#refunds).
6
+
7
+ ## Requirements
8
+
9
+ - Medusa `2.19.0` or later
10
+ - A Blockonomics account with a store and a wallet (xPub) attached
11
+ - A Medusa server reachable from the internet, for callbacks
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @blockonomics/medusa-payment-blockonomics
17
+ ```
18
+
19
+ ## Configure
20
+
21
+ ### 1. Register the plugin and the provider
22
+
23
+ ```ts
24
+ // medusa-config.ts
25
+ module.exports = defineConfig({
26
+ plugins: [
27
+ { resolve: "@blockonomics/medusa-payment-blockonomics", options: {} },
28
+ ],
29
+ modules: [
30
+ {
31
+ resolve: "@medusajs/medusa/payment",
32
+ options: {
33
+ providers: [
34
+ {
35
+ resolve: "@blockonomics/medusa-payment-blockonomics/providers/blockonomics",
36
+ id: "blockonomics",
37
+ options: {
38
+ apiKey: process.env.BLOCKONOMICS_API_KEY,
39
+ callbackSecret: process.env.BLOCKONOMICS_CALLBACK_SECRET,
40
+ confirmations: 2,
41
+ },
42
+ },
43
+ ],
44
+ },
45
+ },
46
+ ],
47
+ })
48
+ ```
49
+
50
+ Both entries are required. Without the `plugins` entry, callbacks return `404` and no payment completes.
51
+
52
+ Generate the callback secret in your project and add the printed line to `.env` with your API key:
53
+
54
+ ```bash
55
+ npx blockonomics-callback-secret
56
+ # BLOCKONOMICS_CALLBACK_SECRET=MEDUSA_...
57
+ ```
58
+
59
+ The secret must start with `MEDUSA_`. It is yours, not issued by Blockonomics.
60
+
61
+ ### 2. Set the callback URL in Blockonomics
62
+
63
+ In the Blockonomics dashboard, under **Stores**, set the store's callback URL to:
64
+
65
+ ```
66
+ https://your-store.com/hooks/blockonomics/blockonomics_blockonomics?secret=YOUR_CALLBACK_SECRET
67
+ ```
68
+
69
+ The path is `/hooks/blockonomics/blockonomics_{id}`, where `{id}` is the provider `id`. Callbacks with a wrong secret are ignored, so a typo leaves orders unpaid without any error.
70
+
71
+ ### 3. Enable the provider in a region
72
+
73
+ In the Medusa admin, under **Settings → Regions**, add **Blockonomics** (`pp_blockonomics_blockonomics`) to the region.
74
+
75
+ ## Options
76
+
77
+ | Option | Default | Description |
78
+ | --- | --- | --- |
79
+ | `apiKey` | - | **Required.** Blockonomics API key. |
80
+ | `callbackSecret` | - | **Required.** Secret on the callback URL, starting with `MEDUSA_`. |
81
+ | `confirmations` | `2` | Confirmations before the order is marked paid: `0`, `1` (~10 min) or `2` (~20 min). `0` risks double-spends. |
82
+ | `priceLockSeconds` | `600` | How long the BTC quote is valid (300–1800). |
83
+ | `underpaymentTolerance` | `0` | Shortfall still counted as paid. `0.01` = 1%. |
84
+ | `overpaymentTolerance` | `0.05` | Excess above which the payment is flagged as overpaid. |
85
+ | `matchCallback` | - | Substring of the store's callback URL. Required if the account has more than one store (else error `1032`). |
86
+ | `baseUrl` | `https://www.blockonomics.co` | API base URL, for testing. |
87
+
88
+ Unconfirmed Replace-By-Fee transactions are never treated as paid. To offer several confirmation levels, register the provider more than once with different `id`s, each with its own Blockonomics store and `matchCallback`.
89
+
90
+ ## Orders
91
+
92
+ - A transaction is seen: the order is placed with payment status **Awaiting**.
93
+ - It reaches `confirmations` and covers the amount: **Captured**. Only fulfil captured orders.
94
+ - **Underpaid:** the order stays **Awaiting**. The remainder is quoted on a new address.
95
+ - **Overpaid:** the order is captured, and the admin order page shows the amount received with an **Overpaid** flag. Refund the difference from your wallet.
96
+ - **Never confirms:** the order stays **Awaiting**. Cancel it in the admin.
97
+
98
+ ## Storefront
99
+
100
+ There is no hosted payment page. Your storefront renders the payment from the session's `data`. [`examples/checkout-page.js`](https://github.com/blockonomics/medusa-plugin/blob/main/examples/checkout-page.js) is a complete payment screen in plain JavaScript that you can port.
101
+
102
+ Key `data` fields: `address`, `expected_satoshis`, `expected_fiat`, `paid_fiat`, `btc_price`, `price_locked_until` (Unix ms), `payment_status` (`0` none, `1` in progress, `2` settled), `confirmations`, `txid`, `payments` (all addresses for the session).
103
+
104
+ The page should:
105
+
106
+ 1. Show a QR code for `bitcoin:{address}?amount={btc}` and count down to `price_locked_until`.
107
+ 2. When the quote expires, or after an underpayment confirms, call the re-quote route below. Do not create a new payment session.
108
+ 3. Listen on `wss://www.blockonomics.co/payment/{address}` to detect the payment.
109
+ 4. Poll the cart until `completed_at` is set. Callbacks complete the order, so don't complete the cart from the storefront.
110
+
111
+ ### Re-quoting
112
+
113
+ ```
114
+ POST /store/blockonomics/payment-sessions/{payment_session_id}
115
+ x-publishable-api-key: pk_...
116
+ ```
117
+
118
+ This refreshes the amount once the price lock has expired. If the session has a settled underpayment, it returns a new `address` for the remainder.
119
+
120
+ ## Refunds
121
+
122
+ Bitcoin payments are irreversible, and refunds cannot go through the provider. **Refund in the Medusa admin throws an error.** Send refunds from your wallet and record them outside Medusa.
123
+
124
+ ## Testing
125
+
126
+ 1. Expose your server (e.g. ngrok) and point the store's callback URL at it.
127
+ 2. Enable **Testmode** on the store in Blockonomics.
128
+ 3. Check out with Blockonomics, then send the amount from the [Test Bench](https://www.blockonomics.co/dashboard#/test-bench).
129
+
130
+ Callbacks arrive with status `0` right away, `1` after about 5 minutes and `2` after about 10.
131
+
132
+ To test without a storefront:
133
+
134
+ ```bash
135
+ MEDUSA_URL=http://localhost:9000 ADMIN_EMAIL=... ADMIN_PASSWORD=... node examples/create-cart.js
136
+ node examples/checkout-page.js <cart_id> # http://localhost:8000, must be in storeCors
137
+ ```
138
+
139
+ ## License
140
+
141
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockonomics/medusa-payment-blockonomics",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Accept Bitcoin payments in Medusa v2 through Blockonomics, straight to the merchant's own wallet.",
5
5
  "author": "Blockonomics (https://www.blockonomics.co)",
6
6
  "license": "MIT",