diff --git a/package-lock.json b/package-lock.json index edf87e7..67d33d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,11 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", + "qrcode.react": "^4.2.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-scripts": "5.0.1", + "socket.io-client": "^4.8.1", "web-vitals": "^2.1.4" } }, @@ -3190,6 +3192,12 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -6893,6 +6901,66 @@ "node": ">= 0.8" } }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.2", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", @@ -13667,6 +13735,15 @@ "teleport": ">=0.2.0" } }, + "node_modules/qrcode.react": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz", + "integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -15010,6 +15087,68 @@ "node": ">=8" } }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -17481,6 +17620,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "license": "MIT" }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index d61c7c7..086604a 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,11 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", + "qrcode.react": "^4.2.0", "react": "^19.1.0", "react-dom": "^19.1.0", "react-scripts": "5.0.1", + "socket.io-client": "^4.8.1", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/src/App.js b/src/App.js index c628c34..a4ba93f 100644 --- a/src/App.js +++ b/src/App.js @@ -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 (
- +
); diff --git a/src/Checkout.css b/src/Checkout.css index 0a70986..09f3bd8 100644 --- a/src/Checkout.css +++ b/src/Checkout.css @@ -264,4 +264,64 @@ font-size: 10px; padding-top: 10px; } -} \ No newline at end of file +} +.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); + } +} diff --git a/src/Checkout.js b/src/Checkout.js index 26d21e8..a8a6c69 100644 --- a/src/Checkout.js +++ b/src/Checkout.js @@ -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 (
@@ -14,43 +130,83 @@ const Checkout = () => {
- -
-
- Or pay another way -
-
-
-

Shipping information

- - - - - Enter address manually -
-
-

Payment method

- - - - -
- + {!qrisData ? ( + <> +

Cart Items

+ {loadingProducts ? ( +

Loading products...

+ ) : products.length === 0 ? ( +

No products found

+ ) : ( + + )} + +
+

Shipping information

+ + + + + Enter address manually + +
+

Payment method

+ + + + +
+ + +
+ + ) : ( + <> + {transactionSuccess ? ( +
+
+ + + + +

Payment Successful!

+
+
+ ) : ( + <> +
+

Scan QRIS to Pay

+ +

{qrisData}

+
+

{value}

+ + )} + + + )} +
Powered by stripe | Terms | Privacy
@@ -59,4 +215,4 @@ const Checkout = () => { ); }; -export default Checkout; \ No newline at end of file +export default Checkout; diff --git a/src/socket.js b/src/socket.js new file mode 100644 index 0000000..1d39771 --- /dev/null +++ b/src/socket.js @@ -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;