From aa75247bd028d1eb2e7b2e83e8f824973de68810 Mon Sep 17 00:00:00 2001 From: Vassshhh Date: Mon, 25 Aug 2025 03:47:25 +0700 Subject: [PATCH] ok --- src/Checkout.js | 425 +++++++++++++++++++--------------------- src/Checkout.module.css | 89 ++++++++- 2 files changed, 286 insertions(+), 228 deletions(-) diff --git a/src/Checkout.js b/src/Checkout.js index 9fb207a..95c0fdf 100644 --- a/src/Checkout.js +++ b/src/Checkout.js @@ -18,6 +18,13 @@ function parseJwt(token) { } } +function formatTimeLeft(ms) { + const totalSeconds = Math.max(Math.floor(ms / 1000), 0); + const minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0'); + const seconds = String(totalSeconds % 60).padStart(2, '0'); + return `${minutes}:${seconds}`; +} + const Checkout = ({ socketId, transactionSuccess }) => { const [products, setProducts] = useState([]); const [itemIds, setItemIds] = useState(null); @@ -30,38 +37,89 @@ const Checkout = ({ socketId, transactionSuccess }) => { const [redirect_uri, setRedirect_Uri] = useState(''); const [redirect_failed, setRedirect_Failed] = useState(''); - const [paymentMethod, setPaymentMethod] = useState('QRIS'); + const [payTimeout, setPayTimeout] = useState(null); + const [timeLeft, setTimeLeft] = useState(null); + + const [activeAccordion, setActiveAccordion] = useState('QRIS'); + + let grandTotal = 0; + let tax = 0; + useEffect(() => { - const urlParams = new URLSearchParams(window.location.search); - const tokenParam = urlParams.get('token'); - const itemsIdString = urlParams.get('itemsId'); + if (!socketId) return; + + let urlParams = new URLSearchParams(window.location.search); + let tokenParam = urlParams.get('token'); + let itemsIdString = urlParams.get('itemsId'); + + const handlePay = async () => { + setLoadingPay(true); + + try { + const newName = urlParams.get('new_name'); + const setName = urlParams.get('set_name'); + + const params = new URLSearchParams(); + itemsIdString.forEach((id) => params.append('itemsId', id)); + params.append('socketId', socketId); + params.append('paymentMethod', paymentMethod); + if (newName) params.append('newName', newName); + if (setName) params.append('setName', setName); + + const response = await fetch('https://bot.kediritechnopark.com/webhook/store-production/pay', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Bearer ${tokenParam}`, + }, + body: params.toString(), + }); + + const result = await response.json(); + if (response.ok) { + setValue(result.total_price); + if (result.pay_timeout && result.time_now) { + const timeout = new Date(result.pay_timeout).getTime(); + const now = new Date(result.time_now).getTime(); + setPayTimeout(timeout); + setTimeLeft(timeout - now); + } + + setQrisData(result.qris_dynamic || null); + setTransferData(result); + + grandTotal = result.total_price; + tax = 0; + + } else { + alert(`Request gagal: ${result?.error || 'Unknown error'}`); + } + } catch (error) { + console.error('Network error:', error); + alert('Terjadi kesalahan jaringan.'); + } finally { + setLoadingPay(false); + } + }; + + itemsIdString = JSON.parse(urlParams.get('itemsId')); setRedirect_Uri(urlParams.get('redirect_uri') || ''); setRedirect_Failed(urlParams.get('redirect_failed') || ''); setToken(tokenParam); - if (!itemsIdString) { + if (!itemsIdString || !Array.isArray(itemsIdString) || itemsIdString.length === 0) { window.location.href = urlParams.get('redirect_failed') || '/'; return; } - try { - const parsedIds = JSON.parse(itemsIdString); - if (!Array.isArray(parsedIds) || parsedIds.length === 0) { - window.location.href = urlParams.get('redirect_failed') || '/'; - return; - } - setItemIds(parsedIds); - } catch (e) { - console.error('Invalid itemsId format', e); - window.location.href = urlParams.get('redirect_failed') || '/'; - } - }, []); + setItemIds(itemsIdString); + handlePay(); + }, [socketId]); - // Fetch products useEffect(() => { - if (itemIds && Array.isArray(itemIds) && itemIds.length > 0) { + if (itemIds?.length > 0) { fetch('https://bot.kediritechnopark.com/webhook/store-production/products', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -73,73 +131,6 @@ const Checkout = ({ socketId, transactionSuccess }) => { } }, [itemIds]); - const handleRemove = (id) => { - const updatedItemIds = itemIds.filter((itemId) => itemId !== id); - const updatedProducts = products.filter((product) => product.id !== id); - - if (updatedItemIds.length === 0) { - window.location.href = redirect_failed; - return; - } - - setItemIds(updatedItemIds); - setProducts(updatedProducts); - - const url = new URL(window.location); - url.searchParams.set('itemsId', JSON.stringify(updatedItemIds)); - window.history.replaceState(null, '', url.toString()); - }; - - const handlePay = async () => { - if (!itemIds || !token) { - alert('Token atau itemsId tidak ditemukan.'); - return; - } - - setLoadingPay(true); - try { - const urlParams = new URLSearchParams(window.location.search); - const newName = urlParams.get('new_name'); - const setName = urlParams.get('set_name'); - - const params = new URLSearchParams(); - itemIds.forEach((id) => params.append('itemsId', id)); - params.append('socketId', socketId); - params.append('paymentMethod', paymentMethod); - if (newName) params.append('newName', newName); - if (setName) params.append('setName', setName); - - const response = await fetch('https://bot.kediritechnopark.com/webhook/store-production/pay', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Authorization: `Bearer ${token}`, - }, - body: params.toString(), - }); - - const result = await response.json(); - if (response.ok) { - if (paymentMethod === 'QRIS' && result?.qris_dynamic) { - setQrisData(result.qris_dynamic); - setValue(result.total_price); - } else if (paymentMethod === 'Bank Transfer' && result?.bank_account) { - setTransferData(result); - setValue(result.total_price); - } else { - alert(`Gagal memproses pembayaran: ${result?.error || 'Unknown error'}`); - } - } else { - alert(`Request gagal: ${result?.error || 'Unknown error'}`); - } - } catch (error) { - console.error('Network error:', error); - alert('Terjadi kesalahan jaringan.'); - } finally { - setLoadingPay(false); - } - }; - useEffect(() => { if (transactionSuccess) { const timer = setTimeout(() => { @@ -149,157 +140,139 @@ const Checkout = ({ socketId, transactionSuccess }) => { } }, [transactionSuccess, redirect_uri]); - const subtotal = products.reduce((acc, item) => acc + (item.price || 0), 0); - const shipping = 0; - const tax = 0; - const grandTotal = subtotal + shipping + tax; + useEffect(() => { + if (!payTimeout) return; + const interval = setInterval(() => { + const now = Date.now(); + const remaining = payTimeout - now; + setTimeLeft(remaining); + if (remaining <= 0) { + clearInterval(interval); + alert('Waktu pembayaran habis.'); + window.location.href = redirect_failed; + } + }, 1000); + return () => clearInterval(interval); + }, [payTimeout]); return (
- {!qrisData && !transferData ? ( - <> -
-
-

- KEDIRITECHNOPARK -

-

- Hello, {parseJwt(token)?.username || 'User'}
- Thank you for your order -

-
-
-
Invoice
-
ORDER #{String(itemIds?.[0] || '').padStart(5, '0')}
-
{new Date().toLocaleDateString()}
-
-
- - - - - - - - - - - - {products.map((item) => ( - - - - - - - ))} - -
ItemQuantitySubtotal
{item.name}{item.qty ?? 1} - Rp{(item.price || 0).toLocaleString('id-ID')} - - -
- -
-
- SUBTOTAL - Rp{subtotal.toLocaleString('id-ID')} -
-
- SHIPPING - Rp{shipping.toLocaleString('id-ID')} -
-
- TAX - Rp{tax.toLocaleString('id-ID')} -
-
- Total - Rp{grandTotal.toLocaleString('id-ID')} -
-
- - ) : paymentMethod === 'QRIS' ? ( - <> -

Silakan scan QRIS ini

-
- - {transactionSuccess && ( -
- - - - -
- )} - {!transactionSuccess &&

Rp{value?.toLocaleString('id-ID')}

} -
- - ) : ( -
-

Bank Transfer Information

-
Bank{transferData?.bank_name}
-
Account No{transferData?.bank_account}
-
Account Name{transferData?.account_name}
-
TotalRp{value?.toLocaleString('id-ID')}
+
+
+

+ KEDIRITECHNOPARK +

+

+ Hello, {parseJwt(token)?.username || 'User'}
+ Thank you for your order +

- )} +
+
Invoice
+
ORDER #{String(itemIds?.[0] || '').padStart(5, '0')}
+
{new Date().toLocaleDateString()}
+
+
+ + + + + + + + + + {products.map((item) => ( + + + + + ))} + +
Item{grandTotal}
{item.name} + Rp{(item.price || 0).toLocaleString('id-ID')} +
+ +
+
+ SUBTOTAL + Rp{grandTotal.toLocaleString('id-ID')} +
+
+ TAX + Rp{tax.toLocaleString('id-ID')} +
+
+ Total + Rp{grandTotal.toLocaleString('id-ID')} +
+
-
-

- Rp{(qrisData || transferData ? value : grandTotal).toLocaleString('id-ID')} -

+ {(qrisData || transferData) && ( +
+ {qrisData && ( +
+
+ setActiveAccordion(activeAccordion === 'QRIS' ? '' : 'QRIS') + } + > + QRIS Payment +
+ {activeAccordion === 'QRIS' && ( +
+ + {!transactionSuccess && ( + <> +
Rp{value?.toLocaleString('id-ID')}
+ {timeLeft !== null && ( +

Waktu tersisa: {formatTimeLeft(timeLeft)}

+ )} + + )} + {transactionSuccess && ( +
+ + + + +
+ )} +
+ )} +
+ )} -
-

PAYMENT INFORMATION

- - + {transferData && ( +
+
+ setActiveAccordion(activeAccordion === 'Bank' ? '' : 'Bank') + } + > + Bank Transfer +
+ {activeAccordion === 'Bank' && ( +
+
Bank: {transferData?.bank_name}
+
Account No: {transferData?.bank_account}
+
Account Name: {transferData?.account_name}
+
Total: Rp{value?.toLocaleString('id-ID')}
+ {timeLeft !== null && ( +
Waktu Tersisa: {formatTimeLeft(timeLeft)}
+ )} +
+ )} +
+ )}
- -
- -
- - -
+ )}
Powered by KEDIRITECHNOPARK diff --git a/src/Checkout.module.css b/src/Checkout.module.css index 3a13e54..5284c88 100644 --- a/src/Checkout.module.css +++ b/src/Checkout.module.css @@ -46,18 +46,20 @@ margin-bottom: 1rem; } .brand { + margin-top: 0; font-size: 1.25rem; font-weight: 800; color: #2563eb; } .brandLight { font-weight: 500; } -.greeting { margin-top: .5rem; font-size: .875rem; color: #374151; } +.greeting { text-align: left; margin-top: .5rem; font-size: .875rem; color: #374151; } .orderInfo { text-align: right; font-size: .875rem; color: #6b7280; } .invoiceLabel { font-size: 1rem; color: #4b5563; } .orderMeta { margin-top: .125rem; } /* Table */ .table { + text-align: left; width: 100%; border-collapse: collapse; margin-top: .5rem; @@ -67,7 +69,6 @@ .table th, .table td { padding: .75rem 0; } .table th { border-bottom: 1px solid #d1d5db; - text-align: left; color: #374151; font-weight: 600; } @@ -210,3 +211,87 @@ @media (max-width: 767px) { .checkoutCard { flex-direction: column; } } + +.selectPayment { + width: 100%; + padding: 10px; + font-size: 16px; + border-radius: 6px; + border: 1px solid #ccc; + margin-top: 8px; + margin-bottom: 16px; +} + +.selectPaymentModern { + width: 100%; + padding: 0.65rem 1rem; + font-size: 1rem; + border-radius: 0.5rem; + border: 1px solid #d1d5db; + background-color: #f9fafb; + appearance: none; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: right 1rem center; + background-size: 1rem; + cursor: pointer; +} +.selectPaymentModern:focus { + outline: none; + border-color: #3b82f6; + background-color: #fff; +} + +.transferBox { + margin-top: 2rem; + padding: 1.5rem; + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + background: #f9fafb; + text-align: left; +} +.transferBox span { + text-align: right; +} +.transferTitle { + font-size: 1.125rem; + font-weight: 700; + margin-bottom: 1rem; + color: #111827; +} +.transferRow { + display: flex; + justify-content: space-between; + padding: 0.5rem 0; + border-bottom: 1px dashed #d1d5db; + font-size: 0.95rem; + color: #374151; +} +.transferRow:last-child { + border-bottom: none; +} + +.accordion { + border: 1px solid #ccc; + border-radius: 4px; + margin-bottom: 1rem; + overflow: hidden; +} + +.accordionHeader { + background-color: #f5f5f5; + padding: 12px 16px; + cursor: pointer; + font-weight: bold; + transition: background 0.2s ease; +} + +.accordionHeader:hover { + background-color: #e0e0e0; +} + +.accordionBody { + padding: 16px; + background-color: #fff; + border-top: 1px solid #ddd; +}