基于Square API集成Cash App时的ontokenization事件触发异常问题
Cash App Pay集成:解决ontokenization提前触发的问题
问题背景
我通过Square API SDK完成Cash App Payment集成,使用Javascript Web SDK与PHP SDK,移动端应用通过WebView打开Cash App支付页面,当前流程:
- Web SDK生成Cash App Payment按钮
- 点击按钮跳转至Cash App页面,显示「Open Cash App」和「Get Cash App」按钮
- 点击「Open Cash App」打开Cash App支付界面
- 问题:跳转至Cash App支付界面时,
ontokenization事件立即触发,获取到status=OK及token,即使用户未点击支付界面的「OK」按钮或直接返回,事件仍会触发。预期逻辑是用户点击「OK」确认支付后,再触发事件并处理支付。
核心原因
Square Cash App Pay的Web SDK中,ontokenization事件仅代表成功生成支付源(sourceId),而非用户完成支付授权。在移动端WebView场景下,跳转至Cash App后SDK会提前完成token生成,但用户是否确认支付需要通过redirectURL回调来确认。
解决方案
调整逻辑,将支付处理从ontokenization事件中剥离,改为:
- 在
ontokenization事件中仅存储token,不立即发起支付请求 - 当用户从Cash App跳转回WebView时,再调用PHP SDK创建支付并验证状态
修改后的代码片段
const appId = 'MYAPPID'; const locationId = 'MY LOCATION ID'; function buildPaymentRequest(payments) { const paymentRequest = payments.paymentRequest({ countryCode: 'US', currencyCode: 'USD', total: { amount: '10', label: 'Total', }, }); return paymentRequest; } async function initializeCashApp(payments) { const paymentRequest = buildPaymentRequest(payments); const cashAppPay = await payments.cashAppPay(paymentRequest, { redirectURL: window.location.href, referenceId: 'my-website-00000001' }); const buttonOptions = { shape: 'semiround', width: 'full', }; await cashAppPay.attach('#cash-app-pay', buttonOptions); return cashAppPay; } async function createPayment(token, resData) { const body = JSON.stringify({ locationId, sourceId: token, temp_order_id: '123' }); const paymentResponse = await fetch('/web/payment/cash-app-payment', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body, }); if (paymentResponse.ok) { return paymentResponse.json(); } const errorBody = await paymentResponse.text(); throw new Error(errorBody); } async function paymentFail(tokenResponse) { const body = JSON.stringify({ locationId, temp_order_id: '123', tokenResponse: tokenResponse }); const paymentResponse = await fetch('/web/payment/cash-app-payment-fail', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body, }); if (paymentResponse.ok) { return paymentResponse.json(); } const errorBody = await paymentResponse.text(); throw new Error(errorBody); } function redirectToPaymentResults(status, errorMessage) { let redirectUrl = ''; if (status === 'SUCCESS') { redirectUrl = 'api/payment/close?action=success'; } else { redirectUrl = `api/payment/close?action=error&message=${encodeURIComponent(errorMessage)}`; } window.location.href = redirectUrl; } // 检查是否从Cash App跳转回来,处理支付 async function handlePostRedirectPayment() { const storedToken = sessionStorage.getItem('cashAppPayToken'); if (storedToken) { try { const paymentResults = await createPayment(storedToken); if (paymentResults.status === 'SUCCESS') { redirectToPaymentResults('SUCCESS', ''); } else { redirectToPaymentResults('FAILURE', paymentResults.message || '支付失败'); } } catch (error) { await paymentFail({ tokenResult: { status: 'ERROR', errors: [error.message] } }); redirectToPaymentResults('FAILURE', error.message); } finally { sessionStorage.removeItem('cashAppPayToken'); } } } document.addEventListener('DOMContentLoaded', async function () { if (!window.Square) { throw new Error('Square.js failed to load properly'); } // 先处理从Cash App返回后的支付逻辑 await handlePostRedirectPayment(); let payments; try { payments = window.Square.payments(appId, locationId); } catch (e) { const statusContainer = document.getElementById('payment-status-container'); statusContainer.className = 'missing-credentials'; statusContainer.style.visibility = 'visible'; console.log(e.message); return; } let cashAppPay; try { cashAppPay = await initializeCashApp(payments); } catch (e) { console.error('Initializing Cash App Pay failed', e); return; } cashAppPay.addEventListener('ontokenization', async function ({ detail }) { const tokenResult = detail.tokenResult; if (tokenResult.status === 'OK') { // 仅存储token,等待用户从Cash App返回后处理 sessionStorage.setItem('cashAppPayToken', tokenResult.token); } else { let errorMessage = `Tokenization failed with status: ${tokenResult.status}`; if (tokenResult.errors) { errorMessage += ` and errors: ${JSON.stringify(tokenResult.errors)}`; } await paymentFail(detail); redirectToPaymentResults('FAILURE', errorMessage); } }); });
关键修改点
- 新增
handlePostRedirectPayment函数:页面加载时检查是否有存储的token,若有则发起支付请求,确保只有用户从Cash App返回后才处理支付。 - 调整
ontokenization事件逻辑:仅在token生成成功时将其存入sessionStorage,不立即调用支付接口;token生成失败时直接处理错误。 - 修复
redirectToPaymentResults的URL编码:对错误消息进行encodeURIComponent处理,避免URL参数异常。 - 移除原事件中提前调用的
paymentFail:该调用不符合逻辑,仅在明确失败场景下调用。
后端PHP SDK补充说明
在PHP后端处理支付时,需调用Square的CreatePayment接口,并检查返回的支付状态:
- 若支付状态为
COMPLETED,则标记订单成功 - 若为
FAILED或其他状态,则处理失败逻辑,避免提前确认订单
内容的提问来源于stack exchange,提问作者Pranav Gandhi
相关产品推荐
相关产品推荐

