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

分享 RunnerLee ⋅ 于 3个月前 ⋅ 最后回复由 RunnerLee 3周前 ⋅ 2646 阅读

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

本文章首发在 Laravel China 社区
本帖已被设为精华帖!
本帖由系统于 3个月前 自动加精
回复数量: 12
  • 赞一个,测试之后再来反馈。

    3个月前
  • 很好用

    3个月前
  • @ThinkCsly 哈, 文档还不完善, 踩坑了可以随时反馈哈

    3个月前
  • @daviLee 哈, 欢迎随时反馈哈, 简直不能再好用哈

    3个月前
  • 来波666,支持

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

    https://github.com/yansongda/pay

    供大家参考

    3个月前
  • 哈哈, 楼上这个也是我参考的库之一. 但各有所长吧.
    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();

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

    3个月前
  • 同时, 针对微信H5做了个小功能. 不太了解微信H5支付的小伙伴, 可以看下这里: https://laravel-china.org/topics/7570/pull-up-wechat-h5-payment-from-my-own-page-from-wechat.

    如果需要在自己的站点下拉起微信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>

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

    3个月前
  • 退款有吗

    3个月前
  • @hezhizheng 有的

    3个月前
  • 真的很好啊!

    2个月前
  • @ThinkCsly 有用到项目里面了吗 :stuck_out_tongue_closed_eyes:

    2个月前
您需要登陆以后才能留下评论!

Composer 中国全量镜像

Top 250 扩展包

Lumen 中文文档

Laravel 速查表

Laravel 中文文档

Laravel 项目开发规范

Laravel 开发环境部署

Composer 中文文档

Elasticsearch-PHP 中文文档

Lumen 中文文档

GraphQL PHP 中文文档

社区文档撰写指南

TDD 构建 Laravel 论坛笔记

PHP PSR 标准规范

PHP 设计模式全集

Dingo API 中文文档