This commit is contained in:
Vassshhh
2025-07-24 19:12:08 +07:00
parent ba09e0ad86
commit 348ba2b827
6 changed files with 441 additions and 44 deletions

View File

@@ -1,12 +1,34 @@
import logo from './logo.svg';
// src/App.js
import { useEffect, useState } from 'react';
import './App.css';
import Checkout from './Checkout';
import socket from './socket';
function App() {
const [socketId, setSocketId] = useState(null);
const [transactionSuccess, setTransactionSuccess] = useState(null);
useEffect(() => {
socket.on('connect', () => {
console.log('Connected with socket ID:', socket.id);
setSocketId(socket.id);
});
socket.on('transactionSuccess', (data) => {
console.log('Transaction success:', data);
setTransactionSuccess(data); // data bisa berisi transactionId, status, dll
});
return () => {
socket.off('connect');
socket.off('transactionSuccess');
};
}, []);
return (
<div className="App">
<main>
<Checkout />
<Checkout socketId={socketId} transactionSuccess={transactionSuccess} />
</main>
</div>
);

View File

@@ -264,4 +264,64 @@
font-size: 10px;
padding-top: 10px;
}
}
}
.checkmark-container {
display: flex;
flex-direction: column;
align-items: center;
animation: fadeIn 0.5s ease-in;
}
.checkmark {
width: 100px;
height: 100px;
stroke-width: 2;
stroke: #4CAF50;
stroke-miterlimit: 10;
animation: scaleIn 0.3s ease-in-out;
}
.checkmark-circle {
stroke-dasharray: 166;
stroke-dashoffset: 166;
stroke-width: 2;
stroke: #4CAF50;
fill: none;
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
}
.checkmark-check {
transform-origin: 50% 50%;
stroke-dasharray: 48;
stroke-dashoffset: 48;
stroke: #4CAF50;
stroke-linecap: round;
stroke-linejoin: round;
animation: stroke 0.3s 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
}
@keyframes stroke {
to {
stroke-dashoffset: 0;
}
}
@keyframes scaleIn {
0% {
transform: scale(0);
}
100% {
transform: scale(1);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

View File

@@ -1,7 +1,123 @@
import React from 'react';
import './Checkout.css'; // Assuming you'll create a CSS file for styling
import React, { useState, useEffect } from 'react';
import './Checkout.css';
import { QRCodeCanvas } from 'qrcode.react';
const Checkout = ({ socketId, transactionSuccess }) => {
const [qrisData, setQrisData] = useState(null); // QRIS string
const [value, setValue] = useState(null); // QRIS value (optional)
const [products, setProducts] = useState([]); // Produk dari itemsId
const [loadingProducts, setLoadingProducts] = useState(false);
// Helper get cookie value
const getCookie = (name) => {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return null;
};
useEffect(() => {
const fetchProducts = async () => {
const itemsIdRaw = getCookie('itemsId');
if (!itemsIdRaw) return;
let itemsId = [];
try {
itemsId = JSON.parse(itemsIdRaw);
} catch (e) {
console.error('Gagal parse itemsId dari cookie:', e);
return;
}
if (itemsId.length === 0) return;
setLoadingProducts(true);
try {
const token = getCookie('token');
if (!token) {
console.warn('Token tidak ditemukan');
return;
}
const params = new URLSearchParams();
itemsId.forEach(id => params.append('itemsId', id));
const res = await fetch(`https://bot.kediritechnopark.com/webhook/store-dev/products`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString(),
});
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
const data = await res.json();
setProducts(data);
} catch (error) {
console.error('Error fetching products:', error);
} finally {
setLoadingProducts(false);
}
};
fetchProducts();
}, []);
const handlePay = async (e) => {
e.preventDefault();
let itemsIdRaw = getCookie('itemsId');
let token = getCookie('token');
if (!itemsIdRaw || !token) {
alert("Token atau itemsId tidak ditemukan di cookies.");
return;
}
let itemsId = [];
try {
itemsId = JSON.parse(itemsIdRaw);
} catch (e) {
alert("Gagal parsing itemsId.");
return;
}
try {
const params = new URLSearchParams();
itemsId.forEach(id => params.append('itemsId', id));
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[0].qris_dynamic) {
setQrisData(result[0].qris_dynamic);
setValue(result[0].total_price);
} else {
alert(`Gagal mendapatkan QRIS: ${result?.error || 'Unknown error'}`);
}
} catch (error) {
console.error('Network error:', error);
alert("Terjadi kesalahan jaringan.");
}
};
const Checkout = () => {
return (
<div className="checkout-container">
<div className="left-panel">
@@ -14,43 +130,83 @@ const Checkout = () => {
</div>
</div>
<div className="right-panel">
<button className="apple-pay-button"> Pay</button>
<div className="separator">
<hr className="line" />
<span>Or pay another way</span>
<hr className="line" />
</div>
<form className="shipping-info">
<h3>Shipping information</h3>
<input type="email" placeholder="Email" required />
<input type="text" placeholder="Name" required />
<select required>
<option value="United States">United States</option>
</select>
<input type="text" placeholder="Address" required />
<a href="#" className="manual-address">Enter address manually</a>
</form>
<div className="payment-methods">
<h3>Payment method</h3>
<label>
<input type="radio" name="payment" value="card" />
<span className="icon">💳</span> Card
</label>
<label>
<input type="radio" name="payment" value="bank" />
<span className="icon">🏦</span> Bank
</label>
<label>
<input type="radio" name="payment" value="klarna" />
<span className="icon klarna">K</span> Klarna
<div className="klarna-subtext">Buy now pay later</div>
</label>
<label>
<input type="radio" name="payment" value="ideal" />
<span className="icon ideal">iD</span> iDEAL
</label>
</div>
<button type="submit" className="pay-button">Pay</button>
{!qrisData ? (
<>
<h3>Cart Items</h3>
{loadingProducts ? (
<p>Loading products...</p>
) : products.length === 0 ? (
<p>No products found</p>
) : (
<ul>
{products.map((product) => (
<li key={product.id || product._id}>
{product.name} - ${product.price}
</li>
))}
</ul>
)}
<form className="shipping-info" onSubmit={handlePay}>
<h3>Shipping information</h3>
<input type="email" placeholder="Email" />
<input type="text" placeholder="Name" />
<select >
<option value="United States">United States</option>
</select>
<input type="text" placeholder="Address" />
<a href="#" className="manual-address">Enter address manually</a>
<div className="payment-methods">
<h3>Payment method</h3>
<label>
<input type="radio" name="payment" value="card" />
<span className="icon">💳</span> Card
</label>
<label>
<input type="radio" name="payment" value="bank" />
<span className="icon">🏦</span> Bank
</label>
<label>
<input type="radio" name="payment" value="klarna" />
<span className="icon klarna">K</span> Klarna
<div className="klarna-subtext">Buy now pay later</div>
</label>
<label>
<input type="radio" name="payment" value="ideal" />
<span className="icon ideal">iD</span> iDEAL
</label>
</div>
<button type="submit" className="pay-button">Pay</button>
</form>
</>
) : (
<>
{transactionSuccess ? (
<div className="success-section">
<div className="checkmark-container">
<svg className="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
<circle className="checkmark-circle" cx="26" cy="26" r="25" fill="none" />
<path className="checkmark-check" fill="none" d="M14 27l7 7 16-16" />
</svg>
<h2>Payment Successful!</h2>
</div>
</div>
) : (
<>
<div className="qris-section">
<h3>Scan QRIS to Pay</h3>
<QRCodeCanvas value={qrisData} size={256} />
<p className="qris-string">{qrisData}</p>
</div>
<h1>{value}</h1>
</>
)}
</>
)}
<div className="footer">
Powered by <strong>stripe</strong> | Terms | Privacy
</div>
@@ -59,4 +215,4 @@ const Checkout = () => {
);
};
export default Checkout;
export default Checkout;

10
src/socket.js Normal file
View File

@@ -0,0 +1,10 @@
// src/socket.js
import { io } from 'socket.io-client';
const socket = io('https://payment.kediritechnopark.com', {
transports: ['websocket'], // pastikan pakai websocket saja
secure: true,
reconnection: true
});
export default socket;