支付宝PC电脑支付文档传送门:点这里
参考文档:https://blog.csdn.net/meixi_android/article/details/86489870
官方SDK下载地址:SDK传送门
我项目里没有使用官方SDK,这里使用的是github一个大神提供的支付宝支付和微信支付集成的包,
GitBook传送门:点这里
GitHub传送门:点这里
根据文档要求在项目根目录执行composer,安装sdk包:
composer require yansongda/pay -vvv
安装完成后会发现vendor目录多了几个文件夹目录,这都是该SDK的依赖包:
文档中的示例吧配置文件写到了控制器里面,但是不够优雅,我决定把配置文件提取出来放到一个专门的配置文件中:
配置文件:pay_config.php:
'你的apiiid',//去支付宝平台查看
'ali_public_key' => '支付宝公钥',//注意这里是公钥,而不是应用公钥,一定要与上面支付宝开发者填的保证一直,否则支付时会有错误提示
'private_key' => '你生成的私钥文件里面的内容',//由于生成的私钥文件内有回车键,需要吧回车键去掉,私钥内容是-----BEGIN RSA PRIVATE KEY-----与-----END RSA PRIVATE KEY-----之间的字符串,首行和尾行禁止包含
'mode' => 'dev', // optional,设置此参数,将进入沙箱模式,改参数是指定使用支付宝网关正式地址还是还发地址的
];
}else if($sub_domain == 'hibuilding'){
$cfg_ary = [
//正式环境
'app_id' => '你的apiiid',//去支付宝平台查看
'ali_public_key' => '支付宝公钥',//注意这里是公钥,而不是应用公钥,一定要与上面支付宝开发者填的保证一直,否则支付时会有错误提示
'private_key' => '你生成的私钥文件里面的内容',//由于生成的私钥文件内有回车键,需要吧回车键去掉,私钥内容是-----BEGIN RSA PRIVATE KEY-----与-----END RSA PRIVATE KEY-----之间的字符串,首行和尾行禁止包含
];
}else{
$cfg_ary = [
//企业沙箱
'app_id' => '你的apiiid',//去支付宝平台查看
'ali_public_key' => '支付宝公钥',//注意这里是公钥,而不是应用公钥,一定要与上面支付宝开发者填的保证一直,否则支付时会有错误提示
'private_key' => '你生成的私钥文件里面的内容',//由于生成的私钥文件内有回车键,需要吧回车键去掉,私钥内容是-----BEGIN RSA PRIVATE KEY-----与-----END RSA PRIVATE KEY-----之间的字符串,首行和尾行禁止包含
'mode' => 'dev', // optional,设置此参数,将进入沙箱模式
];
}
$common = [
'notify_url' => $notify_url,
'return_url' => $return_url,
// 加密方式: **RSA2**
'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,
// 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
],
];
$config['ali_pay_cfg'] = array_merge($cfg_ary, $common);
以上四步全部写在下面的两个文件中,开发者可以参考:
控制器文件,Assemble_pay.php:
'https://openapi.alipay.com/gateway.do',
self::MODE_DEV => 'https://openapi.alipaydev.com/gateway.do',
];
* 改为:
* const URL = [
self::MODE_NORMAL => 'https://openapi.alipay.com/gateway.do?charset=utf-8',
self::MODE_DEV => 'https://openapi.alipaydev.com/gateway.do?charset=utf-8',
];
*/
/**
* Created by PhpStorm.
* User: 15237
* Date: 2019/6/22
* Time: 15:16
*/
use Yansongda\Pay\Pay;
use Yansongda\Pay\Log;
use Yansongda\Pay\Gateways\Alipay;
use Symfony\Component\HttpFoundation\Response;
use Yansongda\Supports\Config;
class Assemble_pay extends PS_Controller
{
public $CI;
protected $alipay_cfg;
protected $wechat_pay_cfg;
protected $alipay;//支付宝支付对象
protected $wechatpay;//微信支付对象
function __construct()
{
parent::__construct();
$this->CI = get_instance();
$this->alipay_cfg = $this->config->item('ali_pay_cfg');
$this->wechat_pay_cfg = $this->config->item('wechat_pay_cfg');
$this->CI->load->helper('pay');
$this->alipay = Pay::alipay($this->alipay_cfg);
//$this->wechatpay = Pay::wechat($this->wechat_pay_cfg);
$this->load->service('assemble_pay_service', '', 'assemble_pay_s');
}
public function pay()
{
$params = $this->input->post();
$test = [
'pay_type' => 'ali_pay',
'pay_way' => 'web',
'order_no' => '15614439004',
];
$params = !empty($params) ? $params : $test;
$res = $this->assemble_pay_s->pay_serv($params);
//output_data(['html' => $res]);
}
/**
* 方法 ali_pay_return,支付宝支付同步回调接口
*
*/
public function ali_pay_return()
{
$params = $this->input->get();
$result = $this->alipay->verify(); // 是的,验签就这么简单!
$params = deal_ary_trim($params);
$trade_no = htmlspecialchars($params['trade_no']);
$params['total_amount'] = $params['total_amount']*100;
//实际验证过程建议商户添加以下校验。
//1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
//2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
//3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
//4、验证app_id是否为该商户本身。
$order = $this->check_ali_pay_params($params, 3);
if($result) {//验证成功
//请在这里加上商户的业务逻辑程序代码
//修改订单状态
if(empty($order)){
$order_where = ['order_no'=>$params['out_trade_no']];
$order_data = [
'order_status'=>3,
'pay_date'=>$params['timestamp'],
'pay_money'=>$params['total_amount'],
'trade_no'=>$trade_no,
'trade_date'=>$params['timestamp'],
];
$this->assemble_pay_s->update_order_serv($order_where, $order_data);
}
//获取订单信息
$order_info_where = ['o.order_no' => $params['out_trade_no'], 'order_status' => 3];//1,未支付,3:已支付,4:关闭,5:完成
$order = $this->assemble_pay_s->get_order_info_serv($order_info_where);
//增加收支流水记录
//查看是否有订单流水产生,有则不处理,无则需要添加
$order_bill = $this->assemble_pay_s->get_order_bill_info_serv(['order_id'=>$order['order_id']]);
if(empty($order_bill)){
$this->create_order_bill($order, $trade_no);
}
//——请根据您的业务逻辑来编写程序(以下代码仅作参考)——
//获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表
//商户订单号
//$out_trade_no = htmlspecialchars($params['out_trade_no']);
//支付宝交易号
//echo "验证成功
支付宝交易号:".$trade_no;
//echo '
去支付页查看订单信息吧!
';
$data['res'] = 'success';
$data['message'] = '验证支付成功。支付宝交易号:%s 去支付页查看订单信息吧!';
$data['trade_no'] = $trade_no;
//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——
} else {
//验证失败
//echo "验证失败";
//echo '请去支付页确认问题!';
$data['res'] = 'fail';
$data['message'] = '验证失败。请去支付页确认问题!';
}
$this->load->view('assemble_pay/ali_pay_return', $data);
// 订单号:$data->out_trade_no
// 支付宝交易号:$data->trade_no
// 订单总金额:$data->total_amount
}
/**
* 方法 ali_pay_notify,支付宝支付异步回调接口
*
*/
public function ali_pay_notify()
{
//接收待验签的数据
$input_stream = $this->input->raw_input_stream;
$input_stream = html_entity_decode(htmlspecialchars(iconv('GBK', 'UTF-8', urldecode($input_stream))));
$input_stream_ary = explode('&', $input_stream);
$data = array();
foreach($input_stream_ary as $value){
$value_ary = explode('=', $value);
if($value_ary[0] == 'sign'){
$data[$value_ary[0]] = mb_substr($value, 5);
}else{
$data[$value_ary[0]] = $value_ary[1];
}
}
file_put_contents(FCPATH."logs/post.log", json_encode($input_stream).PHP_EOL);
file_put_contents(FCPATH."logs/post.log", json_encode($data).PHP_EOL);
try{
$result = $this->alipay->verify($data); // 是的,验签就这么简单!
$params = $result->all();
$trade_status = $result->get('trade_status');
$params['total_amount'] = $result->get('total_amount')*100;
$trade_no = $result->get('trade_no');
// 请自行对 trade_status 进行判断及其它逻辑进行判断,在支付宝的业务通知中,只有交易通知状态为 TRADE_SUCCESS 或 TRADE_FINISHED 时,支付宝才会认定为买家付款成功。
// 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号;
// 2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额);
// 3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email);
// 4、验证app_id是否为该商户本身。
// 5、其它业务逻辑情况
$order = $this->check_ali_pay_params($params, 1);
if($result) {//验证成功
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//请在这里加上商户的业务逻辑程序代
//订单状态未修改,这里进行再次处理
$order_data = [
'order_status'=>3,
'pay_date'=>$result->get('gmt_payment'),
'pay_money'=>$result->get('total_amount')*100,
'trade_no'=>$trade_no,
'trade_date'=>$result->get('notify_time'),
'pay_user' => $result->get('buyer_id'),
'trade_result' => $trade_status
];
$order_where = ['order_no'=>$result->get('out_trade_no')];
$this->assemble_pay_s->update_order_serv($order_where, $order_data);
//增加订单流水记录
$this->create_order_bill($order, $trade_no);
//——请根据您的业务逻辑来编写程序(以下代码仅作参考)——
//获取支付宝的通知返回参数,可参考技术文档中服务器异步通知参数列表
//商户订单号
//$out_trade_no = $params['out_trade_no'];
//支付宝交易号
//$trade_no = $params['trade_no'];
//交易状态
if($trade_status == 'TRADE_FINISHED') {
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//请务必判断请求时的total_amount与通知时获取的total_fee为一致的
//如果有做过处理,不执行商户的业务程序
//注意:
//退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
}else if ($trade_status == 'TRADE_SUCCESS') {
//判断该笔订单是否在商户网站中已经做过处理
//如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序
//请务必判断请求时的total_amount与通知时获取的total_fee为一致的
//如果有做过处理,不执行商户的业务程序
//注意:
//付款完成后,支付宝系统发送该交易状态通知
}
//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——
//header("HTTP/1.1 200 OK");
echo "success"; //请不要修改或删除
//$res = true;
}else {
//验证失败
echo "fail";
//$res = false;
}
Log::debug('Alipay notify', $result->all());
} catch (\Exception $e) {
// $e->getMessage();
}
return $this->alipay->success()->send();
//exit();
//$this->load->view('assemble_pay/ali_pay_notify', $res);
}
/**
* 方法 check_ali_pay_params,支付包支付返回的参数校验
*
* @param array $params 参数数组
* @param string $order_status 订单状态
*
*/
private function check_ali_pay_params($params, $order_status){
$order_info_where = ['o.order_no' => $params['out_trade_no'], 'order_status' => $order_status];//1,未支付,3:已支付,4:关闭,5:完成
$order = $this->assemble_pay_s->get_order_info_serv($order_info_where);
if($order_status == 3 && empty($order)){
return $order;
}
if(empty($order)) output_error(-1, '订单状态异常,订单不存在,请联系卖家!');
if($order['order_money'] != $params['total_amount']) output_error(-1, '订单金额异常,订单金额与实际支付金额不一致,请联系卖家确认!');
return $order;
}
/**
* 方法 create_order_bill,创建订单流水记录
*
* @param array $order 订单信息
*
* @param string $trade_no 支付宝交易流水号
*
*/
private function create_order_bill($order, $trade_no){
$this->load->model('XT_Model');
$order_bill = $this->XT_Model->execute("select f_bill_no() as order_bill_no");
$order_bill_no = $order_bill['order_bill_no'];
$order_bill_data = [
'order_id' => $order['order_id'],
'order_bill_no' => $order_bill_no,
'trade_no' => $trade_no,
'bill_date' => date('Y-m-d H:i:s', time()),
'income' => $order['order_money'],
'pay' => '0',
'balance' => '0',
'bill_period' => date('Ym', time()),
'remark' => '购物下单',
];
$res = $this->assemble_pay_s->create_order_bill_serv($order_bill_data);
}
/**
* 方法 wechat_pay_notify,微信支付异步回调接口
*
*/
public function wechat_pay_notify()
{
$pay = Pay::wechat($this->wechat_pay_cfg);
try{
$result = $pay->verify(); // 是的,验签就这么简单!
Log::debug('Wechat notify', $result->all());
} catch (\Exception $e) {
// $e->getMessage();
}
return $pay->success()->send();
}
}
service服务层文件,assemble_pay_service.php:
CI = get_instance();
//$this->config->load('pay_config');
$this->alipay_cfg = $this->CI->config->item('ali_pay_cfg');
$this->wechat_pay_cfg = $this->CI->config->item('wechat_pay_cfg');
$this->CI->load->helper('pay');
$this->alipay = Pay::alipay($this->alipay_cfg);
//$this->wechatpay = Pay::wechat($this->wechat_pay_cfg);
}
/**
* 方法 pay_serv,聚合支付判断方法
*
* @param array $params 参数数组
*
*/
public function pay_serv($params){
//1.参数数组两边去空处理
$params = deal_ary_trim($params);
//2.判断支付类型调用相关方法
if(!array_key_exists('pay_type', $params)) output_error(-1, '支付类型参数为空或不存在,无法进行支付!');
if(!array_key_exists('pay_way', $params)) output_error(-1, '支付方式参数为空或不存在,无法进行支付!');
$pay_type = $params['pay_type'];
$pay_way = $params['pay_way'];
unset($params['pay_type'], $params['pay_way']);
//3.检查订单是否存在
$check_where = ['order_no'=>"'".$params['order_no']."'", 'order_status'=>1];
check_exists_name($check_where, $this->_order_tb, -1, '付款失败,订单不存在或不在待付款状态!',1);
//4.修改订单状态:支付中
/*
$order_where = ['order_no'=>$params['order_no']];
$order_data = ['order_status'=>2,'pay_type'=>$pay_type];
$this->update_order_serv($order_where, $order_data);
*/
//5.库存检查
//$this->check_product_num($params['order_no']);
//5.获取订单详情信息
$order_info_where = ['o.order_no' => $params['order_no'], 'order_status' => 1];//1,未支付,3:已支付,4:关闭,5:完成
$order = $this->get_order_info_serv($order_info_where);
if(empty($order)) output_error(-1, '付款失败,不存在未付款的订单记录!');
$order_name = $order['order_name'];
$data = [
'order_no' => $order['order_no'],
'order_money' => $order['order_money'],
'order_name' => mb_strlen($order_name) > 256 ? (mb_substr($order_name, 0, 252).'等') : $order_name,
'auth_code' => $order['auth_code'] ?? '',
];
switch($pay_type){
case 'ali_pay':
$this->ali_pay_serv($pay_way, $data);
break;
case 'wechat_pay':
$this->wechat_pay_serv($pay_way, $data);
break;
default:
output_error(-1, '未知的支付类型,请确认!');
}
}
/**
*方法 check_product_num,付款前核对订单库存
*
* @param string $order_no 订单编号(数据库订单唯一编号)
*
*/
public function check_product_num(){
}
/**
* 方法 get_order_info_serv,获取订单信息
*
* @param array $where 条件数组
*
* @return array $order 返回订单信息数组
*
*/
public function get_order_info_serv($where){
$fields = "o.order_id, o.order_no, o.order_status, o.order_money, group_concat(product_name) as order_name";
$order = $this->CI->db->select($fields, true)
->from($this->_order_tb.' as o')
->join($this->_order_det_tb, $this->_order_det_tb.'.order_no=o.order_no')
->where($where)
->group_by($this->_order_det_tb.'.order_no')
->get()
->row_array();
return $order;
}
/**
* 方法 update_order_serv,更新订单信息
*
* @param array $where 条件数组
* @param array $data 更新数据
*
*/
public function update_order_serv($where, $data){
$this->CI->load->model('XT_Model');
$this->CI->XT_Model->mTable = $this->_order_tb;
$res = $this->CI->XT_Model->update_by_where($where, $data);
return $res;
}
/**
* 方法 get_order_bill_info_serv,获取订单流水信息
*
* @param array $where 条件数组
*
* @return array $order_bill 返回订单流水信息数组
*
*/
public function get_order_bill_info_serv($where){
$fields = "order_bill_id, order_id, order_bill_no, trade_no, remark";
$order_bill = $this->CI->db->select($fields, true)
->from($this->_order_bill_tb)
->where($where)
->get()
->row_array();
return $order_bill;
}
/**
* 方法 create_order_bill_serv,新增订单流水记录
*
* @param array $data 插入数据数组
*
*/
public function create_order_bill_serv($data){
$this->CI->load->model('XT_Model');
$this->CI->XT_Model->mTable = $this->_order_bill_tb;
$res = $this->CI->XT_Model->insert_string($data);
return $res;
}
/**
* 方法 ali_pay_serv,支付宝支付方法
*
* @param string $pay_way 支付方式
* @param array $params 参数数组
*
*/
private function ali_pay_serv($pay_way, $params){
//判断支付方式,拼装发起支付的参数数组
$data = [
'out_trade_no' => $params['order_no'],
'total_amount' => round($params['order_money']/100, 2),
'subject' => $params['order_name'],
// 'http_method' => 'GET' // 如果想在 wap 支付时使用 GET 方式提交,请加上此参数。默认使用 POST 方式提交
];
//5.发起支付
switch($pay_way){
case 'web'://电脑支付
$pay_res = $this->alipay->web($data)->send();
output_data(['html_str'=>$pay_res->getContent()]);
break;
case 'wap'://手机网站支付
$pay_res = $this->alipay->wap($data)->send();
break;
case 'app'://app支付
$pay_res = $this->alipay->app($data)->send();
break;
case 'pos'://刷卡支付
$data['auth_code'] = $params['auth_code'];
$pay_res = $this->alipay->pos($data);
break;
case 'scan'://扫码支付
$pay_res = $this->alipay->scan($data);
break;
case 'transfer'://账户转账
$order = [
'out_biz_no' => time(),
'payee_type' => 'ALIPAY_LOGONID',
'payee_account' => '[email protected]',
'amount' => '0.01',
];
$pay_res = $this->alipay->transfer($order);
break;
case 'mini'://小程序支付
$data['buyer_id'] = $params['buyer_id'];
$pay_res = $this->alipay->mini($data);
break;
default:
output_error(-1, '未知的支付方式,请确认!');
}
return $pay_res;
}
/**
* 方法 wechat_pay_serv,微信支付方法
*
* @param string $pay_way 支付方式
* @param array $params 参数数组
*
*/
private function wechat_pay_serv($pay_way, $params){
//判断支付方式,拼装发起支付的参数数组
$data = [
];
//判断支付方式,拼装发起支付的参数数组
switch($pay_way){
case 'mp'://公众号支付
break;
case 'miniapp'://小程序支付
break;
case 'wap'://H5支付
break;
case 'scan'://扫码支付
break;
case 'pos'://刷卡支付
break;
case 'app'://app只发
break;
case 'transfer'://企业付款
break;
case 'redpack'://普通红包
break;
case 'groupRedpack'://分类红包
break;
default:
output_error(-1, '未知的支付方式,请确认!');
}
//5.发起支付
$pay = Pay::wechat($this->wechat_pay_cfg)->mp($data);
}
}
同步通知跳转页面文件ali_pay_return.php:
支付宝电脑网站支付return_url
以上是支付宝PC网站支付的业务代码,从支付到异步回调通知,再到同步回调通知,仅供参考,欢迎交流。
问题原因:
应用未上线或支付服务未签约,参考:https://openclub.alipay.com/club/history/read/13201
解决方案参考:
问题原因:
配置文件中ali_public_key使用的是支付宝公钥,而不是应用公钥
解决方案:
去支付宝开发者平台查看支付宝公钥,或到最开始使用秘钥生成工具生成的公钥文件中复制公钥内容,注意公钥中不需要带回车换行符,使用时应去掉,且不包含-----BEGIN PUBLIC KEY-----和-----END PUBLIC KEY-----
问题原因:
可能是由于支付宝post请求返回的参数编码市GBK,导致中文乱码,致使验签失败,在我使用的这个SDK中,由于支付宝网关设置成了常量,且不能在SDK中直接修改(因为修改过后,每次执行composer都会更新掉,如果不更新代码,可以在网关后面加上?charset=utf-8,指定编码格式解决),所以使用字符编码转换处理。
解决方案:
使用php://input流接收参数,处理中文字符编码问题。
完整代码参考上面代码块文件。
从支付开始开发,到支付功能开发完成,深有体会的是,可能代码的编写、业务逻辑处理并不是最难的,最难的是对于支付账号信息,你摸不到、碰不了,可能本来给你的信息就是错的,你以为是对的(就像支付宝公钥给成了应用公钥),这是最痛苦的事情,所以,开发之前或者测试出问题的时候,一定要先确认配置信息的正确性,否则可能永远都找不到问题。