Skip to content

Cell - 支付宝组件 ​

在依赖中添加 @celljs/alipay 以使用 支付宝 接收支付:一个可注入的 AlipayClient 对网关请求进行签名(RSA2)、验证响应的签名并构建页面、WAP 和 App 支付,而通知端点则验证异步通知并交给你的处理器处理。支持公钥模式和证书模式。它使用 Node 的 crypto 和 fetch,没有第三方依赖。

yaml
# cell.yml: 配置默认仅限服务端;从环境变量获取凭证
alipay:
  appId: ${env.ALIPAY_APP_ID}
  privateKey: ${env.ALIPAY_PRIVATE_KEY}             # 应用私钥: PEM、base64(PKCS#8 或 PKCS#1)或文件路径
  alipayPublicKey: ${env.ALIPAY_PUBLIC_KEY}         # 支付宝公钥(公钥模式)
  notifyUrl: https://shop.example.com/alipay/notify
  # gateway: https://openapi-sandbox.dl.alipaydev.com/gateway.do   # 沙箱环境
ts
import { Autowired } from '@celljs/core';
import { Body, Controller, Post } from '@celljs/mvc/lib/node';
import { AlipayClient, AlipayNotification, AlipayNotificationHandler } from '@celljs/alipay';

@Controller('orders')
export class OrderController {
    @Autowired(AlipayClient)
    protected readonly alipay: AlipayClient;

    @Post('page')        // 电脑网站支付: 重定向浏览器到该 URL
    page(@Body() order: { id: string }) {
        return { url: this.alipay.pagePay({ out_trade_no: order.id, total_amount: '88.88', subject: 'Cell T-shirt' }, { return_url: 'https://shop.example.com/paid' }) };
    }

    @Post('qr')          // 当面付扫码: 显示 qr_code 作为二维码
    async qr(@Body() order: { id: string }) {
        const { qr_code } = await this.alipay.precreate({ out_trade_no: order.id, total_amount: '88.88', subject: 'Cell T-shirt' });
        return { qr_code };
    }
}

@AlipayNotificationHandler(['TRADE_SUCCESS', 'TRADE_FINISHED'])
export class PaidHandler implements AlipayNotificationHandler {
    async handle(notification: AlipayNotification) {
        // out_trade_no, trade_no, total_amount... 如支付宝发送,签名已验证
        // 仅标记一次订单已支付:同一通知可能多次到达
    }
}

客户端 ​

biz_content 使用支付宝文档中的字段名(蛇形命名);金额是元的字符串('88.88')。支付方法默认 notify_url 为 alipay.notifyUrl;第二个参数设置其他常见参数(return_url、app_auth_token 等)。

方法API
pagePay(request, { httpMethod?, return_url? })alipay.trade.page.pay(电脑网站支付):重定向到的 URL,或带 httpMethod: 'POST' 时是自提交的 HTML 表单
wapPay(request, options?)alipay.trade.wap.pay(手机网站支付),结果相同
appPay(request)alipay.trade.app.pay(App 支付):App SDK 的订单字符串
precreate(request) → { qr_code }alipay.trade.precreate(当面付扫码)
create(request) → { trade_no }alipay.trade.create(JSAPI / 小程序,buyer_id 或 buyer_open_id);页面调用 my.tradePay({ tradeNO })
query(query) / close(query)alipay.trade.query / alipay.trade.close
refund(request) / queryRefund(query)alipay.trade.refund / alipay.trade.fastpay.refund.query;部分退款时给 out_request_no
billDownloadUrl({ bill_type, bill_date })alipay.data.dataservice.bill.downloadurl.query
execute(method, biz?, options?)其他任何 API:返回 <method>_response
pageExecute(method, biz?, options?) / sdkExecute(method, biz?, options?)其他任何浏览器或 App SDK API
verifyNotification(params)验证在你自己的路由上收到的通知

响应的 code 不是 10000 时会用 CELL_ALIPAY_API_ERROR 拒绝;detail 是响应(sub_code: 'ACQ.TRADE_NOT_EXIST' 等)。响应的签名在其 JSON 文本上检查,如支付宝发送的。

通知 ​

端点是 POST /alipay/notify(alipay.notification.url,在 server.path 下);将其绝对 URL 设置为 notifyUrl。它在请求体解析和认证之前运行,验证表单的签名和 app_id 是否是该应用的,然后按 trade_status(WAIT_BUYER_PAY、TRADE_SUCCESS、TRADE_FINISHED、TRADE_CLOSED;没有时用 notify_type)运行对应的 @AlipayNotificationHandler 组件,再运行没有指定类型的处理器(依次执行)。当所有处理器都成功时返回 success,否则返回 fail(签名错误时 400,处理器抛出异常时 500),所以支付宝会在稍后重新发送通知:处理器必须是幂等的。部分退款作为通知到达,带有 refund_fee 和 gmt_refund。

通知仅说明发生的事情:发货前检查 total_amount 是否与订单相符。

证书模式 ​

用证书创建的应用(公钥证书)给三个证书而不是 alipayPublicKey(PEM 文本或文件路径):

yaml
alipay:
  appId: ${env.ALIPAY_APP_ID}
  privateKey: ${env.ALIPAY_PRIVATE_KEY}
  appCert: certs/appCertPublicKey_2021000000000001.crt
  alipayPublicCert: certs/alipayCertPublicKey_RSA2.crt
  alipayRootCert: certs/alipayRootCert.crt

请求携带 app_cert_sn 和 alipay_root_cert_sn,如支付宝 SDK 计算的;用另一支付宝证书签名的响应被拒,所以当支付宝更新时替换 alipayPublicCert。

配置 ​

yaml
alipay:
  gateway: https://openapi.alipay.com/gateway.do
  timeout: 10000               # 毫秒,每个请求的超时
  notification:
    enabled: true
    url: /alipay/notify

错误 ​

失败是 AlipayError,有一个稳定的 code 和 hint:

代码何时
CELL_ALIPAY_NOT_CONFIGUREDappId 或 privateKey 缺失,或既没有 alipayPublicKey 也没有全部三个证书
CELL_ALIPAY_API_ERROR响应的 code 不是 10000:detail.sub_code 说明原因
CELL_ALIPAY_HTTP_ERROR网关返回 HTTP 错误或不是 JSON
CELL_ALIPAY_SIGNATURE_INVALID响应或通知未由支付宝签名
CELL_ALIPAY_APP_ID_MISMATCH通知是给另一应用的

不支持内容加密(encrypt_key、AES)。