前期准备:
1.下载SDK,里面还有Demo,可以参照Demo里面的内容快速接入
2.把下载的zip文件解压,放到项目目录里,这里作者放在app文件夹里,方便查看
3.因为Laravel框架的原因,文件的入口在/public/index.php这个文件这里,所以所有需要require的文件的路径都要在相应的文件里修改一下,这里作者的路劲修改后举例为:
require_once '../app/AliPay/config.php';
为了方便使用,对部分类文件使用命名空间,这样使用起来目标明确,虽然会额外费点功夫,但作者觉得思路最重要。
因为是支付这块的内容,然后为了区别微信支付还有网银支付,所以命名空间定为:
namespace APP\Pay\Alipay;
4.为了方便日后管理,把一些相关的类文件集中起来放置在一个文件夹里,文件夹名为 qy ,意为为企业支付建立的这个文件夹
qy文件夹内有2个目录:
buildermodel : 存放构造供接口使用的builder
service : 存放相关的接口实现类
这里对部分需要引入命名空间的文件列举一下(目前只做了支付,退款还没做,所以退款涉及的类文件不做举例)
//支付宝手机网站支付(alipay.trade.page.pay)接口业务参数封装
/app/Alipay/qy/buildermodel/AlipayTradeWapPayContentBuilder.php
//支付宝手机网站支付查询接口(alipay.trade.query)接口业务参数封装
/app/Alipay/qy/buildermodel/AlipayTradeQueryContentBuilder.php
//支付宝手机网站支付接口实现类
/app/Alipay/qy/service/AlipayTradeServiceWap.php
这里特别要注意,在引入命名空间后,要执行一下composer的自动加载类命令(应该是这个意思)
首先,进入项目的根目录,然后执行下面的命令
composer dump-autoload
这个命令很重要,不然上面引入命名空间的类文件无法被识别
作者就在这里吃了大亏,说到底是对这个框架不熟悉
5.配置支付宝接口需要的基础信息,在/app/Alipay/config.php这个文件里设置,主要为以下几个变量
//应用ID,您的APPID。
'app_ id' => "",
//收款支付宝账号
'seller_id' => "",
//支付宝公钥
'alipay_public_key' => "",
//商户私钥
'merchant_private_key' => "",
//异步通知地址,只有扫码支付预下单可用
'notify_url' => 'http://'.$_SERVER['HTTP_HOST']."/Notify/AlipayCallBack",
//同步通知地址,个人认为相当于一个跳转回原页面的中间页,好像是可以不设置,会自动跳回员阿里的下单页面
'return_url' => 'http://'.$_SERVER['HTTP_HOST']."/user/check",
开始调用接口
6.前端 - 传递下单的金额,调用后端下单接口,即第7步的接口
//充值按钮绑定事件
$("#btnRecharge").click(function(){
$_money = $("input#money");
var money = $_money.val();
var min = "1";
var msg = "";
if(money==""){
msg = "请填写充值金额";
}
if(parseFloat(money)<parseFloat(min)){
msg = "充值金额不能小于充值最小金额,请重新填写";
}
if(msg!=""){
$_money.val(money).focus();
alert(msg);
return false;
}
var send = {_token:$('meta[name="csrf-token"]').attr('content'),money:money};
$.ajax({
type: "POST",
url: "{{ route( 'filling.AlipayOrder')}}",
dataType: "html",
data: {'_token': '{{csrf_token()}}','money':money,'pay_way':'mobile'},
success: function (json) {
document.write(json);
},
error: function (error) {
console.log(error);
}
});
});
7.后端 - 生成支付订单,打印(返回)下单结果(不知道这样子描述是否有误。。)
use APP\Pay\Alipay\AlipayTradeServiceWap;
use APP\Pay\Alipay\AlipayTradeWapPayContentBuilder;
use APP\Pay\Alipay\AlipayTradeQueryContentBuilder;
use APP\Pay\Alipay\Tool;
public function AlipayOrder(Request $request){
//检查参数是否正确
if(!$request->has('money') || floatval($request->money)<floatval(config('recharge.minRecharge'))){
$this->jsonError('参数错误');
return false;
}
//付款金额,必填
$money = $request->money;
$totalAmount = 0.01;
require_once '../app/AliPay/config.php';
//商户订单号,商户网站订单系统中唯一订单号,必填
//这里自己按需要写个方法生成,方便多个地方调用
$outTradeNo = $this->getOutTradeNo(16);
//订单名称,必填
$subject = $this->getSubject();
//商品描述,可空
$body = $this->getBody();
//最晚付款时间,逾期将关闭交易
$timeExpress = "5m";
$selleId = $config['seller_id'];
//构造参数
$builder = new AlipayTradeWapPayContentBuilder();
$builder->setBody($body);
$builder->setSubject($subject);
$builder->setOutTradeNo($outTradeNo);
$builder->setTotalAmount($totalAmount);
$builder->setTimeExpress($timeExpress);
$payResponse = new AlipayTradeServiceWap($config);
$response=$payResponse->wapPay($builder, $config['return_url'], $config['notify_url']);
//预生成充值订单 - PC端
$remarks = json_encode(Array('app_id'=>$config['app_id'], 'seller_id'=>$selleId));
$this->addChargeRecord($money, "alipay", $outTradeNo, 0, $remarks);
//输出表单
var_dump($response);
}
8.后端 - 支付结果异步通知接口
use APP\Pay\Alipay\AlipayTradeServicePC;
public function AliPayCallbackHandler(Request $request){
require_once '../app/AliPay/config.php';
$arr=$request->all();
$alipaySevice = new AlipayTradeServicePC($config);
//$alipaySevice->writeLog(var_export($_POST,true));
$result = $alipaySevice->check($arr);
if($result) {
//商户订单号
$out_trade_no = $arr['out_trade_no'];
//支付宝交易号
$trade_no = $arr['trade_no'];
//交易状态
$trade_status = $arr['trade_status'];
$buyer_id = $arr['buyer_id'];
$seller_id = $arr['seller_id'];
$app_id = $arr['app_id'];
$total_amount = $arr['total_amount'];
if($arr['trade_status'] == 'TRADE_FINISHED') {
//注意:
//退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知
$this->UserCharge($total_amount,'alipay',$out_trade_no, $buyer_id, 2, $seller_id, $app_id);
}
else if ($arr['trade_status'] == 'TRADE_SUCCESS') {
//注意:
//付款完成后,支付宝系统发送该交易状态通知
$this->UserCharge($total_amount,'alipay',$out_trade_no, $buyer_id, 1, $seller_id, $app_id);
}
echo "success"; //请不要修改或删除
}else {
//验证失败
echo "fail";
}
}
/**
* [会员充值回调处理,调用UserPayController写好的回调处理接口,与查询接口共用]
* @param int $money [订单金额]
* @param int $payType [订单类型]
* @param int $outTradeNo [商户订单号]
* @param int $payAccount [支付账号]
* @param int $status [支付状态]
*/
function UserCharge($money, $payType, $outTradeNo, $payAccount, $status, $seller_id, $app_id){
$UserPayController = new UserPayController();
$UserPayController->UserCharge($money, $payType, $outTradeNo, $payAccount, $status, $seller_id, $app_id);
}
这里和电脑网站支付用的是同一个回调接口,作者检查过代码,发现电脑网站支付和手机网站支付的回调接口其实都是指向Service文件的check接口,而在config配置文件指定了签名方式的情况下,两个Service文件的check接口其实都是一样的。所以这里就不在写多一个手机网站支付的回调接口了,因为在作者这里,只会是重复的代码。
但如果读者的给电脑网站支付还有手机网站支付使用了不用的签名方式,那这里就得分开处理了。
电脑网站支付:AlipayTradeServicePC默认使用的签名方式是RSA2
手机网站支付:AlipayTradeServiceWap默认使用的签名方式是RSA
9.支付接口、查询接口对应的回调处理
/**
* [会员充值回调处理]
* @param int $money [订单金额]
* @param int $payType [订单类型 weixin|alipay]
* @param int $outTradeNo [商户订单号]
* @param int $payAccount [支付账号]
* @param int $status [支付状态]
* @param int $appID [商务ID]
* @param int $smId [收款支付宝账号对应的支付宝唯一用户号/微信支付分配的商户号]
*/
function UserCharge($money, $payType, $outTradeNo, $payAccount, $status, $smId, $appID=null){
//支付类型 判断设置 日志类型
$payLogType = "";
if($payType=="alipay") $payLogType='Alipay';
else if($payType=="weixin") $payLogType='WechatPay';
//防止异步支付通知接口与查询接口同时调用回调处理,使用事务,通知在事务内使用lockForUpdate阻止同时读取修改用户的信息
DB::beginTransaction();
$Function= new Function();
//按照商务订单号获取预充值订单
$record = $Function->getUserChargeRecordByOutTradeNo($outTradeNo);
$record = json_decode(json_encode($record),true);
$remarks = json_decode($record['remarks'],true);
if($record['status']!="0"){
//已经处理过充值回调,直接返回true
//防止多次处理
return true;
}
/* -------------------- 检验 开始 -------------------- */
$msg = "";
$checkResult=1;
//检验1:out_trade_no 商务订单号
if($outTradeNo != $record['trade_no']){
$msg = "商务订单号参数错误";
$checkResult = 0;
}
//检验2:total_amount/total_fee 实际金额
if($checkResult==1 && floatval($money) != floatval($record['money'])){
$msg = "订单金额参数错误";
$checkResult = 0;
}
//检验3:app_id/appid 商务号ID
//支付宝查询订单时未返回这个参数,跳过这个检查
if($checkResult==1 && $appID != null){
if($payType == "alipay" && $appID != $remarks['app_id']){
$msg = "支付宝分配给开发者的应用ID参数错误";
$checkResult = 0;
} else if($payType == "weixin" && $appID != $remarks['appid']){
$msg = "微信支付分配的公众账号ID参数错误";
$checkResult = 0;
}
}
//检验4:seller_id/mch_id
if($checkResult==1 && $payType == "alipay" && $smId != $remarks['seller_id']){
$msg = "收款支付宝账号参数错误";
$checkResult = 0;
} else if($checkResult==1 && $payType == "weixin" && $smId != $remarks['mch_id']){
$msg = "商户号参数错误";
$checkResult = 0;
}
if($checkResult == 0){
Log::error("用户充值失败,out_trade_no为{$outTradeNo},原因为{$msg}");
$this->writePayLog("用户充值失败,out_trade_no为{$outTradeNo},原因为{$msg}", $payLogType);
//更新用户预充值记录信息
$res = ...;
if(!$res){
DB::rollback();
Log::error("用户充值失败,更新充值表失,out_trade_no为{$outTradeNo},原因为{$msg}");
$this->writePayLog("用户充值失败,更新充值表失败,out_trade_no为{$outTradeNo},原因为{$msg}", $payLogType);
} else{
DB::commit();
Log::info("用户充值失败,更新充值表成功,out_trade_no为{$outTradeNo},原因为{$msg}");
$this->writePayLog("用户充值失败,更新充值表成功,out_trade_no为{$outTradeNo},原因为{$msg}", $payLogType);
}
return Array('result'=>0,'msg'=>$msg);
}
/* -------------------- 检验 结束 -------------------- */
//检验通过,开始处理业务流程
//更新充值表
...
//更新用户余额
...
DB::commit();
}
这里和第8步一样,都是使用的同一个接口
10.实现查询接口,作者在第4步设置的同步通知地址里使用。该地址会传递商务订单号(out_trade_no),使用该参数进行查询轮询
前端 - 调用查询接口
$(function(){
//支付宝轮询充值结果
function queryOrderAliPay() {
var out_trade_no = URL.getUrlParam('out_trade_no');
if(out_trade_no==null) return false;
load();
//设置每隔1000毫秒执行一次load() 方法 轮询订单
var myIntval = setInterval(function () {
load()
}, 500);
function load() {
$.ajax({
type: "POST",
url: "{{ route( 'filling.queryOrderAliPay')}}",
dataType: "json",
data: {'_token': '{{csrf_token()}}',out_trade_no:out_trade_no},
success: function (json) {
if(json.trade_status == 'TRADE_SUCCESS'){
setTimeout(function(){
window.location.href="/user/recharge";
clearInterval(myIntval);
},500);
}
}
});
}
}
queryOrderAliPay();
});
后端 - 实现查询功能
//查询支付宝订单
public function queryOrderAliPay(Request $request){
require_once '../app/AliPay/config.php';
//商户订单号,商户网站订单系统中唯一订单号
$out_trade_no = trim($request->out_trade_no);
//构造参数
$builder = new AlipayTradeQueryContentBuilder();
$builder->setOutTradeNo($out_trade_no);
$aop = new AlipayTradeServicePC($config);
//调用支付宝查询接口
$response = $aop->Query($builder);
$response = json_decode(json_encode($aop->query($builder)), true);
if(array_key_exists("code", $response) && $response['code']==10000){
if($response['trade_status']=="TRADE_SUCCESS" || $response['trade_status']=="TRADE_FINISHED"){
DB::beginTransaction();
$outTradeNo = $response['out_trade_no'];
$record = ...; //按商务订单号获取预生成的订单信息
$record = json_decode(json_encode($record),true);
if($record['status']==="0"){
Log::info(" 充值后轮询更新用户信息 ");
$status = $response['trade_status']=="TRADE_SUCCESS" ? 1 : ($response['trade_status']=="TRADE_FINISHED" ? 2 : 0);
$this->UserCharge(floatval($response['total_amount']),'alipay',$outTradeNo,$response['buyer_user_id'],$status,$config['seller_id'],$config['app_id']);
}
DB::commit();
return json_encode([
'return_code'=>$response["code"],
'trade_status'=>$response["trade_status"]
],true);
}
}
}
后续有时间再做完善这篇文章