{ errcode: 200, msg: "SUCCESS", data: { status: 1, //狀態,爲1表示成功,其餘的表示失敗 result: "success", data: { appId: "xxx", //小程序的appid timeStamp: 1545909092, //時間戳 nonceStr: "vuryhptlafvpee92pxhji6zs5jl2n0gu", //隨機串 package: "prepay_id=wx27191130962951f060bfa1323531879649", //支付的包參數 signType: "MD5", //簽名方式 paySign: "B04272BB9BBDB1F52863D3B0EF580BE8" //支付簽名 } } }
2) 微信支付回調接口,http://test.dev.com/wechat/pa... ,此接口最好是get和post都設置,由於 微信在進行回調的時候會以post的形式進行請求php
5.建表
1) 商品訂單表(shop_goods_order),其中重要的字段有out_trade_no,out_trade_no傳遞給微信支付的支付訂單號,也是咱們本身的系統與微信對接的訂單惟一標識;bill_no表示微信支付的交易訂單號,這個字段只有在訂單支付成功以後進行更新,該字段也是查詢位置支付訂單的惟一標識,詳細的表結構以下前端
CREATE TABLE `shop_goods_order` ( `id` int(10) NOT NULL AUTO_INCREMENT, `uid` int(10) DEFAULT '0' COMMENT '用戶編號', `goods_id` int(10) DEFAULT '0' COMMENT '商品編號', `out_trade_no` varchar(30) DEFAULT '' COMMENT '訂單序列號', `bill_no` varchar(30) DEFAULT '' COMMENT '支付方返回的交易訂單號', `paid_money` int(10) DEFAULT '0' COMMENT '支付的金額', `paid_integral` int(10) DEFAULT '0' COMMENT '支付的健康幣', `paid_type` varchar(15) DEFAULT 'WXPAY' COMMENT '支付類型,有WXPAY和INTEGRAL等值', `paid_status` varchar(10) DEFAULT 'CHECKED' COMMENT '支付狀態,CHECKED表示初始狀態,SUCC表示支付成功,FAILED表示支付失敗,REFUND表示已退款', `add_time` int(10) DEFAULT '0' COMMENT '添加時間', `paid_time` int(10) DEFAULT '0' COMMENT '支付時間', `update_time` int(10) DEFAULT '0' COMMENT '更新時間', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;
2) 商品信息表(shop_goods_info),字段以下redis
CREATE TABLE `shop_goods_info` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵', `name` varchar(100) DEFAULT '' COMMENT '商品名稱', `note` varchar(300) DEFAULT '' COMMENT '商品描述', `market_price` int(10) DEFAULT '0' COMMENT '原價', `sale_price` int(10) DEFAULT '0' COMMENT '售價', `integral` int(8) DEFAULT '0' COMMENT '健康幣', `main_thumbnail` varchar(40) DEFAULT '' COMMENT '主圖', `thumbnail1` varchar(40) DEFAULT '' COMMENT '縮略圖1', `thumbnail2` varchar(40) DEFAULT '' COMMENT '縮略圖2', `thumbnail3` varchar(40) DEFAULT '' COMMENT '縮略圖3', `thumbnail4` varchar(40) DEFAULT '' COMMENT '縮略圖4', `thumbnail5` varchar(40) DEFAULT '' COMMENT '縮略圖5', `content` text COMMENT '詳細介紹', `add_time` int(10) DEFAULT '0' COMMENT '添加時間', `update_time` int(10) DEFAULT '0' COMMENT '更新時間', `is_online` tinyint(1) DEFAULT '1' COMMENT '商品是否上線', `sort` int(4) DEFAULT '0' COMMENT '排序值,越大越靠前', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
wxPay:function () { var that = this var params = { goods_id: that.data.goods_id, uid: that.data.uid, paid_type: 'WXPAY' } var param = JSON.stringify(params) console.log(param) param = app.Encrypt(param) var url = app.data.API_DOMAIN + "/wechat/prepay?param=" + param wx.showModal({ title: '提示', content: '肯定要微信支付購買此係列課嗎?', success(res) { if (res.confirm) { if (that.data.iswxpay == 0) { that.setData({ iswxpay: 1 }) app.httpRequest(that.data.uid, url, function (response) { var payinfo = response.data.data.data wx.requestPayment({ timeStamp: payinfo.timeStamp.toString(), nonceStr: payinfo.nonceStr, package: payinfo.package, signType: 'MD5', paySign: payinfo.paySign, success(res) { wx.showToast({ title: '購買成功', icon: 'success' }) that.setData({ is_paid: 1 }) that.getSeminarInfo(that.data.sid, that.data.uid) }, fail(res) { that.setData({ iswxpay: 0 }) wx.showToast({ title: '購買失敗', icon: 'none' }) } }) console.log(response.data.data.data) }, function (f_res) { }, function (f_res) { }) } } else { that.setData({ iswxpay: 0 }) console.log('取消微信支付') } } }) },
一、入口方法:orderPay算法
/** * 微信支付的獲取支付參數的接口 * 1.先要用戶編號和支付方式獲取對應的訂單,若是存在則取存在的,若不存在則建立,一種支付類型的訂單值存在一條記錄 * 2.建立訂單後根據out_trade_no來調用微信支付的統一下單接口獲得微信支付的支付參數 * 3.將參數返回給前端進行支付 * 4.支付成功以後進行回掉 */ public function orderPay($uid, $goodsId, $paidType){ $result = []; $lockKey = BusinessHelper::getPayOrderLockRedisKey(); //枷鎖是爲了防止併發 $this->doWithLock(function()use(&$result,$uid,$goodsId,$paidType){ error_log('$paidType ================>'.$paidType); switch ($paidType){ case Constant::PAID_TYPE_MIXED : error_log('doIntegralPay ================>'); $result = $this->doMixedPay($uid,$goodsId,$paidType); error_log('integral pay result ================>'.json_encode($result)); break; case Constant::PAID_TYPE_WXPAY : $result = $this->doWxaPay($uid,$goodsId,$paidType); error_log('wx pay result ================>'.json_encode($result)); break; } },$lockKey,5); error_log('result ================>'.json_encode($result)); return $result; }
二、微信核心支付方法:doWxaPayjson
/** * 經過小程序支付的邏輯 * @param $uid 用戶編號 * @param $goodsId 系列課編號 * @param $paidType 支付類型,有INTEGRAL和WXPAY兩種 * @return array */ public function doWxaPay($uid, $goodsId, $paidType){ $goodsInfo = ShopGoodsInfoService::getById($goodsId); if(!$goodsInfo){ return [ 'status' => -1, 'result' => '商品已經下架或者不存在' ]; } $config = BusinessHelper::getWechatPayConfig(); $payHelper = new WechatPayHelper($config); $payContent = $this->getWxaPrepayContent($uid,$paidType,$goodsId); $params = $payHelper->prepay($payContent); error_log('param ==============>'.json_encode($params)); return $params; }
三、建立訂單方法:createOrder
這個方法是爲了創建訂單,爲了保證表示每一次支付都創建一個訂單,我這邊作兩重的訂單複用,先根據訂單狀態去查詢是否有待支付的訂單,若是有在判斷這個訂單的差功能鍵時間是否已經超過7天,若是超過七天則另外建立新的訂單,盡最大的進行數據複用小程序
/** * 建立和驗證訂單,接口方法 * @param $uid 用戶編號 * @param $paidType 支付類型 * @param $goodsId 系列課編號 * @return array */ protected function createOrder($uid, $paidType, $goodsId){ $existOrder = $this->getUserGoodsOrderWithPaidType($uid,$paidType,$goodsId); if(!$existOrder){ return $this->generateOrder($uid,$paidType,$goodsId); } //驗證7天之類訂單有效 $createTime = date('Y-m-d',$existOrder['add_time']); $today = date('Y-m-d'); $diff = TimeHelper::getDiffBetweenTwoDays($today,$createTime); if($diff > 7){ return $this->generateOrder($uid,$paidType,$goodsId); } return $existOrder; }
四、訂單查重方法:getUserGoodsOrderWithPaidType微信小程序
/** * 根據支付類型獲取用戶對應的商品的訂單 */ public function getUserGoodsOrderWithPaidType($uid, $paidType, $goodsId){ $order = BeanHelper::convertStdClsToArr( ShopGoodsOrder::where('uid', $uid) ->where('goods_id',$goodsId) ->where('paid_type',$paidType) ->whereIn('paid_status',[Constant::PAID_STATUS_CHECKED]) ->orderBy('add_time','desc') ->first() ); return $order; }
五、生成訂單方法:api
/** * 生成訂單,輔助方法 * @param $uid 用戶編號 * @param $paidType 支付類型 * @param $goodsId 系列課編號 * @return array */ public function generateOrder($uid, $paidType, $goodsId){ $goodsInfo = ShopGoodsInfoService::getById($goodsId); $priceKey = $paidType == Constant::PAID_TYPE_WXPAY ? 'market_price' : 'sale_price'; $price = formatArrValue($goodsInfo,$priceKey,0); $integral = $paidType == Constant::PAID_TYPE_WXPAY ? 0 : formatArrValue($goodsInfo,'integral',0); $baseMeasureUnit = 100; $insertOrderData = [ 'uid' => $uid, 'goods_id' => $goodsId, 'out_trade_no' => BusinessHelper::generateOutTradeNo(Constant::PAID_SCENE_SHOP_GOODS_ORDER), 'paid_money' => $price * $baseMeasureUnit, 'paid_integral' => $integral, 'paid_type' => $paidType, 'paid_status' => Constant::PAID_STATUS_CHECKED, 'add_time' => time(), 'update_time' => time(), ]; $existOrder = BeanHelper::convertStdClsToArr($this->store($insertOrderData)); return $existOrder; }
六、生成outTradeNo方法
這個方法中的getPaidSceneMapping方法返回的是一個數組,out_trade_no方法有3個部分組成,分別是當前時間,場景值(這個是爲了保證不一樣的支付場景對應的不一樣的業務代碼)以及10位隨機數字組成數組
/** * 生成第三方支付的外部訂單號 */ public static function generateOutTradeNo($paidScene = Constant::PAID_SCENE_SEMINAR_ORDER){ $prefix = date('YmdHis'); $paidSceneMap = self::getPaidSceneMapping(); $scene = formatArrValue($paidSceneMap,$paidScene,'0001'); $suffix = generateRandomNum(10); return $prefix.$scene.$suffix; } /** * 獲取支付場景的map,這個是爲了區分不一樣的支付場景時候更新不一樣的業務字段,爲了拓展進行的預留 */ public static function getPaidSceneMapping(){ return [ Constant::PAID_SCENE_SEMINAR_ORDER => '0001', Constant::PAID_SCENE_SHOP_GOODS_ORDER => '0002' ]; }
入口方法:payNotify服務器
/** * 支付的回掉 */ public function payNotify(Request $request){ error_log('notify request param ========>'); $config = BusinessHelper::getWechatPayConfig(); $helper = new WechatPayHelper($config); $result = $helper->notify($request); return $result; }
<?php namespace App\Http\Helper\Pay; use App\Http\Helper\Jz\BusinessHelper; use App\Http\Helper\Jz\Constant; use App\Http\Helper\LogHelper; use SeminarOrderService; use ShopGoodsOrderService; /** * Created by PhpStorm. * User: Auser * Date: 2018/12/17 * Time: 15:41 */ class WechatPayHelper { public $config; public function __construct($config) { $this->config = $config; } /** * 預支付請求接口(POST) * 返回json的數據 */ public function prepay($payContent) { $config = $this->config; $unifiedorder = [ 'appid' =>$config['appid'], 'mch_id' =>$config['mchid'], 'nonce_str' =>self::getNonceStr(), 'body' =>$payContent['body'], 'out_trade_no' =>$payContent['out_trade_no'], 'total_fee' =>$payContent['fee'], 'spbill_create_ip'=>$_SERVER['REMOTE_ADDR'], 'notify_url' =>$config['notify_url'], 'trade_type' =>'JSAPI', 'openid' =>$payContent['openid'] ]; error_log('config ===============>'.json_encode($config)); $unifiedorder['sign'] = $this->makeSign($unifiedorder); error_log('unifine order param ===============>'.json_encode($unifiedorder)); //請求數據 $xmldata = $this->array2xml($unifiedorder); $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; $res = $this->request($url, $xmldata); if(!$res){ return $this->errorResult("Can't connect the server"); } $content = $this->xml2array($res); error_log('unifine order result ===============>'.json_encode($content)); if(strval($content['result_code']) == 'FAIL'){ return $this->errorResult(strval($content['return_msg'])); } if(strval($content['return_code']) == 'FAIL'){ return $this->errorResult(strval($content['return_msg'])); } //拼接小程序的接口數據 $resData = [ 'appId' => strval($content['appid']), 'timeStamp' => time(), 'nonceStr' => $this->getNonceStr(), 'package' => 'prepay_id='.strval($content['prepay_id']), 'signType' => 'MD5' ]; //加密簽名 $resData['paySign'] = $this->makeSign($resData); return $this->successResult($resData); } /** * @return array|bool * 微信支付回調驗證 * 返回數據 */ public function notify(){ //$xml = $GLOBALS['HTTP_RAW_POST_DATA']; error_log("wechat pay notify message ============>"); $xml = file_get_contents('php://input'); //將服務器返回的XML數據轉化爲數組 $data = $this->xml2array($xml); // 保存微信服務器返回的簽名sign $dataSign = $data['sign']; // sign不參與簽名算法 unset($data['sign']); $sign = $this->makeSign($data); // 判斷簽名是否正確 判斷支付狀態 $result = false; error_log("return data ============>".json_encode($data)); //驗證訂單是否已經支付,調用訂單查詢接口 $isPayment = $this->verifyPament($data); error_log("isPayment ============>".$isPayment); if($isPayment && ($data['return_code']=='SUCCESS') && ($data['result_code']=='SUCCESS')) { error_log("isPayment success============>"); $outTradeNo = $data['out_trade_no']; $concurrentTime = 30; $lockKey = getCacheKey('redis_key.cache_key.zset_list.lock') . $outTradeNo; //採用併發鎖控制併發 SeminarOrderService::doWithLock(function()use(&$result , $data){ $result = $data; $this->setPaidSuccess($data); },$lockKey,$concurrentTime); }else{ error_log("isPayment failed============>"); $this->setPaidFail($data); } // 返回狀態給微信服務器 if($result){ $str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'; }else { $str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[簽名失敗]]></return_msg></xml>'; } return $str; } /** * 支付成功 */ public function setPaidSuccess($data){ error_log('current paid data =============>'.json_encode($data)); $paidType = substr($data['out_trade_no'], 14, 4); error_log('current paid type is =============>'.$paidType); switch ($paidType){ case '0001' : SeminarOrderService::setOrderPaid($data); break; case '0002': ShopGoodsOrderService::setOrderPaid($data); break; } } /** * 支付失敗 */ public function setPaidFail($data){ $paidType = intval(substr($data['out_trade_no'], 14, 4)); LogHelper::info('current paid type is =============>'.$paidType); switch ($paidType){ case '0001' : SeminarOrderService::setOrderPaidFailed($data); break; case '0002': ShopGoodsOrderService::setOrderPaidFailed($data); break; } } /** * 驗證支付的問題 */ public function verifyPament($wxPayResp){ error_log("verify paymnent method=======>".json_encode($wxPayResp)); $url = "https://api.mch.weixin.qq.com/pay/orderquery"; //檢測必填參數 if(!$wxPayResp['transaction_id'] && !$wxPayResp['out_trade_no']) { error_log("訂單查詢接口中,out_trade_no、transaction_id至少填一個!"); return false; } error_log("開始查詢==============》接口"); $config = BusinessHelper::getWechatPayConfig(); error_log("post config ==============》".json_encode($config)); error_log("transaction is===============>".$wxPayResp['transaction_id']); error_log("appid is===============>".$config['appid']); error_log("transaction is===============>".$config['mchid']); error_log("nonce_string is===============>".$this->getNonceStr()); $params = [ 'appid' => $config['appid'], 'mch_id' => $config['mchid'], 'nonce_str' => $this->getNonceStr(), 'transaction_id' => $wxPayResp['transaction_id'] ]; error_log("post PARAM without sign==============》"); $params['sign'] = $this->makeSign($params); error_log("post PARAM0 with sign ==============》"); $xmlData = $this->array2xml($params); $response = $this->request($url,$xmlData); if(!$response){ error_log("接口請求錯誤:"); return false; } $result = $this->xml2array($response); error_log("查詢訂單接口返回結果:".json_encode($result)); if(array_key_exists("return_code", $result) && array_key_exists("trade_state", $result) && $result["return_code"] == "SUCCESS" && $result["trade_state"] == "SUCCESS"){ return true; } return false; } //---------------------------------------------------------------用到的函數------------------------------------------------------------ /** * 錯誤返回提示 * @param string $errMsg 錯誤信息 * @param string $status 錯誤碼 * @return array json的數據 */ protected function errorResult($errMsg = 'error', $status = Constant::PAID_RESULT_FAILED) { return [ 'status'=>$status, 'result'=>'fail', 'data'=>$errMsg ]; } /** * 正確返回 * @param array $data 要返回的數組 * @return array json的數據 */ protected function successResult($data=[]){ return [ 'status'=> Constant::PAID_RESULT_SUCCESS, 'result'=>'success', 'data'=>$data ]; } /** * 將一個數組轉換爲 XML 結構的字符串 * @param array $arr 要轉換的數組 * @param int $level 節點層級, 1 爲 Root. * @return string XML 結構的字符串 */ protected function array2xml($arr, $level = 1){ $s = $level == 1 ? "<xml>" : ''; foreach($arr as $tagname => $value) { if (is_numeric($tagname)) { $tagname = $value['TagName']; unset($value['TagName']); } if(!is_array($value)) { $s .= "<{$tagname}>".(!is_numeric($value) ? '<![CDATA[' : '').$value.(!is_numeric($value) ? ']]>' : '')."</{$tagname}>"; }else { $s .= "<{$tagname}>" . $this->array2xml($value, $level + 1)."</{$tagname}>"; } } $s = preg_replace("/([\x01-\x08\x0b-\x0c\x0e-\x1f])+/", ' ', $s); return $level == 1 ? $s."</xml>" : $s; } /** * 將xml轉爲array * @param string $xml xml字符串 * @return array 轉換獲得的數組 */ protected function xml2array($xml) { //禁止引用外部xml實體 libxml_disable_entity_loader(true); $result= json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return $result; } /** * * 產生隨機字符串,不長於32位 * @param int $length * @return 產生的隨機字符串 */ protected function getNonceStr($length = 32){ $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; $str =""; for ( $i = 0; $i < $length; $i++ ) { $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1); } return $str; } /** * 生成簽名 * @return 簽名 */ protected function makeSign($data){ //獲取微信支付祕鑰 $key = $this->config['mch_secret']; //去空 $data = array_filter($data); //簽名步驟一:按字典序排序參數 ksort($data); $signParam = http_build_query($data); $signParam = urldecode($signParam); //簽名步驟二:在string後加入KEY $signContent = $signParam."&key=".$key; //簽名步驟三:MD5加密 $sign = md5($signContent); // 簽名步驟四:全部字符轉爲大寫 $result=strtoupper($sign); return $result; } /** * 微信支付發起請求 */ protected function request($url, $xmldata, $second=30, $aHeader=array()){ $ch = curl_init(); //超時時間 curl_setopt($ch,CURLOPT_TIMEOUT,$second); curl_setopt($ch,CURLOPT_RETURNTRANSFER, 1); //這裏設置代理,若是有的話 //curl_setopt($ch,CURLOPT_PROXY, '10.206.30.98'); //curl_setopt($ch,CURLOPT_PROXYPORT, 8080); curl_setopt($ch,CURLOPT_URL,$url); curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false); curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false); if( count($aHeader) >= 1 ){ curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader); } curl_setopt($ch,CURLOPT_POST, 1); curl_setopt($ch,CURLOPT_POSTFIELDS,$xmldata); $data = curl_exec($ch); if($data){ curl_close($ch); return $data; } else { $error = curl_errno($ch); echo "call faild, errorCode:$error\n"; curl_close($ch); return false; } } }
一、支付回調接口http://test.dev.com/wechat/pa... 必定要設置成get、post都能訪問,我當初只設置了get請求能夠訪問,浪費了好多時間進行排查,而微信回調的數據基本都是以post形式進行調用的