Files
alrahma_sunday_school_api/apps/api/dist/modules/payments/payment.service.js
T
2026-06-11 03:22:12 -04:00

178 lines
8.9 KiB
JavaScript

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.listByCompany = listByCompany;
exports.listByReservation = listByReservation;
exports.handleAmanpayWebhook = handleAmanpayWebhook;
exports.handlePaypalWebhook = handlePaypalWebhook;
exports.initCharge = initCharge;
exports.capturePaypal = capturePaypal;
exports.recordManualPayment = recordManualPayment;
exports.refundPayment = refundPayment;
const errors_1 = require("../../http/errors");
const amanpay = __importStar(require("../../services/amanpayService"));
const paypal = __importStar(require("../../services/paypalService"));
const repo = __importStar(require("./payment.repo"));
const webhookIdempotency_1 = require("../../security/webhookIdempotency");
function listByCompany(companyId) {
return repo.findByCompany(companyId);
}
function listByReservation(reservationId, companyId) {
return repo.findByReservation(reservationId, companyId);
}
async function applyAmanpayWebhook(event) {
const transactionId = event.transaction_id ?? event.id;
const status = event.status?.toUpperCase();
if (status === 'PAID' || status === 'SUCCEEDED') {
const payment = await repo.findByAmanpay(transactionId);
if (payment && payment.status !== 'SUCCEEDED') {
await repo.markPaymentSucceeded(payment.id);
await repo.incrementReservationPaid(payment.reservationId, payment.amount);
}
}
else if (status === 'FAILED') {
await repo.markPaymentFailed({ amanpayTransactionId: transactionId });
}
}
async function applyPaypalWebhook(event) {
const eventType = event.event_type;
if (eventType === 'PAYMENT.CAPTURE.COMPLETED') {
const captureId = event.resource?.id;
const payment = await repo.findByPaypal(captureId);
if (payment && payment.status !== 'SUCCEEDED') {
await repo.markPaymentSucceeded(payment.id);
await repo.incrementReservationPaid(payment.reservationId, payment.amount);
}
}
else if (eventType === 'PAYMENT.CAPTURE.DENIED') {
await repo.markPaymentFailed({ paypalCaptureId: event.resource?.id });
}
}
async function handleAmanpayWebhook(event, rawBody = JSON.stringify(event)) {
return (0, webhookIdempotency_1.processWebhookOnce)({
provider: 'amanpay:payments',
providerEventId: (0, webhookIdempotency_1.getWebhookEventId)('amanpay', event),
eventType: String(event.status ?? 'unknown'),
rawBody,
handle: () => applyAmanpayWebhook(event),
});
}
async function handlePaypalWebhook(event, rawBody = JSON.stringify(event)) {
return (0, webhookIdempotency_1.processWebhookOnce)({
provider: 'paypal:payments',
providerEventId: (0, webhookIdempotency_1.getWebhookEventId)('paypal', event),
eventType: String(event.event_type ?? 'unknown'),
rawBody,
handle: () => applyPaypalWebhook(event),
});
}
async function initCharge(reservationId, companyId, body) {
const reservation = await repo.findReservationOrThrow(reservationId, companyId);
if (reservation.paymentStatus === 'PAID')
throw new errors_1.ConflictError('Reservation is already fully paid');
const amount = body.type === 'DEPOSIT' ? reservation.depositAmount : reservation.totalAmount;
const description = `${body.type === 'DEPOSIT' ? 'Deposit' : 'Rental'}: ${reservation.vehicle.make} ${reservation.vehicle.model}`;
const orderId = `${reservationId}-${body.type}-${Date.now()}`;
const webhookBase = process.env.API_URL ?? 'http://localhost:4000';
let checkoutUrl;
let amanpayTransactionId = null;
let paypalCaptureId = null;
if (body.provider === 'AMANPAY') {
if (!amanpay.isConfigured())
throw new errors_1.ValidationError('AmanPay is not configured');
const result = await amanpay.createCheckout({
amount, currency: body.currency, orderId, description,
customerEmail: reservation.customer.email,
customerName: `${reservation.customer.firstName} ${reservation.customer.lastName}`,
successUrl: body.successUrl, failureUrl: body.failureUrl,
webhookUrl: `${webhookBase}/api/v1/payments/webhooks/amanpay`,
});
checkoutUrl = result.checkoutUrl;
amanpayTransactionId = result.transactionId;
}
else {
if (!paypal.isConfigured())
throw new errors_1.ValidationError('PayPal is not configured');
const result = await paypal.createOrder({ amount, currency: body.currency, orderId, description, returnUrl: body.successUrl, cancelUrl: body.failureUrl });
checkoutUrl = result.approveUrl;
paypalCaptureId = result.orderId;
}
const payment = await repo.createPayment({ companyId, reservationId, amount, currency: body.currency, status: 'PENDING', type: body.type, paymentProvider: body.provider, amanpayTransactionId, paypalCaptureId });
return { payment, checkoutUrl };
}
async function capturePaypal(reservationId, companyId, paypalOrderId) {
const payment = await repo.findByPaypalForCompany(paypalOrderId, companyId);
const capture = await paypal.captureOrder(paypalOrderId);
const captureId = capture.purchase_units?.[0]?.payments?.captures?.[0]?.id ?? paypalOrderId;
const updated = await repo.updatePaypalCapture(payment.id, captureId);
await repo.incrementReservationPaid(payment.reservationId, payment.amount);
return updated;
}
async function recordManualPayment(reservationId, companyId, body) {
const reservation = await repo.findReservation(reservationId, companyId);
const remaining = Math.max(reservation.totalAmount - reservation.paidAmount, 0);
if (remaining <= 0)
throw new errors_1.ConflictError('Reservation is already fully paid');
if (body.amount > remaining)
throw new errors_1.ValidationError('Payment amount exceeds remaining balance');
const payment = await repo.createPayment({ companyId, reservationId, amount: body.amount, currency: body.currency, status: 'SUCCEEDED', type: body.type, paymentProvider: body.paymentMethod === 'PAYPAL' ? 'PAYPAL' : 'AMANPAY', paymentMethod: body.paymentMethod, paidAt: new Date() });
const newPaidAmount = reservation.paidAmount + body.amount;
await repo.setReservationPaidAmount(reservationId, newPaidAmount, newPaidAmount >= reservation.totalAmount ? 'PAID' : 'PARTIAL');
return payment;
}
async function refundPayment(reservationId, paymentId, companyId, amount, reason) {
const payment = await repo.findPaymentOrThrow(paymentId, companyId, reservationId);
if (payment.status !== 'SUCCEEDED')
throw new errors_1.ValidationError('Only succeeded payments can be refunded');
if (!payment.amanpayTransactionId && !payment.paypalCaptureId)
throw new errors_1.ValidationError('Manual payments must be refunded outside the online gateway flow');
const refundAmount = amount ?? payment.amount;
if (payment.paymentProvider === 'AMANPAY') {
if (!payment.amanpayTransactionId)
throw new Error('No AmanPay transaction ID');
await amanpay.refundTransaction(payment.amanpayTransactionId, refundAmount, reason);
}
else {
if (!payment.paypalCaptureId)
throw new Error('No PayPal capture ID');
await paypal.refundCapture(payment.paypalCaptureId, refundAmount, payment.currency, reason);
}
const isPartial = refundAmount < payment.amount;
const updated = await repo.setPaymentRefunded(payment.id, isPartial);
if (!isPartial)
await repo.setReservationRefunded(payment.reservationId);
return updated;
}
//# sourceMappingURL=payment.service.js.map