feat(payments): extend payment link expiration to 24 hours

Update default payment link TTL from 30 minutes to 24 hours across frontend and backend. Also modify countdown display to show hours in addition to minutes and seconds.
This commit is contained in:
Tengku Achmad 2025-11-22 11:54:46 +07:00
parent 96c4cd3aba
commit ec96b71161
4 changed files with 14 additions and 11 deletions

View File

@ -124,7 +124,7 @@ const ENABLE = {
// --- Payment Link Config // --- Payment Link Config
const EXTERNAL_API_KEY = process.env.EXTERNAL_API_KEY || '' const EXTERNAL_API_KEY = process.env.EXTERNAL_API_KEY || ''
const PAYMENT_LINK_SECRET = process.env.PAYMENT_LINK_SECRET || '' const PAYMENT_LINK_SECRET = process.env.PAYMENT_LINK_SECRET || ''
const PAYMENT_LINK_TTL_MINUTES = parseInt(process.env.PAYMENT_LINK_TTL_MINUTES || '30', 10) const PAYMENT_LINK_TTL_MINUTES = parseInt(process.env.PAYMENT_LINK_TTL_MINUTES || '1440', 10)
const PAYMENT_LINK_BASE = process.env.PAYMENT_LINK_BASE || 'http://localhost:5174/pay' const PAYMENT_LINK_BASE = process.env.PAYMENT_LINK_BASE || 'http://localhost:5174/pay'
const activeOrders = new Map() // order_id -> expire_at const activeOrders = new Map() // order_id -> expire_at
// Map untuk menyimpan mercant_id per order_id agar notifikasi ERP bisa dinamis // Map untuk menyimpan mercant_id per order_id agar notifikasi ERP bisa dinamis
@ -234,7 +234,7 @@ app.get('/api/payment-links/:token', (req, res) => {
if (result.error) { if (result.error) {
logWarn('paymentlink.invalid', { error: result.error }) logWarn('paymentlink.invalid', { error: result.error })
if (isDevEnv()) { if (isDevEnv()) {
const ttlMin = PAYMENT_LINK_TTL_MINUTES > 0 ? PAYMENT_LINK_TTL_MINUTES : 30 const ttlMin = PAYMENT_LINK_TTL_MINUTES > 0 ? PAYMENT_LINK_TTL_MINUTES : 1440
const fallback = { order_id: token, nominal: 150000, expire_at: Date.now() + ttlMin * 60 * 1000 } const fallback = { order_id: token, nominal: 150000, expire_at: Date.now() + ttlMin * 60 * 1000 }
logInfo('paymentlink.dev.fallback', { order_id: fallback.order_id }) logInfo('paymentlink.dev.fallback', { order_id: fallback.order_id })
return res.json(fallback) return res.json(fallback)
@ -386,7 +386,7 @@ app.post('/createtransaksi', async (req, res) => {
} }
const nominal = Number(nominalRaw) const nominal = Number(nominalRaw)
const now = Date.now() const now = Date.now()
const ttlMin = PAYMENT_LINK_TTL_MINUTES > 0 ? PAYMENT_LINK_TTL_MINUTES : 30 const ttlMin = PAYMENT_LINK_TTL_MINUTES > 0 ? PAYMENT_LINK_TTL_MINUTES : 1440
const expire_at = now + ttlMin * 60 * 1000 const expire_at = now + ttlMin * 60 * 1000
// Block jika sudah selesai // Block jika sudah selesai

View File

@ -14,9 +14,10 @@ function useCountdown(expireAt: number) {
}, []) }, [])
const remainMs = Math.max(0, expireAt - now) const remainMs = Math.max(0, expireAt - now)
const totalSec = Math.floor(remainMs / 1000) const totalSec = Math.floor(remainMs / 1000)
const mm = String(Math.floor(totalSec / 60)).padStart(2, '0') const hh = String(Math.floor(totalSec / 3600)).padStart(2, '0')
const mm = String(Math.floor((totalSec % 3600) / 60)).padStart(2, '0')
const ss = String(totalSec % 60).padStart(2, '0') const ss = String(totalSec % 60).padStart(2, '0')
return `${mm}:${ss}` return `${hh}:${mm}:${ss}`
} }
export interface PaymentSheetProps { export interface PaymentSheetProps {

View File

@ -19,7 +19,7 @@ export function PayPage() {
const { token } = useParams() const { token } = useParams()
const [orderId, setOrderId] = useState<string>('') const [orderId, setOrderId] = useState<string>('')
const [amount, setAmount] = useState<number>(0) const [amount, setAmount] = useState<number>(0)
const [expireAt, setExpireAt] = useState<number>(Date.now() + 30 * 60 * 1000) const [expireAt, setExpireAt] = useState<number>(Date.now() + 24 * 60 * 60 * 1000)
const [selectedMethod, setSelectedMethod] = useState<Method>(null) const [selectedMethod, setSelectedMethod] = useState<Method>(null)
const [locked, setLocked] = useState<boolean>(false) const [locked, setLocked] = useState<boolean>(false)
const [selectedBank, setSelectedBank] = useState<BankKey | null>(null) const [selectedBank, setSelectedBank] = useState<BankKey | null>(null)
@ -39,7 +39,7 @@ export function PayPage() {
if (cancelled) return if (cancelled) return
setOrderId(payload.order_id) setOrderId(payload.order_id)
setAmount(payload.nominal) setAmount(payload.nominal)
setExpireAt(payload.expire_at ?? Date.now() + 30 * 60 * 1000) setExpireAt(payload.expire_at ?? Date.now() + 24 * 60 * 60 * 1000)
setAllowedMethods(payload.allowed_methods) setAllowedMethods(payload.allowed_methods)
setError(null) setError(null)
} catch (err) { } catch (err) {

View File

@ -137,14 +137,16 @@ export async function getPaymentLinkPayload(token: string): Promise<PaymentLinkP
order_id: json.order_id || token, order_id: json.order_id || token,
nominal: Number(json.nominal) || 150000, nominal: Number(json.nominal) || 150000,
customer: json.customer || {}, customer: json.customer || {},
expire_at: json.expire_at || Date.now() + 30 * 60 * 1000, expire_at: json.expire_at || Date.now() + 24 * 60 * 60 * 1000
,
allowed_methods: json.allowed_methods || undefined, allowed_methods: json.allowed_methods || undefined,
} }
} catch { } catch {
return { return {
order_id: token, order_id: token,
nominal: 150000, nominal: 150000,
expire_at: Date.now() + 30 * 60 * 1000, expire_at: Date.now() + 24 * 60 * 60 * 1000
,
} }
} }
} }