Initial release of JSON payment protocol module

This commit is contained in:
Rob Riddle 2018-02-01 16:52:44 -05:00
commit 72ccb98518
No known key found for this signature in database
GPG Key ID: D8021933F751A0DD
8 changed files with 750 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

32
.jshintrc Normal file
View File

@ -0,0 +1,32 @@
{
"esversion": 6,
"bitwise": true,
"camelcase": true,
"curly": true,
"devel": false,
"eqeqeq": true,
"eqnull": false,
"freeze": true,
"funcscope": false,
"immed": true,
"indent": 2,
"latedef": "nofunc",
"maxdepth": 4,
"maxerr": 99999, // so that jshint doesn't give up on a file with tons of errors
"maxlen": 120,
"maxparams": 4,
"maxstatements": 35,
"mocha": true,
"newcap": true,
"noarg": true,
"node": true,
"noempty": true,
"nonew": true,
"quotmark": "single",
"regexp": true,
"smarttabs": false,
"strict": true,
"trailing": true,
"undef": true,
"unused": true
}

1
.npmignore Normal file
View File

@ -0,0 +1 @@
examples

246
examples/bitcoinRpc.js Normal file
View File

@ -0,0 +1,246 @@
'use strict';
const async = require('async');
const request = require('request');
const promptly = require('promptly');
const JsonPaymentProtocol = require('../index'); //or require('json-payment-protocol')
const paymentProtocol = new JsonPaymentProtocol({strictSSL: false});
let config = {
network: 'test',
currency: 'BTC',
rpcServer: {
username: 'fakeUser',
password: 'fakePassword',
ipAddress: '127.0.0.1',
port: '18332'
}
};
if (config.rpcServer.username === 'fakeUser') {
return console.log('You should update the config in this file to match the actual configuration of your bitcoind' +
' RPC interface');
}
/**
* While this client does show effective use of json payment protocol, it may not follow best practices generate
* payments via bitcoinRPC. We do not recommend copying this code verbatim in any product designed for actual users.
*/
let paymentUrl;
let requiredFee = 0;
let outputObject = {};
async.waterfall([
function askForPaymentUrl(cb) {
promptly.prompt('What is the payment protocol uri?', {required: true}, cb);
},
function retrievePaymentRequest(uri, cb) {
paymentProtocol.getRawPaymentRequest(uri, (err, rawResponse) => {
if (err) {
console.log('Error retrieving payment request', err);
return cb(err);
}
return cb(null, rawResponse);
});
},
function parsePaymentRequest(rawResponse, cb) {
paymentProtocol.parsePaymentRequest(rawResponse.rawBody, rawResponse.headers, (err, paymentRequest) => {
if (err) {
console.log('Error parsing payment request', err);
return cb(err);
}
return cb(null, paymentRequest);
});
},
function checkAndDisplayPaymentRequestToUser(paymentRequest, cb) {
requiredFee = (paymentRequest.requiredFeePerByte * 1024) / 1e8;
//Make sure request is for the currency we support
if (paymentRequest.currency.toLowerCase() !== config.currency.toLowerCase()) {
console.log('Server requested a payment in', paymentRequest.currency, 'but we are configured to accept',
config.currency);
return cb(new Error('Payment request currency did not match our own'));
}
if (paymentRequest.network.toLowerCase() !== config.network.toLowerCase()) {
console.log('Server requested a payment on the', paymentRequest.network, 'network but we are configured for the',
config.network, 'network');
return cb(new Error('Payment request network did not match our own'));
}
console.log('Server is requesting payments for:');
console.log('---');
paymentRequest.outputs.forEach(function (output) {
let cryptoAmount = round(output.amount / 1e8, 8);
console.log(cryptoAmount + ' to ' + output.address);
outputObject[output.address] = cryptoAmount;
});
console.log('---');
paymentUrl = paymentRequest.paymentUrl;
cb();
},
function createRawTransaction(cb) {
let createCommand = {
jsonrpc: '1.0',
method: 'createrawtransaction',
params: [
[],
outputObject
]
};
execRpcCommand(createCommand, (err, rawTransaction) => {
if (err) {
console.log('Error creating raw transaction', err);
return cb(err);
}
else if (!rawTransaction) {
console.log('No raw tx generated');
return cb(new Error('No tx generated'));
}
else {
return cb(null, rawTransaction);
}
});
},
function fundRawTransaction(rawTransaction, cb) {
let fundCommand = {
jsonrpc: '1.0',
method: 'fundrawtransaction',
params: [
rawTransaction,
{
feeRate: requiredFee
}
]
};
execRpcCommand(fundCommand, (err, fundedRawTransaction) => {
if (err) {
console.log('Error funding transaction', err);
return cb(err);
}
if (!fundedRawTransaction) {
console.log('No funded tx generated');
return cb(new Error('No funded tx generated'));
}
else {
cb(null, fundedRawTransaction.hex);
}
});
},
function signRawTransaction(fundedRawTransaction, cb) {
let command = {
jsonrpc: '1.0',
method: 'signrawtransaction',
params: [fundedRawTransaction]
};
execRpcCommand(command, function (err, signedTransaction) {
if (err) {
console.log('Error signing transaction:', err);
return cb(err);
}
if (!signedTransaction) {
console.log('Bitcoind did not return a signed transaction');
return cb(new Error('Missing signed tx'));
}
cb(null, signedTransaction.hex);
});
},
function displayTransactionToUserForApproval(signedRawTransaction, cb) {
let command = {
jsonrpc: '1.0',
method: 'decoderawtransaction',
params: [signedRawTransaction]
};
execRpcCommand(command, function (err, decodedTransaction) {
if (err) {
console.log('Error signing transaction:', err);
return cb(err);
}
if (!decodedTransaction) {
console.log('Bitcoind did not return a decoded transaction');
return cb(new Error('Missing decoded tx'));
}
console.log(JSON.stringify(decodedTransaction, null, 2));
promptly.confirm('Send payment shown above? (y/n)', function (err, accept) {
if (!accept) {
console.log('Payment cancelled');
return cb(new Error('Payment Cancelled'));
}
return cb(null, signedRawTransaction);
});
});
},
function sendTransactionToServer(signedRawTransaction, cb) {
paymentProtocol.sendPayment(config.currency, signedRawTransaction, paymentUrl, function (err, response) {
if (err) {
console.log('Error sending payment to server', err);
return cb(err);
}
else {
console.log('Payment accepted by server');
return cb(null, signedRawTransaction);
}
});
},
//Note we only broadcast AFTER a SUCCESS response from the server
function broadcastPayment(signedRawTransaction, cb) {
let command = {
jsonrpc: '1.0',
method: 'sendrawtransaction',
params: [signedRawTransaction]
};
execRpcCommand(command, function (err, signedTransaction) {
if (err) {
console.log('Error broadcasting transaction:', err);
return cb(err);
}
if (!signedTransaction) {
console.log('Bitcoind failed to broadcast transaction');
return cb(new Error('Failed to broadcast tx'));
}
cb();
});
}
]);
function execRpcCommand(command, callback) {
request
.post({
url: 'http://' + config.rpcServer.ipAddress + ':' + config.rpcServer.port,
body: command,
json: true,
auth: {
user: config.rpcServer.username,
pass: config.rpcServer.password,
sendImmediately: false
}
}, function (err, response, body) {
if (err) {
return callback(err);
}
if (body.error) {
return callback(body.error);
}
if (body.result) {
return callback(null, body.result);
}
return callback();
});
}
function round(value, places) {
let tmp = Math.pow(10, places);
return Math.ceil(value * tmp) / tmp;
}

163
index.js Normal file
View File

@ -0,0 +1,163 @@
'use strict';
//Native
const crypto = require('crypto');
const query = require('querystring');
const url = require('url');
const util = require('util');
//Modules
const _ = require('lodash');
const request = require('request');
function PaymentProtocol(options) {
this.options = _.merge({
strictSSL: true
}, options);
}
/**
* Makes a request to the given url and returns the raw JSON string retrieved as well as the headers
* @param paymentUrl {string} the payment protocol specific url
* @param callback {function} (err, body, headers)
*/
PaymentProtocol.prototype.getRawPaymentRequest = function getRawPaymentRequest(paymentUrl, callback) {
let paymentUrlObject = url.parse(paymentUrl);
//Detect 'bitcoin:' urls and extract payment-protocol section
if (paymentUrlObject.protocol !== 'http' && paymentUrlObject.protocol !== 'https') {
let uriQuery = query.decode(paymentUrlObject.query);
if (!uriQuery.r) {
return callback(new Error('Invalid payment protocol url'));
}
else {
paymentUrl = uriQuery.r;
}
}
let requestOptions = _.merge(this.options, {
url: paymentUrl,
headers: {
'Accept': 'application/payment-request'
}
});
request.get(requestOptions, (err, response) => {
if (err) {
return callback(err);
}
if (response.statusCode !== 200) {
return callback(new Error(response.body.toString()));
}
return callback(null, {rawBody: response.body, headers: response.headers});
});
};
/**
* Makes a request to the given url and returns the raw JSON string retrieved as well as the headers
* @param url {string} the payment protocol specific url (https)
*/
PaymentProtocol.prototype.getRawPaymentRequestAsync = util.promisify(PaymentProtocol.prototype.getRawPaymentRequest);
/**
* Given a raw payment protocol body, parses it and validates it against the digest header
* @param rawBody {string} Raw JSON string retrieved from the payment protocol server
* @param headers {object} Headers sent by the payment protocol server
* @param callback {function} (err, paymentRequest)
*/
PaymentProtocol.prototype.parsePaymentRequest = function parsePaymentRequest(rawBody, headers, callback) {
let paymentRequest;
if (!rawBody) {
return callback(new Error('Parameter rawBody is required'));
}
if (!headers) {
return callback(new Error('Parameter headers is required'));
}
try {
paymentRequest = JSON.parse(rawBody);
}
catch (e) {
return callback(new Error(`Unable to parse request - ${e}`));
}
if (!headers.digest) {
return callback(new Error('Digest missing from response headers'));
}
let digest = headers.digest.split('=')[1];
let hash = crypto.createHash('sha256').update(rawBody, 'utf8').digest('hex');
if (digest !== hash) {
return callback(new Error(`Response body hash does not match digest header. Actual: ${hash} Expected: ${digest}`));
}
return callback(null, paymentRequest);
};
/**
* Given a raw payment protocol body, parses it and validates it against the digest header
* @param rawBody {string} Raw JSON string retrieved from the payment protocol server
* @param headers {object} Headers sent by the payment protocol server
*/
PaymentProtocol.prototype.parsePaymentRequestAsync = util.promisify(PaymentProtocol.prototype.parsePaymentRequest);
/**
* Sends a given payment to the server for validation
* @param currency {string} Three letter currency code of proposed transaction (ie BTC, BCH)
* @param signedRawTransaction {string} Hexadecimal format raw signed transaction
* @param url {string} the payment protocol specific url (https)
* @param callback {function} (err, response)
*/
PaymentProtocol.prototype.sendPayment = function sendPayment(currency, signedRawTransaction, url, callback) {
let paymentResponse;
//Basic sanity checks
if (typeof signedRawTransaction !== 'string') {
return callback(new Error('signedRawTransaction must be a string'));
}
if (!/^[0-9a-f]+$/i.test(signedRawTransaction)) {
return callback(new Error('signedRawTransaction must be in hexadecimal format'));
}
let requestOptions = _.merge(this.options, {
url: url,
headers: {
'Content-Type': 'application/payment'
},
body: JSON.stringify({
currency: currency,
transactions: [signedRawTransaction]
})
});
request.post(requestOptions, (err, response) => {
if (err) {
return callback(err);
}
if (response.statusCode !== 200) {
return callback(new Error(response.body.toString()));
}
try {
paymentResponse = JSON.parse(response.body);
}
catch (e) {
return callback(new Error('Unable to parse response from server'));
}
callback(null, paymentResponse);
});
};
/**
* Sends a given payment to the server for validation
* @param currency {string} Three letter currency code of proposed transaction (ie BTC, BCH)
* @param signedRawTransaction {string} Hexadecimal format raw signed transaction
* @param url {string} the payment protocol specific url (https)
*/
PaymentProtocol.prototype.sendPaymentAsync = util.promisify(PaymentProtocol.prototype.sendPayment);
module.exports = PaymentProtocol;

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "json-payment-protocol",
"version": "0.1.0",
"description": "Simple interface for retrieving JSON payment requests and submitting payments",
"main": "index.js",
"dependencies": {
"lodash": "^4.17.4",
"request": "^2.83.0"
},
"devDependencies": {
"async": "^2.6.0",
"promptly": "^2.2.0"
},
"engines": {
"node": ">=8.0.0"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "BitPay",
"repository": {
"type": "git",
"url": "https://github.com/bitpay/jsonPaymentProtocol.git"
},
"license": "MIT"
}

91
readme.md Normal file
View File

@ -0,0 +1,91 @@
### JSON Payment Protocol Interface
This is the first version of the JSON payment protocol interface. If you have questions about the specification itself, [view the documentation](specification.md).
### Getting Started
`npm install json-payment-protocol`
### Usage
We support both callbacks and promises. For promises just add Async to the end of the function name. Be careful to follow the notes about when to broadcast your payment. **Broadcasting a payment before getting a success notification back from the server in most cases will lead to a failed payment for the sender.** The sender will bear the cost of paying transaction fees yet again to get their money back.
#### Callbacks
```
const JsonPaymentProtocol = require('json-payment-protocol');
const paymentProtocol = new JsonPaymentProtocol();
let requestUrl = 'bitcoin:?r=https://test.bitpay.com/i/Jr629pwsXKdTCneLyZja4t';
paymentProtocol.getRawPaymentRequest(requestUrl, function (err, response) {
if (err) {
return console.log('Error retrieving payment request', err);
}
paymentProtocol.parsePaymentRequest(response.body, response.headers, function (err, paymentRequest) {
if (err) {
return console.log('Error parsing payment request', err);
}
console.log('Payment request retrieved');
console.log(paymentRequest);
//TODO: Create the rawTransaction and sign it in your wallet instead of this, do NOT broadcast yet
let currency = 'BTC'
let signedRawTransaction = '02000000010c2b0d60448d5cdfebe222014407bdb408b8427f837447484911efddea700323000000006a47304402201d3ed3117f1968c3b0a078f15f8462408c745ff555b173eff3dfe0a25e063c0c02200551572ec33d45ece8e64275970bd1b1694621f0ed8fac2f7e18095f170fe3fe012102d4edb773e3bd94e1251790f5cc543cbfa76c2b0abad14898674b1c4e27176ef2ffffffff02c44e0100000000001976a914dd826377dcf2075e5065713453cfad675ba9434f88aca070002a010000001976a914e7d0344ba970301e93cd7b505c7ae1b5bcf5639288ac00000000';
paymentProtocol.sendPayment(currency, signedRawTransaction, paymentRequest.paymentUrl, function(err, response) {
if (err) {
//DO NOT BROADCAST PAYMENT
return console.log('Error sending payment to server');
}
console.log('Payment sent successfully');
//TODO: Broadcast payment to network here
});
});
});
```
#### Promises
```
const JsonPaymentProtocol = require('json-payment-protocol');
const paymentProtocol = new JsonPaymentProtocol();
let requestUrl = 'bitcoin:?r=https://test.bitpay.com/i/Jr629pwsXKdTCneLyZja4t';
paymentProtocol
.getRawPaymentRequestAsync(requestUrl)
.then((response) => {
return paymentProtocol.parsePaymentRequestAsync(response.rawBody, response.headers);
})
.then((paymentRequest) => {
console.log('Payment request retrieved');
console.log(paymentRequest);
//TODO: Create the rawTransaction and sign it in your wallet instead of this, do NOT broadcast yet
let currency = 'BTC'
let signedRawTransaction = '02000000010c2b0d60448d5cdfebe222014407bdb408b8427f837447484911efddea700323000000006a47304402201d3ed3117f1968c3b0a078f15f8462408c745ff555b173eff3dfe0a25e063c0c02200551572ec33d45ece8e64275970bd1b1694621f0ed8fac2f7e18095f170fe3fe012102d4edb773e3bd94e1251790f5cc543cbfa76c2b0abad14898674b1c4e27176ef2ffffffff02c44e0100000000001976a914dd826377dcf2075e5065713453cfad675ba9434f88aca070002a010000001976a914e7d0344ba970301e93cd7b505c7ae1b5bcf5639288ac00000000';
return paymentProtocol.sendPaymentAsync(currency, signedRawTransaction, paymentRequest.paymentUrl);
})
.then((response) => {
console.log('Payment sent successfully');
//TODO: Broadcast payment to network here
})
.catch((err) => {
//DO NOT BROADCAST PAYMENT
return console.log('Error processing payment request', err);
});
```
### Options
Options passed to `new JsonPaymentProtocol()` are passed to request, so if you need to use a proxy or set any other request.js flags you can do so by including them when instantiating your instance. For example:
```
new JsonPaymentProtocol({
proxy: 'socks://mySocksProxy.local'
})
```
### URI Formats
You can provide either the `bitcoin:?r=https://bitpay.com/i/invoice` format or `https://bitpay.com/i/invoice` directly.

190
specification.md Normal file
View File

@ -0,0 +1,190 @@
# JSON Payment Protocol Specification
Revision 0.5
## Application Logic
1. (Web) User selects preferred currency on invoice if multiple options are available
2. (Client) Wallet obtains payment protocol uri
3. (Client) Fetches payment information from server
4. (Server) Verifies invoice exists and is still accepting payments, responds with payment request
5. (Client) Validates payment request hash
6. (Client) Generates a payment to match conditions on payment request
7. (Client) Submits proposed signed transaction to server
8. (Server) Validates invoice exists and is still accepting payments
9. (Server) Validates payment matches address, amount, and currency of invoice and has a reasonable transaction fee.
10. (Server) Broadcasts payment to network and notifies client payment was accepted.
11. (Client) If payment is accepted by server, wallet broadcasts payment
If at any time the payment is rejected by the server **your client should not broadcast the payment**.
Broadcasting a payment before getting a success notification back from the server will in most cases lead to a failed payment for the sender. The sender will bear the cost of paying transaction fees yet again to get their money back.
## Payment Request
### Request
A GET request should be made to the payment protocol url.
### Response
The response will be a JSON format payload quite similar to the BIP70 format.
#### Headers
On a successful request, the response will contain one header of note.
* `digest` - A SHA256 hash of the JSON response string, should be verified by the client before proceeding
#### Body
* `network` - Which network is this request for (main / test / regtest)
* `currency` - Three digit currency code representing which coin the request is based on
* `requiredFeePerByte` - The minimum fee per byte required on this transaction, if lower than that we will reject it
* `outputs` - What output(s) your transaction must include in order to be accepted
* `time` - ISO Date format of when the invoice was generated
* `expires` - ISO Date format of when the invoice will expire
* `memo` - A plain text description of the payment request, can be displayed to the user / kept for records
* `paymentUrl` - The url where the payment should be sent
* `paymentId` - The invoice ID, can be kept for records
#### Response Body Example
```
{
"network": "test",
"currency": "BTC",
"requiredFeePerByte": 200,
"outputs": [
{
"amount": 39300,
"address": "mthVG9kuRTJQtXieJVDSrrvWyM7QDZ3rcV"
}
],
"time": "2018-01-12T22:04:54.364Z",
"expires": "2018-01-12T22:19:54.364Z",
"memo": "Payment request for BitPay invoice TmyrxFvAi4DjFNy3c7EjVm for merchant Robs Fake Business",
"paymentUrl": "https://test.bitpay.com/i/TmyrxFvAi4DjFNy3c7EjVm",
"paymentId": "TmyrxFvAi4DjFNy3c7EjVm"
}
```
## Payment Payload
### Request
A POST request should be made to the payment protocol url with a `Content-Type` header set to `application/payment`. A JSON format body should be included with the following fields:
```
{
"currency": "<currency 3 letter code>",
"transactions": [
"<transaction in hexedecimal string format>"
]
}
```
#### Example Request Body
```
{
"currency": "BTC",
"transactions": [
"02000000011f0f762184cbc8e94b307fab6f805168724f123a23cd48aac4a9bac8768cfd67000000004847304402205079b96def679f04de9698dd8b9f58dff3e4a13c075f5939c6edfbb8698c8cc802203eac5a3d6410a9f94a86828a4e207f8083fe0bf1c77a74a0cb7add49100d427001ffffffff0284990000000000001976a9149097a519e42061e4977b07b69735ed842b755c0088ac08cd042a010000001976a914cf4b90bca14deab1315c125b8b74b7d31eea97b288ac00000000"
]
}
```
### Response
The response will be a JSON format payload containing the original payment body and a memo field which should be displayed to the user.
#### Response Example
```
{
"payment": {
"transactions": [
"020000000121053733b28b90707a3c63a48171f71abfdc7288bf9d78170e73cfedbbbdfcea00000000484730440220545d53b54873a5afbaf01a77943828f25c6a28d9c5ca4d0968130b5788fc6f9302203e45125723844e4752202792b764b6538342ad169d3828dad18eb231ea01f05101ffffffff02b09a0000000000001976a9149659267896dda4e5aef150e4ca83f0d76022c7b288ac84dd042a010000001976a914fa1a5ed99ce09fd901e9ca7d6f8fcc56d3d5eccf88ac00000000"
]
},
"memo": "Transaction received by BitPay. Invoice will be marked as paid if the transaction is confirmed."
}
```
### Curl Example
```
curl -v -H 'Content-Type: application/payment' -d '{"currency": "BTC", "transactions":["02000000012319227d3995427b05429df7ea30b87cb62f986ba3003311a2cf2177fb5b0ae8000000004847304402205bd75d6b654a70dcc8f548b630c39aec1d2c1de6900b5376ef607efc705f65b002202dd1036f091d4d6047e2f5bcd230ec8bcd5ad2f0785908d78f08a52b8850559f01ffffffff02b09a0000000000001976a9140b2a833c4183c51b86f5dcbb2eeeaca2dfb44bae88acdccb042a010000001976a914f0fd63e5880cbed2fa856e1f4174fc875eeccc5a88ac00000000"]}' https://test.bitpay.com/i/7QBCJ2TpazTKKnczzJQJMc
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to test.bitpay.com (127.0.0.1) port 8088 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
> POST /i/7QBCJ2TpazTKKnczzJQJMc HTTP/1.1
> Host: test.bitpay.com
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type: application/payment-ack
> Content-Length: 403
>
* upload completely sent off: 403 out of 403 bytes
< HTTP/1.1 200 OK
< Content-Length: 520
< Date: Fri, 12 Jan 2018 22:44:13 GMT
< Connection: keep-alive
<
* Connection #0 to host test.bitpay.com left intact
{"payment":{"transactions":["02000000012319227d3995427b05429df7ea30b87cb62f986ba3003311a2cf2177fb5b0ae8000000004847304402205bd75d6b654a70dcc8f548b630c39aec1d2c1de6900b5376ef607efc705f65b002202dd1036f091d4d6047e2f5bcd230ec8bcd5ad2f0785908d78f08a52b8850559f01ffffffff02b09a0000000000001976a9140b2a833c4183c51b86f5dcbb2eeeaca2dfb44bae88acdccb042a010000001976a914f0fd63e5880cbed2fa856e1f4174fc875eeccc5a88ac00000000"]},"memo":"Transaction received by BitPay. Invoice will be marked as paid if the transaction is confirmed."}%
```
## Errors
All errors are communicated in plaintext with an appropriate status code.
### Example Error
```
curl -v https://test.bitpay.com/i/48gZau8ao76bqAoEwAKSwx -H 'Accept: application/payment-request'
* Trying 104.17.68.20...
* TCP_NODELAY set
* Connected to test.bitpay.com (104.17.68.20) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
> GET /i/48gZau8ao76bqAoEwAKSwx HTTP/1.1
> Host: test.bitpay.com
> User-Agent: curl/7.54.0
> Accept: application/payment-request
>
< HTTP/1.1 400 Bad Request
< Date: Fri, 26 Jan 2018 01:54:03 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 44
< Connection: keep-alive
< Strict-Transport-Security: max-age=31536000
< X-Download-Options: noopen
< X-Content-Type-Options: nosniff
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET, POST, OPTIONS
< Access-Control-Allow-Headers: Host, Connection, Content-Length, Accept, Origin, User-Agent, Content-Type, Accept-Encoding, Accept-Language
<
* Connection #0 to host test.bitpay.com left intact
This invoice is no longer accepting payments
```
### Common Errors
| Http Status Code | Response | Cause |
|---|---|---|
| 404 | This invoice was not found or has been archived | Invalid invoiceId, or invoice has been archived (current TTL is 3 days) |
| 400 | Unsupported Content-Type for payment | Your Content-Type header was not valid |
| 400 | Invoice no longer accepting payments | Invoice is either paid or has expired |
| 400 | We were unable to parse your payment. Please try again or contact your wallet provider | Request body could not be parsed / empty body |
| 400 | Request must include exactly one (1) transaction | Included no transaction in body / Included multiple transactions in body |
| 400 | Your transaction was an in an invalid format, it must be a hexadecimal string | Make sure you're sending the raw hex string format of your signed transaction
| 400 | We were unable to parse the transaction you sent. Please try again or contact your wallet provider | Transaction was hex, but it contained invalid transaction data or was in the wrong format |
| 400 | The transaction you sent does not have any output to the bitcoin address on the invoice | The transaction you sent does not pay to the address listed on the invoice |
| 400 | The amount on the transaction (X BTC) does not match the amount requested (Y BTC). This payment will not be accepted. | Payout amount to address does not match amount that was requested |
| 400 | Transaction fee (X sat/kb) is below the current minimum threshold (Y sat/kb) | Your fee must be at least the amount sent in the payment request as `requiredFeePerByte`|
| 400 | This invoice is priced in BTC, not BCH. Please try with a BTC wallet instead | Your transaction currency did not match the one on the invoice |
| 422 | One or more input transactions for your transaction were not found on the blockchain. Make sure you're not trying to use unconfirmed change | Spending outputs which have not yet been broadcast to the network |
| 422 | One or more input transactions for your transactions are not yet confirmed in at least one block. Make sure you're not trying to use unconfirmed change | Spending outputs which have not yet confirmed in at least one block on the network |
| 500 | Error broadcasting payment to network | Our Bitcoin node returned an error when attempting to broadcast your transaction to the network. This could mean our node is experiencing an outage or your transaction is a double spend. |
Another issue you may see is that you are being redirected to `bitpay.com/invoice?id=xxx` instead of being sent a payment-request. In that case you are not setting your `Accept` header to a valid value and we assume you are a browser or other unknown requester.
## MIME Types
|Mime|Description|
|---|---|
|application/payment-request| Associated with the server's payment request, this specified on the client `Accept` header when retrieving the payment request|
|application/payment| Used by the client when sending their proposed payment transaction payload|
|application/payment-ack| Used by the server to state acceptance of the client's proposed payment transaction|