一个简易的支持支付宝 / 微信 / paypal / 银联 的支付 SDK

nazha-cashier

GitHub

https://github.com/RunnerLee/nezha-cashier

支持产品

  • 支付宝 PC 网页/WAP网页/扫码/APP
  • 微信公众号/扫码/H5/APP/小程序, H5支付支持免跳转抓取页面获取拉起 url.
  • 银联网页/APP
  • paypal 快速结账

思路

如何对接第三方支付? 最直接的方式, 直接用第三方 SDK, 或是自己对接第三方 API 实现.

其中无论是使用支付渠道提供的 SDK, 还是使用开源的SDK, 基本都需要先了解每个支付渠道的接口调用参数, 通知参数. 虽然在调用方式上能做到 "优雅", 但是在拼装参数和接收通知上, 依旧是不尽人意.

那能否把拼装参数这个最后的问题解决掉呢?

首先抽象一下支付流程:

在完整的支付流程中, 有两种类型的动作:

  • 请求. 包括拼装支付请求参数, 调用支付渠道下单, 查询支付结果, 查询付款结果等
  • 通知. 包括支付结果通知, 支付关闭通知, 退款进度通知等

基本每个支付渠道的流程都是这样的, 难点在于构建请求的参数和处理响应以及通知的内容.

那么将请求, 请求的响应, 以及通知都抽象为表单, 则分别对应为:

  • 请求表单. request form
  • 响应表单. response form
  • 通知表单. notification form

以请求支付为例, 用同一个订单记录, 分别调用支付宝跟微信的请求支付参数:

订单

<?php
$order = [
    'id' => '123456789',
    'subject' => 'testing',
    'description' => 'testing description',
    'return_url' => 'https://charge.return.url',
    'amount' => '0.01',
    'currency' => 'CNY',
];

微信扫码支付

<?php
$parameters = [
    'body' => $order['subject'],
    'out_trade_no' => $order['id'],
    'fee_type' => $order['currency'],
    'total_fee' => intval($order['amount'] * 100),
    'trade_type' => 'NATIVE',
    'notify_url' => 'https://charge.notify.url',
    'detail' => $order['subject'],
    'appid' => 'xxx',
    'mch_id' => 'xxx',
    'nonce_str' => uniqid(),
    'sign_type' => 'MD5',
    'sign' => 'xxx',
];

$response = request('POST', 'https://api.mch.weixin.qq.com/pay/unifiedorder', generateXml($parameters));

$res = parseXml($response);

$chargeUrl = $res['code_url'];

支付宝PC网站支付

<?php
$parameters = [
    'app_id' => 'xxxx',
    'method' => 'alipay.trade.page.pay',
    'format' => 'JSON',
    'return_url' => 'https://charge.return.url',
    'charset' => 'utf8',
    'sign_type' => 'RSA2',
    'timestamp' => date('Y-m-d H:i:s'),
    'version' => '1.0',
    'notify_url' => 'https://charge.notify.url',
    'biz_content' => json_encode([
        'out_trade_no' => $order['id'],
        'total_amount' => $order['amount'],
        'subject' => $order['subject'],
        'body' => $order['description'],
        'product_code' => 'FAST_INSTANT_TRADE_PAY',    
    ]),
];

到这里, 在获取到 $order 的情况下, 可以想象一下最简单的调用支付的方式:

<?php
charge($order, 'alipay_web');   // 调用支付宝PC网站支付
charge($order, 'wechat_qr'); // 调用微信扫码支付

那么在这个扩展包中, 提供的支付调用方式也是如此简单:

<?php
use Runner\NezhaCashier\Cashier;

$config = [
    'app_id' => 'xxxx',
    'private_key' => '',
    'public_key' => '',
];

$cashier = new Cashier('alipay_web', $config);

$response = $cashier->charge([
    'order_id' => '151627101400000071',
    'subject' => 'testing',
    'amount' => '0.01',
    'currency' => 'CNY',
    'description' => 'testing description',
    'return_url' => 'https://www.baidu.com',
    'notify_url' => 'https://www.baidu.com',
    'expired_at' => '2018-01-23 19:00:00',
]);

echo $response->get('charge_url');

定义好了每个请求动作使用的表单及其字段, 请求响应的表单及其字段, 以及每个通知的表单及其字段. 将表单传递给网关, 由网关完成拼装参数, 解析响应.

在使用过程中, 只需要修改调用的网关即可, 例如上述案例中, 只需要将 alipay_web 修改为 alipay_wap 即可完成接入支付宝手机网站支付.

Usage

<?php

use Runner\NezhaCashier\Cashier;

$config = [
    'app_id' => 'xxxx',
    'private_key' => '',
    'public_key' => '',
];

$cashier = new Cashier('alipay_web', $config);

// 请求支付
$chargeResponseForm = $cashier->charge([
    'order_id' => '151627101400000071',
    'subject' => 'testing',
    'amount' => '0.01',
    'currency' => 'CNY',
    'description' => 'testing description',
    'return_url' => 'https://www.baidu.com',
    'notify_url' => 'https://www.baidu.com',
    'expired_at' => '2018-01-23 19:00:00',
]);
echo $response->get('charge_url');

// 查询支付
$queryResponseForm = $cashier->query([
    'order_id' => '151627101400000071',
]);

// 获取支付通知
$chargeNotifyForm = $cashier->notify('charge');

// 返回成功
echo $cashier->success();

// 返回失败
echo $cashier->fail();

表单及字段说明

ChargeRequestForm

字段名 是否必须 字段说明 备注
order_id 订单号
subject 订单标题
amount 订单金额 注意部分支付渠道有金额上线限制
currency 订单货币 注意支付渠道支付
description 订单简述 支付渠道会有不同的长度限制
user_ip 用户IP
return_url 回调地址 web类型的支付渠道必须填
show_url 展示地址
body 订单详细说明 这个参数我应该删掉
expired_at 过期时间 unix 时间戳
created_at 创建时间 unix 时间戳, 想不到吧, 连这个鬼都要??

// TODO

本帖已被设为精华帖!
本帖由系统于 5年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 12

赞一个,测试之后再来反馈。

5年前 评论
ThinkQ

很好用

5年前 评论

@ThinkCsly 哈, 文档还不完善, 踩坑了可以随时反馈哈

5年前 评论

@daviLee 哈, 欢迎随时反馈哈, 简直不能再好用哈

5年前 评论

可能是我用过的最优雅的 Alipay 和 WeChat 的支付 SDK 扩展包了

https://github.com/yansongda/pay

供大家参考

5年前 评论

哈哈, 楼上这个也是我参考的库之一. 但各有所长吧.
nezha-cashier 的时候, 以接收支付通知为例.

1) 路由

POST /charge_notify/{gateway}

2) 编写控制器.

// 获取网关配置
$config = get_gateway_config($gateway);

// 创建 Cashier 实例
$cashier = new Cashier($gateway, $config);

// 获取经过格式化的通知表单
$form = $cashier->notify('charge');

// 更新订单状态
// 这一步非常重要, 有部分支付渠道在创建交易(未完成支付)的时候, 或是交易关闭的时候会通知
if ($form->get('status') === 'paid') {
    $order = Order::find($form->get('order_id'));
    $order->out_trade_sn = $form->get('out_trade_sn');
    $order->receipt_amount = $form->get('amount');
    $order->receipt_currency = $form->get('currency');
    $order->payer = $form->get('buyer_identifiable_id');
    $order->save();
}

// 返回正确
return $cashier->success();

// 返回错误时, 部分渠道会再次通知
return $cashier->error();

这样, 就完成了一个通用的接收支付通知的钩子. 一步到位.

5年前 评论

同时, 针对微信H5做了个小功能. 不太了解微信H5支付的小伙伴, 可以看下这里: 分享:从微信外我自己的页面拉起微信 H5 支付.

如果需要在自己的站点下拉起微信H5, 那么只需要在网关配置中增加一个配置

[
    'spider' => true,
    'spider_ip' => '1.1.1.1',
]

通过 $casher->charge($order)->get('charge_url') , 即可获得 weixin://xxxx 的 url,在 view 中这么操作:

<script>
location.href = "{!! $charge_url !!}";
</script>

就可以在自己的页面拉起微信客户端支付了.

5年前 评论
hezhizheng

退款有吗

5年前 评论
ThinkQ

真的很好啊!

5年前 评论

@ThinkCsly 有用到项目里面了吗 :stuck_out_tongue_closed_eyes:

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!