如何处理通过 Square Register API 完成的付款退款?
结论
Android Register API(后更名为 Square Point of Sale API)的付款结果通常只包含用于关联交易的 transactionId,不会直接返回能在 Square app 中搜索的 receipt ID。因此,收银员无法仅凭 Register API 的返回值,在 Square app 中按 receipt ID 找到交易并退款。
建议按以下方式处理:
- 保存 Register API 返回的
transactionId。 - 一并保存应用自己的订单号,以及金额、币种、门店等信息。
- 退款时,通过 Square 的服务端 API 查询交易并提交退款。
- 如果查询结果中有
receipt_number或receipt_url,可以展示给用户,但不要将其作为退款主键。
具体接口要根据所用的 Square API 版本确定。旧交易通常使用 Transactions/Connect API,新集成则应优先使用 Payments API 和 Refunds API。
为什么不能直接依赖 receipt ID
Register API 负责将付款请求交给 Square app,并在付款完成后把结果通知给调用方。返回的 transactionId 用于在 API 中关联交易,但 Square app 的收据搜索界面不保证能用这个 ID 搜索。
receipt number、receipt URL 和 transaction/payment ID 分别表示不同的信息:
transactionId或payment_id:用于通过 API 查询交易和退款。receipt_number:向商户或顾客展示的收据编号。receipt_url:在线收据地址。tender_id:旧版 Transactions API 中某种付款方式的标识。
即使后续查到了收据编号,发起退款时仍应使用交易 ID 或付款 ID。
推荐处理流程
1. 保存付款回调中的交易 ID
Android 端收到 Square app 返回的结果后,应立即将 Square 交易 ID 与本地订单绑定:
String transactionId = data.getStringExtra("com.squareup.pos.TRANSACTION_ID");
order.setSquareTransactionId(transactionId);
order.setPaymentStatus("PAID");
orderRepository.save(order);
常量名称和结果解析方式会因 Square SDK 版本而异,应以当前版本的 SDK 定义为准,不要直接依赖示例中的字符串。
至少应保存以下字段:
local_order_id
square_transaction_id 或 square_payment_id
location_id
amount
currency
payment_status
created_at
refund_status
退款必须在服务端执行,不能将 Square access token 放入 Android app。
2. 查询对应交易
使用旧版 Transactions API 时,可以根据 Register API 返回的交易 ID 查询交易详情:
GET /v2/locations/{location_id}/transactions/{transaction_id}
Authorization: Bearer {ACCESS_TOKEN}
Square-Version: {SUPPORTED_VERSION}
示例:
curl \
-H "Authorization: Bearer ${SQUARE_ACCESS_TOKEN}" \
-H "Square-Version: ${SQUARE_VERSION}" \
"https://connect.squareup.com/v2/locations/${LOCATION_ID}/transactions/${TRANSACTION_ID}"
通过查询结果可以确认:
- 交易是否存在;
- 交易所属门店;
- 原始金额和币种;
- 可退款的付款记录;
- 旧退款接口所需的
tender_id; - 当前 API 是否返回可供展示的收据信息。
不要只按金额、时间或商品名称匹配交易,因为这些字段可能重复。
3. 通过旧版 Transactions API 退款
旧版退款接口通常需要提供 transaction_id、tender_id、退款金额和幂等键:
POST /v2/locations/{location_id}/transactions/{transaction_id}/refund
{
"idempotency_key": "refund-order-8472-01",
"tender_id": "TENDER_ID_FROM_TRANSACTION",
"amount_money": {
"amount": 1250,
"currency": "USD"
},
"reason": "Customer returned item"
}
示例:
curl -X POST \
-H "Authorization: Bearer ${SQUARE_ACCESS_TOKEN}" \
-H "Square-Version: ${SQUARE_VERSION}" \
-H "Content-Type: application/json" \
"https://connect.squareup.com/v2/locations/${LOCATION_ID}/transactions/${TRANSACTION_ID}/refund" \
-d '{
"idempotency_key": "refund-order-8472-01",
"tender_id": "TENDER_ID_FROM_TRANSACTION",
"amount_money": {
"amount": 1250,
"currency": "USD"
},
"reason": "Customer returned item"
}'
amount 使用货币的最小单位。例如,1250 美分表示 12.50 USD。
4. 新系统使用 Refunds API
如果查询或迁移后拿到的是 payment_id,应通过 Refunds API 退款:
POST /v2/refunds
{
"idempotency_key": "refund-order-8472-01",
"payment_id": "PAYMENT_ID",
"amount_money": {
"amount": 1250,
"currency": "USD"
},
"reason": "Customer returned item"
}
Register API 返回的旧 transactionId 能否直接作为新版 payment_id 使用,取决于交易创建时间和 Square 的兼容映射。不能默认两者相同,应先通过对应的查询接口确认。
注意事项
- 每次退款都要使用稳定且唯一的
idempotency_key,以免网络重试导致重复退款。 - 退款币种必须与原付款一致,退款总额不能超过可退余额。
- 一笔交易可能包含多个 tender。使用旧接口时,必须选择正确的
tender_id。 - 应保存退款请求 ID、Square refund ID、退款金额和最终状态,不能只记录“已提交”。
- 退款状态可能包括
PENDING、COMPLETED或FAILED,具体名称以当前 API 的返回值为准。 - Square app 的搜索方式和 API 字段可能随版本变化。即使交易详情中包含
receipt_number或receipt_url,这些信息也更适合展示和人工核对,不能替代transaction_id或payment_id。 - 如果业务要求员工在 Square app 内手工退款,可以额外保存交易时间、金额、支付卡尾号等辅助信息。程序化退款仍应以 Square 的交易标识为准。
备注:内容仅供参考。
