PAYATHON 2026

Square SDK v42.2.0 中 createPayment 为何是 undefined?

排障赵师傅

结论

square.paymentsApi 为 undefined,原因是代码混用了 Square Node.js SDK 的新旧接口。

在 square SDK v42.2.0 中,SquareClient 调用 Payments API 的方式是:

square.payments.create(...)

旧版写法:

square.paymentsApi.createPayment(...)

已经不适用于这个版本。初始化 SquareClient 时,参数名也从旧版客户端的 accessToken 改成了 token:

const square = new SquareClient({
  token: process.env.SQUARE_ACCESS_TOKEN,
});

这个错误与请求对象中的字段或前端生成的 card token 无关。程序还没有发送 HTTP 请求就已经报错,因为 square.paymentsApi 属性不存在。

原因:混用了两代 SDK 接口

旧版 Square Node.js SDK 通常使用 Client:

const client = new Client({
  accessToken: process.env.SQUARE_ACCESS_TOKEN,
});

const { result } = await client.paymentsApi.createPayment(request);

新版 SDK 使用 SquareClient,调用方式改成了:

const client = new SquareClient({
  token: process.env.SQUARE_ACCESS_TOKEN,
});

const response = await client.payments.create(request);

相关接口需要一并调整:

旧接口v42.2.0 接口
ClientSquareClient
accessTokentoken
paymentsApi.createPayment()payments.create()

如果只把 Client 改成 SquareClient,却仍然访问 paymentsApi,程序就会报错:

TypeError: Cannot read properties of undefined (reading 'createPayment')

v42.2.0 的正确调用方式

CommonJS 写法如下:

const { SquareClient, SquareEnvironment } = require('square');
const crypto = require('crypto');

const square = new SquareClient({
  token: process.env.SQUARE_ACCESS_TOKEN,
  environment: SquareEnvironment.Production,
});

async function createPayment(token, name) {
  const response = await square.payments.create({
    sourceId: token,
    idempotencyKey: crypto.randomUUID(),
    amountMoney: {
      amount: 10n,
      currency: 'USD',
    },
    locationId: process.env.SQUARE_LOCATION_ID,
    note: `${name} - Signup Payment`,
  });

  return response.payment;
}

使用 Sandbox 时,需要明确指定对应环境:

const square = new SquareClient({
  token: process.env.SQUARE_ACCESS_TOKEN,
  environment: SquareEnvironment.Sandbox,
});

访问令牌、Location ID 和环境必须匹配。Sandbox 凭据不能用于 Production,Production 凭据也不能用于 Sandbox。

修改后的处理逻辑

原有代码中的支付部分可以改成:

try {
  const idempotencyKey = crypto.randomUUID();

  const response = await square.payments.create({
    sourceId: token,
    idempotencyKey,
    amountMoney: {
      amount: 10n,
      currency: 'USD',
    },
    locationId: process.env.SQUARE_LOCATION_ID,
    note: `${name} - Signup Payment`,
  });

  const payment = response.payment;

  if (!payment || payment.status !== 'COMPLETED') {
    return res.status(400).json({
      error: 'Payment failed or incomplete',
      status: payment?.status,
    });
  }

  // 支付成功后继续写入 Google Sheets
} catch (error) {
  console.error('Payment error:', error);

  return res.status(500).json({
    error: error.message || 'Internal server error',
  });
}

旧版代码从 result 中读取返回值:

const { result } = await square.paymentsApi.createPayment(...);

新版调用可以直接从响应中读取 payment:

const response = await square.payments.create(...);
const payment = response.payment;

完整修正版示例

const {
  SquareClient,
  SquareEnvironment,
} = require('square');
const crypto = require('crypto');
const fetch = require('node-fetch');

const square = new SquareClient({
  token: process.env.SQUARE_ACCESS_TOKEN,
  environment: SquareEnvironment.Production,
});

const handler = async (req, res) => {
  const { name, email, phone, token } = req.body;

  if (!token) {
    return res.status(400).json({ error: 'Missing card token' });
  }

  const cleanedPhone = phone ? phone.replace(/\D/g, '') : '';

  if (!phone || cleanedPhone.length < 10) {
    return res.status(400).json({ error: 'Invalid phone number' });
  }

  const formatPhone = (raw) => {
    const digits = raw.replace(/\D/g, '');

    return digits.length === 10
      ? `+1 (${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`
      : raw;
  };

  const formattedPhone = formatPhone(phone);

  try {
    const idempotencyKey = crypto.randomUUID();

    const response = await square.payments.create({
      sourceId: token,
      idempotencyKey,
      amountMoney: {
        amount: 10n,
        currency: 'USD',
      },
      locationId: process.env.SQUARE_LOCATION_ID,
      note: `${name} - Signup Payment`,
    });

    const payment = response.payment;

    if (!payment || payment.status !== 'COMPLETED') {
      return res.status(400).json({
        error: 'Payment failed or incomplete',
        status: payment?.status,
      });
    }

    const sheetData = {
      name,
      email,
      phone: formattedPhone,
      token: process.env.GOOGLE_SHEET_KEY,
    };

    const sheetRes = await fetch(process.env.GOOGLE_SHEET_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(sheetData),
    });

    if (!sheetRes.ok) {
      return res.status(500).json({
        error: `Failed to save data to Google Sheets. Status: ${sheetRes.status}`,
      });
    }

    return res.status(200).json({
      message: 'Payment verified and data saved',
      paymentId: payment.id,
    });
  } catch (error) {
    console.error('Payment error:', error);

    return res.status(500).json({
      error: error.message || 'Internal server error',
    });
  }
};

module.exports = handler;

还需要注意的几个问题

金额应使用 bigint

v42.2.0 的金额字段需要按照 SDK 类型要求使用 bigint:

amountMoney: {
  amount: 10n,
  currency: 'USD',
}

10n 表示 10 美分,即 USD 0.10。收取 USD 10.00 时,应写成:

amount: 1000n

不要直接对含有 bigint 的对象执行 JSON.stringify(),否则 Node.js 会抛出序列化错误。向客户端返回支付信息时,可以只返回所需的字符串字段,例如 payment.id 和 payment.status。

幂等键应与一次业务支付绑定

crypto.randomUUID() 可以生成合法的幂等键。同一笔支付因网络问题而重试时,需要复用原来的 idempotencyKey。如果每次重试都生成新值,Square 会将其视为不同的支付请求,也就无法防止重复扣款。

Google Sheets 写入失败不等于支付失败

当前流程会先完成支付,再将数据写入 Google Sheets。如果写入失败,接口会返回 500,但支付可能已经成功。客户端重新提交时,如果使用新的幂等键,就可能再次扣款。

可以保存支付记录及其幂等键,并把 Google Sheets 写入失败交给后续补偿任务处理。

备注:内容仅供参考。