|
@@ -2,12 +2,14 @@ package com.ruoyi.app.omgpay;
|
|
|
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
|
|
|
|
+import java.net.URI;
|
|
|
import java.net.URLEncoder;
|
|
import java.net.URLEncoder;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
|
|
|
|
/** Renders a fixed, escaped page that opens the configured App route and keeps a manual fallback button. */
|
|
/** Renders a fixed, escaped page that opens the configured App route and keeps a manual fallback button. */
|
|
|
@Component
|
|
@Component
|
|
|
public class OmgPaymentReturnPageRenderer {
|
|
public class OmgPaymentReturnPageRenderer {
|
|
|
|
|
+ private static final String UNI_WEB_VIEW_BRIDGE = "/static/omg/uni.webview.1.5.8.js";
|
|
|
private final OmgPaymentProperties properties;
|
|
private final OmgPaymentProperties properties;
|
|
|
|
|
|
|
|
public OmgPaymentReturnPageRenderer(OmgPaymentProperties properties) {
|
|
public OmgPaymentReturnPageRenderer(OmgPaymentProperties properties) {
|
|
@@ -19,9 +21,13 @@ public class OmgPaymentReturnPageRenderer {
|
|
|
if (appLink == null) {
|
|
if (appLink == null) {
|
|
|
return error(503, "App 返回網址尚未設定");
|
|
return error(503, "App 返回網址尚未設定");
|
|
|
}
|
|
}
|
|
|
|
|
+ String appRoute = buildAppRoute(appLink);
|
|
|
String escapedLink = escapeHtml(appLink);
|
|
String escapedLink = escapeHtml(appLink);
|
|
|
|
|
+ String escapedRoute = escapeHtml(appRoute);
|
|
|
String html = page("正在返回 App", "請稍候,App 開啟後會確認最終付款狀態。",
|
|
String html = page("正在返回 App", "請稍候,App 開啟後會確認最終付款狀態。",
|
|
|
- "<a id=\"open-app\" href=\"" + escapedLink + "\">返回 App</a>"
|
|
|
|
|
|
|
+ "<a id=\"open-app\" href=\"" + escapedLink + "\" data-app-route=\""
|
|
|
|
|
+ + escapedRoute + "\">返回 App</a>"
|
|
|
|
|
+ + "<script src=\"" + UNI_WEB_VIEW_BRIDGE + "\"></script>"
|
|
|
+ diagnosticScript());
|
|
+ diagnosticScript());
|
|
|
return new OmgPaymentReturnPage(200, html);
|
|
return new OmgPaymentReturnPage(200, html);
|
|
|
}
|
|
}
|
|
@@ -39,6 +45,11 @@ public class OmgPaymentReturnPageRenderer {
|
|
|
return base + "?ddId=" + encode(orderId);
|
|
return base + "?ddId=" + encode(orderId);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private static String buildAppRoute(String appLink) {
|
|
|
|
|
+ URI uri = URI.create(appLink);
|
|
|
|
|
+ return "/" + uri.getHost() + uri.getPath() + "?" + uri.getRawQuery();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private static String page(String title, String message, String action) {
|
|
private static String page(String title, String message, String action) {
|
|
|
return "<!doctype html><html lang=\"zh-Hant-TW\"><head><meta charset=\"utf-8\">"
|
|
return "<!doctype html><html lang=\"zh-Hant-TW\"><head><meta charset=\"utf-8\">"
|
|
|
+ "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
|
|
+ "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
|
|
@@ -52,16 +63,19 @@ public class OmgPaymentReturnPageRenderer {
|
|
|
|
|
|
|
|
private static String diagnosticScript() {
|
|
private static String diagnosticScript() {
|
|
|
return "<script>(function(){'use strict';"
|
|
return "<script>(function(){'use strict';"
|
|
|
- + "var PREFIX='[OMG-RETURN]',startedAt=Date.now(),link=document.getElementById('open-app'),target=link.href;"
|
|
|
|
|
|
|
+ + "var PREFIX='[OMG-RETURN]',startedAt=Date.now(),link=document.getElementById('open-app'),"
|
|
|
|
|
+ + "target=link.href,appRoute=link.getAttribute('data-app-route'),handoffObserved=false,bridgeStarted=false,schemeStarted=false;"
|
|
|
+ "function targetSummary(value){try{var parsed=new URL(value);return {protocol:parsed.protocol,host:parsed.host,pathname:parsed.pathname};}"
|
|
+ "function targetSummary(value){try{var parsed=new URL(value);return {protocol:parsed.protocol,host:parsed.host,pathname:parsed.pathname};}"
|
|
|
+ "catch(error){return {parseError:true};}}"
|
|
+ "catch(error){return {parseError:true};}}"
|
|
|
+ "function orderRef(value){try{var id=new URL(value).searchParams.get('ddId')||'';return id?'***'+id.slice(-4):'';}"
|
|
+ "function orderRef(value){try{var id=new URL(value).searchParams.get('ddId')||'';return id?'***'+id.slice(-4):'';}"
|
|
|
+ "catch(error){return '';}}"
|
|
+ "catch(error){return '';}}"
|
|
|
|
|
+ + "function routePath(value){return String(value||'').split('?')[0];}"
|
|
|
+ "function snapshot(details){var data={elapsedMs:Date.now()-startedAt,readyState:document.readyState,"
|
|
+ "function snapshot(details){var data={elapsedMs:Date.now()-startedAt,readyState:document.readyState,"
|
|
|
+ "visibilityState:document.visibilityState,hidden:document.hidden,hasFocus:document.hasFocus()};"
|
|
+ "visibilityState:document.visibilityState,hidden:document.hidden,hasFocus:document.hasFocus()};"
|
|
|
+ "if(details){Object.keys(details).forEach(function(key){data[key]=details[key];});}return data;}"
|
|
+ "if(details){Object.keys(details).forEach(function(key){data[key]=details[key];});}return data;}"
|
|
|
- + "function log(event,details){console.info(PREFIX,event,snapshot(details));}"
|
|
|
|
|
- + "function lifecycle(event){log(event.type,{persisted:event.persisted===true});}"
|
|
|
|
|
|
|
+ + "function log(event,details){console.info(PREFIX+' '+event+' '+JSON.stringify(snapshot(details)));}"
|
|
|
|
|
+ + "function lifecycle(event){if(event.type==='pagehide'||event.type==='beforeunload'||document.hidden){handoffObserved=true;}"
|
|
|
|
|
+ + "log(event.type,{persisted:event.persisted===true});}"
|
|
|
+ "document.addEventListener('visibilitychange',function(event){lifecycle(event);});"
|
|
+ "document.addEventListener('visibilitychange',function(event){lifecycle(event);});"
|
|
|
+ "window.addEventListener('pagehide',lifecycle);window.addEventListener('pageshow',lifecycle);"
|
|
+ "window.addEventListener('pagehide',lifecycle);window.addEventListener('pageshow',lifecycle);"
|
|
|
+ "window.addEventListener('blur',lifecycle);window.addEventListener('focus',lifecycle);"
|
|
+ "window.addEventListener('blur',lifecycle);window.addEventListener('focus',lifecycle);"
|
|
@@ -69,16 +83,35 @@ public class OmgPaymentReturnPageRenderer {
|
|
|
+ "window.addEventListener('error',function(event){log('js_error',{line:event.lineno||0,column:event.colno||0});});"
|
|
+ "window.addEventListener('error',function(event){log('js_error',{line:event.lineno||0,column:event.colno||0});});"
|
|
|
+ "window.addEventListener('unhandledrejection',function(event){var reason=event.reason;"
|
|
+ "window.addEventListener('unhandledrejection',function(event){var reason=event.reason;"
|
|
|
+ "log('unhandledrejection',{reasonType:typeof reason,reasonName:reason&&reason.name?String(reason.name):''});});"
|
|
+ "log('unhandledrejection',{reasonType:typeof reason,reasonName:reason&&reason.name?String(reason.name):''});});"
|
|
|
|
|
+ + "function bridgeAvailable(){return !!(window.uni&&uni.webView&&uni.webView.getEnv&&uni.webView.redirectTo);}"
|
|
|
|
|
+ + "function schemeFallback(reason){if(handoffObserved||schemeStarted){return;}schemeStarted=true;"
|
|
|
|
|
+ + "log('scheme_fallback_start',{reason:reason,target:targetSummary(target),orderRef:orderRef(target)});"
|
|
|
|
|
+ + "try{window.location.replace(target);}catch(error){log('scheme_fallback_exception',{name:error&&error.name?String(error.name):'Error'});}}"
|
|
|
|
|
+ + "function bridgeRedirect(trigger,force){if(handoffObserved||(bridgeStarted&&!force)||!bridgeAvailable()){return false;}"
|
|
|
|
|
+ + "bridgeStarted=true;var envResolved=false;var envTimer=setTimeout(function(){if(!envResolved){"
|
|
|
|
|
+ + "log('bridge_environment_timeout',{trigger:trigger});schemeFallback('bridge_environment_timeout');}},500);"
|
|
|
|
|
+ + "try{uni.webView.getEnv(function(env){envResolved=true;clearTimeout(envTimer);env=env||{};"
|
|
|
|
|
+ + "log('bridge_environment',{trigger:trigger,plus:env.plus===true,nvue:env.nvue===true,uvue:env.uvue===true,h5:env.h5===true});"
|
|
|
|
|
+ + "if(!(env.plus||env.nvue||env.uvue)){schemeFallback('bridge_not_app');return;}"
|
|
|
|
|
+ + "log('bridge_redirect_start',{trigger:trigger,routePath:routePath(appRoute),orderRef:orderRef(target)});"
|
|
|
|
|
+ + "try{uni.webView.redirectTo({url:appRoute});setTimeout(function(){if(!handoffObserved){"
|
|
|
|
|
+ + "schemeFallback('bridge_no_handoff');}},1200);}catch(error){log('bridge_redirect_exception',"
|
|
|
|
|
+ + "{name:error&&error.name?String(error.name):'Error'});schemeFallback('bridge_exception');}});}"
|
|
|
|
|
+ + "catch(error){envResolved=true;clearTimeout(envTimer);log('bridge_environment_exception',"
|
|
|
|
|
+ + "{name:error&&error.name?String(error.name):'Error'});schemeFallback('bridge_environment_exception');}return true;}"
|
|
|
+ "link.addEventListener('touchstart',function(event){log('manual_open_touch',{isTrusted:event.isTrusted===true,"
|
|
+ "link.addEventListener('touchstart',function(event){log('manual_open_touch',{isTrusted:event.isTrusted===true,"
|
|
|
+ "target:targetSummary(target),orderRef:orderRef(target)});},{passive:true});"
|
|
+ "target:targetSummary(target),orderRef:orderRef(target)});},{passive:true});"
|
|
|
+ "link.addEventListener('click',function(event){log('manual_open_click',{isTrusted:event.isTrusted===true,"
|
|
+ "link.addEventListener('click',function(event){log('manual_open_click',{isTrusted:event.isTrusted===true,"
|
|
|
- + "target:targetSummary(target),orderRef:orderRef(target)});});"
|
|
|
|
|
|
|
+ + "target:targetSummary(target),orderRef:orderRef(target)});if(bridgeAvailable()){event.preventDefault();"
|
|
|
|
|
+ + "schemeStarted=false;bridgeRedirect('manual',true);}});"
|
|
|
|
|
+ + "document.addEventListener('UniAppJSBridgeReady',function(){log('bridge_ready',{});bridgeRedirect('ready',false);});"
|
|
|
+ "log('page_loaded',{userAgent:navigator.userAgent,platform:navigator.platform||'',language:navigator.language||'',"
|
|
+ "log('page_loaded',{userAgent:navigator.userAgent,platform:navigator.platform||'',language:navigator.language||'',"
|
|
|
+ "page:{origin:window.location.origin,pathname:window.location.pathname},target:targetSummary(target),orderRef:orderRef(target)});"
|
|
+ "page:{origin:window.location.origin,pathname:window.location.pathname},target:targetSummary(target),orderRef:orderRef(target)});"
|
|
|
+ "[250,1000,3000].forEach(function(delay){setTimeout(function(){log('auto_open_observation',{delayMs:delay,"
|
|
+ "[250,1000,3000].forEach(function(delay){setTimeout(function(){log('auto_open_observation',{delayMs:delay,"
|
|
|
+ "stillVisible:!document.hidden,target:targetSummary(target),orderRef:orderRef(target)});},delay);});"
|
|
+ "stillVisible:!document.hidden,target:targetSummary(target),orderRef:orderRef(target)});},delay);});"
|
|
|
- + "log('auto_open_start',{target:targetSummary(target),orderRef:orderRef(target)});"
|
|
|
|
|
- + "try{window.location.replace(target);}catch(error){log('auto_open_exception',{name:error&&error.name?String(error.name):'Error'});}"
|
|
|
|
|
|
|
+ + "log('auto_open_start',{target:targetSummary(target),routePath:routePath(appRoute),orderRef:orderRef(target)});"
|
|
|
|
|
+ + "if(window.UniAppJSBridge){setTimeout(function(){bridgeRedirect('already_ready',false);},0);}"
|
|
|
|
|
+ + "setTimeout(function(){if(!bridgeStarted){schemeFallback('bridge_ready_timeout');}},1000);"
|
|
|
+ "})();</script>";
|
|
+ "})();</script>";
|
|
|
}
|
|
}
|
|
|
|
|
|