You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Authorize.Net沙箱环境订阅请求报invalid OTS token错误求助

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是否一致,传输过程中数据截断或篡改会导致验证失败。

修复建议

  1. 修复前端cardData的语法错误,删除多余逗号:
cardData: {
  cardNumber: data.cardNumber,
  month: extractMonth(data.expiryDate),
  year: extractYear(data.expiryDate),
  cardCode: data.cardCode
}
  1. 确保opaqueData生成后立即提交到后端,避免令牌过期。
  2. 核对前后端所有密钥,确保均为沙箱环境的有效密钥。
  3. 确认后端AUTHORIZE_API_URL配置为沙箱正确地址。
  4. 在前端增加opaqueData有效性校验,确保dataValue和dataDescriptor存在且不为空后再提交。

内容的提问来源于stack exchange,提问作者Aayman Khalid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 14:14:57