作者:阿dai哥
教程分享
TUTORIAL TO SHAR
最近很多朋友在博客给我留言问,叫我写一个支付宝支付的功能,今天整理了一套比较完整的支付宝支付相关的demo改进版,下面的代码都是在我一个真实的项目中改进出来的。包含:【pc扫码支付】、【查询订单】、【余额提现】、【取消订单】、【关闭订单】
效果说明
SHARE THE BODY
1、pc扫码支付
2、手机支付成功截图
3、支付宝商家后台账单截图
开发前提
SHARE THE BODY
开发支付宝必须用注册一个企业账号,现在支付宝比较人性化了,如果你没有企业的信息也是可以只用的,因为支付宝有一个沙箱的测试功能,个人也是可以开发支付宝支付的功能。
登录支付开发平台后添加一个应用,填写你的开发者信息等待审核,沙箱模式下直接使用即可。下图就是我本地测试的域名和祝福吧异步通知回调地址。
实现代码
THE IMPLEMENTATION CODE
先建一个数据表吧,具体的表设计根据自己的项目该设计,因为我在此只是给大家展示功能,我就大概的设计了一下:
CREATE TABLE `jk_users_financial` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '用户id',
`subject` varchar(100) NOT NULL DEFAULT '余额充值' COMMENT '订单标题',
`body` varchar(255) NOT NULL DEFAULT '余额充值' COMMENT '描述',
`total_amount` decimal(10,2) NOT NULL COMMENT '金额',
`gmt_create` varchar(50) DEFAULT '0' COMMENT '交易创建时间',
`gmt_payment` varchar(50) DEFAULT '0' COMMENT '交易付款时间',
`out_trade_no` varchar(50) DEFAULT '0' COMMENT '商户订单号',
`trade_no` varchar(50) DEFAULT '0' COMMENT '支付宝交易号',
`out_biz_no` varchar(50) DEFAULT '0' COMMENT '商户业务ID,主要是退款通知中返回退款申请的流水号',
`buyer_id` varchar(50) DEFAULT '0' COMMENT '买家支付宝账号对应的支付宝唯一用户号。以2088开头的纯16位数字',
`seller_id` varchar(50) DEFAULT '0' COMMENT '卖家支付宝用户号',
`trade_status` varchar(32) DEFAULT 'WAIT_BUYER_PAY' COMMENT '交易状态',
`status` tinyint(1) DEFAULT '1' COMMENT '支付状态:1-未支付,2-支付成功,3-退款种,4-已退款',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COMMENT='用户消费表';
}
下面正式进入代码的介绍阶段。由于我使用的是Thinkphp5框架实现的效果,所以开发先composer一下要用到的包。
"require": {
"php": ">=5.4.0",
"topthink/framework": "5.0.*", "yansongda/pay": "^2.6", "phpoffice/phpexcel": "^1.8"
},
二话不说先引入要用到的命名空间吧
use Yansongda\Pay\Pay;
use Yansongda\Pay\Log;
任何的支付功能都必须要配置一些参数,支付宝也一样,下面这个是标准的格式,具体的参数在支付宝开发平台都能拿到,不懂的话使用沙箱的功能自己了解一下,反正都是傻瓜式操作,瞎子也能看懂。
protected $config = [
'app_id' => '2018120562457322',
'return_url' => 'http://www.jobya.cc/pay/index/notify',
'notify_url' => 'http://www.jobya.cc/pay/index/notify',
'ali_public_key' => 'xxxxxx',
// 加密方式: **RSA2**
'private_key' => '',
'log' => [ // optional
'file' => './logs/alipay.log',
'level' => 'info', // 建议生产环境等级调整为 info,开发环境为 debug
'type' => 'single', // optional, 可选 daily.
'max_file' => 30, // optional, 当 type 为 daily 时有效,默认 30 天
],
'http' => [ // optional
'timeout' => 5.0,
'connect_timeout' => 5.0,
],
// 'mode' => 'dev', // optional,设置此参数,将进入沙箱模式
];
这是一个支付的方法,下面的代码我都是实现逻辑,没有html代码的部分,模拟用户的数据。直接访问当前的方法就是在数据库生成一条没有付款的订单;
模拟支付代码如下
public function index()
{
$order = [
'out_trade_no' => time(), //订单号
'total_amount' => '0.01',
'subject' => '测试账户余额充值',
'body' => '余额充值',
'gmt_create' => time(), //交易创建时间
'status' => 1,
'user_id' => 1
];
// dump($order);exit;
$alipay = Pay::alipay($this->config)->web($order);
// dump($alipay);exit;
\think\Db::table('jk_users_financial')->insert($order);
return $alipay->send();// laravel 框架中请直接 `return $alipay`
}
上面的方法会生成一个付款的二维码。
然后,根据支付宝异步返回的数据判断用户是否成功支付了,根据返回来的标示在我们的服务器修改用户成功付款的状态。核心的代码还是在回调的方法里面。回调的方法如下:
public function notify()
{
$alipay = Pay::alipay($this->config);
try{
$data = $alipay->verify(); // 是的,验签就这么简单!
$data = json_decode( json_encode( $data),true); // 把支付宝回调的json数据转数组
//a.先判断用户是否支付成功
$is_pay_data = $this->find($data['out_trade_no']);
$is_pay_data = json_decode( json_encode( $is_pay_data),true); // 查询订单的json数据转数组
if($is_pay_data['trade_status'] == 'TRADE_SUCCESS'){
// 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号;
$is_order = ['user_id' => 1, 'out_trade_no' => $data['out_trade_no']];
$is_data = \think\Db::table('jk_users_financial')->where($is_order)->find(); //在数据库中查出的订单数据
if(!$is_data){
echo '没有该订单'; exit;
}
// 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额);
if($is_data['total_amount'] != $data['total_amount']){
echo '充值金额异常~'; exit;
}
// 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email);
if($data['seller_id'] != $is_pay_data['buyer_user_id']) {
echo '不是当前订单'; exit;
}
// 4、验证app_id是否为该商户本身。
if($this->config['app_id'] != $data['app_id']){
echo '不是当前支付的商户';exit;
}
// 5、其它业务逻辑情况
$update = array(
'gmt_payment' => $data['timestamp'], //付款时间
'trade_no' => $is_pay_data['trade_no'], //支付宝交易号
'buyer_id' => $is_pay_data['buyer_logon_id'], //买家账户
'seller_id' => $data['seller_id'],
'trade_status' => $is_pay_data['trade_status'],
'status' => 2
);
$is_ok = \think\Db::table('jk_users_financial')->where($is_order)->update($update);
if($is_ok){
echo '支付成功,可以设置跳转';exit;
} else{
echo '操作失败';exit;
}
} else{
echo '请你先支付~'; exit;
}
Log::debug('Alipay notify', $data->all());
} catch (\Exception $e) {
$aa = $e->getMessage();
echo '错误信息--';
dump($aa);exit;
测试
}
// exit;
return $alipay->success()->send();// laravel 框架中请直接 `return $alipay->success()`
}
话付款成功后,数据库中的订单等信息都更新了,如下
订单查询
//查询订单 out_trade_no 订单号
public function find($out_trade_no)
{
$order = [
'out_trade_no' => $out_trade_no,
'bill_type' => 'trade'
];
// $order = '1514027114';
$alipay = Pay::alipay($this->config);
$result = $alipay->find($order); //json格式
return $result;
}
余额提现
//单笔转账(提现)
public function zhuanzhang($payee_account = '18826747803')
{
$order = [
'out_biz_no' => time(), //商户转账唯一订单号
'payee_type' => 'ALIPAY_LOGONID',
'payee_account' => $payee_account,
'amount' => '0.2',
'remark' => '提现余额'
];
$alipay = Pay::alipay($this->config);
$result = $alipay->transfer($order);
dump($result);
}
取消订单
//取消订单
public function quxiao($out_trade_no = '1551253633')
{
//先查看当前定时是否已经完成交易
$res = json_decode($this->find($out_trade_no), true);
if($res['trade_status'] == 'TRADE_FINISHED' || $res['trade_status'] == 'TRADE_SUCCESS'){
echo '订单已经完成不能退款';exit;
}
dump($res);exit;
$order = [
'out_trade_no' => $out_trade_no
];
// $order = '1514027114';
$alipay = Pay::alipay($this->config);
$result = $alipay->cancel($order);
dump($result);
}
关闭订单
//关闭订单
public function close($out_trade_no = '1551253633')
{
$order = [
'out_trade_no' => $out_trade_no
];
$alipay = Pay::alipay($this->config);
$result = $alipay->close($order);
dump($result);
}