payment是一款集成了阿里支付、微信支付的组件。它对php的版本要求很低,大于等于5.6。
但是个人认为其文档做的不很完善,特别是异步通知并没有文档,所以有必要将一些细节分享给大家。下面介绍的是laravel5.1框架如何使用(什么框架并不重要),并重点介绍阿里支付的PC网页支付,因支付宝有沙箱测试,微信我未测试,故省略。
composer require "riverslei/payment:*"
在项目的配置目录下,新建payment.php,内容如下:
[
'name' => '支付宝支付',
'use_sandbox' => true, //是否沙箱模式
'partner' => '2088********1126', //收款支付宝用户ID(2088开头)
'app_id' => '2016********9779',
'sign_type' => 'RSA2',
'ali_public_key' => '', //支付宝公钥
'rsa_private_key' => storage_path('security/alipay/private_key.txt'),
'limit_pay' => [],
'notify_url' => env('APP_URL').'/usradmin/notify/ali_charge', //异步回调地址
'return_url' => env('APP_URL').'/usradmin/payres', //同步回调地址
'return_raw' => false, //是否返回原始数据
],
'wx_charge' => [
'name' => '微信支付',
//微信支付配置数组
'app_id' => '',
'mch_id' => '',
'md5_key' => '',
'app_cert_pem' => storage_path('security/wechat/apiclient_cert.pem'),
'app_key_pem' => storage_path('security/wechat/apiclient_key.pem'),
'sign_type' => 'MD5',// MD5 HMAC-SHA256
'limit_pay' => [ ],
'fee_type' => 'CNY',// 货币类型 当前仅支持该字段
'notify_url' => env('APP_URL') . '/usradmin/notify/wx_charge',
'return_url' => env('APP_URL').'/usradmin/payres', //同步回调地址
'return_raw' => false,
]
];
说明:
在控制器建立充值方法,调用Charge::run方法,返回发起支付的url,header函数跳转到该url即可。
use Payment\Config;
use Payment\Client\Charge;
public function recharge(Request $request)
{
$money = intval($request->input('pay_amount', 0)); //金额
$pay_way = $request->input('pay_way', ''); //支付方式
/**
* ali_web:支付宝 PC 网页支付
* wx_qr:微信 扫码支付 (可以使用app的帐号,也可以用公众的帐号完成)
* 如果这里不清楚,去Payment\Config类查看,都在这里。
*/
if (!in_array($pay_way, ['ali_web', 'wx_qr'])) {
return Y::json(1001, '不支持当前支付方式');
}
//生成订单
$payData = [
'body' => 'test', //对一笔交易的具体描述信息。
'subject' => 'test', //标题
'order_no' => Order::build_order_no('D'),//订单号不可重复向支付宝发送
'amount' => $money, //单位:元
'timeout_express' => time() + 15 * 60, //过期时间(当前时间+过期s数),必须大于当前支付请求时间
'client_ip' => $request->ip(),
'goods_type' => '1', //0:虚拟商品 1:实物商品
'return_param' => 'recharge' //异步通知时原样返回的数据,不需要urlencode。不可包含特殊符号
];
/**
* Config::ALI_CHANNEL_WEB == 'ali_web'| 'wx_qr'
* Config::ALI_CHARGE == 'ali_charge'
* Config::ALI_CHANNEL_WEB == 'wx_charge'
*/
$type = $pay_way == Config::ALI_CHANNEL_WEB ? Config::ALI_CHARGE : Config::WX_CHARGE;
$config = config('payment');
// 调用payment组件接口 返回发起支付的url
$payment_url = Charge::run($pay_way, $config[$type], $payData);
if ($payment_url) {
DealRecord::insert([
'user_id' => Auth::id(),
'type' => 'recharge', //充值
'order_no' => $payData['order_no'], //订单号
'status' => 'waiting', //发起支付默认等待状态 等待支付宝回调
]);
}
//跳转到支付宝
header("location:" . $payment_url);
}
大概看完上面代码有几点疑惑,分别补充:
/**
* $letter 可作为订单类型标识 也可以不用 假如你的项目有多种订单类型
*/
public static function build_order_no($letter = '')
{
return $letter . date('Ymd') . substr(time(), -5) . substr(microtime(), 2, 5) . sprintf('%02d', rand(0, 99));
}
’return_param’ => ‘recharge’, 异步通知时原样返回的数据,这就比较重要了,因为业务不光只是支付,还可能查询,退款。那么充值的时候带上recharge,回调的时候,根据这个参数就知道是充值操作,调用充值回调后的业务逻辑,进行逻辑的分发。
DealRecord::insert 充值记录表,在发起请求之前预先插入一条记录,并默认waiting,支付回调后改为success。这么设计有它的好处,第一我可以知道哪些人发起付款了,第二支付回调后,订单号状态更新为success,如果异常情况多次回调,如果状态不是waiting,那么就拒绝处理业务逻辑(给用户充值)。
CREATE TABLE `deal_records` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) unsigned NOT NULL,
`type` varchar(20) NOT NULL DEFAULT '', //充值or退款or其他?
`order_no` varchar(24) NOT NULL DEFAULT '',
`channel` varchar(20) NOT NULL DEFAULT '', //支付宝or微信?
`status` varchar(20) NOT NULL DEFAULT '0' COMMENT 'waiting success',
`serial_no` varchar(64) NOT NULL DEFAULT '', //支付平台返回的
`pay_amount` decimal(10,2) unsigned NOT NULL DEFAULT '0.00',
`pay_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`notify_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
可以单独创建一个控制器来存放,为什么?因为往往框架都有auth权限中间件,laravel还有csrf防护。我们把一些公共的块分离出来,比如在OtherController里,路由名称为other/notify,那么只需在auth和csrf中间件中排除这个路由:
protected $except = [
'other/*',
];
use Payment\Config;
use Payment\Client\Notify;
use Payment\Common\PayException;
use Illuminate\Support\Facades\Log;
//支付宝微信通知 $channel = ali_charge | wx_charge
public function notify($channel)
{
if (!in_array($channel, [Config::ALI_CHARGE, Config::WX_CHARGE])) {
return '暂不支持';
}
$config = config('payment');
$callback = new PaymentNotify();
try {
$ret = Notify::run($channel, $config[$channel], $callback);
Log::error($ret);
} catch (PayException $e) {
Log::error($channel . ':支付通知:' . $e->getTraceAsString());
}
return $ret;
}
//同步通知
public function returnUrl(){
return view('alipay');
}
解释下上边代码:
PaymentNotify
在项目的一个目录,比如Service目录下(不要纠结,放在一个目录下即可)。
recharge($data);//充值
break;
case 'order':
$result = $this->orderPay($data);//查询订单
break;
default:
$result = false;
}
return $result;
}
//充值
public function recharge($data)
{
DB::beginTransaction();
try {
$order_no = $data['order_no'];
$record = DealRecord::where('order_no', $order_no)->first();
if (!$record || $record->status != 'waiting') {
Log::error('订单不存在');
DB::rollBack();
return false;
}
$temp = [];
$temp['status'] = $data['trade_state'];
$temp['channel'] = $data['channel'];
$temp['notify_time'] = date('Y-m-d H:i:s');
$temp['pay_amount'] = $data['amount'];
$temp['pay_time'] = $data['pay_time'];
$temp['serial_no'] = $data['transaction_id'];
//$temp['data'] = json_encode($data);
//生成充值记录 不管成功或者失败
DealRecord::where('id', $record->id)->update($temp);
//如果成功 在这里写主要业务逻辑
if ($data['trade_state'] == 'success')
{
//更新用户资产表 user_assets 总充值:
UserAsset::where('user_id', $record->user_id)->increment('total_recharge', $data['amount']);
//余额
UserAsset::where('user_id', $record->user_id)->increment('balance', $data['amount']);
}
DB::commit();
} catch (\Exception $e) {
Log::error($e->getTraceAsString());
Log::error($e->getMessage());
DB::rollBack();
return false;
}
return true;
}
//查询支付订单
public function orderPay($data)
{
}
}
资产表结构这里就不贴了,自己定义即可。
回调地址需要线上地址怎么办?这里给大家介绍个本地也可以进行测试的方法。
叫“内网穿透”,可以将本地地址转成网络地址。
https://natapp.cn
购买免费隧道,并且设置后:
下载后:
双击运行出现cmd黑窗口;
F:\natapp_windows_amd64_2_3_9>natapp -authtoken=xxxxxxxx //token在第二图中
那么这就是你项目的外网地址了,将其填入支付宝沙箱的回调地址中就可以测试啦!注意:黑窗口不要关闭,重新运行,该地址会更改,你不得不重新配置回调地址,测试过程挂着黑窗口就行了。