diff --git a/apps/portal/src/app/features/certificates/pages/CoCApplicationPage.tsx b/apps/portal/src/app/features/certificates/pages/CoCApplicationPage.tsx index 667077cc3..dcbf801b9 100644 --- a/apps/portal/src/app/features/certificates/pages/CoCApplicationPage.tsx +++ b/apps/portal/src/app/features/certificates/pages/CoCApplicationPage.tsx @@ -39,6 +39,7 @@ import { IconUpload, } from '@tabler/icons-react'; import { notify } from '@ema-platform/ui'; +import { TelebirrPayment } from '../../payment/components/TelebirrPayment'; // --------------------------------------------------------------------------- // STCW certificate catalog — sourced from the STCW Convention & Code @@ -650,8 +651,13 @@ export function CoCApplicationPage() { const [paymentDate, setPaymentDate] = useState(''); const [paymentFile, setPaymentFile] = useState(null); + // Telebirr order id, set once TelebirrPayment confirms — shown as the ref in Step 3 + const [telebirrOrderId, setTelebirrOrderId] = useState(null); + const docsStepOk = docSlots.filter((d) => d.required).every((d) => !!docs[d.key]); - const payOk = !!paymentMethod && paymentRef.trim().length > 0 && paymentDate.length > 0 && !!paymentFile; + const payOk = paymentMethod === 'telebirr' + ? !!telebirrOrderId + : !!paymentMethod && paymentRef.trim().length > 0 && paymentDate.length > 0 && !!paymentFile; const FEES = [ { label: 'Application Fee', amount: selectedCert?.type === 'CoC' ? 800 : 300 }, @@ -976,7 +982,7 @@ export function CoCApplicationPage() { ))} - {paymentMethod && ( + {paymentMethod === 'cbe' && ( @@ -1011,6 +1017,18 @@ export function CoCApplicationPage() { )} + {paymentMethod === 'telebirr' && ( + + + { + setTelebirrOrderId(orderId); + setStep(3); + }} + /> + + )} + + + + )} + + {choice === 'qr' && paymentUrl && ( + + + + )} + + {status === 'polling' && ( + + + Waiting for payment confirmation… + + )} + + {status === 'confirmed' && ( + }> + Payment confirmed + + )} + + ); +} diff --git a/apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx b/apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx index e71f1df71..61f0f08fe 100644 --- a/apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx +++ b/apps/portal/src/app/features/seaman-book/pages/SeamanBookApplicationPage.tsx @@ -33,6 +33,7 @@ import { } from '@tabler/icons-react'; import { useNavigate } from 'react-router-dom'; import { notify } from '@ema-platform/ui'; +import { TelebirrPayment } from '../../payment/components/TelebirrPayment'; // --------------------------------------------------------------------------- // Steps @@ -234,6 +235,8 @@ export function SeamanBookApplicationPage() { const [paymentDate, setPaymentDate] = useState(''); const [paymentFile, setPaymentFile] = useState(null); const payResetRef = useRef<() => void>(null); + // Telebirr order id, set once TelebirrPayment confirms — real gateway flow, no manual ref for telebirr + const [telebirrOrderId, setTelebirrOrderId] = useState(null); // Validation const bstComplete = BST_SLOTS.every((s) => { @@ -241,7 +244,10 @@ export function SeamanBookApplicationPage() { return !!d.file && !!d.certNumber.trim() && !!d.issuer.trim() && !!d.issueDate; }); const medComplete = !!medFile && !!medCertNumber.trim() && !!medIssuer.trim() && !!medIssueDate && !!medExpiryDate; - const payComplete = !!paymentMethod && !!paymentRef.trim() && !!paymentDate; + // const payComplete = !!paymentMethod && !!paymentRef.trim() && !!paymentDate; // old: telebirr used to require a manual ref too + const payComplete = paymentMethod === 'telebirr' + ? !!telebirrOrderId + : !!paymentMethod && !!paymentRef.trim() && !!paymentDate; const canNext = () => { if (active === 0) return bstComplete; @@ -523,6 +529,7 @@ export function SeamanBookApplicationPage() { )} + {/* old manual Telebirr flow — replaced by the real gateway component below {paymentMethod === 'telebirr' && ( <> }> @@ -534,6 +541,19 @@ export function SeamanBookApplicationPage() { )} + */} + + {paymentMethod === 'telebirr' && ( + <> + + { + setTelebirrOrderId(orderId); + setActive(3); + }} + /> + + )} {paymentMethod && ( <> @@ -622,7 +642,8 @@ export function SeamanBookApplicationPage() { Payment - + {/* old: telebirr no longer collects a manual ref */} + diff --git a/package-lock.json b/package-lock.json index 0d01c8092..019356f9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "react-dom": "^19.0.0", "react-hook-form": "^7.71.2", "react-i18next": "^15.7.4", + "react-qr-code": "^2.2.0", "react-redux": "^9.2.0", "react-router": "^7.12.0", "react-router-dom": "^7.13.1", @@ -16542,6 +16543,12 @@ "node": ">=6" } }, + "node_modules/qrcode-generator": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-2.0.4.tgz", + "integrity": "sha512-mZSiP6RnbHl4xL2Ap5HfkjLnmxfKcPWpWe/c+5XxCuetEenqmNFf1FH/ftXPCtFG5/TDobjsjz6sSNL0Sr8Z9g==", + "license": "MIT" + }, "node_modules/qs": { "version": "6.15.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", @@ -16880,6 +16887,19 @@ "@napi-rs/canvas": "^0.1.80" } }, + "node_modules/react-qr-code": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.2.0.tgz", + "integrity": "sha512-e5nS0UUN22K3Nf8KBRUzemfdJ6OmnN5w+kbnj1lvJaol9RyVRFeGl05bCkxSN2ZegbLxjjYjX1+mmAoX9+fAhw==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1", + "qrcode-generator": "^2.0.4" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-redux": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.3.0.tgz", diff --git a/package.json b/package.json index a37f5a120..50536ea24 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "react-dom": "^19.0.0", "react-hook-form": "^7.71.2", "react-i18next": "^15.7.4", + "react-qr-code": "^2.2.0", "react-redux": "^9.2.0", "react-router": "^7.12.0", "react-router-dom": "^7.13.1",