第一:第三方支付原理
第二:支付接口申请流程
地址:https://docs.open.alipay.com/270/105899/ ;
参考地址:https://blog.csdn.net/november22/article/details/54233269#comments
1. 注册是支付宝商家账号 —— 注册地址:https://memberprod.alipay.com/account/reg/index.htm;
2. 注册成功后,找到蚂蚁金服开放平台,点击支付应用;
3、创建应用
4、 添加应用中的电脑支付功能
5、开发配置
说明:
(1)其中的支付宝网关地址不用改;
(2)应用网关设置为你网站的域名(如:https://www.xxx.com/),所以这个位置的设置就决定了你的网站首先要上线、然后域 名解析到服务器上才可以申请支付宝的支付接口;
(3)授权回调地址不用设置,一般如果涉及到第三方访问应用的时候才需要设置;
(4)设置应用公钥 —— 获取应用公钥【支付宝公钥】和私钥【商户私钥】的参考文档。
下载地址:https://docs.open.alipay.com/291/105971
下载、解压,然后生成秘钥,注意:文件夹的目录中一定不能带有中文字符吗,不然工具不能使用,这个步骤我就不一步步的说了。,将生成的公钥如下图,配置到查看应用公钥那儿,第一次登陆的应该现实的是配置公钥。点击确定,然后会生成支付宝公钥【这里一定 不要配置错误了,不然后面的程序会跑不起来的】
6、 当配置好后,直接提交审核,之后就等待审核了。
7、配置参数
1. app_id :应用ID,在这个地址中查看:https://openhome.alipay.com/platform/keyManage.htm;
2. merchant_private_key:商户私钥。这个在获取应用公钥和私钥时就已经获取到,公钥上传到支付宝中,
私钥就在项目中与之匹配验证;
3. notify_url : 异步通知回调地址。支付成功后,支付宝回调服务器的地址;
4. return_url:同步跳转地址。支付成功回调了服务器的地址后,自动跳转到目标地址;
5. charset:编码格式,一般为“UTF-8”;
6. sign_type:签名方式,一般为“RSA2”;
7. gatewayUrl:支付宝网关,https://openapi.alipay.com/gateway.do;
8. alipay_public_key:支付宝公钥,在这个地址查看:https://openhome.alipay.com/platform/keyManage.htm。
第三:PC扫码支付实现【以TP5为例】
1、下载支付宝支付demo,下载地址:https://docs.open.alipay.com/270/106291/;
2、解压放入Vendor文件夹下
3、在配置文件中配置支付宝配置参数
【app_id、alipay_public_key、merchant_private_key、charset、sign_type、alipay_public_key与APP支付宝支付的配置参数一致】
'alipay' => [
//应用ID,您的APPID。
'app_id' => "",
//商户私钥
'merchant_private_key' => "",
//异步通知地址
'notify_url' => \think\Request::instance()->domain()."/order/pay/alipay_notify",
//同步跳转
'return_url' => \think\Request::instance()->domain()."/order/pay/alipay_notify",
//编码格式
'charset' => "UTF-8",
//签名方式
'sign_type'=>"RSA2",
//支付宝网关
'gatewayUrl' => "https://openapi.alipay.com/gateway.do",
//支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
'alipay_public_key' => "",
],
4、生成支付二维码
// 支付
public function orderpay($ordersn)
{
$config = Config::get('alipay');
$order = db('shop_order');
$where = ['uid'=>$this->uid,'ordersn'=>$ordersn];
$orderinfo = $order->field('ordersn,pay_price,status')->where($where)->find();
if(empty($orderinfo)) return $this->error('抱歉,指定订单找不到!');
if($orderinfo['status']==1) return $this->error('订单已支付成功,请前往订单中心查看!');
//商户订单号,商户网站订单系统中唯一订单号,必填
$out_trade_no = trim($orderinfo['ordersn']);
//订单名称,必填
$subject = empty($orderinfo['title']) ? '购买雄松智课课程' : $orderinfo;
//付款金额,必填
$total_amount = trim($orderinfo['pay_price']);
//商品描述,可空
$body = empty($orderinfo['remark']) ? '' : $orderinfo['remark'];
Vendor('alipay.AopSdk');
Vendor('alipay.pagepay.buildermodel.AlipayTradePagePayContentBuilder');
Vendor('alipay.pagepay.service.AlipayTradeService');
// 构造参数
$payRequestBuilder = new \AlipayTradePagePayContentBuilder();
$payRequestBuilder->setBody($body);
$payRequestBuilder->setSubject($subject);
$payRequestBuilder->setTotalAmount($total_amount);
$payRequestBuilder->setOutTradeNo($out_trade_no);
$aop = new \AlipayTradeService($config);
/**
* pagePay 电脑网站支付请求
* @param $builder 业务参数,使用buildmodel中的对象生成。
* @param $return_url 同步跳转地址,公网可以访问
* @param $notify_url 异步通知地址,公网可以访问
* @return $response 支付宝返回的信息
*/
$response =
$aop->pagePay($payRequestBuilder,$config['return_url'],$config['notify_url']);
//输出二维码
// var_dump($response);
}
HTML代码:
<div class="outer-wrap">
<div class="inner-wrap">
<p class="order-txt">订单提交成功!订单号:<span id="ordersn">{$ordersn}</span></p>
<div class="xzyhq-wrap wx_pay" style="display:none">
<div class="zf-wrap">
<span class="zf-wrap-txt1">微信支付</span>
<span class="zf-wrap-txt2">距离二维码过期还剩<i id="count_down">60</i>秒,过期后请刷新页面重新获取二维码。</span>
</div>
<div class="syt-content">
<img src="/static/mba/images/syt_wxzf_ewm.png"/>
</div>
</div>
<div class="xzyhq-wrap">
<div class="xzyhq-title">选择其他支付方式</div>
<div class="xzzf-list-wrap">
<div class="xzzf-list-inner">
<div class="xzzf-list xzzf-list2" data-flag="wechatpay">
<i class="xxzf-icon"></i>
<span>微信支付</span>
</div>
<div class="xzzf-list" data-flag="alipay">
<i class="zfb-icon"></i>
<span>支付宝支付</span>
</div>
<div class="xzzf-list margin-r0" data-flag="bank_cart">
<i class="yhk-icon"></i>
<span>银行卡支付</span>
</div>
</div>
</div>
</div>
</div>
</div>
JavaScript代码:
JavaScript:
$(".xzzf-list").on("click",function(){
var paytype=$(this).attr('data-flag');
if (paytype=='wechatpay') {
$('.wx_pay').css('display','block');
default_paytype();
}else if(paytype == 'alipay'){
$('.wx_pay').css('display','none');
if(pay_flag){
window.clearInterval(paycheck); //销毁定时器
}
var url = "{:url('order/pay/orderpay')}";
var ordersn = $("#ordersn").html();
console.log(ordersn);
url += '?ordersn=' + ordersn;
window.location.href=url;
}
else
{
}
});
5、支付宝回调,更新支付状态
public function alipay_notify()
{
$arr = Request::instance()->param(); //file_get_contents("php://input");
empty($arr) ? $arr=$_GET : $arr;
$config = Config::get('alipay');
Vendor('alipay.pagepay.service.AlipayTradeService');
$alipaySevice = new \AlipayTradeService($config);
$result = $alipaySevice->check($arr);
/* 实际验证过程建议商户添加以下校验。
1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号,
2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额),
3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)
4、验证app_id是否为该商户本身。
*/
if($result) {//验证成功
//请在这里加上商户的业务逻辑程序代
//——请根据您的业务逻辑来编写程序(以下代码仅作参考)——
//获取支付宝的通知返回参数,可参考技术文档中服务器异步通知参数列表
//商户订单号
$out_trade_no = $arr['out_trade_no'];
//支付宝交易号
$trade_no = $arr['trade_no'];
// 交易金额
$money = $arr['total_amount'];
$order = db('shop_order');
$order_info = $order->where("ordersn='{$out_trade_no}'")->find();
// 判断是否是同一订单
if($out_trade_no != $order_info['ordersn']){
//验证失败
echo "fail";
}
if($money == $order_info['pay_price'])
{
if($order_info['status'] == 0)
{
//更新支付状态和支付时间
$OrderObj = new Order();
$result = $OrderObj->UpdateOrder($order_info['ordersn'],1);
}
if(empty($out_trade_no)) $this->error('订单状态已改变,请到个人中心查看!');
$OrderObj = new Order();
$order = $OrderObj->GetOneOrder($out_trade_no,$this->uid);
if(empty($order)) $this->error('订单状态已改变,请到个人中心查看!');
$this->assign('order',$order);
return view('pay/ordersuccess');
}else{
//验证失败
echo "fail";
}
echo "success"; //请不要修改或删除
}else {
//验证失败
echo "fail";
}
}
第四:APP实现支付宝支付接口
参考:https://www.jianshu.com/p/178324ca2561
APP官方文档:https://docs.open.alipay.com/204/105465/
1、配置config文件的信息
【app_id、alipay_public_key、merchant_private_key、charset、sign_type、alipay_public_key与PC端扫码支付的配置参数一致】
'appAlipay' => [
//应用ID,您的APPID。
'app_id' => "",//
//商户私钥
'merchant_private_key' => "",
//支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
'alipay_public_key' => "",
//异步通知地址【设置该接口无需token验证】
'notify_url' => \think\Request::instance()-
>domain()."/app/paynotify/alipy_notify",
//同步跳转【设置该接口无需token验证】
'return_url' => \think\Request::instance()-
>domain()."/app/paynotify/alipy_notify",
//编码格式
'charset' => "UTF-8",
//签名方式
'sign_type'=>"RSA2",
//支付宝网关
'gatewayUrl' => "https://openapi.alipay.com/gateway.do",
//收款支付宝用户ID。 如果该值为空,则默认为商户签约账号对应的支付宝用户ID
'seller_id' => '',
//商品信息
'body'=>"雄松智课",
//商品信息
'subject'=>"智课方案",
'format' =>'json',
],
2、商户APP端请求商户服务器接口,提交订单数据。
3、商户服务器端接收数据,然后对数据进行签名,返回请求参数到商户APP端
//引入Alipay的SDK
vendor('alipay/aop/AopClient');
vendor('alipay/aop/request/AlipayTradeAppPayRequest');
/**
* 支付宝支付[添加订单],商户服务器端接收数据,对数据进行签名,返回请求参数到商户APP端
*/
public function aliBuyPlans()
{
$rule = [
'orderSn' => 'require',
'plans' => 'require',
'payPrice' => 'require|float|egt:0.01',
];
$msg = [
'orderSn.require' => '订单号不能为空',
'plans.require' => '请选择购买方案信息',
'payPrice.require' => '请填写正确的金额',
'payPrice.float' => '请填写正确的金额',
];
//验证
$data = $this->params;
$validate = new Validate($rule, $msg);
!$validate->check($data) and $this->returnError($validate->getError());
//购买方案
$uid = $this->user['id'];
$data['payPrice'] = number_format($data['payPrice'],2);
$plans = json_decode(htmlspecialchars_decode($this->params['plans']), true);
if (!is_array($plans)) $this->returnError('方案信息格式错误');
// 启动事务
Db::startTrans();
$title = '推荐方案';
$body = config('appAlipay.body');
$subject = config('appAlipay.subject');
$orderModel = new OrderModel();
$orderInfo = $orderModel->checkOrdsn($data['orderSn'], $uid);
// 该订单号不存在
if (empty($orderInfo))
{
$orderid = (int)$orderModel->addOrder($uid, $data['orderSn'], $data['payPrice'], 1, $title,0);
$flag = $orderModel->addOrderGoods($uid, $orderid, $plans);
if ($flag)
{
// 提交事务
Db::commit();
$wxData['orderId'] = $orderid;
$wxData['orderSn'] = $data['orderSn'];
//数据进行签名,返回请求参数到商户APP端
$orderString = $this->alipay($body, $subject, $data['orderSn'], $data['payPrice']);
$wxData['orderString'] = $orderString;
$this->returnSuccess('添加订单成功',$wxData);
}
else
{
// 回滚事务
Db::rollback();
$this->returnError('添加订单失败');
}
}
else
{
$wxData['orderId'] = $orderInfo['id'];
$wxData['orderSn'] = $orderInfo['ordersn'];
//数据进行签名,返回请求参数到商户APP端
$orderString = $this->alipay($body, $subject, $data['orderSn'], $data['payPrice']);
$wxData['orderString'] = $orderString;
$this->returnSuccess('添加订单成功',$wxData);
}
}
/**
* 对数据进行签名,返回请求参数到商户APP端
* @param string $body 商品描述
* @param string $subject 商品名称
* @param string $outTradeNo 交易订单号
* @param float $totalAmount 支付金额
*/
protected function alipay($body, $subject, $outTradeNo, $totalAmount)
{
$aop = new \AopClient();
//正式环境网关 https://openapi.alipay.com/gateway.do
$aop->gatewayUrl = config('appAlipay.gatewayUrl');//沙箱环境网关
$aop->format = "json";
$aop->charset = "UTF-8";
$aop->signType = "RSA2";
$aop->appId = config('appAlipay.app_id');
$aop->rsaPrivateKey = config('appAlipay.merchant_private_key');//rsaPrivateKey私钥
$aop->alipayrsaPublicKey = config('appAlipay.alipay_public_key');//alipayrsaPublicKey请填写支付宝公钥
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
$request = new \AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数
$bizcontent = "{\"body\":\"{$body}\","
. "\"subject\": \"{$subject}\","
. "\"out_trade_no\": \"{$outTradeNo}\","
. "\"timeout_express\": \"30m\","
. "\"total_amount\": \"{$totalAmount}\","
. "\"product_code\":\"QUICK_MSECURITY_PAY\""
. "}";
$request->setNotifyUrl(config('appAlipay.notify_url'));//异步通知地址
$request->setBizContent($bizcontent);
//这里和普通的接口调用不同,使用的是sdkExecute,sdkExecute()方法,作用生成签名,
$response = $aop->sdkExecute($request);
//htmlspecialchars是为了输出到页面时防止被浏览器将关键参数html转义,
//实际打印到日志以及http传输不会有这个问题
//就是orderString 可以直接给客户端请求,无需再做处理。
//$response = json_encode(array("res"=>htmlspecialchars($response)));
return $response;
}
说明:sdkExecute()方法,作用生成签名,详细步骤如下:
将请求参数组装分下列3步,以最后第三步获取到的请求为准。
1)将请求参数的键按字典排序,然后按照key=value&key=value方式拼接,得到未签名原始字符串如下:
app_id=2015052600090779&biz_content={"timeout_express":"30m","product_code":"QUICK_MSECURITY_PAY","total_amount":"0.01","subject":"1","body":"我是测试数据","out_trade_no":"IQJZSRC1YMQB5HU"}&charset=utf-8&format=json&method=alipay.trade.app.pay¬ify_url=http://domain.merchant.com/payment_notify&sign_type=RSA2×tamp=2016-08-25 20:26:31&version=1.0
2)再对原始字符串进行签名
app_id=2015052600090779&biz_content={"timeout_express":"30m","product_code":"QUICK_MSECURITY_PAY","total_amount":"0.01","subject":"1","body":"我是测试数据","out_trade_no":"IQJZSRC1YMQB5HU"}&charset=utf-8&format=json&method=alipay.trade.app.pay¬ify_url=http://domain.merchant.com/payment_notify&sign_type=RSA2×tamp=2016-08-25 20:26:31&version=1.0&sign=cYmuUnKi5QdBsoZEAbMXVMmRWjsuUj+y48A2DvWAVVBuYkiBj13CFDHu2vZQvmOfkjE0YqCUQE04kqm9Xg3tIX8tPeIGIFtsIyp/M45w1ZsDOiduBbduGfRo1XRsvAyVAv2hCrBLLrDI5Vi7uZZ77Lo5J0PpUUWwyQGt0M4cj8g=
3)最后对请求字符串的所有一级value(biz_content作为一个value)进行encode,编码格式按请求串中的charset为准,没传charset按UTF-8处理,获得最终的请求字符串:
app_id=2015052600090779&biz_content=%7B%22timeout_express%22%3A%2230m%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%2C%22total_amount%22%3A%220.01%22%2C%22subject%22%3A%221%22%2C%22body%22%3A%22%E6%88%91%E6%98%AF%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE%22%2C%22out_trade_no%22%3A%22IQJZSRC1YMQB5HU%22%7D&charset=utf-8&format=json&method=alipay.trade.app.pay¬ify_url=http%3A%2F%2Fdomain.merchant.com%2Fpayment_notify&sign_type=RSA2×tamp=2016-08-25%2020%3A26%3A31&version=1.0&sign=cYmuUnKi5QdBsoZEAbMXVMmRWjsuUj%2By48A2DvWAVVBuYkiBj13CFDHu2vZQvmOfkjE0YqCUQE04kqm9Xg3tIX8tPeIGIFtsIyp%2FM45w1ZsDOiduBbduGfRo1XRsvAyVAv2hCrBLLrDI5Vi7uZZ77Lo5J0PpUUWwyQGt0M4cj8g%3D
4、商户APP接收从商户服务器端返回的请求参数,然后调起支付宝支付面板。
若用户支付成功,支付宝会同步给商户APP端返回一个支付结果。相应地,支付宝也会通过异步通知给商户服务器端返回一个支付结果。
注意:由于同步通知和异步通知都可以作为支付完成的凭证,且异步通知支付宝一定会确保发送给商户服务端。为了简化集成流程,商户可以将同步结果仅仅作为一个支付结束的通知(忽略执行校验),实际支付是否成功,完全依赖服务端异步通知。
5、服务端异步通知处理机制(支付宝主动发起通知,该方式才会被启用)
官方接口文档:https://docs.open.alipay.com/204/105301/
注意点:
1)必须保证服务器异步通知页面(notify_url)上无任何字符,如空格、HTML标签、开发系统自带抛出的异常提示信息等;
2)支付宝是用POST方式发送通知信息,因此该页面中获取参数的方式,如:$_POST[‘out_trade_no’];
3)程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是success这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);
4)当商户收到服务器异步通知并打印出success时,服务器异步通知参数notify_id才会失效。
/**
* 支付异步通知
*/
public function notify()
{
$aop = new \AopClient;
$aop->alipayrsaPublicKey = config('alipay.alipay_public_key');
$flag = $aop->rsaCheckV1($_POST, NULL, "RSA2");
if($flag)
{
//校验通知数据的正确性
$out_trade_no = $_POST['out_trade_no']; //商户订单号
$trade_no = $_POST['trade_no']; //支付宝交易号
$trade_status = $_POST['trade_status']; //交易状态
$total_amount = $_POST['total_amount']; //订单的实际金额
$app_id = $_POST['app_id'];
//验证app_id是否为该商户本身
if($app_id != $this->config['app_id'])
{
echo 'fail';
}
//只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时,支付宝才会认定为买家付款成功。
if ($trade_status != 'TRADE_FINISHED' && $trade_status != 'TRADE_SUCCESS')
{
echo 'fail';
}
//校验订单的正确性
if(!empty($out_trade_no))
{
//1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号;
//2、判断total_amount是否确实为该订单的实际金额(即商户订单创建时的金额);
//3、校验通知中的seller_id(或者seller_email) 是否为out_trade_no这笔单据的对应的操作方(有的时候,一个商户可能有多个seller_id/seller_email)。
//上述1、2、3有任何一个验证不通过,则表明本次通知是异常通知,务必忽略。在上述验证通过后商户必须根据支付宝不同类型的业务通知,正确的进行不同的业务处理,并且过滤重复的通知结果数据。
//校验成功后在response中返回success,校验失败返回failure
}
echo 'fail';
}
echo 'fail';//验证签名失败
}
6、当商户APP端接收到支付宝的同步返回结果为成功时,商户APP端再请求商户服务器端API,判断订单最终支付结果,并做出最终响应。
。
第五:微信公众号接入支付宝支付