UNPKG

@lomray/microservice-payment-stripe

Version:

Stripe payment microservice based on NodeJS & inverted json.

190 lines (186 loc) 10.4 kB
'use strict'; var tslib = require('tslib'); var refundStatus = require('../../constants/refund-status.js'); var transactionType = require('../../constants/transaction-type.js'); var dispute = require('../../entities/dispute.js'); var evidenceDetails = require('../../entities/evidence-details.js'); var refund$1 = require('../../entities/refund.js'); var transaction = require('../../entities/transaction.js'); var extractIdFromStripeInstance = require('../../helpers/extract-id-from-stripe-instance.js'); var messages = require('../../helpers/validators/messages.js'); var refund = require('../../repositories/refund.js'); var dispute$1 = require('../dispute.js'); var parser = require('../parser.js'); /** * Charge webhook handler */ class Charge { /** * @constructor */ constructor(manager) { this.manager = manager; } /** * Handles charge refunded */ handleChargeRefunded(event, manager, sdk) { return tslib.__awaiter(this, void 0, void 0, function* () { const { status, payment_intent: paymentIntent, amount_refunded: refundedAmount, id, } = event.data.object; if (!paymentIntent || !status) { throw new Error("Payment intent id or charge status wasn't provided."); } yield manager.transaction((entityManager) => tslib.__awaiter(this, void 0, void 0, function* () { // Sequence required yield this.syncStripeRefunds(extractIdFromStripeInstance(paymentIntent), id, sdk, entityManager); yield refund.updateTransactionsRefundStatus(extractIdFromStripeInstance(paymentIntent), entityManager, refundedAmount); })); }); } /** * Handles charge dispute created * @description Should create microservice dispute and related evidence details */ handleChargeDisputeCreated(event, manager) { return tslib.__awaiter(this, void 0, void 0, function* () { const { id, evidence_details: evidenceDetails$1, currency, amount, payment_intent: paymentIntent, status, reason, is_charge_refundable: isChargeRefundable, created: issuedAt, metadata, balance_transactions: balanceTransactions, } = event.data.object; if (!paymentIntent || !status) { throw new Error("Dispute was not handled. Payment intent or dispute status wasn't provided."); } yield manager.transaction((entityManager) => tslib.__awaiter(this, void 0, void 0, function* () { const disputeRepository = entityManager.getRepository(dispute); const evidenceDetailsRepository = entityManager.getRepository(evidenceDetails); /** * Get transactions by payment intent id * @description Stripe send events in parallel, so at this moment transactions may not exist in db */ const transactionId = extractIdFromStripeInstance(paymentIntent); const { chargedAmount, chargedFees, netWorth } = dispute$1.getChargedAmounts(balanceTransactions); const disputeEntity = yield disputeRepository.save(disputeRepository.create({ disputeId: id, amount, transactionId, status: parser.parseStripeDisputeStatus(status), reason: parser.parseStripeDisputeReason(reason), metadata, chargedAmount, chargedFees, netWorth, params: { issuedAt: new Date(Number(issuedAt) * 1000), currency: currency, isChargeRefundable, }, })); yield evidenceDetailsRepository.save(evidenceDetailsRepository.create(Object.assign({ disputeId: disputeEntity.id, isPastBy: evidenceDetails$1.past_due, submissionCount: evidenceDetails$1.submission_count, hasEvidence: evidenceDetails$1.has_evidence }, (evidenceDetails$1.due_by ? { dueBy: new Date(evidenceDetails$1.due_by * 1000) } : {})))); })); }); } /** * Handles charge dispute updated */ handleChargeDisputeUpdated(event, manager) { return tslib.__awaiter(this, void 0, void 0, function* () { const dispute$2 = event.data.object; if (!(dispute$2 === null || dispute$2 === void 0 ? void 0 : dispute$2.payment_intent) || !dispute$2.status) { throw new Error("Dispute was not handled. Payment intent or dispute status wasn't provided."); } yield manager.transaction((entityManager) => tslib.__awaiter(this, void 0, void 0, function* () { const disputeRepository = entityManager.getRepository(dispute); const transactionId = extractIdFromStripeInstance(dispute$2.payment_intent); const disputeEntity = yield disputeRepository.findOne({ relations: ['evidenceDetails'], where: { transactionId, }, }); if (!disputeEntity) { throw new Error(messages.getNotFoundMessage(`Dispute transaction "${transactionId}"`)); } yield dispute$1.update(disputeEntity, dispute$2, entityManager); })); }); } /** * Handles refund updated */ handleRefundUpdated(event, manager, sdk) { return tslib.__awaiter(this, void 0, void 0, function* () { const refundRepository = manager.getRepository(refund$1); const transactionRepository = manager.getRepository(transaction); const { id, status, reason, failure_reason: failedReason, payment_intent: paymentIntent, amount, metadata, } = event.data.object; if (!paymentIntent || !status) { throw new Error("Payment intent id or refund status wasn't provided."); } const refund = yield refundRepository .createQueryBuilder('r') .where("r.params ->> 'refundId' = :refundId", { refundId: id }) .getOne(); const refundStatus = parser.parseStripeRefundStatus(status); if (!refund) { const transaction = yield transactionRepository.findOne({ where: { transactionId: extractIdFromStripeInstance(paymentIntent), type: transactionType.DEBIT, }, }); if (!transaction) { throw new Error(messages.getNotFoundMessage('Dashboard refund was not registered. Transaction')); } const refundEntity = refundRepository.create(Object.assign(Object.assign(Object.assign({ transactionId: transaction.transactionId, amount }, (refundStatus ? { status: refundStatus } : {})), ((metadata === null || metadata === void 0 ? void 0 : metadata.entityId) ? { entityId: metadata.entityId } : {})), { params: { refundId: id, reason: reason, errorReason: failedReason, } })); const savedRefund = yield refundRepository.save(refundEntity); // Sync stripe refund with the microservice refund if (!(metadata === null || metadata === void 0 ? void 0 : metadata.hasOwnProperty('refundId'))) { yield sdk.refunds.update(id, { metadata: { refundId: savedRefund.id } }); } return; } if (!refundStatus) { throw new Error('Failed to get transaction status for refund.'); } refund.status = refundStatus; refund.params.errorReason = failedReason; refund.params.refundId = id; refund.amount = amount; if (reason && refund.params.reason !== reason) { refund.params.reason = reason; } yield refundRepository.save(refund); }); } /** * Sync Stripe refunds with the microservice refunds * @description Stripe does not automatically create refunds if first partial refund was successful. * Iterate from all existing refund and save refund that STRIPE DOES NOT SENT IN WEBHOOK * @private */ syncStripeRefunds(transactionId, chargeId, sdk, entityManager) { var _a, _b; return tslib.__awaiter(this, void 0, void 0, function* () { const refundRepository = entityManager.getRepository(refund$1); let refunds; let lastRefundTargetId = null; do { refunds = yield sdk.refunds.list(Object.assign({ charge: chargeId, limit: 10 }, (lastRefundTargetId ? { starting_after: lastRefundTargetId } : {}))); lastRefundTargetId = (_b = (_a = refunds === null || refunds === void 0 ? void 0 : refunds.data) === null || _a === void 0 ? void 0 : _a[9]) === null || _b === void 0 ? void 0 : _b.id; const existingRefunds = yield refundRepository .createQueryBuilder('r') .where("r.params ->> 'refundId' IN (:...ids)", { ids: refunds.data.map(({ id: refundId }) => refundId), }) .getMany(); const notRegisteredRefunds = refunds.data.filter((refund) => !existingRefunds.find(({ params }) => params.refundId === refund.id)); yield Promise.all(notRegisteredRefunds.map(({ id, amount, status: refundStatus$1, metadata, reason }) => { const { entityId, type, refundAmountType } = metadata; return refundRepository.save(refundRepository.create(Object.assign(Object.assign({ transactionId: extractIdFromStripeInstance(transactionId), amount, status: parser.parseStripeRefundStatus(refundStatus$1) || refundStatus.INITIAL }, (entityId ? { entityId } : {})), { params: Object.assign(Object.assign(Object.assign({ refundId: id }, (reason ? { reason } : {})), (type ? { type } : {})), (refundAmountType ? { refundAmountType } : {})) }))); })); } while (refunds.has_more); }); } } module.exports = Charge;