今天继续总结前几天搞的支付部分,微信支付已经介绍完了,还有支付宝支付,对于这两大支付开放文档来说,不得不说,支付宝的开发文档要比微信的开发文档明朗多了,也可能是两大平台的业务不同导致的,微信的授权页,微信公众号的后台配置,对于支付宝来说,都可以省略,下面就介绍一下支付宝支付中遇到的一些问题:
一、支付宝的网页支付跟支付宝app支付服务器端可以共用一套核心包,支付宝比较费点劲的是支付宝的签名验证方式,网页支付有两md5、rsa验证,而app支付目前之支持rsa验证,导致用md5写的网页支付没法用到app支付中,所以我弃用了md5验证,统一用rsa验证。
二、这是我在yii2中支付宝核心包的目录安排,跟微信支付一样,放到了common/lib下了。
在支付宝包官方demo中,还有alipay.config.php,其中lib下的AlipayRsaSubmit.php是以页面的形式展现的,我这里做了一下合并,网页支付的控制器代码如下:
public function actionAlipay($id){//支付宝网页支付不需要任何页面,直接调用这个方法即可触发支付 /**************************商铺及商品订单信息**************************/ if (empty($id)) throw new \Exception('参数错误!'); $model = new Order(); $rs = $model->isPayResult($id); //判断是否支付 $store = new Store(); $storeInfo = $store->getFrontOne(['id'=>$rs['info']->storeid]); $storename = '小程府-'.$storeInfo->name; $yingshou = $rs['info']->yingshou; /**************************请求参数**************************/ //alipay的各种配置我都放在了yii2中的Parmas.php参数配置文件中了,包括公钥跟私钥 $alipay_config = Yii::$app->params['alipay_rsa_config']; //商户订单号,商户网站订单系统中唯一订单号,必填 $out_trade_no = $id; //订单名称,必填 $subject = $storename; //付款金额,必填 $total_fee = $yingshou; //收银台页面上,商品展示的超链接,必填 $show_url = ''; //商品描述,可空 $body = ''; /************************************************************/ //构造要请求的参数数组,无需改动 $parameter = array( "service" => $alipay_config['service'], "partner" => $alipay_config['partner'], "seller_id" => $alipay_config['seller_id'], "payment_type" => $alipay_config['payment_type'], "notify_url" => $alipay_config['notify_url'], "return_url" => $alipay_config['return_url'], "_input_charset" => trim(strtolower($alipay_config['input_charset'])), "out_trade_no" => $out_trade_no, "subject" => $subject, "total_fee" => $total_fee, "show_url" => $show_url, //"app_pay" => "Y",//启用此参数能唤起钱包APP支付宝 "body" => $body, //其他业务参数根据在线开发文档,添加参数.文档地址:https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.2Z6TSk&treeId=60&articleId=103693&docType=1 //如"参数名" => "参数值" 注:上一个参数末尾需要“,”逗号。 ); //建立请求 $alipaySubmit = new AlipayRsaSubmit($alipay_config); $html_text = $alipaySubmit->buildRequestForm($parameter,"get", "确认"); return $html_text; } public function actionCallback(){ //支付成功后的同步验证,异步回调在api中的pays里 $alipay_config = Yii::$app->params['alipay_rsa_config']; /////////////////////////////////////////////////////////////////////////// $alipayNotify = new AlipayRsaNotify($alipay_config); $verify_result = $alipayNotify->verifyReturn(); if($verify_result) {//验证成功 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //请在这里加上商户的业务逻辑程序代码 //——请根据您的业务逻辑来编写程序(以下代码仅作参考)—— //获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表 //商户订单号 $out_trade_no = $_GET['out_trade_no']; //支付宝交易号 $trade_no = $_GET['trade_no']; //交易状态 $trade_status = $_GET['trade_status']; if($_GET['trade_status'] == 'TRADE_FINISHED' || $_GET['trade_status'] == 'TRADE_SUCCESS') { //判断该笔订单是否在商户网站中已经做过处理 //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序 //如果有做过处理,不执行商户的业务程序 } else { echo "trade_status=".$_GET['trade_status']; } //这里跳转到的自定义的支付成功页 $this->redirect("http://www.xxx.cn/sdf/succeed?ordersn=$out_trade_no"); // echo "验证成功<br />"; //——请根据您的业务逻辑来编写程序(以上代码仅作参考)—— ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } else { //验证失败 //如要调试,请看alipay_notify.php页面的verifyReturn函数 echo "验证失败"; } }
注意:以上注释部分为重点,同步验证不作为最后的支付结果,在自定义支付页中的 支付成功状态是异步回调处理后的结果。
三、支付回调部分,代码如下:
public function actionNotifyzfb(){ //支付宝app跟h5的回调 file_put_contents('99.php',"<?php\r\nreturn ".var_export($_POST,true)."?>"); $alipay_config = Yii::$app->params['alipay_rsa_config']; ///////////////////////////////////////////////////////////////////////// $alipayNotify = new AlipayRsaNotify($alipay_config); $verify_result = $alipayNotify->verifyNotify(); if($verify_result) {//验证成功 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //请在这里加上商户的业务逻辑程序代 //——请根据您的业务逻辑来编写程序(以下代码仅作参考)—— //获取支付宝的通知返回参数,可参考技术文档中服务器异步通知参数列表 //商户订单号 $out_trade_no = $_POST['out_trade_no']; //支付宝交易号 $trade_no = $_POST['trade_no']; //交易状态 $trade_status = $_POST['trade_status']; if($_POST['trade_status'] == 'TRADE_FINISHED') { //判断该笔订单是否在商户网站中已经做过处理 //如果没有做过处理,根据订单号(out_trade_no)在商户网站的订单系统中查到该笔订单的详细,并执行商户的业务程序 //请务必判断请求时的total_fee、seller_id与通知时获取的total_fee、seller_id为一致的 //如果有做过处理,不执行商户的业务程序 //注意: //退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知 //调试用,写文本函数记录程序运行情况是否正常 //logResult("这里写入想要调试的代码变量值,或其他运行的结果记录"); } else if ($_POST['trade_status'] == 'TRADE_SUCCESS') { file_put_contents('100.php',"<?php\r\nreturn ".var_export($_POST,true)."?>"); // 保存支付宝支付订单流水 $WxPayRecord = new AlipayRecord(); $WxPayRecord->setAttributes($_POST); $WxPayRecord->id = 0; if($WxPayRecord->save(false) === false) throw new \WxPayException('支付记录保存失败!'); $ordersn = $_POST['out_trade_no']; $where['ordersn'] = $ordersn; $where['ispay'] = 2; $transaction = Yii::$app->db->beginTransaction(); try{ //修改订单状态 $order = new Order(); $order= $order->getOne($where); $order->ispay = Order::STATUS_PAID; $order->status = Order::STATUS_PAYED; if(!$order->save(false)) throw new \WxPayException('支付失败!'); $cashRecord = new Cashrecord(); if($cashRecord->inComes($order->ordersn) === false) throw new \WxPayException('支付金额记录失败!'); //记录支付流水 $paylog = new Paylog(); $paylog->ordersn = $ordersn; $paylog->payswiftnumber = $_POST['trade_no']; $paylog->status = $paylog::STATUS_NORMAL; $paylog->text = $_POST['payment_type']; if(!$paylog->save(false)) throw new \WxPayException('支付日志记录失败!'); $transaction->commit(); }catch (\Exception $e){ $transaction->rollBack(); return false; } } //——请根据您的业务逻辑来编写程序(以上代码仅作参考)—— echo "success"; //请不要修改或删除 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// } else { //验证失败 echo "fail"; //调试用,写文本函数记录程序运行情况是否正常 //logResult("这里写入想要调试的代码变量值,或其他运行的结果记录"); } }
以上为app支付的回调处理跟网页支付的回调,在验证方式相同的情况下,两种验证是可以共同使用的,这里都是用的rsa验证,所以可以共用一个。
四、app支付
app支付服务器端的签名处理,由于支付宝不知不觉的进行了文档的更新,跟新时间正好是在我做好网页支付,app端服务器支付还待完善的时候,结果按支付宝的demo,app端签名始终失败,最后发现文档跟新了,之后移步旧版本,自己拼接的待签名字符串,注意、字符串的拼接必须带双引号,排序必须按文档上的走。
partner="2088101568358171"&seller_id="xxx@alipay.com"&out_trade_no="0819145412-6177" &subject="测试"&body="测试测试"&total_fee="0.01"¬ify_url="http://notify.msp.hk/notify.htm" &service="mobile.securitypay.pay"&payment_type="1"&_input_charset="utf-8"&it_b_pay="30m"
我在common/lib/AliAppPay下面写了一个签名方法,代码如下:
<?php /* * * 功能:支付宝移动支付服务端签名页面 * 版本:1.0 * 日期:2016-06-06 * 说明: * 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要编写。 * 该代码仅供学习和研究支付宝接口使用,只是提供一个参考。 *************************页面功能说明************************* * 本页面代码示例用于处理客户端使用http(s) post传输到此服务端的移动支付请求参数待签名字符串。 * 本页面代码示例采用客户端创建订单待签名的请求字符串传输到服务端的这里进行签名操作并返回。 */ namespace common\lib\AliAppPay\lib; require_once("AlipayRsaNotify.php"); require_once("alipay_rsa.function.php"); require_once("alipay_core.function.php"); //确认PID和接口名称是否匹配。 class AliPaySign { function getRsaSign($parameter,$alipay_config){ date_default_timezone_set("PRC"); if (str_replace('"','',$parameter['partner'])==$alipay_config['partner']&&str_replace('"','',$parameter['service'])==$alipay_config['service']) { $data = "partner"."=".'"'.$parameter['partner'].'"'."&seller_id=".'"'.$parameter['seller_id'].'"'."&out_trade_no=".'"'.$parameter['out_trade_no'] .'"'."&subject=".'"'.$parameter['subject'].'"'."&body=".'"'.$parameter['body'].'"'."&total_fee=".'"'.$parameter['total_fee'] .'"'."¬ify_url=".'"'.$parameter['notify_url'].'"'."&service=".'"'.$parameter['service'].'"'."&payment_type=".'"'.$parameter['payment_type'] .'"'."&_input_charset=".'"'.$parameter['_input_charset'].'"'."&it_b_pay=".'"'.$parameter['it_b_pay'].'"'; //将待签名字符串使用私钥签名,且做urlencode. 注意:请求到支付宝只需要做一次urlencode. $rsa_sign = urlencode(rsaSign($data, $alipay_config['private_key'])); //把签名得到的sign和签名类型sign_type拼接在待签名字符串后面。 // $data = $data.'&sign='.'"'.$rsa_sign.'"'.'&sign_type='.'"'.$alipay_config['sign_type'].'"'; //返回给客户端,建议在客户端使用私钥对应的公钥做一次验签,保证不是他人传输。 return $rsa_sign; } else{ echo "不匹配或为空!"; logResult(createLinkstring($parameter)); } } } ?>
这一方法,进行了一下签名,然后,将支付宝要用的字符串拼接返回给app,这里我只把支付要用的字符以数组的形式返回给app的,没有进行字符串的拼接。代码如下:
public function actionAlipay($id){ //支付宝app的支付准备(本地签名与服务器签名都好的) /**************************商铺及商品订单信息**************************/ if (empty($id)) throw new \Exception('参数错误!'); $model = new Order(); $rs = $model->isPayResult($id); //判断是否支付 $store = new Store(); $storeInfo = $store->getFrontOne(['id'=>$rs['info']->storeid]); $storename = '饭小二点餐网-'.$storeInfo->name; $yingshou = $rs['info']->yingshou; /**************************请求参数**************************/ $alipay_config = Yii::$app->params['alipay_rsa_config']; //商户订单号,商户网站订单系统中唯一订单号,必填 $out_trade_no = $id; //订单名称,必填 $subject = $storename; //付款金额,必填 $total_fee = $yingshou; //收银台页面上,商品展示的超链接,必填 $show_url = ''; //商品描述,可空 $body = ''; /************************************************************/ // 代签名数据 $parameter = array( "service" => $alipay_config['service'], "partner" => $alipay_config['partner'], "_input_charset" => trim(strtolower($alipay_config['input_charset'])), 'sign_type' => 'RSA', "notify_url" => urlencode($alipay_config['notify_url']), "out_trade_no" => $out_trade_no, "subject" => $subject, "payment_type" => $alipay_config['payment_type'], "seller_id" => $alipay_config['seller_id'], "total_fee" => $total_fee, "body" => $subject, 'it_b_pay'=>"30m", // "show_url" => $show_url, //"app_pay" => "Y",//启用此参数能唤起钱包APP支付宝 //'private_key' =>$alipay_config['private_key'], //其他业务参数根据在线开发文档,添加参数.文档地址:https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.2Z6TSk&treeId=60&articleId=103693&docType=1 //如"参数名" => "参数值" 注:上一个参数末尾需要“,”逗号。 ); $rsaSign = new AliPaySign(); $rsaSign = $rsaSign->getRsaSign($parameter,$alipay_config); $sign = [ //以下顺序不可变 'partner'=>$parameter['partner'], 'seller_id'=>$parameter['seller_id'], 'out_trade_no'=>$parameter['out_trade_no'], 'subject'=>$parameter['subject'], 'body'=>$parameter['subject'], 'total_fee'=>$parameter['total_fee'], 'notify_url'=>$parameter['notify_url'], 'service'=>$parameter['service'], 'payment_type'=>$parameter['payment_type'], '_input_charset'=>$parameter['_input_charset'], 'it_b_pay'=>"30m", 'sign'=>$rsaSign, 'sign_type'=>"RSA" ]; return $this->ajaxReturn($sign); }
五、注意,公钥跟私钥,私钥用户支付宝的签名,而公钥则用于支付宝的回调,在写的过程中,配置公钥跟私钥这一块遇到了问题,就是以文件的实现存储公钥和私钥,没有找到合适的方法引入文件,所有这里统一放到了params.php配置中了,这了我把支付宝正常使用的包贡献出来,绝对可以正常使用,百度云链接: https://pan.baidu.com/s/1i5yNeaL 密码: 2jtm
好了,支付博客就到这里了,遇到问题了,可以qq联系我!