erlang_pay 为 Erlang 补上支付这块拼图:一个库接支付宝、微信、Stripe

本文是 erlang_pay 系列三篇中的第一篇(总览)。第二篇讲回调验签,第三篇讲金额与退款的两个坑。

你的产品要收钱了。加个会员、卖个课程、开个打赏,总之要接支付。

然后你打开支付宝开放平台,发现官方 SDK 有 Java 版、Go 版、Python 版、PHP 版、.NET 版------就是没有 Erlang 版。打开微信支付文档,同样。Stripe 好一点,官方库列表里依然没有 Erlang。

这不是 erlang_pay 要解决的唯一问题,但它是起点。

先说结论

erlang_pay 是一个纯 Erlang 的支付网关库,2026 年 6 月 14 日提交第一行代码,2026 年 9 月 23 日发布 0.3.0,Apache-2.0 协议。

它现在支持三家:

  • 支付宝 App 支付
  • 微信支付 v3(JSAPI / Native 扫码)
  • Stripe(PaymentIntent)

全部源码 10 个文件、2741 行 。运行时依赖只有 OTP 自带的几个库(crypto、public_key、inets、ssl),加一个 JSON 库 jsone------你的 rebar.config 里只需要写一行 {deps, [{jsone, "1.8.1"}]}。

它管的事:下单、退款、回调验签、查单、对账单下载、关单、撤单,从收钱到退钱的完整生命周期。

它不管的事:订单是你的、账是你的、发货逻辑也是你的。它只负责跟支付机构打交道那一段。

为什么"统一门面"值得单独说

三家支付机构的接口风格,打个比方,就像三家快递公司各有一个下单 App:注册三套账号、学三套下单流程、看三种格式的物流通知。

支付宝用 RSA2 签名,微信 v3 用平台公钥加 AES-GCM 解密,Stripe 用 webhook secret 算 HMAC。下单参数一个叫 out_trade_no,一个叫 payment_intent,返回值更是各说各话。

erlang_pay 做的事,是给你的业务代码面前放一个统一的前台 。整个库对外的入口就 12 个函数,都在 erlang_pay 这一个模块里:

erlang 复制代码
%% 下单
erlang_pay:create_payment(Gateway, Cfg, Order).
%% 退款
erlang_pay:refund(Gateway, Cfg, Refund).
%% 回调验签
erlang_pay:verify_notify(Gateway, Cfg, Ctx).
%% 查单 / 下载对账单 / 关单 / 撤单
erlang_pay:query(Gateway, Cfg, Q).
erlang_pay:download_bill(Gateway, Cfg, Bill).
erlang_pay:close(Gateway, Cfg, O).
erlang_pay:cancel(Gateway, Cfg, O).
%% 还有 version/0、build_pay_sign/3、gateway_module/1、capabilities/1、supports/2

第一个参数换一下,alipay、wechat、stripe,流程不变。三家之间的差异,库用「打了 tag 的返回 map」隔开------你拿到的结果 map 里,支付宝是 order_str(交给客户端 SDK 唤起),微信 Native 是 code_url(生成二维码),Stripe 是 client_secret(给前端 Stripe.js)。字段名不同,但都在 {ok, Map} 这个统一的壳里,你的代码 switch 一下就行。

三段代码走一遍

支付宝 App 支付

erlang 复制代码
Cfg = #{app_id => AppId, private_key => MchPriPem, public_key => AlipayPubPem,
        notify_url => <<"https://example.com/pay/callback/alipay">>},
{ok, #{order_str := OrderStr}} =
    erlang_pay:create_payment(alipay, Cfg, #{out_trade_no => <<"R123">>, amount_fen => 1000}).
%% 把 OrderStr 交给客户端的 AlipaySDK,用户手机的支付宝就被唤起了

这一笔是 10 元(amount_fen => 1000,单位是"分"------为什么用分,系列第三篇细讲)。

微信 Native(用户扫码)

erlang 复制代码
Cfg = #{app_id => AppId, mch_id => MchId, api_v3_key => V3Key,
        mch_serial_no => Serial, private_key => MchPriPem,
        platform_public_key => PlatformPubPem,
        notify_url => <<"https://example.com/pay/callback/wechat">>},
{ok, #{code_url := CodeUrl}} =
    erlang_pay:create_payment(wechat, Cfg, #{out_trade_no => <<"R123">>,
                                             amount_fen => 1000, pay_type => native}).

Stripe

erlang 复制代码
Cfg = #{secret_key => <<"sk_...">>, webhook_secret => <<"whsec_...">>},
{ok, #{payment_no := Pi, client_secret := Cs}} =
    erlang_pay:create_payment(stripe, Cfg, #{out_trade_no => <<"R123">>,
                                             amount_fen => 1000, currency => <<"usd">>}).

注意到没有:三段代码长得几乎一样。差异全在 Cfg(每家要的凭据不同)和返回 map 的字段上。

几条值得说的设计规矩

这些规矩本身比代码更有普适性,做任何支付接入都用得上:

1. 凭据用参数传,库不偷看配置。 商户私钥、API 密钥全部从 Cfg 这个 map 传进去,库绝不读 application env (Erlang 里的全局配置)。好处:密钥的存放方式是你的架构决策------放数据库、放 KMS、放启动参数都行,库不掺和;密钥缺了也不会崩溃,直接返回 {error, {no_credential, _}},而且密钥、签名、报文绝不写进日志。

2. 金额只收整数。 amount_fen => 1000 就是 1000 分。库内部没有任何浮点数。为什么,第三篇用一整篇讲,这里只说一句:0.1 + 0.2 在绝大多数编程语言里不等于 0.3。

3. 出错不崩溃,统一格式。 所有失败返回 {error, {Code, Msg}},比如 bad_signature(验签失败)、timestamp_expired(回调时间戳超窗,疑似重放)、insecure_url(你要我访问的地址不是 https,拒发)。业务代码 case 一层就能兜住所有错误,不用担心哪天支付网关抽风把你的进程带崩。

4. 出站只走 HTTPS。 URL 校验不通过,请求根本不会发出去。

丑话说到前头

两条,都是实话:

  1. 还没跟真实沙箱联调过。 0.3.0 的状态是:本地 213 个测试用例全绿,dialyzer 零警告,CI 跑 OTP 28/29 双版本矩阵------但这些都发生在测试环境里。跟支付宝、微信、Stripe 的真实沙箱环境对一遍,是发布前还没做完的功课。想在生产用,请先跑通沙箱再上小流量。

  2. 测试是真的,但 mock 的位置要说清楚。 213 个用例里,签名和验签全部用测试时即时生成的 RSA 密钥对做真签名、真验签,mock 只打在 HTTP 边界上(不会真的向支付机构发请求)。也就是说,协议逻辑是动真格验证过的,"支付机构会怎么回"这一半还没有。

另外 0.3.0 还删掉了一个模块 epay_cert_mgr(微信平台证书的自动轮换)------因为证书生命周期没有做成闭环,与其留一个"看起来能用"的模块,不如删掉,改成调用方直接注入平台公钥。这个决策的来龙去脉,第二篇展开。

仓库信息

IMBoy(一个开源可私有化的 IM 平台,Erlang/OTP 后端)要收钱,就有了这个库。erlang_pay 从 IMBoy 项目里长出来,然后独立成仓回馈社区------它不绑定 IMBoy,任何需要接支付渠道的 Erlang/OTP 项目都可以直接拿去用。

下一篇:先验章,再拆包:支付回调的验签到底在防什么

相关推荐
yunwei372 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
用户8356290780512 小时前
Python 设置 Excel 单元格边框与样式
后端·python
波加曼大王2 小时前
记一次用 AI 重构三年前老 Spring Boot 服务的真实经历:爽是真爽,账单也是真疼
后端
大勇前进2 小时前
大模型的上下文窗口越大越好吗?长文本模型暗藏哪些缺陷
后端
Bazingga2 小时前
RAG进阶-分块Chunking从原理到企业级实践
后端
回家路上绕了弯2 小时前
智能体编排平台中,工作流与 Agent 如何分工?
后端·ai编程
GoGeekBaird2 小时前
手机远控 DeepSeek Harness?四款 DSH Desktop 测评
后端·github
PC2005_cloud2 小时前
RabbitMQ 在 Spring Boot 中的完整使用
前端·后端
IT枫斗者枫哥2 小时前
Spring Boot导入返回409,为什么第一行还是入库了?
java·spring boot·后端