Initial version
Copy changes Renamed events and lots of small improvements for validation Improved auth + cleanup WIP CreateInvoice First version of the CreateInvoice action WIP moving store to an inputField and using dynamic dropdowns Working dynamic dropdown for Invoice Created Minor refactoring Added dynamic dropdown for Store ID to all events WIP Setting an invoice as Settled works now New test for MarkInvoiceInvalid New Search "Find Invoice" Minor fixes
This commit is contained in:
parent
e34d4260e3
commit
13f3f361ba
64
.gitignore
vendored
Normal file
64
.gitignore
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# environment variables file
|
||||
.env
|
||||
.environment
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
.idea/
|
||||
3
.zapierapprc
Normal file
3
.zapierapprc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"id": 138823
|
||||
}
|
||||
45
README.md
45
README.md
@ -1,23 +1,56 @@
|
||||
# BTCPay Server Integration for Zapier.com
|
||||
|
||||
This repository contains the Zapier integration for BTCPay Server.
|
||||
It allows you to integrate and automate BTCPay Server.
|
||||
The Zapier integration allows you to use triggers from BTCPay Server (like when a new invoice was created, paid or expired) to trigger actions in other systems, like send an email, post a chat message, etc.
|
||||
|
||||
## Requirements
|
||||
- An account at Zapier.com
|
||||
- A BTCPay Server instance where you have 1 or more stores
|
||||
- An API Key that has access to the store and resources you want to automate
|
||||
- An API key that has access to the store and resources you want to automate (BEWARE: Never grant more permissions that is absolutely needed!)
|
||||
|
||||
## Features
|
||||
The Zapier integration allows you to use triggers from BTCPay Server (like when a new invoice was created, paid or expired) to trigger actions in other systems, like send an email, post a chat message, etc.
|
||||
## Security Considerations
|
||||
With great power comes great responsibility. So when you automate your BTCPay Server, you should consider the things that could go wrong or get abused if someone were to get hold of your API Key.
|
||||
Because you will be providing Zapier with your API key, we strongly encourage you to create an API key with as little permissions as possible.
|
||||
If you are only going to automate a single store, make sure the API key only has access to the resources for that specific store.
|
||||
BTCPay Server allows your to configure access per store, so use it.
|
||||
|
||||
## How does it work technically?
|
||||
- Every trigger registers its own webhook in the store it is for. There are no global webhooks in BTCPay Server, only ones per store.
|
||||
- The webhook only handles the single event it is created for.
|
||||
- When you enable the Zap, the webhook is automatically created in BTCPay Server and when you disable, the webhook is deleted.
|
||||
- Each webhook uses its own secret, randomly generated by BTCPay Server.
|
||||
- The webhook's request signature `BTCPay-Sig` is used to verify the identity of the sender.
|
||||
|
||||
## Current state
|
||||
This integration uses the new Greenfield API and webhooks for real-time communications.
|
||||
All triggers and actions in this repo should be functional.
|
||||
More triggers and actions will be added over time.
|
||||
|
||||
Working Triggers:
|
||||
- Invoice Created
|
||||
Working Triggers (incl tests):
|
||||
- CreateInvoice Created
|
||||
|
||||
Working Triggers (without tests):
|
||||
- CreateInvoice Expired
|
||||
- CreateInvoice Invalid
|
||||
- CreateInvoice Processing
|
||||
- CreateInvoice Settled
|
||||
- Payment Received
|
||||
|
||||
Working Actions:
|
||||
- Create a New Invoice
|
||||
- Mark Invoice as Invalid
|
||||
- Mark Invoice as Settled (test needed)
|
||||
|
||||
Working Searches:
|
||||
- Find an Invoice by Order ID
|
||||
|
||||
## Tests
|
||||
Tests are implemented with Jest and should work 100%. Just run `zapier test` to run them.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### I deleted the webhook from my BTCPay Server store by mistake.
|
||||
No worries. Just turn your Zap off and on again and the webhook will automatically be recreated in BTCPay Server.
|
||||
|
||||
## Need or prefer professional help?
|
||||
This integration was developed by [Storefront.be](https://www.storefront.be). You can reach out to us for paid support and consultancy.
|
||||
36
authentication.js
Normal file
36
authentication.js
Normal file
@ -0,0 +1,36 @@
|
||||
module.exports = {
|
||||
type: 'custom',
|
||||
connectionLabel: '{{server_url}}',
|
||||
test: {
|
||||
url: '{{bundle.authData.server_url}}/api/v1/server/info',
|
||||
method: 'GET',
|
||||
params: {
|
||||
},
|
||||
headers: {
|
||||
Authorization: 'token {{bundle.authData.api_key}}'
|
||||
},
|
||||
body: {},
|
||||
removeMissingValuesFrom: {},
|
||||
// TODO validate the API key. Does it have all the Store ID permissions? Nothing more? Nothing less?
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
computed: false,
|
||||
key: 'server_url',
|
||||
required: true,
|
||||
label: 'BTCPay Server URL',
|
||||
type: 'string',
|
||||
helpText: 'Enter your BTC Pay Server URL.',
|
||||
default: 'https://mybtcpay.com',
|
||||
},
|
||||
{
|
||||
computed: false,
|
||||
key: 'api_key',
|
||||
required: true,
|
||||
label: 'API Key',
|
||||
type: 'string',
|
||||
helpText: 'You can create an API key by going to My Account > API Keys in BTCPay Server.',
|
||||
}
|
||||
],
|
||||
customConfig: {},
|
||||
};
|
||||
179
common/Invoice.js
Normal file
179
common/Invoice.js
Normal file
@ -0,0 +1,179 @@
|
||||
const Util = require('../common/functions');
|
||||
|
||||
module.exports = {
|
||||
noun: 'Invoice',
|
||||
getById: async function (z, bundle, storeId, invoiceId) {
|
||||
const options = {
|
||||
url: bundle.authData.server_url + '/api/v1/stores/' + storeId + '/invoices/' + invoiceId,
|
||||
method: 'GET',
|
||||
params: {},
|
||||
body: {},
|
||||
};
|
||||
|
||||
let response = await z.request(options);
|
||||
if (response.status === 200) {
|
||||
return this.format(response.json, storeId);
|
||||
} else {
|
||||
throw new z.errors.Error('Invoice could not be found.', 'NotFound', 404);
|
||||
}
|
||||
},
|
||||
|
||||
create: async function (z, serverUrl, storeId, amount, currency, orderId, buyerName, buyerEmail, buyerCountry, buyerZip, buyerState, buyerCity, buyerAddress1, buyerAddress2, buyerPhone) {
|
||||
const options = {
|
||||
url: serverUrl + '/api/v1/stores/' + storeId + '/invoices/',
|
||||
method: 'POST',
|
||||
params: {},
|
||||
body: {
|
||||
amount: amount,
|
||||
currency: currency,
|
||||
metadata: {
|
||||
orderId: orderId,
|
||||
buyerName: buyerName,
|
||||
buyerEmail: buyerEmail,
|
||||
buyerCountry: buyerCountry,
|
||||
buyerZip: buyerZip,
|
||||
buyerState: buyerState,
|
||||
buyerCity: buyerCity,
|
||||
buyerAddress1: buyerAddress1,
|
||||
buyerAddress2: buyerAddress2,
|
||||
buyerPhone: buyerPhone,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let response = await z.request(options);
|
||||
if (response.status === 200) {
|
||||
return this.format(response.json, storeId);
|
||||
} else if (response.status === 403) {
|
||||
throw new z.errors.Error('Forbidden. Invoice could not be created.', 'Forbidden', response.status);
|
||||
} else {
|
||||
let errorMsg = 'Error: ';
|
||||
for (let i = 0; i < response.json.length; i++) {
|
||||
errorMsg += response.json[i].message + ', ';
|
||||
}
|
||||
throw new z.errors.Error(errorMsg, 'InvalidData', response.status);
|
||||
}
|
||||
},
|
||||
|
||||
performForOne: async function (z, bundle) {
|
||||
// TODO this is a bit ugly, but I can't seam to be able to access this.format() here :(
|
||||
const Invoice = require('../common/Invoice');
|
||||
|
||||
Util.validateSignature(z, bundle);
|
||||
const invoice = await Invoice.getById(z, bundle, bundle.inputData.store_id, bundle.cleanedRequest.invoiceId);
|
||||
|
||||
// TODO should we merge a few fields that came with the webhook, like "partiallyPaid", "afterExpiration", "overPaid" and "manuallyMarked"? Also add to "outputFields" if we decide to do so.
|
||||
|
||||
return [invoice];
|
||||
},
|
||||
|
||||
|
||||
format: function (invoice, storeId) {
|
||||
|
||||
if (typeof invoice.storeId === 'undefined') {
|
||||
// TODO remove this when https://github.com/btcpayserver/btcpayserver/pull/2592 is merged.
|
||||
invoice.storeId = storeId; // storeId is not a field that we normally have, so we add the Store ID here for convenience.
|
||||
}
|
||||
|
||||
invoice.amount = Number(invoice.amount);
|
||||
invoice.monitoringExpiration = new Date(invoice.monitoringExpiration * 1000).toISOString();
|
||||
invoice.expirationTime = new Date(invoice.expirationTime * 1000).toISOString();
|
||||
invoice.createdTime = new Date(invoice.createdTime * 1000).toISOString();
|
||||
return invoice;
|
||||
},
|
||||
|
||||
getSampleData: function (z, bundle) {
|
||||
// Used for testing and setup. Just return the latest invoice in the same format as you'd normally get (see: perform() method).
|
||||
const options = {
|
||||
url: bundle.authData.server_url + '/api/v1/stores/' + bundle.inputData.store_id + '/invoices',
|
||||
method: 'GET',
|
||||
params: {},
|
||||
body: {},
|
||||
};
|
||||
|
||||
return z.request(options).then((response) => {
|
||||
// TODO this is a bit ugly, but I can't seam to be able to access this.format() here :(
|
||||
const Invoice = require('../common/Invoice');
|
||||
|
||||
let results = response.json;
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
results[i] = Invoice.format(results[i], bundle.inputData.store_id);
|
||||
}
|
||||
return results;
|
||||
});
|
||||
},
|
||||
|
||||
setStatus: async function (z, bundle, invoice_id, newStatus) {
|
||||
const options = {
|
||||
url: bundle.authData.server_url + '/api/v1/stores/' + bundle.inputData.store_id + '/invoices/' + invoice_id + '/status',
|
||||
method: 'POST',
|
||||
params: {},
|
||||
body: {
|
||||
status: newStatus
|
||||
}
|
||||
};
|
||||
|
||||
let response = await z.request(options);
|
||||
if (response.status === 200) {
|
||||
return this.format(response.json, bundle.inputData.store_id);
|
||||
} else if (response.status === 403) {
|
||||
throw new z.errors.Error('Forbidden. Invoice could not be marked as invalid.', 'Forbidden', response.status);
|
||||
} else {
|
||||
let errorMsg = 'Error: ';
|
||||
for (let i = 0; i < response.json.length; i++) {
|
||||
errorMsg += response.json[i].message + ', ';
|
||||
}
|
||||
throw new z.errors.Error(errorMsg, 'InvalidData', response.status);
|
||||
}
|
||||
},
|
||||
|
||||
outputFields: [
|
||||
{key: 'id', label: 'Invoice ID', type: 'string'},
|
||||
{key: 'metadata__orderId', label: 'Order ID', type: 'string'},
|
||||
{key: 'metadata__posData', label: 'POS Data', type: 'string'},
|
||||
{key: 'metadata__itemDesc', label: 'Item Description', type: 'string'},
|
||||
{key: 'metadata__buyerEmail', label: 'Buyer Email', type: 'string'},
|
||||
{key: 'metadata__buyerName', label: 'Buyer Name', type: 'string'},
|
||||
{key: 'metadata__physical', label: 'Is Physical', type: 'boolean'},
|
||||
{key: 'metadata__taxIncluded', label: 'Tax Amount Included', type: 'boolean'}, // TODO This field is not in the Greenfield API docs. Should we remove it using format() ?
|
||||
{key: 'checkout__speedPolicy', label: 'Speed Policy', type: 'string'},
|
||||
{key: 'checkout__paymentMethods[]', label: 'Payment Method', type: 'string'},
|
||||
{key: 'checkout__expirationMinutes', label: 'Expiration Minutes', type: 'string'},
|
||||
{key: 'checkout__monitoringMinutes', label: 'Monitoring Minutes', type: 'string'},
|
||||
{key: 'checkout__paymentTolerance', label: 'Payment Tolerance', type: 'number'},
|
||||
{key: 'checkout__redirectURL', label: 'Redirect URL', type: 'string'},
|
||||
{key: 'checkout__redirectAutomatically', label: 'Redirect Automatically', type: 'boolean'},
|
||||
{key: 'checkout__defaultLanguage', label: 'Default Language', type: 'string'}, // TODO This field may be abandoned once we have auto language detection? Should we remove it using format() ?
|
||||
{key: 'checkoutLink', label: 'Payment URL', type: 'string'},
|
||||
{key: 'status', label: 'Status', type: 'string'},
|
||||
{key: 'additionalStatus', label: 'Additional Status', type: 'string'},
|
||||
{key: 'createdTime', label: 'Invoice Created At', type: 'datetime'},
|
||||
{key: 'amount', label: 'Amount', type: 'number'},
|
||||
{key: 'currency', label: 'Currency', type: 'string'},
|
||||
{key: 'expirationTime', label: 'Invoice Expiration', type: 'datetime'},
|
||||
{key: 'monitoringExpiration', label: 'Monitoring Expiration', type: 'datetime'},
|
||||
],
|
||||
sample: {
|
||||
"id": "VDdmfYJzJm9VtqW8hqhypF",
|
||||
"checkoutLink": ":censored:25:c3b580afa6:/i/VDdmfYJzJm9VtqW8hqhypF",
|
||||
"status": "Expired",
|
||||
"additionalStatus": "None",
|
||||
"createdTime": "2021-07-08T12:17:24.000Z",
|
||||
"expirationTime": "2021-07-08T12:32:24.000Z",
|
||||
"monitoringExpiration": "2021-07-08T13:17:24.000Z",
|
||||
"amount": 6.15,
|
||||
"currency": "EUR",
|
||||
"metadata": {},
|
||||
"checkout": {
|
||||
"speedPolicy": "MediumSpeed",
|
||||
"paymentMethods": ["BTC", "BTC-LightningNetwork"],
|
||||
"expirationMinutes": 15,
|
||||
"monitoringMinutes": 60,
|
||||
"paymentTolerance": 0.0,
|
||||
"redirectURL": null,
|
||||
"redirectAutomatically": false,
|
||||
"defaultLanguage": null
|
||||
}
|
||||
}
|
||||
}
|
||||
103
common/Store.js
Normal file
103
common/Store.js
Normal file
@ -0,0 +1,103 @@
|
||||
const Util = require('../common/functions');
|
||||
|
||||
module.exports = {
|
||||
noun: 'Store',
|
||||
getById: async function (z, bundle, storeId) {
|
||||
const options = {
|
||||
url: bundle.authData.server_url + '/api/v1/stores/' + storeId,
|
||||
method: 'GET',
|
||||
params: {},
|
||||
body: {},
|
||||
};
|
||||
|
||||
let response = await z.request(options);
|
||||
if (response.status === 200) {
|
||||
return this.format(response.json);
|
||||
} else {
|
||||
throw new z.errors.Error('Invoice could not be found.', 'NotFound', 404);
|
||||
}
|
||||
},
|
||||
|
||||
// performForOne: async function (z, bundle) {
|
||||
// // TODO this is a bit ugly, but I can't seam to be able to access this.format() here :(
|
||||
// const Invoice = require('../common/Invoice');
|
||||
//
|
||||
// Util.validateSignature(z, bundle);
|
||||
// const invoice = await Invoice.getById(z, bundle, bundle.inputData.store_id, bundle.cleanedRequest.invoiceId);
|
||||
//
|
||||
// // TODO should we merge a few fields that came with the webhook, like "partiallyPaid", "afterExpiration", "overPaid" and "manuallyMarked"? Also add to "outputFields" if we decide to do so.
|
||||
//
|
||||
// return [invoice];
|
||||
// },
|
||||
|
||||
|
||||
format: function (store) {
|
||||
// invoice.amount = Number(invoice.amount);
|
||||
// invoice.monitoringExpiration = new Date(invoice.monitoringExpiration * 1000).toISOString();
|
||||
// invoice.expirationTime = new Date(invoice.expirationTime * 1000).toISOString();
|
||||
// invoice.createdTime = new Date(invoice.createdTime * 1000).toISOString();
|
||||
return store;
|
||||
},
|
||||
|
||||
inputFields:{
|
||||
store_id: {
|
||||
key: 'store_id',
|
||||
label: 'Store',
|
||||
type: 'string',
|
||||
required: true,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
helpText: 'The BTCPay Server store that contains the invoice.',
|
||||
dynamic: 'storeList.id.name' // Meaning: resource "store" with method "list". The value is in the "id" field and the label is in the "name" field.
|
||||
}
|
||||
},
|
||||
|
||||
getAll: function (z, bundle) {
|
||||
// Used for testing and setup. Just return the latest invoice in the same format as you'd normally get (see: perform() method).
|
||||
const options = {
|
||||
url: bundle.authData.server_url + '/api/v1/stores/',
|
||||
method: 'GET',
|
||||
params: {},
|
||||
body: {},
|
||||
};
|
||||
|
||||
return z.request(options).then((response) => {
|
||||
// TODO this is a bit ugly, but I can't seam to be able to access this.format() here :(
|
||||
const Store = require('../common/Store');
|
||||
|
||||
let results = response.json;
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
results[i] = Store.format(results[i]);
|
||||
}
|
||||
return results;
|
||||
});
|
||||
},
|
||||
|
||||
outputFields: [
|
||||
{key: 'id', label: 'Store ID', type: 'string'},
|
||||
{key: 'name', label: 'Store Name', type: 'string'}
|
||||
],
|
||||
// sample: {
|
||||
// "id": "VDdmfYJzJm9VtqW8hqhypF",
|
||||
// "checkoutLink": ":censored:25:c3b580afa6:/i/VDdmfYJzJm9VtqW8hqhypF",
|
||||
// "status": "Expired",
|
||||
// "additionalStatus": "None",
|
||||
// "createdTime": "2021-07-08T12:17:24.000Z",
|
||||
// "expirationTime": "2021-07-08T12:32:24.000Z",
|
||||
// "monitoringExpiration": "2021-07-08T13:17:24.000Z",
|
||||
// "amount": 6.15,
|
||||
// "currency": "EUR",
|
||||
// "metadata": {},
|
||||
// "checkout": {
|
||||
// "speedPolicy": "MediumSpeed",
|
||||
// "paymentMethods": ["BTC", "BTC-LightningNetwork"],
|
||||
// "expirationMinutes": 15,
|
||||
// "monitoringMinutes": 60,
|
||||
// "paymentTolerance": 0.0,
|
||||
// "redirectURL": null,
|
||||
// "redirectAutomatically": false,
|
||||
// "defaultLanguage": null
|
||||
// }
|
||||
// }
|
||||
}
|
||||
64
common/functions.js
Normal file
64
common/functions.js
Normal file
@ -0,0 +1,64 @@
|
||||
const crypto = require('crypto');
|
||||
|
||||
function HMAC256(data, secret) {
|
||||
const r = crypto.createHmac('sha256', secret)
|
||||
.update(data)
|
||||
.digest('hex');
|
||||
return r;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateSignature: function (z, bundle) {
|
||||
const correctSecret = bundle.subscribeData.secret;
|
||||
const requestHash = bundle.rawRequest.headers['Http-Btcpay-Sig'];
|
||||
const ourHash = 'sha256=' + HMAC256(bundle.rawRequest.content, correctSecret);
|
||||
if (requestHash !== ourHash) {
|
||||
throw new z.errors.Error('Incoming webhook message is not signed correctly. Cannot trust the sender.', 'BadSignature', 403);
|
||||
}
|
||||
},
|
||||
|
||||
performSubscribe: function (z, bundle, eventName) {
|
||||
const options = {
|
||||
url: `${bundle.authData.server_url}/api/v1/stores/${bundle.inputData.store_id}/webhooks`,
|
||||
method: 'POST',
|
||||
params: {},
|
||||
body: {
|
||||
url: bundle.targetUrl,
|
||||
enabled: true,
|
||||
automaticRedelivery: true,
|
||||
authorizedEvents: {
|
||||
everything: false,
|
||||
specificEvents: [eventName],
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return z.request(options).then((response) => {
|
||||
const results = response.json;
|
||||
|
||||
// You can do any parsing you need for results here before returning them
|
||||
|
||||
return results;
|
||||
});
|
||||
},
|
||||
|
||||
performUnsubscribe: function (z, bundle) {
|
||||
const hookId = bundle.subscribeData.id
|
||||
console.log(bundle.subscribeData);
|
||||
|
||||
const options = {
|
||||
url: `${bundle.authData.server_url}/api/v1/stores/${bundle.authData.store_id}/webhooks/` + hookId,
|
||||
method: 'DELETE',
|
||||
params: {},
|
||||
body: {},
|
||||
};
|
||||
|
||||
return z.request(options).then((response) => {
|
||||
const results = response.json;
|
||||
|
||||
// You can do any parsing you need for results here before returning them
|
||||
|
||||
return results;
|
||||
});
|
||||
}
|
||||
}
|
||||
197
creates/CreateInvoice.js
Normal file
197
creates/CreateInvoice.js
Normal file
@ -0,0 +1,197 @@
|
||||
const Invoice = require('../common/Invoice');
|
||||
const Store = require('../common/Store');
|
||||
|
||||
const createInvoice = async function (z, bundle) {
|
||||
// const options = {
|
||||
// url: `${bundle.authData.server_url}/api/v1/stores/${bundle.inputData.store_id}/invoices/`,
|
||||
// method: 'POST',
|
||||
// params: {},
|
||||
// body: {
|
||||
// amount: bundle.inputData.amount,
|
||||
// currency: bundle.inputData.currency_code,
|
||||
// metadata: {
|
||||
// orderId: bundle.inputData.order_id,
|
||||
// buyerName: bundle.inputData.buyer_name,
|
||||
// buyerEmail: bundle.inputData.buyer_email,
|
||||
// buyerCountry: bundle.inputData.buyer_country,
|
||||
// buyerZip: bundle.inputData.buyer_zip,
|
||||
// buyerState: bundle.inputData.buyer_state,
|
||||
// buyerCity: bundle.inputData.buyer_city,
|
||||
// buyerAddress1: bundle.inputData.buyer_address1,
|
||||
// buyerAddress2: bundle.inputData.buyer_address2,
|
||||
// buyerPhone: bundle.inputData.buyer_phone,
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// let response = await z.request(options);
|
||||
// if (response.status === 200) {
|
||||
// return Invoice.format(response.json, bundle.inputData.store_id);
|
||||
// } else if (response.status === 403) {
|
||||
// throw new z.errors.Error('Forbidden. Invoice could not be created.', 'Forbidden', response.status);
|
||||
// } else {
|
||||
// let errorMsg = 'Error: ';
|
||||
// for (let i = 0; i < response.json.length; i++) {
|
||||
// errorMsg += response.json[i].message + ', ';
|
||||
// }
|
||||
// throw new z.errors.Error(errorMsg, 'InvalidData', response.status);
|
||||
// }
|
||||
|
||||
return Invoice.create(
|
||||
z,
|
||||
bundle.authData.server_url,
|
||||
bundle.inputData.store_id,
|
||||
bundle.inputData.amount,
|
||||
bundle.inputData.currency_code,
|
||||
bundle.inputData.order_id,
|
||||
bundle.inputData.buyer_name,
|
||||
bundle.inputData.buyer_email,
|
||||
bundle.inputData.buyer_country,
|
||||
bundle.inputData.buyer_zip,
|
||||
bundle.inputData.buyer_state,
|
||||
bundle.inputData.buyer_city,
|
||||
bundle.inputData.buyer_address1,
|
||||
bundle.inputData.buyer_address2,
|
||||
bundle.inputData.buyer_phone
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
operation: {
|
||||
perform: createInvoice,
|
||||
inputFields: [
|
||||
Store.inputFields.store_id,
|
||||
{
|
||||
key: 'amount',
|
||||
label: 'Amount To Pay',
|
||||
type: 'number',
|
||||
required: true,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'currency_code',
|
||||
label: 'Currency Code',
|
||||
type: 'string',
|
||||
helpText:
|
||||
'The currency code the invoice is in. Can be in fiat (USD, EUR, etc) or in cryptocurrency (BTC, LTC, etc).',
|
||||
required: true,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'order_id',
|
||||
label: 'Order ID',
|
||||
type: 'string',
|
||||
helpText: 'The Order ID you want to see in BTCPay Server',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'buyer_name',
|
||||
label: 'Buyer Name',
|
||||
type: 'string',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'buyer_email',
|
||||
label: 'Buyer Email',
|
||||
type: 'string',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'buyer_country',
|
||||
label: 'Buyer Country',
|
||||
type: 'string',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'buyer_zip',
|
||||
label: 'Buyer Zip',
|
||||
type: 'string',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'buyer_state',
|
||||
label: 'Buyer State',
|
||||
type: 'string',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'buyer_city',
|
||||
label: 'Buyer City',
|
||||
type: 'string',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'buyer_address1',
|
||||
label: 'Buyer Address Line 1',
|
||||
type: 'string',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'buyer_address2',
|
||||
label: 'Buyer Address Line 2',
|
||||
type: 'string',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
{
|
||||
key: 'buyer_phone',
|
||||
label: 'Buyer Phone',
|
||||
type: 'string',
|
||||
required: false,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
],
|
||||
sample: {
|
||||
id: 'VDdmfYJzJm9VtqW8hqhypF',
|
||||
checkoutLink: 'https://mybtcpayserver.com/i/VDdmfYJzJm9VtqW8hqhypF',
|
||||
status: 'New',
|
||||
additionalStatus: 'None',
|
||||
createdTime: "2021-07-08T12:17:24.000Z",
|
||||
expirationTime: "2021-07-08T12:32:24.000Z",
|
||||
monitoringExpiration: "2021-07-08T13:17:24.000Z",
|
||||
amount: '7.0',
|
||||
currency: 'EUR',
|
||||
metadata: {},
|
||||
checkout: {
|
||||
speedPolicy: 'MediumSpeed',
|
||||
paymentMethods: ['BTC', 'BTC-LightningNetwork'],
|
||||
expirationMinutes: 15,
|
||||
monitoringMinutes: 60,
|
||||
paymentTolerance: 0,
|
||||
redirectURL: null,
|
||||
redirectAutomatically: false,
|
||||
defaultLanguage: null,
|
||||
},
|
||||
},
|
||||
outputFields: Invoice.outputFields,
|
||||
},
|
||||
key: 'CreateInvoice',
|
||||
noun: Invoice.noun,
|
||||
display: {
|
||||
label: 'Create Invoice',
|
||||
description: 'Creates an Invoice',
|
||||
hidden: false,
|
||||
important: true,
|
||||
},
|
||||
};
|
||||
72
creates/MarkInvoiceInvalid.js
Normal file
72
creates/MarkInvoiceInvalid.js
Normal file
@ -0,0 +1,72 @@
|
||||
const Invoice = require('../common/Invoice');
|
||||
const Store = require('../common/Store');
|
||||
|
||||
const markInvoiceInvalid = function (z, bundle) {
|
||||
return Invoice.setStatus(z, bundle, bundle.inputData.invoice_id, 'Invalid');
|
||||
}
|
||||
|
||||
async function integrationTest(z, bundle) {
|
||||
// 1. Prepare a new invoice to run the test on
|
||||
const invoice = await Invoice.create(
|
||||
z,
|
||||
process.env.SERVER_URL,
|
||||
process.env.STORE_ID,
|
||||
1,
|
||||
'EUR',
|
||||
);
|
||||
|
||||
// 2. Set the inputs so we can run
|
||||
bundle.inputData = {
|
||||
store_id: invoice.storeId,
|
||||
invoice_id: invoice.id
|
||||
};
|
||||
|
||||
// 3. Do the logic we wanna test
|
||||
return markInvoiceInvalid(z, bundle);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
operation: {
|
||||
perform: markInvoiceInvalid,
|
||||
inputFields: [
|
||||
Store.inputFields.store_id,
|
||||
{
|
||||
key: 'invoice_id',
|
||||
label: 'Invoice ID',
|
||||
type: 'string',
|
||||
helpText: 'The invoice to mark as invalid.',
|
||||
required: true
|
||||
},
|
||||
],
|
||||
sample: {
|
||||
id: 'VDdmfYJzJm9VtqW8hqhypF',
|
||||
checkoutLink: 'https://mybtcpayserver.com/i/VDdmfYJzJm9VtqW8hqhypF',
|
||||
status: 'Invalid',
|
||||
additionalStatus: 'None',
|
||||
createdTime: "2021-07-08T12:17:24.000Z",
|
||||
expirationTime: "2021-07-08T12:32:24.000Z",
|
||||
monitoringExpiration: "2021-07-08T13:17:24.000Z",
|
||||
amount: '7.0',
|
||||
currency: 'EUR',
|
||||
metadata: {},
|
||||
checkout: {
|
||||
speedPolicy: 'MediumSpeed',
|
||||
paymentMethods: ['BTC', 'BTC-LightningNetwork'],
|
||||
expirationMinutes: 15,
|
||||
monitoringMinutes: 60,
|
||||
paymentTolerance: 0,
|
||||
redirectURL: null,
|
||||
redirectAutomatically: false,
|
||||
defaultLanguage: null,
|
||||
},
|
||||
},
|
||||
outputFields: Invoice.outputFields,
|
||||
},
|
||||
key: 'MarkInvoiceInvalid',
|
||||
noun: Invoice.noun,
|
||||
display: {
|
||||
label: 'Mark Invoice Invalid',
|
||||
description: 'Marks an invoice as invalid',
|
||||
important: false,
|
||||
},
|
||||
};
|
||||
52
creates/MarkInvoiceSettled.js
Normal file
52
creates/MarkInvoiceSettled.js
Normal file
@ -0,0 +1,52 @@
|
||||
const Invoice = require('../common/Invoice');
|
||||
const Store = require('../common/Store');
|
||||
|
||||
const markInvoiceSettled = function (z, bundle) {
|
||||
return Invoice.setStatus(z, bundle, bundle.inputData.invoice_id, 'Settled');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
operation: {
|
||||
perform: markInvoiceSettled,
|
||||
inputFields: [
|
||||
Store.inputFields.store_id,
|
||||
{
|
||||
key: 'invoice_id',
|
||||
label: 'Invoice ID',
|
||||
type: 'string',
|
||||
helpText: 'The invoice to mark as complete.',
|
||||
required: true
|
||||
},
|
||||
],
|
||||
sample: {
|
||||
id: 'VDdmfYJzJm9VtqW8hqhypF',
|
||||
checkoutLink: 'https://mybtcpayserver.com/i/VDdmfYJzJm9VtqW8hqhypF',
|
||||
status: 'Complete',
|
||||
additionalStatus: 'None',
|
||||
createdTime: "2021-07-08T12:17:24.000Z",
|
||||
expirationTime: "2021-07-08T12:32:24.000Z",
|
||||
monitoringExpiration: "2021-07-08T13:17:24.000Z",
|
||||
amount: '7.0',
|
||||
currency: 'EUR',
|
||||
metadata: {},
|
||||
checkout: {
|
||||
speedPolicy: 'MediumSpeed',
|
||||
paymentMethods: ['BTC', 'BTC-LightningNetwork'],
|
||||
expirationMinutes: 15,
|
||||
monitoringMinutes: 60,
|
||||
paymentTolerance: 0,
|
||||
redirectURL: null,
|
||||
redirectAutomatically: false,
|
||||
defaultLanguage: null,
|
||||
},
|
||||
},
|
||||
outputFields: Invoice.outputFields,
|
||||
},
|
||||
key: 'MarkInvoiceComplete',
|
||||
noun: Invoice.noun,
|
||||
display: {
|
||||
label: 'Mark Invoice Settled',
|
||||
description: 'Marks an invoice as settled',
|
||||
important: false,
|
||||
},
|
||||
};
|
||||
75
index.js
Normal file
75
index.js
Normal file
@ -0,0 +1,75 @@
|
||||
const authentication = require('./authentication');
|
||||
const invoiceCreated = require('./triggers/InvoiceCreated.js');
|
||||
const invoiceExpired = require('./triggers/InvoiceExpired.js');
|
||||
const invoiceInvalid = require('./triggers/InvoiceInvalid.js');
|
||||
const invoiceProcessing = require('./triggers/InvoicePaidInFull.js');
|
||||
const invoiceSettled = require('./triggers/InvoiceConfirmed.js');
|
||||
const paymentReceived = require('./triggers/InvoiceReceivedPayment.js');
|
||||
|
||||
const createInvoice = require("./creates/CreateInvoice");
|
||||
const markInvoiceInvalid = require("./creates/MarkInvoiceInvalid");
|
||||
const markInvoiceSettled = require("./creates/MarkInvoiceSettled");
|
||||
|
||||
const findInvoice = require("./searches/FindInvoice");
|
||||
|
||||
const beforeRequest = (request, z, bundle) => {
|
||||
request.headers['Content-Type'] = 'application/json';
|
||||
request.headers['Accept'] = 'application/json';
|
||||
request.headers['Authorization'] = 'token ' + bundle.authData.api_key;
|
||||
request.headers['X-Client'] = 'Zapier ' + require('./package.json').version;
|
||||
|
||||
// Avoid double // in the URL if the user entered the server_url with a trailing slash.
|
||||
if (bundle.authData.server_url.endsWith('/')) {
|
||||
request.url = request.url.replace(bundle.authData.server_url + '/', bundle.authData.server_url);
|
||||
}
|
||||
|
||||
return request;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
version: require('./package.json').version,
|
||||
platformVersion: require('zapier-platform-core').version,
|
||||
authentication: authentication,
|
||||
beforeRequest: [beforeRequest],
|
||||
|
||||
resources: {
|
||||
store: {
|
||||
key: 'store',
|
||||
noun: 'Store',
|
||||
// ...
|
||||
list: {
|
||||
display: {
|
||||
label: 'Stores',
|
||||
description: 'A list of all stores',
|
||||
hidden: true,
|
||||
},
|
||||
operation: {
|
||||
perform: (z, bundle) => {
|
||||
// called for store_id dropdown
|
||||
const Stores = require('./common/Store.js');
|
||||
return Stores.getAll(z, bundle);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
triggers: {
|
||||
[invoiceCreated.key]: invoiceCreated,
|
||||
[paymentReceived.key]: paymentReceived,
|
||||
[invoiceProcessing.key]: invoiceProcessing,
|
||||
[invoiceSettled.key]: invoiceSettled,
|
||||
[invoiceExpired.key]: invoiceExpired,
|
||||
[invoiceInvalid.key]: invoiceInvalid,
|
||||
},
|
||||
|
||||
creates: {
|
||||
[createInvoice.key]: createInvoice,
|
||||
[markInvoiceInvalid.key]: markInvoiceInvalid,
|
||||
[markInvoiceSettled.key]: markInvoiceSettled
|
||||
},
|
||||
|
||||
searches: {
|
||||
[findInvoice.key]: findInvoice
|
||||
}
|
||||
};
|
||||
7300
package-lock.json
generated
Normal file
7300
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
package.json
Normal file
25
package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "btc-pay-server",
|
||||
"version": "1.0.1",
|
||||
"description": "Start Accepting Bitcoin Payments With 0% Fees & No Third-party",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test-old": "mocha --recursive -t 10000",
|
||||
"test": "jest"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v14",
|
||||
"npm": ">=5.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"zapier-platform-core": "11.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^27.0.4",
|
||||
"should": "^13.2.0"
|
||||
},
|
||||
"private": true,
|
||||
"zapier": {
|
||||
"convertedByCLIVersion": "11.0.1"
|
||||
}
|
||||
}
|
||||
69
searches/FindInvoice.js
Normal file
69
searches/FindInvoice.js
Normal file
@ -0,0 +1,69 @@
|
||||
const Invoice = require('../common/Invoice');
|
||||
const Store = require('../common/Store');
|
||||
|
||||
const perform = async (z, bundle) => {
|
||||
const response = await z.request({
|
||||
url: bundle.authData.server_url + '/api/v1/stores/' + bundle.inputData.store_id + '/invoices',
|
||||
params: {'orderId[]': bundle.inputData.order_id},
|
||||
});
|
||||
|
||||
if (response.json.length > 0) {
|
||||
return [Invoice.format(response.json[0])];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// see here for a full list of available properties:
|
||||
// https://github.com/zapier/zapier-platform/blob/master/packages/schema/docs/build/schema.md#searchschema
|
||||
key: 'FindInvoice',
|
||||
noun: Invoice.noun,
|
||||
|
||||
display: {
|
||||
label: 'Find Invoice by Order ID',
|
||||
description: 'Finds an Invoice for a given order.',
|
||||
hidden: false,
|
||||
important: false,
|
||||
},
|
||||
|
||||
operation: {
|
||||
perform,
|
||||
inputFields: [
|
||||
Store.inputFields.store_id,
|
||||
{
|
||||
key: 'order_id',
|
||||
label: 'Order ID',
|
||||
type: 'string',
|
||||
helpText: 'Search for an invoice by the given Order ID.',
|
||||
required: true,
|
||||
list: false,
|
||||
altersDynamicFields: false,
|
||||
},
|
||||
],
|
||||
sample: {
|
||||
id: 'VDdmfYJzJm9VtqW8hqhypF',
|
||||
checkoutLink: 'https://mybtcpayserver.com/i/VDdmfYJzJm9VtqW8hqhypF',
|
||||
status: 'Expired',
|
||||
additionalStatus: 'None',
|
||||
createdTime: "2021-07-08T12:17:24.000Z",
|
||||
expirationTime: "2021-07-08T12:32:24.000Z",
|
||||
monitoringExpiration: "2021-07-08T13:17:24.000Z",
|
||||
amount: 7.0,
|
||||
currency: 'EUR',
|
||||
metadata: {},
|
||||
checkout: {
|
||||
speedPolicy: 'MediumSpeed',
|
||||
paymentMethods: ['BTC', 'BTC-LightningNetwork'],
|
||||
expirationMinutes: 15,
|
||||
monitoringMinutes: 60,
|
||||
paymentTolerance: 0,
|
||||
redirectURL: null,
|
||||
redirectAutomatically: false,
|
||||
defaultLanguage: null,
|
||||
},
|
||||
},
|
||||
outputFields: Invoice.outputFields
|
||||
|
||||
}
|
||||
};
|
||||
77
test/creates/CreateInvoice.test.js
Normal file
77
test/creates/CreateInvoice.test.js
Normal file
@ -0,0 +1,77 @@
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
// Use this to make test calls into your app:
|
||||
const App = require('../../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
// read the `.env` file into the environment, if available
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('creates.invoice', () => {
|
||||
test(App.creates.CreateInvoice.key, async () => {
|
||||
const amount = 1 + Math.floor(Math.random() * 100);
|
||||
const currencyCode = 'EUR';
|
||||
const orderId = process.env.ZAPIER_ORDER;
|
||||
const buyerName = 'Wouter';
|
||||
|
||||
const bundle = {
|
||||
authData: {
|
||||
server_url: process.env.SERVER_URL,
|
||||
api_key: process.env.API_KEY
|
||||
},
|
||||
rawRequest: {
|
||||
headers: {
|
||||
'Http-Btcpay-Sig': 'sha256=4ec27a6ca16dbc7b8c7ddfb5654b1f8dbf8c69a439e970fd2bfac6e19713f211'
|
||||
},
|
||||
content: '{\n' +
|
||||
' "deliveryId": "PENf2czGBzTepjzSJdt6Nz",\n' +
|
||||
' "webhookId": "6KQ4EmzqKowRgyBL65TwJg",\n' +
|
||||
' "originalDeliveryId": "PENf2czGBzTepjzSJdt6Nz",\n' +
|
||||
' "isRedelivery": false,\n' +
|
||||
' "type": "InvoiceCreated",\n' +
|
||||
' "timestamp": 1623954207,\n' +
|
||||
' "storeId": "Hf9GvFK2dHJehm9J8A6kYfbc1ruc5jEZBKEr9r7jsrLo",\n' +
|
||||
' "invoiceId": "CQZj4Qbm475EJQ5HsWeAbd"\n' +
|
||||
'}' // Hard coded because it is used to calculate the "BTCPay-Sig"
|
||||
},
|
||||
inputData: {
|
||||
store_id: process.env.STORE_ID,
|
||||
amount: amount,
|
||||
currency_code: currencyCode,
|
||||
order_id: orderId,
|
||||
buyer_name: buyerName,
|
||||
buyer_email: 'zapiertest@btcpayserver.org',
|
||||
buyer_country: 'BE',
|
||||
buyer_zip: '1000',
|
||||
buyer_state: 'Happy State',
|
||||
buyer_city: "Test City",
|
||||
buyer_address1: 'Test street line 1',
|
||||
buyer_address2: 'Test street line 2',
|
||||
buyer_phone: '123 456 789'
|
||||
},
|
||||
}
|
||||
|
||||
const invoice = await appTester(App.creates.CreateInvoice.operation.perform, bundle);
|
||||
|
||||
expect(invoice).toBeDefined();
|
||||
expect(invoice.id).toBeDefined();
|
||||
expect(invoice.checkoutLink).toBeDefined();
|
||||
expect(invoice.status).toBe('New');
|
||||
expect(invoice.additionalStatus).toBe('None');
|
||||
expect(invoice.monitoringExpiration).toBeDefined();
|
||||
expect(invoice.expirationTime).toBeDefined();
|
||||
expect(invoice.createdTime).toBeDefined();
|
||||
expect(invoice.amount).toBe(amount);
|
||||
expect(invoice.currency).toBe(currencyCode);
|
||||
expect(invoice.metadata.orderId).toBe(orderId);
|
||||
expect(invoice.metadata.buyerName).toBe(buyerName);
|
||||
expect(invoice.metadata.buyerEmail).toBe('zapiertest@btcpayserver.org');
|
||||
expect(invoice.metadata.buyerCountry).toBe('BE');
|
||||
expect(invoice.metadata.buyerZip).toBe('1000');
|
||||
expect(invoice.metadata.buyerState).toBe('Happy State');
|
||||
expect(invoice.metadata.buyerCity).toBe("Test City");
|
||||
expect(invoice.metadata.buyerAddress1).toBe('Test street line 1');
|
||||
expect(invoice.metadata.buyerAddress2).toBe('Test street line 2');
|
||||
expect(invoice.metadata.buyerPhone).toBe('123 456 789');
|
||||
}
|
||||
);
|
||||
});
|
||||
40
test/creates/MarkInvoiceInvalid.test.js
Normal file
40
test/creates/MarkInvoiceInvalid.test.js
Normal file
@ -0,0 +1,40 @@
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
const App = require('../../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
zapier.tools.env.inject();
|
||||
|
||||
|
||||
describe('creates.invoice', () => {
|
||||
test(App.creates.MarkInvoiceInvalid.key, async () => {
|
||||
const bundle = {
|
||||
authData: {
|
||||
server_url: process.env.SERVER_URL,
|
||||
api_key: process.env.API_KEY
|
||||
},
|
||||
rawRequest: {
|
||||
headers: {
|
||||
'Http-Btcpay-Sig': 'sha256=4ec27a6ca16dbc7b8c7ddfb5654b1f8dbf8c69a439e970fd2bfac6e19713f211'
|
||||
},
|
||||
content: '{\n' +
|
||||
' "deliveryId": "PENf2czGBzTepjzSJdt6Nz",\n' +
|
||||
' "webhookId": "6KQ4EmzqKowRgyBL65TwJg",\n' +
|
||||
' "originalDeliveryId": "PENf2czGBzTepjzSJdt6Nz",\n' +
|
||||
' "isRedelivery": false,\n' +
|
||||
' "type": "InvoiceCreated",\n' +
|
||||
' "timestamp": 1623954207,\n' +
|
||||
' "storeId": "Hf9GvFK2dHJehm9J8A6kYfbc1ruc5jEZBKEr9r7jsrLo",\n' +
|
||||
' "invoiceId": "CQZj4Qbm475EJQ5HsWeAbd"\n' +
|
||||
'}' // Hard coded because it is used to calculate the "BTCPay-Sig"
|
||||
}
|
||||
}
|
||||
|
||||
const invalidInvoice = await appTester(App.creates.MarkInvoiceInvalid.operation.test, bundle);
|
||||
|
||||
expect(invalidInvoice).toBeDefined();
|
||||
expect(invalidInvoice.id).toBeDefined();
|
||||
expect(invalidInvoice.status).toBe('Invalid');
|
||||
}
|
||||
);
|
||||
});
|
||||
44
test/searches/FindInvoice.test.js
Normal file
44
test/searches/FindInvoice.test.js
Normal file
@ -0,0 +1,44 @@
|
||||
/* globals describe, expect, test, it */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
// Use this to make test calls into your app:
|
||||
const App = require('../../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('searches.FindInvoice', () => {
|
||||
it(App.searches.FindInvoice.key, async () => {
|
||||
|
||||
const bundle = {
|
||||
authData: {
|
||||
server_url: process.env.SERVER_URL,
|
||||
api_key: process.env.API_KEY
|
||||
},
|
||||
rawRequest: {
|
||||
headers: {
|
||||
'Http-Btcpay-Sig': 'sha256=4ec27a6ca16dbc7b8c7ddfb5654b1f8dbf8c69a439e970fd2bfac6e19713f211'
|
||||
},
|
||||
content: '{\n' +
|
||||
' "deliveryId": "PENf2czGBzTepjzSJdt6Nz",\n' +
|
||||
' "webhookId": "6KQ4EmzqKowRgyBL65TwJg",\n' +
|
||||
' "originalDeliveryId": "PENf2czGBzTepjzSJdt6Nz",\n' +
|
||||
' "isRedelivery": false,\n' +
|
||||
' "type": "InvoiceCreated",\n' +
|
||||
' "timestamp": 1623954207,\n' +
|
||||
' "storeId": "Hf9GvFK2dHJehm9J8A6kYfbc1ruc5jEZBKEr9r7jsrLo",\n' +
|
||||
' "invoiceId": "CQZj4Qbm475EJQ5HsWeAbd"\n' +
|
||||
'}' // Hard coded because it is used to calculate the "BTCPay-Sig"
|
||||
},
|
||||
inputData: {
|
||||
store_id: process.env.STORE_ID,
|
||||
order_id: process.env.ORDER_ID
|
||||
},
|
||||
}
|
||||
|
||||
const results = await appTester(App.searches.FindInvoice.operation.perform, bundle);
|
||||
|
||||
expect(results).toBeDefined();
|
||||
});
|
||||
});
|
||||
91
test/triggers/InvoiceCreated.test.js
Normal file
91
test/triggers/InvoiceCreated.test.js
Normal file
@ -0,0 +1,91 @@
|
||||
/* globals describe, expect, test */
|
||||
|
||||
const zapier = require('zapier-platform-core');
|
||||
|
||||
// createAppTester() makes it easier to test your app. It takes your raw app
|
||||
// definition, and returns a function that will test you app.
|
||||
const App = require('../../index');
|
||||
const appTester = zapier.createAppTester(App);
|
||||
|
||||
// Inject the vars from the .env file to process.env. Do this if you have a .env
|
||||
// file.
|
||||
zapier.tools.env.inject();
|
||||
|
||||
describe('triggers', () => {
|
||||
test(App.triggers.InvoiceCreated.key + ' webhook', async () => {
|
||||
|
||||
const invoiceId = process.env.INVOICE_ID;
|
||||
|
||||
const bundle = {
|
||||
authData: {
|
||||
server_url: process.env.SERVER_URL,
|
||||
api_key: process.env.API_KEY
|
||||
},
|
||||
subscribeData: {
|
||||
secret: '5TjrCfkTgfjY4rJj85bTJj'
|
||||
},
|
||||
rawRequest: {
|
||||
headers: {
|
||||
'Http-Btcpay-Sig': 'sha256=4ec27a6ca16dbc7b8c7ddfb5654b1f8dbf8c69a439e970fd2bfac6e19713f211'
|
||||
},
|
||||
content: '{\n' +
|
||||
' "deliveryId": "PENf2czGBzTepjzSJdt6Nz",\n' +
|
||||
' "webhookId": "6KQ4EmzqKowRgyBL65TwJg",\n' +
|
||||
' "originalDeliveryId": "PENf2czGBzTepjzSJdt6Nz",\n' +
|
||||
' "isRedelivery": false,\n' +
|
||||
' "type": "InvoiceCreated",\n' +
|
||||
' "timestamp": 1623954207,\n' +
|
||||
' "storeId": "Hf9GvFK2dHJehm9J8A6kYfbc1ruc5jEZBKEr9r7jsrLo",\n' +
|
||||
' "invoiceId": "CQZj4Qbm475EJQ5HsWeAbd"\n' +
|
||||
'}' // Hard coded because it is used to calculate the "BTCPay-Sig"
|
||||
},
|
||||
cleanedRequest: {
|
||||
invoiceId: invoiceId,
|
||||
},
|
||||
inputData: {
|
||||
store_id: process.env.STORE_ID
|
||||
},
|
||||
};
|
||||
|
||||
const results = await appTester(
|
||||
App.triggers.InvoiceCreated.operation.perform,
|
||||
bundle
|
||||
);
|
||||
|
||||
expect(results.length).toBe(1);
|
||||
const invoice = results[0];
|
||||
expect(invoice.id).toBe(invoiceId);
|
||||
expect(invoice.storeId).toBe(process.env.STORE_ID);
|
||||
expect(invoice.amount).toBeGreaterThan(0);
|
||||
expect(invoice.checkoutLink).toBeDefined();
|
||||
});
|
||||
|
||||
test(App.triggers.InvoiceCreated.key + ' list sample data', async () => {
|
||||
|
||||
const z = {};
|
||||
|
||||
const bundle = {
|
||||
authData: {
|
||||
server_url: process.env.SERVER_URL,
|
||||
store_id: process.env.STORE_ID,
|
||||
api_key: process.env.API_KEY
|
||||
},
|
||||
rawRequest: {},
|
||||
cleanedRequest: {},
|
||||
inputData: {
|
||||
store_id: process.env.STORE_ID
|
||||
},
|
||||
};
|
||||
|
||||
const results = await appTester(
|
||||
App.triggers.InvoiceCreated.operation.performList,
|
||||
bundle
|
||||
);
|
||||
|
||||
expect(results.length).toBeGreaterThan(1);
|
||||
const invoice = results[0];
|
||||
|
||||
expect(invoice.amount).toBeGreaterThan(0);
|
||||
expect(invoice.checkoutLink).toBeDefined();
|
||||
});
|
||||
});
|
||||
30
triggers/InvoiceConfirmed.js
Normal file
30
triggers/InvoiceConfirmed.js
Normal file
@ -0,0 +1,30 @@
|
||||
const Util = require('../common/functions');
|
||||
const Invoice = require('../common/Invoice');
|
||||
const Store = require('../common/Store');
|
||||
|
||||
const eventName = 'InvoiceConfirmed';
|
||||
|
||||
const performSubscribe = function (z, bundle) {
|
||||
return Util.performSubscribe(z, bundle, eventName);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
operation: {
|
||||
perform: Invoice.performForOne,
|
||||
performList: Invoice.getSampleData,
|
||||
inputFields: [Store.inputFields.store_id],
|
||||
type: 'hook',
|
||||
performSubscribe: performSubscribe,
|
||||
outputFields: Invoice.outputFields,
|
||||
performUnsubscribe: Util.performUnsubscribe,
|
||||
sample: Invoice.sample
|
||||
},
|
||||
key: eventName,
|
||||
noun: Invoice.noun,
|
||||
display: {
|
||||
label: 'Invoice Settled',
|
||||
description: 'Triggers when an invoice has enough confirmations on the blockchain according to your store\'s configuration, making the payment final.',
|
||||
hidden: false,
|
||||
important: true,
|
||||
},
|
||||
};
|
||||
30
triggers/InvoiceCreated.js
Normal file
30
triggers/InvoiceCreated.js
Normal file
@ -0,0 +1,30 @@
|
||||
const Util = require('../common/functions');
|
||||
const Invoice = require('../common/Invoice');
|
||||
const Store = require('../common/Store');
|
||||
|
||||
const eventName = 'InvoiceCreated';
|
||||
|
||||
const performSubscribe = function (z, bundle) {
|
||||
return Util.performSubscribe(z, bundle, eventName);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
operation: {
|
||||
perform: Invoice.performForOne,
|
||||
performList: Invoice.getSampleData,
|
||||
inputFields: [Store.inputFields.store_id],
|
||||
type: 'hook',
|
||||
performSubscribe: performSubscribe,
|
||||
outputFields: Invoice.outputFields,
|
||||
performUnsubscribe: Util.performUnsubscribe,
|
||||
sample: Invoice.sample
|
||||
},
|
||||
key: eventName,
|
||||
noun: Invoice.noun,
|
||||
display: {
|
||||
label: 'Invoice Created',
|
||||
description: 'Triggers when a new invoice is created.',
|
||||
hidden: false,
|
||||
important: true,
|
||||
},
|
||||
};
|
||||
30
triggers/InvoiceExpired.js
Normal file
30
triggers/InvoiceExpired.js
Normal file
@ -0,0 +1,30 @@
|
||||
const Util = require('../common/functions');
|
||||
const Invoice = require('../common/Invoice');
|
||||
const Store = require('../common/Store');
|
||||
|
||||
const eventName = 'InvoiceExpired';
|
||||
|
||||
const performSubscribe = function (z, bundle) {
|
||||
return Util.performSubscribe(z, bundle, eventName);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
operation: {
|
||||
perform: Invoice.performForOne,
|
||||
performList: Invoice.getSampleData,
|
||||
inputFields: [Store.inputFields.store_id],
|
||||
type: 'hook',
|
||||
performSubscribe: performSubscribe,
|
||||
outputFields: Invoice.outputFields,
|
||||
performUnsubscribe: Util.performUnsubscribe,
|
||||
sample: Invoice.sample
|
||||
},
|
||||
key: eventName,
|
||||
noun: Invoice.noun,
|
||||
display: {
|
||||
label: 'Invoice Expired',
|
||||
description: 'Triggers when an invoice expires, meaning the customer did not pay in the time he or she was supposed to.',
|
||||
hidden: false,
|
||||
important: true,
|
||||
},
|
||||
};
|
||||
30
triggers/InvoiceInvalid.js
Normal file
30
triggers/InvoiceInvalid.js
Normal file
@ -0,0 +1,30 @@
|
||||
const Util = require('../common/functions');
|
||||
const Invoice = require('../common/Invoice');
|
||||
const Store = require('../common/Store');
|
||||
|
||||
const eventName = 'InvoiceInvalid';
|
||||
|
||||
const performSubscribe = function (z, bundle) {
|
||||
return Util.performSubscribe(z, bundle, eventName);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
operation: {
|
||||
perform: Invoice.performForOne,
|
||||
performList: Invoice.getSampleData,
|
||||
inputFields: [Store.inputFields.store_id],
|
||||
type: 'hook',
|
||||
performSubscribe: performSubscribe,
|
||||
outputFields: Invoice.outputFields,
|
||||
performUnsubscribe: Util.performUnsubscribe,
|
||||
sample: Invoice.sample
|
||||
},
|
||||
key: eventName,
|
||||
noun: Invoice.noun,
|
||||
display: {
|
||||
label: 'Invoice Invalid',
|
||||
description: 'Triggers when an invoice becomes invalid.', // TODO add some more explanation what "invalid" means...
|
||||
hidden: false,
|
||||
important: false,
|
||||
},
|
||||
};
|
||||
30
triggers/InvoicePaidInFull.js
Normal file
30
triggers/InvoicePaidInFull.js
Normal file
@ -0,0 +1,30 @@
|
||||
const Util = require('../common/functions');
|
||||
const Invoice = require('../common/Invoice');
|
||||
const Store = require('../common/Store');
|
||||
|
||||
const eventName = 'InvoicePaidInFull';
|
||||
|
||||
const performSubscribe = function (z, bundle) {
|
||||
return Util.performSubscribe(z, bundle, eventName);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
operation: {
|
||||
perform: Invoice.performForOne,
|
||||
performList: Invoice.getSampleData,
|
||||
inputFields: [Store.inputFields.store_id],
|
||||
type: 'hook',
|
||||
performSubscribe: performSubscribe,
|
||||
outputFields: Invoice.outputFields,
|
||||
performUnsubscribe: Util.performUnsubscribe,
|
||||
sample: Invoice.sample
|
||||
},
|
||||
key: eventName,
|
||||
noun: Invoice.noun,
|
||||
display: {
|
||||
label: 'Invoice Processing',
|
||||
description: 'Triggers when an invoice is fully paid, but doesn\'t have the required amount of confirmations on the blockchain yet according to your store\'s settings.',
|
||||
hidden: false,
|
||||
important: false,
|
||||
},
|
||||
};
|
||||
30
triggers/InvoiceReceivedPayment.js
Normal file
30
triggers/InvoiceReceivedPayment.js
Normal file
@ -0,0 +1,30 @@
|
||||
const Util = require('../common/functions');
|
||||
const Invoice = require('../common/Invoice');
|
||||
const Store = require('../common/Store');
|
||||
|
||||
const eventName = 'InvoiceReceivedPayment';
|
||||
|
||||
const performSubscribe = function (z, bundle) {
|
||||
return Util.performSubscribe(z, bundle, eventName);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
operation: {
|
||||
perform: Invoice.performForOne,
|
||||
performList: Invoice.getSampleData,
|
||||
inputFields: [Store.inputFields.store_id],
|
||||
type: 'hook',
|
||||
performSubscribe: performSubscribe,
|
||||
outputFields: Invoice.outputFields,
|
||||
performUnsubscribe: Util.performUnsubscribe,
|
||||
sample: Invoice.sample
|
||||
},
|
||||
key: eventName,
|
||||
noun: Invoice.noun,
|
||||
display: {
|
||||
label: 'Payment Received',
|
||||
description: 'Triggers when a full or partial payment was received. The payment is not settled yet.',
|
||||
hidden: false,
|
||||
important: false,
|
||||
},
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user