部署在Google Cloud Run的Node.js应用调用Square API遇问题求助
问题描述
在使用Square API创建客户时,前端部署在https://buzzysweets.com,后端部署在Google Cloud Run,发起POST请求时出现两个核心错误:
- 504网关超时:向
https://backend-437525971388.us-central1.run.app/customer的POST请求返回504错误 - CORS错误:跨域请求被拦截,目标资源缺少
Access-Control-Allow-Origin响应头
已验证信息
- 生产环境的应用ID、位置ID、访问令牌、CDN配置均确认正确
- 开发环境表现:
- 沙盒凭证+CDN:可正常创建客户、订单、支付
- 生产凭证+CDN:可正常创建客户、订单
代码片段
前端(React)代码
import React, { useEffect, useState } from 'react'; import { selectCart } from '../../features/cart/cartSlice'; import { useSelector } from 'react-redux'; const PaymentForm = () => { const appId = process.env.REACT_APP_YOUR_SQUARE_APPLICATION_ID; const locationId = process.env.REACT_APP_YOUR_SQUARE_LOCATION_ID; const { customerInfo } = useSelector(selectCart); const [card, setCard] = useState(null); // Load Square Web Payments SDK useEffect(() => { const loadSquare = async () => { if (!window.Square) { return; } const payments = window.Square.payments(appId, locationId); try { const card = await payments.card(); await card.attach('#card-container'); setCard(card); } catch (err) { console.error('Error loading Square SDK', err); } }; loadSquare(); }, []); const handlePayment = async (e) => { e.preventDefault(); if (!card) return; try { const tokenResult = await card.tokenize(); if (tokenResult.status === 'OK') { const customerResults = await createCustomer(tokenResult.token, customerInfo); } } catch (err) { console.error('Payment error:', err); } }; const createCustomer = async (token, customerInfo) => { const bodyParameters = { address: { address: customerInfo.address, country: customerInfo.country, firstName: customerInfo.firstName, lastName: customerInfo.lastName, zipCode: customerInfo.zipcode, }, givenName: customerInfo.firstName, familyName: customerInfo.lastName, emailAddress: customerInfo.emailAddress, idempotency_key: window.crypto.randomUUID(), }; const body = JSON.stringify(bodyParameters); try { const response = await fetch(`${process.env.REACT_APP_API_URL_BACK}/customer`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body, }); const result = await response.json(); if (!response.ok) { throw new Error(`Failed to create customer: ${result.errors[0].detail}`); } return result; } catch (err) { console.error('Error creating customer:', err); } }; };
后端(Node.js)代码
server.js
const { ApiError, client: square } = require('./server/square'); const { json, send, router, post } = require('micro'); const corsMiddleware = require('micro-cors')({ origin: process.env.REACT_APP_API_URL_FRONT, allowMethods: ['POST', 'GET'], allowedHeaders: ['Content-Type', 'Authorization'], }); const retry = require('p-retry'); const logger = console; async function createCustomer(req, res) { const payload = await json(req); await retry(async (bail, attempt) => { try { const customerReq = { address: { addressLine1: payload.address.address, firstName: payload.address.firstName, lastName: payload.address.lastName, country: payload.address.country, postalCode: payload.address.zipCode, }, emailAddress: payload.emailAddress, idempotencyKey: payload.idempotency_key, familyName: payload.familyName, }; console.log('customerReq:', customerReq) // 此日志在Cloud Run中正常输出 const { result, statusCode } = await square.customersApi.createCustomer(customerReq); console.log('customer returned result:', result); // Cloud Run中无此日志输出 send(res, statusCode, { success: true, customer: { id: result.customer.id, status: result.customer.status, }, }); } catch (ex) { if (ex instanceof ApiError) { logger.error("API Error:", ex.errors); bail(ex); } else { logger.error(`Unexpected error: ${ex}`); throw ex; } } }); } const appRouter = router(post('/customer', createCustomer)); const corsWrappedApp = corsMiddleware(appRouter); if (require.main === module) { const PORT = process.env.PORT || 8080; require('http') .createServer(corsWrappedApp) .listen(PORT); } module.exports = corsWrappedApp;
config.js
require('dotenv').config({ path: process.env.NODE_ENV === 'production' ? '.env.production' : '.env.sandbox', }); const { Client, Environment } = require('square'); const client = new Client({ environment: process.env.NODE_ENV === 'production' ? Environment.Production : Environment.Sandbox, accessToken: process.env.SQUARE_ACCESS_TOKEN, }); module.exports = { client, isProduction: process.env.NODE_ENV === 'production', };
square.js
const { client } = require('./config'); const { ApiError } = require('square'); (async () => { try { const { result, statusCode } = await client.customersApi.listCustomers(); console.log('Customers retrieved successfully:', result.customers); } catch (ex) { if (ex instanceof ApiError) { console.error('API Error:', ex.errors); } else { console.error('Unexpected Error:', ex); } } })(); module.exports = { ApiError: client.ApiError, client };
错误分析与修复方案
1. CORS错误修复
从后端配置看,micro-cors中间件的配置存在潜在问题:
- 环境变量未正确加载:Google Cloud Run生产环境中可能未正确设置
REACT_APP_API_URL_FRONT,导致CORS的origin规则失效 - 预检请求未处理:浏览器跨域POST前会发送OPTIONS预检请求,当前配置未允许该方法
修复步骤:
- 登录Google Cloud Run控制台,确认服务的环境变量
REACT_APP_API_URL_FRONT已设置为https://buzzysweets.com(不要带末尾斜杠) - 修改
micro-cors配置,添加OPTIONS方法支持:
const corsMiddleware = require('micro-cors')({ origin: process.env.REACT_APP_API_URL_FRONT, allowMethods: ['POST', 'GET', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'], });
- 用curl验证预检请求响应:
curl -X OPTIONS https://backend-437525971388.us-central1.run.app/customer -H "Origin: https://buzzysweets.com" -v
确保响应头包含Access-Control-Allow-Origin: https://buzzysweets.com
2. 504网关超时修复
Cloud Run日志显示请求卡在square.customersApi.createCustomer(customerReq)步骤,核心原因可能是:
- Square API权限/参数错误:生产环境下的请求参数格式不符合要求,或Access Token缺少
CUSTOMERS_WRITE权限 - 请求超时配置过短:Cloud Run默认超时时间(5秒)不足以等待Square API响应,或Square客户端未设置超时
- 前端Authorization头误用:前端传递的卡片令牌是用于支付的,不是用于调用Square客户API的凭证
修复步骤:
(1)修正Square API调用凭证
前端传递的Bearer ${token}是Square卡片令牌,仅用于支付创建,不能用于客户创建。后端调用Square客户API时,应使用自身配置的Access Token,无需依赖前端的Authorization头:
- 前端
createCustomer请求中移除Authorization头:
const response = await fetch(`${process.env.REACT_APP_API_URL_BACK}/customer`, { method: 'POST', headers: { 'Content-Type': 'application/json', // 移除Authorization头 }, body, });
(2)调整超时配置
- 在Google Cloud Run控制台,将服务的超时时间调整为30秒
- 在Square客户端配置中添加超时限制:
// config.js const client = new Client({ environment: process.env.NODE_ENV === 'production' ? Environment.Production : Environment.Sandbox, accessToken: process.env.SQUARE_ACCESS_TOKEN, timeout: 20000, // 设置20秒超时 });
(3)验证权限与参数
- 在Square开发者控制台,确认生产环境的Access Token拥有
CUSTOMERS_WRITE权限 - 检查
customerReq中的地址字段:生产环境要求addressLine1非空,postalCode符合对应国家格式规范 - 完善错误日志,打印Square API返回的完整错误信息:
catch (ex) { if (ex instanceof ApiError) { console.error("Square API Error:", JSON.stringify(ex.errors)); bail(ex); } else { console.error(`Unexpected error: ${ex.message}`, ex.stack); throw ex; } }
内容的提问来源于stack exchange,提问作者Paul Rose
相关产品推荐
相关产品推荐

