diff --git a/server/src/external/payment-processor/bitpay.mjs b/server/src/external/payment-processor/bitpay.mjs index c71f4a085..506c36178 100644 --- a/server/src/external/payment-processor/bitpay.mjs +++ b/server/src/external/payment-processor/bitpay.mjs @@ -220,6 +220,11 @@ class BitPay extends PaymentProcessor { bitPayInvoice.currency, ); } + + async retrieveIntent(id) { + const bitPayClient = await this.getBitPayClient(); + return await bitPayClient.GetInvoice(id); + } } export default new BitPay(); diff --git a/server/src/jobs/payments.mjs b/server/src/jobs/payments.mjs index de3f5c7bf..f6c66f808 100644 --- a/server/src/jobs/payments.mjs +++ b/server/src/jobs/payments.mjs @@ -1,6 +1,9 @@ +import { Op } from 'sequelize'; + import CommonJob from './job.mjs'; import Stripe from '../external/payment-processor/stripe.mjs'; +import Bitpay from '../external/payment-processor/bitpay.mjs'; import logger from '../logger.mjs'; @@ -8,8 +11,35 @@ import { Order, Payment, OrderItem, OrderItemStatus } from '../models'; // https://stripe.com/docs/payments/intents#intent-statuses const STRIPE_INTENT_SUCCESS = 'succeeded'; +// https://bitpay.com/api/#rest-api-resources-invoices-resource +const BITPAY_PAYMENT_SUCCESS = 'paid'; class PaymentsJob extends CommonJob { + async fetchIntentFromAPI(id, processor) { + let intent; + if (processor === Payment.PROCESSOR.STRIPE) { + intent = await Stripe.retrieveIntent(id); + } else if (processor === Payment.PROCESSOR.BITPAY) { + intent = await Bitpay.retrieveIntent(id); + } + return intent; + } + + getSuccessStatusByProcessor(processor) { + if (processor === Payment.PROCESSOR.STRIPE) { + return STRIPE_INTENT_SUCCESS; + } + if (processor === Payment.PROCESSOR.BITPAY) { + return BITPAY_PAYMENT_SUCCESS; + } + } + + testPaymentSuccess(entry) { + const [processor, status] = entry; + const successStatus = this.getSuccessStatusByProcessor(processor); + return status === successStatus; + } + async execute() { const orders = await Order.findAll({ where: { @@ -19,11 +49,15 @@ class PaymentsJob extends CommonJob { }); const t = await Order.sequelize.transaction(); - try { for (const order of orders) { const payments = await order.getPayments({ - where: { processor: Payment.PROCESSOR.STRIPE }, + where: { + [Op.or]: [ + { processor: Payment.PROCESSOR.STRIPE }, + { processor: Payment.PROCESSOR.BITPAY }, + ], + }, }); const paymentStatuses = []; for (const payment of payments) { @@ -35,9 +69,9 @@ class PaymentsJob extends CommonJob { }); const intentId = payment.externalId; - const intent = await Stripe.retrieveIntent(intentId); - paymentStatuses.push(intent.status); - if (intent.status === STRIPE_INTENT_SUCCESS) { + const intent = this.fetchIntentFromAPI(intentId, payment.processor); + paymentStatuses.push([payment.processor, intent.status]); + if (intent.status === this.getSuccessStatusByProcessor(payment.processor)) { await payment.update( { status: Payment.STATUS.COMPLETED }, { transaction: t }, @@ -55,11 +89,15 @@ class PaymentsJob extends CommonJob { } } - const allSucceeded = paymentStatuses.some( - status => status === STRIPE_INTENT_SUCCESS, - ); + const allSucceeded = paymentStatuses.every(this.testPaymentSuccess); + const someSucceeded = paymentStatuses.some(this.testPaymentSuccess); if (allSucceeded) { await order.update({ status: Order.STATUS.SUCCESS }, { transaction: t }); + } else if (someSucceeded) { + await order.update( + { status: Order.STATUS.PARTIALLY_SUCCESS }, + { transaction: t }, + ); } else { await order.update({ status: Order.STATUS.FAILED }, { transaction: t }); }