178 lines
8.9 KiB
JavaScript
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
|