You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

510 lines
25 KiB

  1. // material-ui
  2. import {
  3. Grid,
  4. Typography,
  5. Stack,
  6. Button,
  7. } from '@mui/material';
  8. import * as React from "react";
  9. import * as HttpUtils from "utils/HttpUtils";
  10. import * as UrlUtils from "utils/ApiPathConst";
  11. import { useNavigate } from "react-router-dom";
  12. import FpsIcon from "assets/images/icons/fps.svg";
  13. import expiredQrcodeEN from "assets/images/icons/expiredQrcodeEN.png";
  14. import expiredQrcodeZH from "assets/images/icons/expiredQrcodeZH.png";
  15. import expiredQrcodeCN from "assets/images/icons/expiredQrcodeCN.png";
  16. import { useLocation } from 'react-router-dom';
  17. // import {paymentPath} from "auth/utils";
  18. import {currencyFormat} from "utils/FormatUtils";
  19. // import {poll} from "utils/Utils";
  20. import Loadable from 'components/Loadable';
  21. const LoadingComponent = Loadable(React.lazy(() => import('pages/extra-pages/LoadingComponent')));
  22. import titleBackgroundImg from 'assets/images/dashboard/gazette-bar.png'
  23. import {FormattedMessage, useIntl} from "react-intl";
  24. const BackgroundHead = {
  25. backgroundImage: `url(${titleBackgroundImg})`,
  26. width: '100%',
  27. height: '100%',
  28. backgroundSize: 'contain',
  29. backgroundRepeat: 'no-repeat',
  30. backgroundColor: '#0C489E',
  31. backgroundPosition: 'right'
  32. }
  33. // ==============================|| DASHBOARD - DEFAULT ||============================== //
  34. const Index = () => {
  35. const navigate = useNavigate()
  36. const location = useLocation();
  37. const intl = useIntl();
  38. const { locale } = intl;
  39. const [locationData, setLocationData] = React.useState({});
  40. const [paymentData, setPaymentData] = React.useState({});
  41. const [onReady, setOnReady] = React.useState(false);
  42. const [qrCodeTimeout, setqrCodeTimeout] = React.useState(false);
  43. const [paymentStatusCode, setPaymentStatusCode] = React.useState("");
  44. const [expiredQrcode, setExpiredQrcode] = React.useState(expiredQrcodeEN);
  45. const [responeData, setResponeDataData] = React.useState({});
  46. const [fpsTransctionData, setFpsTransctionData] = React.useState({});
  47. const [fpsmerchanttimeoutdatetime, setFpsmerchanttimeoutdatetime] = React.useState("");
  48. const [timeDownCount, setTimeDownCount] = React.useState(0);
  49. const [paymentId, setPaymentId] = React.useState("");
  50. const [fpsqrcodeurl, setFpsqrcodeurl] = React.useState("");
  51. const [fpsqrcodeurlPrd, setFpsqrcodeurlPrd] = React.useState("");
  52. const [fpsqrcodeurlFps, setFpsqrcodeurlFps] = React.useState("");
  53. const [browserType, setBrowserType] = React.useState("");
  54. const [sysEnv, setSysEnv] = React.useState("");
  55. const [isError, setIsError] = React.useState(false);
  56. const mobileBrowser = "Mobile";
  57. const desktopBrowser = "Desktop";
  58. const pasgPathPrd = 'https://fps.payapps.hkicl.com.hk'; //PRD
  59. const pasgPath = 'https://sim.fps.payapps.hkicl.com.hk'; //Testing
  60. // const loadPaymentUrl = "/api/payment/wallet/fps";
  61. // const cancelPaymentUrl = "/api/payment/cancelpayment";
  62. // const paymentStatusApi = "/api/payment/status/";
  63. // const payloadUrl = "/api/payment/wallet/fps/enquiryfpspayload/";
  64. // const receiverUrl = "/noti-api/payment/payment-notification";
  65. //timer
  66. const currentTimer = React.useRef();
  67. const [time, setTime] = React.useState(0);
  68. React.useEffect(() => {
  69. // console.log (location.state)
  70. if(Object.keys(location.state).length > 0){
  71. // console.log (location.state)
  72. setLocationData(location.state)
  73. setBrowserType(desktopBrowser)
  74. if (/Android|webOS|iPhone|iPad|iPod|Opera Mini/i.test(navigator.userAgent)) {
  75. // console.log('Mobile web browser');
  76. setBrowserType(mobileBrowser)
  77. // setFpsqrcodeurl(openPASGUrl)
  78. } else {
  79. // console.log('Desktop web browser');
  80. setBrowserType(desktopBrowser)
  81. }
  82. }
  83. }, []);
  84. React.useEffect(() => {
  85. // console.log (locationData)
  86. if (Object.keys(locationData).length > 0){
  87. setPaymentData(locationData)
  88. // loadForm();
  89. }
  90. }, [locationData]);
  91. React.useEffect(() => {
  92. // console.log (locationData)
  93. if (locale === 'zh-HK'){
  94. setExpiredQrcode(expiredQrcodeZH)
  95. } else if (locale === 'en'){
  96. setExpiredQrcode(expiredQrcodeEN)
  97. } else {
  98. setExpiredQrcode(expiredQrcodeCN)
  99. }
  100. }, [locale]);
  101. React.useEffect(() => {
  102. // console.log (paymentData)
  103. if (Object.keys(paymentData).length > 0){
  104. loadForm();
  105. }
  106. }, [paymentData]);
  107. React.useEffect(() => {
  108. // console.log(responeData)
  109. if(Object.keys(responeData).length > 0 && fpsmerchanttimeoutdatetime!=""){
  110. setFpsTransctionData(responeData)
  111. }
  112. }, [responeData]);
  113. React.useEffect(() => {
  114. // console.log(fpsTransctionData)
  115. if(Object.keys(fpsTransctionData).length > 0 ){
  116. setPaymentId(fpsTransctionData.paymentid)
  117. currentTimer.current = setInterval(() => {
  118. setTime((prevTime) => prevTime + 1);
  119. }, 1000);
  120. setOnReady(true);
  121. return () => clearInterval(currentTimer.current);
  122. }
  123. }, [fpsTransctionData]);
  124. const loadForm = () => {
  125. const webtoken = paymentData.webtoken;
  126. const transactionid = paymentData.transactionid;
  127. console.log(webtoken)
  128. console.log(transactionid)
  129. localStorage.removeItem("transactionid")
  130. localStorage.removeItem("webtoken")
  131. localStorage.setItem("transactionid", transactionid)
  132. localStorage.setItem("webtoken", webtoken)
  133. HttpUtils.post({
  134. url: UrlUtils.LOAD_PAYMENT_FPS_URL,
  135. params:{
  136. "transactionid": transactionid,
  137. "webtoken": webtoken,
  138. "paymentmethod":"04,BCFP,FPS",
  139. "order": {
  140. "totalamount":paymentData.amount,
  141. "currency":"HKD",
  142. "orderdetail":
  143. [
  144. {
  145. "itemid": "1",
  146. "qty":"1",
  147. "unitprice":paymentData.amount,
  148. "amount":paymentData.amount
  149. },
  150. ]
  151. },
  152. // "locale":"<locale>",
  153. // "eserviceid":"<eserviceid>"
  154. },
  155. onSuccess: function(responseData){
  156. try {
  157. /*
  158. {
  159. "paymentid": "<paymentid>",
  160. "paymentstatus": "<paymentstatus>",
  161. "fpsmerchanttimeoutdatetime": <fpsmerchanttimeoutdatetime>,
  162. "fpsqrcodeimgbase64": "<fpsqrcodeimgbase64>",
  163. "fpsqrcodeurl": "<fpsqrcodeurl>"
  164. }
  165. */
  166. if (!responseData || !responseData.fpsqrcodeurl) {
  167. throw new Error("Missing expected response data");
  168. }
  169. setResponeDataData(responseData)
  170. const timeoutdatetime = responseData.fpsmerchanttimeoutdatetime
  171. setSysEnv(responseData.sysEnv)
  172. const searchString = "[UTC]";
  173. let convertedDateString = "";
  174. if ( timeoutdatetime.toString().includes(searchString) ){
  175. convertedDateString = timeoutdatetime.replace("[UTC]", "");
  176. } else {
  177. convertedDateString = timeoutdatetime;
  178. }
  179. setFpsmerchanttimeoutdatetime(convertedDateString)
  180. const parsedUrl = new URL(responseData.fpsqrcodeurl);
  181. const fpsqrcodeurl = "https://"+window.location.hostname+'/payment'+parsedUrl.pathname;
  182. const fpsqrcodeurlwithFps = "https://"+"fps."+window.location.hostname+parsedUrl.pathname;
  183. console.log(parsedUrl)
  184. console.log(fpsqrcodeurl)
  185. console.log(fpsqrcodeurlwithFps)
  186. const openPASGUrl = pasgPath + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback='
  187. + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?&PAYMENT_ID='+localStorage.getItem("paymentId"));
  188. const openPASGUrlPrd = pasgPathPrd + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback='
  189. + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?&PAYMENT_ID='+localStorage.getItem("paymentId"));
  190. const openPASGUrlPrdFps = pasgPath + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback='
  191. + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?&PAYMENT_ID='+localStorage.getItem("paymentId"));
  192. // const openPASGUrl = pasgPath + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback='
  193. // + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?TRANSACTION_ID='+transactionid+"&WEB_TOKEN="+webtoken+"&PAYMENT_ID="+localStorage.getItem("paymentId"));
  194. // const openPASGUrlPrd = pasgPathPrd + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback='
  195. // + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?TRANSACTION_ID='+transactionid+"&WEB_TOKEN="+webtoken+"&PAYMENT_ID="+localStorage.getItem("paymentId"));
  196. // const openPASGUrlPrdFps = pasgPath + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback='
  197. // + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?TRANSACTION_ID='+transactionid+"&WEB_TOKEN="+webtoken+"&PAYMENT_ID="+localStorage.getItem("paymentId"));
  198. setFpsqrcodeurl(openPASGUrl)
  199. setFpsqrcodeurlPrd(openPASGUrlPrd)
  200. setFpsqrcodeurlFps(openPASGUrlPrdFps)
  201. } catch (e) {
  202. console.error("Error processing response:", e);
  203. setIsError(true);
  204. setOnReady(true);
  205. }
  206. },
  207. onFail: function(error){
  208. console.error("post onFail:", error);
  209. setIsError(true);
  210. setOnReady(true);
  211. },
  212. onError: function(error){
  213. console.error("post onError:", error);
  214. setIsError(true);
  215. setOnReady(true);
  216. }
  217. });
  218. //testing
  219. // const timeoutdatetime = "2024-11-18T07:04:35Z[UTC]"
  220. // const convertedDateString = timeoutdatetime.replace("[UTC]", "");
  221. // setFpsmerchanttimeoutdatetime(convertedDateString)
  222. // setPaymentId("C202310268000681")
  223. // const responseData=
  224. // {
  225. // "paymentid": "C202310268000681",
  226. // "paymentstatus": "INPR",
  227. // "fpsmerchanttimeoutdatetime": "2024-11-18T07:04:35Z[UTC]",
  228. // "fpsqrcodeimgbase64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAuyklEQVR4Xu3dfcy/d1nff2IAAAAASUVORK5CYII=",
  229. // "fpsqrcodeurl": "http://127.0.0.1:8080/api/payment/wallet/fps/enquiryfpspayload/vm.JKDDlTOavR3ASviSwUnS1Lw4-"
  230. // }
  231. // setResponeDataData(responseData)
  232. // const parsedUrl = new URL(responseData.fpsqrcodeurl);
  233. // const fpsqrcodeurl = "https://"+window.location.hostname+'/payment'+parsedUrl.pathname;
  234. // const fpsqrcodeurlwithFps = "https://"+"fps."+window.location.hostname+parsedUrl.pathname;
  235. // console.log(parsedUrl)
  236. // console.log(fpsqrcodeurl)
  237. // console.log(fpsqrcodeurlwithFps)
  238. // const openPASGUrl = pasgPath + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurl) + '&callback='
  239. // + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?TRANSACTION_ID='+transactionid+"&WEB_TOKEN="+webtoken+"&PAYMENT_ID="+localStorage.getItem("paymentId"));
  240. // const openPASGUrlPrd = pasgPathPrd + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurl) + '&callback='
  241. // + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?TRANSACTION_ID='+transactionid+"&WEB_TOKEN="+webtoken+"&PAYMENT_ID="+localStorage.getItem("paymentId"));
  242. // const openPASGUrlPrdFps = pasgPath + '?pay_req_obj=' + encodeURIComponent(fpsqrcodeurlwithFps) + '&callback='
  243. // + encodeURIComponent("https://"+window.location.hostname+ '/paymentPage/fps/fpscallback?TRANSACTION_ID='+transactionid+"&WEB_TOKEN="+webtoken+"&PAYMENT_ID="+localStorage.getItem("paymentId"));
  244. // setFpsqrcodeurl(openPASGUrl)
  245. // setFpsqrcodeurlPrd(openPASGUrlPrd)
  246. // setFpsqrcodeurlFps(openPASGUrlPrdFps)
  247. }
  248. const getPaymentStatus = () => {
  249. if(Object.keys(paymentData).length > 0){
  250. HttpUtils.post({
  251. url: UrlUtils.PAYMENT_STATUS_API,
  252. params:{
  253. "apprefid": paymentData.transactionid,
  254. // "webtoken": paymentData.webtoken,
  255. "transactionid":Number(paymentData.transactionid)
  256. },
  257. onSuccess: function(responseData){
  258. const paymentstatuscode = responseData.paymentdetail.result.paymentstatuscode;
  259. setPaymentStatusCode(paymentstatuscode)
  260. if (paymentstatuscode != "" && paymentstatuscode != "INPR" ){
  261. if (paymentstatuscode === 'APPR') {
  262. // const timestamp = Date.now();
  263. let page = '/paymentPage/fps/ackpage';
  264. let stateParams = { state: { transactionid: paymentData.transactionid} }
  265. navigate(page, stateParams);
  266. } else if (paymentstatuscode === 'CANC') {
  267. // const timestamp = Date.now();
  268. let page = '/paymentPage/fps/ackpage';
  269. let stateParams = { state: { transactionid: paymentData.transactionid} }
  270. navigate(page, stateParams);
  271. } else {
  272. // window.top.location.href = paymentPath + payment.config.errPagePath;
  273. alert("ERROR")
  274. }
  275. }
  276. },
  277. onError: function(){
  278. alert("ERROR")
  279. // cancelPayment()
  280. // clearInterval(currentTimer.current);
  281. }
  282. });
  283. }
  284. };
  285. React.useEffect(() => {
  286. const timeOutDate = new Date(fpsmerchanttimeoutdatetime);
  287. const currentTime = new Date;
  288. const timedowncount = Math.round((timeOutDate.getTime() - currentTime.getTime()) / 1000);
  289. // console.log(time)
  290. // console.log(timeOutDate)
  291. // console.log(currentTime)
  292. // console.log(timeOutDate.getTime()-currentTime.getTime())
  293. if (browserType === desktopBrowser){
  294. getPaymentStatus();
  295. if (timeOutDate.getTime()<currentTime.getTime()){
  296. // console.log("stop");
  297. clearInterval(currentTimer.current);
  298. setqrCodeTimeout(true)
  299. setTimeDownCount(0);
  300. // cancelPayment()
  301. }else{
  302. setTimeDownCount(timedowncount);
  303. }
  304. }
  305. },[time])
  306. const cancelPayment = ()=>{
  307. if (Object.keys(paymentData).length>0){
  308. getPaymentStatus()
  309. if (paymentStatusCode === "INPR" || isError || paymentStatusCode === ""){
  310. HttpUtils.post({
  311. url: UrlUtils.CANCEL_PAYMENT_URL,
  312. params:{
  313. "transactionid": paymentData.transactionid,
  314. "webtoken": paymentData.webtoken,
  315. "paymentid": fpsTransctionData.paymentid || localStorage.getItem("paymentId")
  316. },
  317. onSuccess: function(){
  318. navigate('/paymentPage/fps/ackpage');
  319. let page = '/paymentPage/fps/ackpage';
  320. let stateParams = { state: { transactionid: paymentData.transactionid} }
  321. navigate(page, stateParams);
  322. },
  323. onFail: function(){
  324. let page = '/paymentPage/fps/ackpage';
  325. let stateParams = { state: { transactionid: paymentData.transactionid} }
  326. navigate(page, stateParams);
  327. },
  328. onError: function(){
  329. let page = '/paymentPage/fps/ackpage';
  330. let stateParams = { state: { transactionid: paymentData.transactionid} }
  331. navigate(page, stateParams);
  332. }
  333. });
  334. }
  335. }
  336. }
  337. const mobliePayment = ()=>{
  338. window.location.assign(fpsqrcodeurl);
  339. }
  340. const mobliePaymentPrd = ()=>{
  341. window.location.assign(fpsqrcodeurlPrd);
  342. }
  343. const mobliePaymentFps = ()=>{
  344. window.location.assign(fpsqrcodeurlFps);
  345. }
  346. return (
  347. !onReady ?
  348. <Grid container sx={{ minHeight: '87vh', mb: 3 }} direction="column" justifyContent="center" alignItems="center">
  349. <Grid item>
  350. <LoadingComponent />
  351. </Grid>
  352. </Grid>
  353. :
  354. (
  355. <Grid container sx={{ minHeight: '110vh', backgroundColor: '#fff' }} direction="column" justifyContent="flex-start" alignItems="center" >
  356. <Grid item xs={12} width="100%">
  357. <div style={BackgroundHead} width="100%">
  358. <Stack direction="row" height='70px'>
  359. <Typography ml={15} color='#FFF' variant="h4" sx={{ pt: 2 }}>
  360. <FormattedMessage id="publicNoticePaymentFPSPay"/>
  361. </Typography>
  362. </Stack>
  363. </div>
  364. </Grid>
  365. {/*row 1*/}
  366. <Grid item xs={12} md={12} >
  367. <Grid container justifyContent="flex-start" alignItems="center" >
  368. <center>
  369. <Grid item xs={12} md={12} >
  370. <Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}>
  371. <FormattedMessage id="payAlert"/>
  372. <br /><br />
  373. <img src={FpsIcon} width="80" height="80" alt="FPS"></img>
  374. <br />
  375. <FormattedMessage id="payTotalDeatail"/>
  376. <br />
  377. {"HK$ " + currencyFormat(paymentData.amount)}
  378. </Typography>
  379. {isError ?
  380. <Typography variant="h3" color="error" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}>
  381. <FormattedMessage id="fpsPaymentErrorMsg"/>
  382. </Typography>
  383. :
  384. browserType==mobileBrowser?
  385. <Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}>
  386. {
  387. sysEnv=="prod"?
  388. <Button
  389. component="span"
  390. variant="contained"
  391. size="large"
  392. color="primary"
  393. onClick={()=>{
  394. mobliePaymentPrd();
  395. }}
  396. sx={{ m: 4 }}
  397. >請選擇支付程式付款</Button>
  398. :
  399. <>
  400. <Button
  401. component="span"
  402. variant="contained"
  403. size="large"
  404. color="primary"
  405. onClick={()=>{
  406. mobliePayment();
  407. }}
  408. sx={{ m: 4 }}
  409. >請選擇支付程式付款-Testing</Button>
  410. <Button
  411. component="span"
  412. variant="contained"
  413. size="large"
  414. color="primary"
  415. onClick={()=>{
  416. mobliePaymentFps();
  417. }}
  418. sx={{ m: 4 }}
  419. >請選擇支付程式付款-fps prefix</Button>
  420. </>
  421. }
  422. </Typography>
  423. :
  424. <Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}>
  425. <FormattedMessage id="fpsQrcodeTitle1"/>
  426. <br />
  427. {
  428. !qrCodeTimeout?
  429. <img src={fpsTransctionData.fpsqrcodeimgbase64} alt="QR Code"/>
  430. :<img src={expiredQrcode} alt="Expired QR Code"/>
  431. }
  432. <br />
  433. {"["+paymentId+"]"}
  434. <br/>
  435. {
  436. timeDownCount<=0?
  437. <FormattedMessage id="fpsQrcodeExpired"/>:
  438. <>
  439. <FormattedMessage id="fpsQrcodeTitle2"/>
  440. <br />
  441. <FormattedMessage id="fpsQrcodeTitle3"/>
  442. <br />
  443. <FormattedMessage id="fpsQrcodeTitle4"/>&nbsp;
  444. {timeDownCount}&nbsp;
  445. <FormattedMessage id="fpsQrcodeTitle5"/>
  446. </>
  447. }
  448. </Typography>
  449. }
  450. <Typography variant="h3" sx={{ ml: 8, mt: 4, mr: 8, textAlign: "center" }}>
  451. <Button
  452. component="span"
  453. variant="contained"
  454. size="large"
  455. color="error"
  456. onClick={()=>{
  457. cancelPayment();
  458. }}
  459. sx={{ m: 4 }}
  460. >
  461. <FormattedMessage id="payCancel"/>
  462. </Button>
  463. </Typography>
  464. </Grid>
  465. </center>
  466. </Grid>
  467. </Grid>
  468. {/*row 2*/}
  469. </Grid >
  470. )
  471. );
  472. };
  473. export default Index;