From ad5e3c7a1c7ec4d3c51a17205c5e4058c3eb8114 Mon Sep 17 00:00:00 2001 From: Vassshhh Date: Tue, 29 Jul 2025 15:32:25 +0700 Subject: [PATCH] ok --- src/Checkout.js | 336 ++++++++++++++++++++++++++++++++-------- src/Checkout.module.css | 57 +++++++ 2 files changed, 325 insertions(+), 68 deletions(-) diff --git a/src/Checkout.js b/src/Checkout.js index e640288..9c520ec 100644 --- a/src/Checkout.js +++ b/src/Checkout.js @@ -1,77 +1,277 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import styles from './Checkout.module.css'; -const Checkout = () => { - return ( -
-
- {/* Product List */} -
-

Your Cart

-
    -
  • -
    - Product 1 -
    -

    Pure Kit

    -

    $65.00

    -
    -
    - -
  • +import { QRCodeCanvas } from 'qrcode.react'; -
  • -
    - Product 2 -
    -

    Energy Drink

    -

    $25.00

    -
    -
    - -
  • -
-
+function parseJwt(token) { + try { + const base64Url = token.split('.')[1]; + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const jsonPayload = decodeURIComponent( + atob(base64) + .split('') + .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) + .join('') + ); + return JSON.parse(jsonPayload); + } catch (e) { + return null; + } +} - {/* Checkout form */} -
-
-

Note / Request

-
- +const Checkout = ({ socketId, transactionSuccess }) => { + const [products, setProducts] = useState([]); + const [itemIds, setItemIds] = useState(null); + const [token, setToken] = useState(null); + + const [qrisData, setQrisData] = useState(null); + const [value, setValue] = useState(null); + const [loadingPay, setLoadingPay] = useState(false); + + const [redirect_uri, setRedirect_Uri] = useState(''); + const [redirect_failed, setRedirect_Failed] = useState(''); + + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + const tokenParam = urlParams.get('token'); + const itemsIdString = urlParams.get('itemsId'); + setRedirect_Uri(urlParams.get('redirect_uri')); + setRedirect_Failed(urlParams.get('redirect_failed')); + + setToken(tokenParam); + + if (!itemsIdString) { + window.location.href = redirect_failed; + return; + } + + try { + const parsedIds = JSON.parse(itemsIdString); + if (!Array.isArray(parsedIds) || parsedIds.length === 0) { + window.location.href = redirect_failed; + return; + } + setItemIds(parsedIds); + } catch (e) { + console.error('Invalid itemsId format', e); + window.location.href = redirect_failed; + } + }, []); + + // Fetch products + useEffect(() => { + if (itemIds && Array.isArray(itemIds) && itemIds.length > 0) { + fetch('https://bot.kediritechnopark.com/webhook/store-dev/products', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ itemsId: itemIds }), + }) + .then((res) => res.json()) + .then((data) => { + setProducts(data); + }) + .catch((err) => { + console.error('Error fetching products:', err); + }); + } + }, [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 params = new URLSearchParams(); + itemIds.forEach((id) => params.append('itemsId', id)); + params.append('socketId', socketId); + // Jika butuh socketId bisa tambahkan di sini, misal: params.append('socketId', socketId); + + const response = await fetch('https://bot.kediritechnopark.com/webhook/store-dev/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 && result?.qris_dynamic) { + setQrisData(result.qris_dynamic); + setValue(result.total_price); + } else { + alert(`Gagal mendapatkan QRIS: ${result?.error || 'Unknown error'}`); + } + } catch (error) { + console.error('Network error:', error); + alert('Terjadi kesalahan jaringan.'); + } finally { + setLoadingPay(false); + } + }; + + useEffect(() => { + if (transactionSuccess) { + const timer = setTimeout(() => { + window.location.href = redirect_uri; + }, 10000); // 10 detik = 10000 ms + + // Bersihkan timer kalau komponen unmount atau transactionSuccess berubah + return () => clearTimeout(timer); + } + }, [transactionSuccess]); + + + return ( +
+
+ {/* Product List */} +
+ {!qrisData && + <> +

Your Cart

+
    + {products.map((item, index) => ( +
  • +
    + {item.name} +
    +

    {item.name}

    +

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

    + {item.duration?.hours && ( +

    + Durasi: {item.duration.hours} jam +

    + )} +
    +
    + +
  • + ))} +
+ + } + + {qrisData && ( + <> +

Silahkan scan QRIS ini

+ +
+ + + {transactionSuccess && ( +
+ + + + +
+ )} + + {!transactionSuccess && ( + <> +

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

+ + )} +
+ + )} + + +
+ + {/* Checkout form */} +
+
+

+ Rp{qrisData ? value : products.reduce((acc, item) => acc + (item.price || 0), 0).toLocaleString('id-ID')} +

+
+ +
+ +
+ + {/* Footer */} +
+ Powered by KEDIRITECHNOPARK •{' '} +
+
- -
- - {/* Footer */} -
- Powered by Stripe •{' '} - Terms •{' '} - Privacy Policy -
-
-
- ); + ); }; export default Checkout; diff --git a/src/Checkout.module.css b/src/Checkout.module.css index 42b54d7..fd4fb7d 100644 --- a/src/Checkout.module.css +++ b/src/Checkout.module.css @@ -181,3 +181,60 @@ max-width: 100%; } } + +.CheckmarkOverlay { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(255, 255, 255, 0.5); + width: 130px; + height: 130px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + pointer-events: none; + z-index: 10; +} + +.CheckmarkSvg { + width: 100px; + height: 100px; + display: block; +} + +.CheckmarkCircle { + fill: none; + stroke: #4BB543; + stroke-width: 4; + stroke-dasharray: 157; /* 2πr where r = 25 */ + stroke-dashoffset: 157; + transform: rotate(-90deg); + transform-origin: center; + animation: CircleFill 1s ease forwards; +} + +.CheckmarkCheck { + fill: none; + stroke: #4BB543; + stroke-width: 4; + stroke-dasharray: 48; + stroke-dashoffset: 48; + animation: DrawCheck 0.5s ease forwards; + animation-delay: 1s; +} + +/* Circle fills in a clockwise motion */ +@keyframes CircleFill { + to { + stroke-dashoffset: 0; + } +} + +/* Checkmark is drawn after circle is full */ +@keyframes DrawCheck { + to { + stroke-dashoffset: 0; + } +}