Authorize.Net沙箱环境订阅请求报invalid OTS token错误求助
我基于Authorize.Net实现订阅功能,当前处于沙箱环境。代码原本能成功创建10次订阅,但现在持续抛出invalid OTS token错误,麻烦帮忙查看代码并分析报错原因:
前端代码
//**the call to get opaque data */ const dataFromDispatch = (secureData) => { return new Promise((resolve, reject) => { if (!window.Accept || !window.Accept.dispatchData) { setError('Payment module not loaded. Please refresh.'); setLoading(false); return; } window.Accept.dispatchData(secureData, function (response) { if (response.messages.resultCode === 'Error') { console.log( 'Error in dispatchData:', response.messages.message[0].text, response.messages.message[0].code ); reject(new Error(response.messages.message[0].text)); } else { resolve(response.opaqueData); } }); }); }; const onSubmitOne = async (data) => { setError(null); setLoading(true); if (!scriptLoaded) { setError('Payment module not loaded. Please refresh.'); setLoading(false); return; } const authData = { clientKey: process.env.NEXT_PUBLIC_AUTHORIZE_CLIENT_KEY, // Replace with your sandbox key apiLoginID: process.env.NEXT_PUBLIC_AUTHORIZE_LOGIN_ID, }; console.log('AUTH DATA:', authData); const secureData = { authData, cardData1, }; let opaqueData; try { opaqueData = await dataFromDispatch({ authData: { clientKey: process.env.NEXT_PUBLIC_AUTHORIZE_CLIENT_KEY, apiLoginID: process.env.NEXT_PUBLIC_AUTHORIZE_LOGIN_ID, }, cardData: { cardNumber: data.cardNumber, month: extractMonth(data.expiryDate), // Format MM/YY year: extractYear(data.expiryDate), // Format MM/YY cardCode: data.cardCode, , }, }); } catch (error) { console.error('Error in dispatchData:', error); setError(error.message || 'Failed to get payment data.'); setLoading(false); return; } console.log('OPAQUE DATA:', opaqueData); try { const response = await axios.post('/api/subscribe', { opaqueData: opaqueData, firstName: data.firstname || 'John', lastName: data.lastname || 'Doe', }); console.log('Response:', response); if (response.status === 200) { router.push('/subscription-success'); } } catch (error) { console.error('Subscription error:', error); setError(error.response?.data?.error || 'Subscription failed.'); } finally { setLoading(false); } };
后端代码
export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } const { firstName, lastName, opaqueData } = req.body; console.log('Extracted opaqueData:', opaqueData); console.log('Extracted firstName:', firstName); console.log('Extracted lastName:', lastName); const dataDescriptor = opaqueData?.dataDescriptor; const dataValue = opaqueData?.dataValue; console.log('Data Descriptor:', dataDescriptor); console.log('Data Value:', dataValue); if (!dataValue || !dataDescriptor) { return res.status(400).json({ error: 'Missing required payment data' }); } const apiLoginId = process.env.AUTHORIZE_LOGIN_ID; const transactionKey = process.env.AUTHORIZE_TRANSACTION_KEY; const apiUrl = process.env.AUTHORIZE_API_URL; console.log('API Login ID:', apiLoginId); console.log('API URL:', apiUrl); console.log('Transaction Key:', transactionKey); // Create subscription request payload const subscriptionRequest = { ARBCreateSubscriptionRequest: { merchantAuthentication: { name: apiLoginId, transactionKey: transactionKey, }, refId: `SUB-${Date.now()}`, // Generate unique reference ID subscription: { name: 'Monthly Subscription', paymentSchedule: { interval: { length: '1', unit: 'months', }, startDate: new Date().toISOString().split('T')[0], // Today's date (YYYY-MM-DD) totalOccurrences: '9999', // 12 for 12 months,9999 for infinite amount trialOccurrences: '0', // No trial }, amount: '50.0', trialAmount: '0.0', payment: { opaqueData: { dataDescriptor: dataDescriptor, dataValue: dataValue, }, }, billTo: { firstName: firstName, lastName: lastName, }, }, }, }; try { const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json', }, body: JSON.stringify(subscriptionRequest), }); console.log('=== QUICK TEST ==='); console.log('Request size:', JSON.stringify(subscriptionRequest).length); // ✅ Add this check if (!response.ok) { console.error('HTTP Error:', response.status, response.statusText); const errorText = await response.text(); console.error('Error response:', errorText); return res.status(500).json({ error: `HTTP ${response.status}: ${response.statusText}`, }); } const result = await response.json(); console.log('Authorize.Net API response:', result); console.log( 'Authorize.Net API response Code:', result.messages.message?.[0]?.code ); console.log( 'Authorize.Net API response IN DETAIL:', result.messages.message?.[0]?.text ); const arbResponse = result.ARBCreateSubscriptionResponse; const messages = arbResponse?.messages; if (result.messages?.message[0].resultCode === 'Error') { return res.status(400).json({ error: messages.message?.[0]?.text || 'Authorize.Net error', }); } if (result.messages?.message[0].resultCode === 'Ok') { return res.status(200).json({ success: true, result: result, }); } } catch (err) { console.error('Authorize.Net API error:', err); return res.status(500).json({ error: 'Internal server error' }); } }
报错原因分析
- OTS令牌过期/重复使用:Authorize.Net生成的opaqueData(OTS令牌)是一次性且有效期极短的。如果前端生成令牌后延迟提交,或者重复提交同一令牌,就会触发该错误。
- 前端参数语法错误:前端cardData对象末尾多了一个多余逗号(
cardCode: data.cardCode, ,),可能导致dispatchData生成的opaqueData不合法,后端验证失败。 - 环境密钥不匹配:前后端使用的apiLoginID、clientKey、transactionKey需完全对应沙箱环境,混用生产密钥会导致令牌验证失败。
- API地址配置错误:沙箱环境的API地址应为
https://apitest.authorize.net/xml/v1/request.api,若配置为生产地址,令牌无法被正确验证。 - 令牌传输损坏:检查前端输出的opaqueData与后端接收的dataValue、dataDescriptor是否一致,传输过程中数据截断或篡改会导致验证失败。
修复建议
- 修复前端cardData的语法错误,删除多余逗号:
cardData: { cardNumber: data.cardNumber, month: extractMonth(data.expiryDate), year: extractYear(data.expiryDate), cardCode: data.cardCode }
- 确保opaqueData生成后立即提交到后端,避免令牌过期。
- 核对前后端所有密钥,确保均为沙箱环境的有效密钥。
- 确认后端
AUTHORIZE_API_URL配置为沙箱正确地址。 - 在前端增加opaqueData有效性校验,确保dataValue和dataDescriptor存在且不为空后再提交。
内容的提问来源于stack exchange,提问作者Aayman Khalid
相关产品推荐
相关产品推荐

