Compare commits

..

3 Commits

Author SHA1 Message Date
root 18513b5968 Merge pull request 'feat/payment-link-flow' (#12) from feat/payment-link-flow into main
Reviewed-on: #12
2025-11-22 05:01:17 +00:00
Tengku Achmad c6b17bfab6 feat: update favicon and page title for branding 2025-11-22 11:58:37 +07:00
Tengku Achmad ec96b71161 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.
2025-11-22 11:54:46 +07:00
6 changed files with 16 additions and 13 deletions

View File

@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/png" href="/simaya.png"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>core-midtrans-cifo</title>
<title>Simaya Midtrans | Retail Payment</title>
</head>
<body>
<div id="root"></div>

BIN
public/simaya.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@ -124,7 +124,7 @@ const ENABLE = {
// --- Payment Link Config
const EXTERNAL_API_KEY = process.env.EXTERNAL_API_KEY || ''
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 activeOrders = new Map() // order_id -> expire_at
// 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) {
logWarn('paymentlink.invalid', { error: result.error })
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 }
logInfo('paymentlink.dev.fallback', { order_id: fallback.order_id })
return res.json(fallback)
@ -386,7 +386,7 @@ app.post('/createtransaksi', async (req, res) => {
}
const nominal = Number(nominalRaw)
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
// Block jika sudah selesai

View File

@ -14,9 +14,10 @@ function useCountdown(expireAt: number) {
}, [])
const remainMs = Math.max(0, expireAt - now)
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')
return `${mm}:${ss}`
return `${hh}:${mm}:${ss}`
}
export interface PaymentSheetProps {

View File

@ -19,7 +19,7 @@ export function PayPage() {
const { token } = useParams()
const [orderId, setOrderId] = useState<string>('')
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 [locked, setLocked] = useState<boolean>(false)
const [selectedBank, setSelectedBank] = useState<BankKey | null>(null)
@ -39,7 +39,7 @@ export function PayPage() {
if (cancelled) return
setOrderId(payload.order_id)
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)
setError(null)
} catch (err) {

View File

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