@lomray/microservice-payment-stripe
Version:
Stripe payment microservice based on NodeJS & inverted json.
190 lines (186 loc) • 10.4 kB
JavaScript
;
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;