x402-bch-axios 2.0.0 → 2.1.0
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/index.js +52 -3
- package/package.json +1 -1
- package/test/unit/index-unit.js +49 -7
package/index.js
CHANGED
|
@@ -123,13 +123,16 @@ export async function createPaymentHeader (
|
|
|
123
123
|
// Support both v1 (minAmountRequired) and v2 (amount) field names
|
|
124
124
|
const amountRequired = paymentRequirements.amount || paymentRequirements.minAmountRequired
|
|
125
125
|
|
|
126
|
+
// Check if this is "check my tab" mode (txid === "*")
|
|
127
|
+
const isCheckMyTabMode = txid === '*'
|
|
128
|
+
|
|
126
129
|
const authorization = {
|
|
127
130
|
from: signer.address,
|
|
128
131
|
to: paymentRequirements.payTo,
|
|
129
132
|
value: amountRequired,
|
|
130
|
-
txid,
|
|
131
|
-
vout,
|
|
132
|
-
amount: signer.paymentAmountSats
|
|
133
|
+
txid: isCheckMyTabMode ? '*' : txid,
|
|
134
|
+
vout: isCheckMyTabMode ? null : vout,
|
|
135
|
+
amount: isCheckMyTabMode ? null : signer.paymentAmountSats
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
const messageToSign = JSON.stringify(authorization)
|
|
@@ -232,10 +235,17 @@ export function withPaymentInterceptor (
|
|
|
232
235
|
return Promise.reject(new Error('Missing axios request configuration'))
|
|
233
236
|
}
|
|
234
237
|
|
|
238
|
+
// Prevent infinite loops
|
|
235
239
|
if (originalConfig.__is402Retry) {
|
|
236
240
|
return Promise.reject(error)
|
|
237
241
|
}
|
|
238
242
|
|
|
243
|
+
// If this is a "check my tab" retry that failed, fall back to UTXO generation
|
|
244
|
+
if (originalConfig.__is402CheckMyTab) {
|
|
245
|
+
// Clear the flag and proceed with UTXO generation
|
|
246
|
+
originalConfig.__is402CheckMyTab = false
|
|
247
|
+
}
|
|
248
|
+
|
|
239
249
|
// Parse payment requirements - v2 can come from header, v1 from body
|
|
240
250
|
let paymentRequired = null
|
|
241
251
|
let x402Version = 1
|
|
@@ -277,6 +287,45 @@ export function withPaymentInterceptor (
|
|
|
277
287
|
// Convert to number for calculations (v2 uses strings, v1 uses numbers)
|
|
278
288
|
const cost = Number(paymentRequirements.amount || paymentRequirements.minAmountRequired)
|
|
279
289
|
|
|
290
|
+
// Try "check my tab" mode first if no UTXO is tracked
|
|
291
|
+
if (!currentUtxo.txid && !originalConfig.__is402CheckMyTab) {
|
|
292
|
+
// Attempt "check my tab" mode
|
|
293
|
+
const checkMyTabHeader = await createPaymentHeader(
|
|
294
|
+
signer,
|
|
295
|
+
paymentRequirements,
|
|
296
|
+
x402Version || 2,
|
|
297
|
+
'*', // txid = "*" for check my tab mode
|
|
298
|
+
null, // vout = null for check my tab mode
|
|
299
|
+
resource,
|
|
300
|
+
extensions
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
originalConfig.__is402CheckMyTab = true
|
|
304
|
+
originalConfig.__is402Retry = true
|
|
305
|
+
originalConfig.headers['PAYMENT-SIGNATURE'] = checkMyTabHeader
|
|
306
|
+
originalConfig.headers['Access-Control-Expose-Headers'] = 'PAYMENT-RESPONSE'
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
const checkMyTabResponse = await axiosInstance.request(originalConfig)
|
|
310
|
+
// "Check my tab" succeeded - return response and continue using check my tab mode
|
|
311
|
+
// Don't update currentUtxo since we're using check my tab mode
|
|
312
|
+
return checkMyTabResponse
|
|
313
|
+
} catch (checkMyTabError) {
|
|
314
|
+
// "Check my tab" failed - check if it's a 402 error
|
|
315
|
+
if (checkMyTabError.response && checkMyTabError.response.status === 402) {
|
|
316
|
+
// 402 error from "check my tab" - fall back to UTXO generation
|
|
317
|
+
// Reset flags and continue with standard flow
|
|
318
|
+
originalConfig.__is402CheckMyTab = false
|
|
319
|
+
originalConfig.__is402Retry = false
|
|
320
|
+
// Continue to UTXO generation logic below
|
|
321
|
+
} else {
|
|
322
|
+
// Non-402 error (network error, etc.) - reject it
|
|
323
|
+
return Promise.reject(checkMyTabError)
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Standard mode: use existing UTXO or generate new one
|
|
280
329
|
let txid = null
|
|
281
330
|
let vout = null
|
|
282
331
|
let satsLeft = null
|
package/package.json
CHANGED
package/test/unit/index-unit.js
CHANGED
|
@@ -315,7 +315,12 @@ describe('#index.js', () => {
|
|
|
315
315
|
.resolves({ txid: 'tx123', vout: 0, satsSent: 2000 })
|
|
316
316
|
__internals.sendPayment = sendPaymentStub
|
|
317
317
|
|
|
318
|
-
|
|
318
|
+
// First call (check my tab) returns 402, second call (with UTXO) succeeds
|
|
319
|
+
axiosInstance.request
|
|
320
|
+
.onFirstCall()
|
|
321
|
+
.rejects(create402Error())
|
|
322
|
+
.onSecondCall()
|
|
323
|
+
.resolves({ data: 'ok' })
|
|
319
324
|
|
|
320
325
|
withPaymentInterceptor(axiosInstance, signer)
|
|
321
326
|
|
|
@@ -326,9 +331,10 @@ describe('#index.js', () => {
|
|
|
326
331
|
|
|
327
332
|
assert.deepEqual(response, { data: 'ok' })
|
|
328
333
|
assert.isTrue(sendPaymentStub.calledOnce)
|
|
329
|
-
assert.isTrue(axiosInstance.request.
|
|
334
|
+
assert.isTrue(axiosInstance.request.calledTwice)
|
|
330
335
|
|
|
331
|
-
|
|
336
|
+
// Check the second call (with UTXO) - first call was check my tab
|
|
337
|
+
const updatedConfig = axiosInstance.request.secondCall.args[0]
|
|
332
338
|
assert.isTrue(updatedConfig.__is402Retry)
|
|
333
339
|
assert.property(updatedConfig.headers, 'PAYMENT-SIGNATURE')
|
|
334
340
|
assert.propertyVal(
|
|
@@ -382,7 +388,22 @@ describe('#index.js', () => {
|
|
|
382
388
|
.resolves({ txid: 'tx123', vout: 0, satsSent: 2000 })
|
|
383
389
|
__internals.sendPayment = sendPaymentStub
|
|
384
390
|
|
|
385
|
-
|
|
391
|
+
// First call (check my tab) returns 402, second call (with UTXO) succeeds
|
|
392
|
+
axiosInstance.request
|
|
393
|
+
.onFirstCall()
|
|
394
|
+
.rejects({
|
|
395
|
+
response: {
|
|
396
|
+
status: 402,
|
|
397
|
+
headers: {},
|
|
398
|
+
data: {
|
|
399
|
+
x402Version: 2,
|
|
400
|
+
accepts: [cloneDeep(basePaymentRequirements)]
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
config: { headers: {} }
|
|
404
|
+
})
|
|
405
|
+
.onSecondCall()
|
|
406
|
+
.resolves({ data: 'ok' })
|
|
386
407
|
|
|
387
408
|
withPaymentInterceptor(axiosInstance, signer)
|
|
388
409
|
|
|
@@ -414,9 +435,10 @@ describe('#index.js', () => {
|
|
|
414
435
|
|
|
415
436
|
assert.deepEqual(response, { data: 'ok' })
|
|
416
437
|
assert.isTrue(sendPaymentStub.calledOnce)
|
|
417
|
-
assert.isTrue(axiosInstance.request.
|
|
438
|
+
assert.isTrue(axiosInstance.request.calledTwice)
|
|
418
439
|
|
|
419
|
-
|
|
440
|
+
// Check the second call (with UTXO) - first call was check my tab
|
|
441
|
+
const updatedConfig = axiosInstance.request.secondCall.args[0]
|
|
420
442
|
const headerPayload = JSON.parse(updatedConfig.headers['PAYMENT-SIGNATURE'])
|
|
421
443
|
assert.equal(headerPayload.x402Version, 2)
|
|
422
444
|
assert.deepEqual(headerPayload.resource, baseResource)
|
|
@@ -432,7 +454,27 @@ describe('#index.js', () => {
|
|
|
432
454
|
.resolves({ txid: 'tx123', vout: 0, satsSent: 2000 })
|
|
433
455
|
__internals.sendPayment = sendPaymentStub
|
|
434
456
|
|
|
435
|
-
|
|
457
|
+
// First call (check my tab) returns 402, second call (with UTXO) succeeds
|
|
458
|
+
axiosInstance.request
|
|
459
|
+
.onFirstCall()
|
|
460
|
+
.rejects({
|
|
461
|
+
response: {
|
|
462
|
+
status: 402,
|
|
463
|
+
headers: {},
|
|
464
|
+
data: {
|
|
465
|
+
x402Version: 1,
|
|
466
|
+
accepts: [{
|
|
467
|
+
network: 'bch',
|
|
468
|
+
scheme: 'utxo',
|
|
469
|
+
payTo: 'bitcoincash:qprecv',
|
|
470
|
+
minAmountRequired: 1500
|
|
471
|
+
}]
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
config: { headers: {} }
|
|
475
|
+
})
|
|
476
|
+
.onSecondCall()
|
|
477
|
+
.resolves({ data: 'ok' })
|
|
436
478
|
|
|
437
479
|
withPaymentInterceptor(axiosInstance, signer)
|
|
438
480
|
|