mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 03:40:56 +00:00
refactor: ( payment ) increase timeout
This commit is contained in:
@@ -53,7 +53,11 @@ function rabbitMQImport(): DynamicModule[] {
|
||||
SeatsModule,
|
||||
TicketsModule,
|
||||
CurrencyModule,
|
||||
HttpModule.register({ timeout: 10_000 }),
|
||||
// The payment service proxies slow provider calls (e.g. CAC Bank initiate, which SMSes an
|
||||
// OTP and can take tens of seconds). Keep this hop generous; overridable via env.
|
||||
HttpModule.register({
|
||||
timeout: Number(process.env.PAYMENT_API_HTTP_TIMEOUT_MS) || 60_000,
|
||||
}),
|
||||
...rabbitMQImport(),
|
||||
],
|
||||
controllers: [PaymentsController, InternalPaymentsController],
|
||||
|
||||
@@ -37,8 +37,10 @@ export default function PaymentPage() {
|
||||
const [selectedMethodCurrency, setSelectedMethodCurrency] = useState<string | null>(null);
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [paymentError, setPaymentError] = useState<string | null>(null);
|
||||
// CAC Bank OTP debit: capture the payer's mobile up front, then collect the SMS'd OTP.
|
||||
// CAC Bank OTP debit: on Pay, collect the payer's mobile in a modal, then the SMS'd OTP.
|
||||
const [payerMobile, setPayerMobile] = useState("");
|
||||
const [phoneModalOpen, setPhoneModalOpen] = useState(false);
|
||||
const [phoneError, setPhoneError] = useState<string | null>(null);
|
||||
const [otpModalOpen, setOtpModalOpen] = useState(false);
|
||||
const [otpCode, setOtpCode] = useState("");
|
||||
const [otpMessage, setOtpMessage] = useState<string | null>(null);
|
||||
@@ -192,37 +194,51 @@ export default function PaymentPage() {
|
||||
|
||||
|
||||
|
||||
const handlePayment = async () => {
|
||||
if (!selectedMethod || !bookingId) {
|
||||
alert("Please select a payment method");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fire the actual initiate. `mobile` is only used for CAC (OTP debit).
|
||||
const startPayment = (mobile?: string) => {
|
||||
if (!selectedMethod || !bookingId || !selectedPaymentMethod) return;
|
||||
setIsProcessing(true);
|
||||
setPaymentError(null);
|
||||
|
||||
if (!selectedPaymentMethod) {
|
||||
alert("Invalid payment method selected");
|
||||
setIsProcessing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedMethod === 'CAC_BANK' && !payerMobile.trim()) {
|
||||
setPaymentError("Please enter the mobile number to receive the OTP");
|
||||
setIsProcessing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
paymentMutation.mutate({
|
||||
bookingId,
|
||||
method: selectedMethod,
|
||||
paymentMethodId: selectedPaymentMethod.id,
|
||||
currency: displayCurrency,
|
||||
amountMinor: totalAmount,
|
||||
payerAccount: selectedMethod === 'CAC_BANK' ? payerMobile.trim() : undefined,
|
||||
payerAccount: selectedMethod === 'CAC_BANK' ? mobile?.trim() : undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const handlePayment = () => {
|
||||
if (!selectedMethod || !bookingId) {
|
||||
alert("Please select a payment method");
|
||||
return;
|
||||
}
|
||||
if (!selectedPaymentMethod) {
|
||||
alert("Invalid payment method selected");
|
||||
return;
|
||||
}
|
||||
setPaymentError(null);
|
||||
|
||||
// CAC Bank needs the payer's mobile for the OTP — collect it in a modal before initiating.
|
||||
if (selectedMethod === 'CAC_BANK') {
|
||||
setPhoneError(null);
|
||||
setPhoneModalOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
startPayment();
|
||||
};
|
||||
|
||||
const submitPhone = () => {
|
||||
if (!payerMobile.trim()) {
|
||||
setPhoneError("Please enter your mobile number");
|
||||
return;
|
||||
}
|
||||
setPhoneModalOpen(false);
|
||||
startPayment(payerMobile);
|
||||
};
|
||||
|
||||
// Redirect if no booking data (but not during navigation)
|
||||
useEffect(() => {
|
||||
// Add a small delay to allow state to be set from previous page
|
||||
@@ -463,6 +479,49 @@ export default function PaymentPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* CAC Bank — collect payer mobile before initiating */}
|
||||
{phoneModalOpen && (
|
||||
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50 px-4">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl p-6 max-w-sm w-full shadow-2xl">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Smartphone className="w-5 h-5 text-primary" />
|
||||
<h3 className="text-lg font-bold text-gray-900 dark:text-gray-100">Your mobile number</h3>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-4">
|
||||
CAC Bank will send a one-time password to this number to authorize the payment.
|
||||
</p>
|
||||
<input
|
||||
type="tel"
|
||||
inputMode="numeric"
|
||||
autoFocus
|
||||
value={payerMobile}
|
||||
onChange={(e) => { setPayerMobile(e.target.value); setPhoneError(null); }}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') submitPhone(); }}
|
||||
placeholder="77 XX XX XX"
|
||||
className="w-full px-3 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:border-primary focus:ring-1 focus:ring-primary outline-none"
|
||||
/>
|
||||
{phoneError && (
|
||||
<p className="text-red-600 dark:text-red-400 text-xs mt-2">⚠️ {phoneError}</p>
|
||||
)}
|
||||
<div className="flex gap-2 mt-4">
|
||||
<button
|
||||
onClick={() => setPhoneModalOpen(false)}
|
||||
className="btn-secondary flex-1 py-2.5"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={submitPhone}
|
||||
disabled={!payerMobile.trim()}
|
||||
className="btn-primary flex-1 py-2.5 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* CAC Bank OTP entry */}
|
||||
{otpModalOpen && (
|
||||
<div className="fixed inset-0 bg-black/60 flex items-center justify-center z-50 px-4">
|
||||
@@ -567,27 +626,6 @@ export default function PaymentPage() {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedMethod === 'CAC_BANK' && (
|
||||
<div className="mt-4 p-4 rounded-xl border-2 border-primary/30 bg-primary/5">
|
||||
<label htmlFor="cac-mobile" className="block text-sm font-semibold text-gray-900 dark:text-gray-100 mb-1">
|
||||
Mobile number for OTP
|
||||
</label>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mb-2">
|
||||
CAC Bank will send a one-time password to this number to authorize the debit.
|
||||
</p>
|
||||
<input
|
||||
id="cac-mobile"
|
||||
type="tel"
|
||||
inputMode="numeric"
|
||||
value={payerMobile}
|
||||
onChange={(e) => setPayerMobile(e.target.value)}
|
||||
placeholder="77 XX XX XX"
|
||||
disabled={isProcessing}
|
||||
className="w-full px-3 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:border-primary focus:ring-1 focus:ring-primary outline-none"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Order summary inline — mobile only */}
|
||||
|
||||
@@ -10,4 +10,7 @@ export default registerAs("cac", () => ({
|
||||
currency: process.env.CAC_CURRENCY || "DJF",
|
||||
tokenTtlMs: Number(process.env.CAC_TOKEN_TTL_MS || 23 * 60 * 60 * 1000),
|
||||
otpExpiryMs: Number(process.env.CAC_OTP_EXPIRY_MS || 10 * 60 * 1000),
|
||||
// The bank's PaymentInitiateRequest sends an OTP by SMS and can be slow; the bank asked us
|
||||
// to raise the client timeout. Generous default, overridable via env.
|
||||
httpTimeoutMs: Number(process.env.CAC_HTTP_TIMEOUT_MS || 60_000),
|
||||
}));
|
||||
|
||||
@@ -180,7 +180,12 @@ export class IntentsService {
|
||||
);
|
||||
}
|
||||
|
||||
if (intent.status !== ProviderPaymentStatus.REQUIRES_ACTION) {
|
||||
// REQUIRES_ACTION is the normal awaiting-OTP state; PROCESSING is tolerated so an intent
|
||||
// that a poll/sweep nudged forward can still be confirmed. Terminal states are rejected.
|
||||
if (
|
||||
intent.status !== ProviderPaymentStatus.REQUIRES_ACTION &&
|
||||
intent.status !== ProviderPaymentStatus.PROCESSING
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
`Intent is not awaiting confirmation (status=${intent.status})`,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user