如何从 Samsung Tizen TV browser 回调 app?
结论
不能把 Samsung Tizen TV Browser 当成可以直接回调 Web App 的原生 WebView。浏览器和 Tizen Web App 通常运行在相互隔离的上下文中,第三方支付页还会受到同源策略限制。App 因此无法直接读取支付页面的状态,也不能稳定接收 JavaScript 回调。
更稳妥的方案是:
- App 向业务服务端创建支付订单,获取唯一的
orderId。 - 支付页面携带该订单号。
- 支付平台通过服务端 webhook 或 notify URL 通知业务服务端。
- Tizen App 通过轮询或 WebSocket 查询订单状态。
- 如果支付页确实位于 App 创建的子窗口或 iframe 中,可以在页面回跳后用
postMessage()辅助通知 App,但不能把它作为判断支付结果的唯一依据。
推荐架构
Tizen Web App
│
├── 创建订单 ──────────────> 业务服务端
│ │
│ ├── 调用支付 API
│ │
│ <── 支付平台 webhook
│
└── 轮询或 WebSocket ──────> 查询最终支付状态
支付是否成功,必须由服务端根据支付平台的签名通知或订单查询结果来确认。浏览器跳转参数只能用于改善交互,不能作为可信的支付凭证。
实现步骤
1. 服务端创建支付订单
App 应先调用自己的服务端,不要在前端保存支付平台密钥或生成签名。
async function createPayment() {
const response = await fetch('https://api.example.com/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
productId: 'premium-plan'
})
});
if (!response.ok) {
throw new Error('Failed to create payment');
}
return response.json();
}
服务端可以返回以下内容:
{
"orderId": "ORDER_20260901_001",
"paymentUrl": "https://payment.example.com/checkout/abc123",
"expiresAt": "2026-09-01T12:30:00Z"
}
可以把 paymentUrl 显示为二维码,让用户通过手机完成支付。如果目标电视型号和支付平台支持,也可以尝试在新窗口中打开支付页面。
const payment = await createPayment();
const paymentWindow = window.open(
payment.paymentUrl,
'payment',
'width=960,height=540'
);
if (!paymentWindow) {
// 新窗口不受支持时,改为显示二维码或提示用户使用手机支付。
}
不同 Samsung TV 型号、Tizen 版本和应用运行方式,对弹窗、外部导航及窗口控制的支持可能不同。上线前需要在目标真机上验证。
2. 支付平台通知业务服务端
在支付平台后台配置 webhook、notify URL 或类似的异步通知地址:
POST https://api.example.com/payment-callback
服务端收到通知后,应执行以下操作:
- 验证支付平台签名。
- 核对商户号、订单号、金额和币种。
- 避免重复处理同一条通知。
- 必要时主动调用支付平台的订单查询 API。
- 将订单状态更新为
PAID、FAILED或其他业务状态。
不能只根据回跳 URL 中的 status=success 判断支付成功。
3. Tizen App 轮询订单状态
轮询的兼容性较好,不依赖浏览器能否唤醒 App。
async function waitForPayment(orderId) {
const intervalMs = 3000;
const timeoutMs = 10 * 60 * 1000;
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
const response = await fetch(
`https://api.example.com/payments/${encodeURIComponent(orderId)}`,
{
method: 'GET',
cache: 'no-store'
}
);
if (!response.ok) {
throw new Error('Failed to query payment status');
}
const result = await response.json();
if (result.status === 'PAID') {
return result;
}
if (result.status === 'FAILED' || result.status === 'CANCELLED') {
throw new Error(`Payment ended with status: ${result.status}`);
}
await new Promise(resolve => setTimeout(resolve, intervalMs));
}
throw new Error('Payment status check timed out');
}
调用方式:
const payment = await createPayment();
try {
const result = await waitForPayment(payment.orderId);
showPaymentSuccess(result);
} catch (error) {
showPaymentError(error.message);
}
如果需要更及时地更新状态,可以使用 WebSocket 或 Server-Sent Events,同时保留订单查询接口,以便在连接中断后恢复查询。
同一窗口链路下使用 postMessage()
如果支付页面由 window.open() 打开,并且支付完成后可以重定向到你控制的 HTTPS 页面,例如:
https://app.example.com/payment/return?orderId=ORDER_20260901_001
回跳页可以向打开它的页面发送通知:
<script>
const params = new URLSearchParams(window.location.search);
const orderId = params.get('orderId');
if (window.opener) {
window.opener.postMessage(
{
type: 'PAYMENT_RETURNED',
orderId
},
'https://app.example.com'
);
}
window.close();
</script>
App 侧接收消息:
window.addEventListener('message', async event => {
if (event.origin !== 'https://app.example.com') {
return;
}
const message = event.data;
if (
!message ||
message.type !== 'PAYMENT_RETURNED' ||
typeof message.orderId !== 'string'
) {
return;
}
// postMessage 只说明页面已回跳,最终状态仍从服务端查询。
const response = await fetch(
`https://api.example.com/payments/${encodeURIComponent(message.orderId)}`,
{ cache: 'no-store' }
);
const payment = await response.json();
if (payment.status === 'PAID') {
showPaymentSuccess(payment);
}
});
这种方式需要满足以下条件:
- 支付平台允许配置回跳地址。
- 回跳发生在 App 打开的同一个子窗口中。
- 浏览器没有清除或隔离
window.opener。 - Tizen TV 的窗口实现支持
postMessage()和window.close()。 targetOrigin和event.origin必须使用明确的 HTTPS Origin,不能写成*。
如果支付流程在独立的 Samsung Browser 中完成,浏览器进程和 App 之间没有 window.opener 关系,这种方案通常无法生效。
iframe 场景
跨域 iframe 受浏览器同源策略限制。父页面不能读取第三方支付页的 DOM、URL 或 JavaScript 变量。只有以下两种情况可以通信:
- 支付平台明确提供 iframe SDK 和事件回调。
- 支付完成后,iframe 跳转到你控制的页面,再由该页面调用
window.parent.postMessage()。
window.parent.postMessage(
{
type: 'PAYMENT_RETURNED',
orderId: 'ORDER_20260901_001'
},
'https://app.example.com'
);
许多支付页面还会通过 X-Frame-Options 或 CSP 的 frame-ancestors 禁止 iframe 嵌入,因此不能预设第三方支付页一定能在电视 App 中加载。
不建议依赖自定义 URL Scheme
理论上可以使用自定义 URI 或 Tizen Application Control 唤起应用,但能否用于 Samsung TV、目标 Tizen 版本和普通 Browser,需要根据对应型号的官方能力并通过真机测试确认。第三方支付平台也不一定接受非 HTTP/HTTPS 的回跳地址。
即使自定义 URI 能够成功唤起 App,其中携带的参数也不能直接用于判断支付成功。App 被唤起后,仍需向服务端查询订单状态。
注意事项
- 优先考虑让电视显示二维码,由用户在手机上完成支付。相比使用电视遥控器操作支付流程,这种方式通常更稳定。
- 支付密钥、签名密钥和 webhook 验签逻辑只能放在服务端。
- 每次支付都应生成不可预测的订单号,并与当前用户和商品绑定。
- webhook 必须支持幂等处理,因为支付平台可能重复发送通知。
- 轮询需要设置间隔和超时时间,并在页面退出时清理,避免无限请求。
- App 重新启动后,应能通过尚未完成的
orderId恢复订单查询。 postMessage()、回跳页和深度链接只用于界面通知,最终结果以服务端订单状态为准。- 上线前需要在实际目标电视型号上测试弹窗、焦点切换、返回键、网络中断,以及支付完成后 App 被挂起或重启等情况。
备注:内容仅供参考。
