前提须知
实现
JsApi
支付前先要重新获取下微信支付获取配置信息
//先验签 然后做逻辑
$appId = Config::get('app.WX_WEB_ID');
$appSecret = Config::get('app.WX_WEB_SECRET');
require($_SERVER['DOCUMENT_ROOT'].'/../vendor/simple/jssdk.php');//文件在SDK中
$jssdk = new \JSSDK($appId, $appSecret);//JSSDK中的参数是appid和appsecret。
$signPackage = $jssdk->GetSignPackage();//获取配置参数
return json(['code' => 200, 'data' => $signPackage, 'tips' => '']);
- 统一下单部分(APP和JSAPI):
!!!! app统一下单
openID不是必填参数,当有微信登录的时候,可以利用存在本地的openID作为参数,如果没有微信登录,我们可以不用传openID,我们也能顺利下单,产生会话id:prepay_id
,不要因此有什么疑惑。但是JSAPI统一下单,openID是必要参数,届时我们可以用code去获取openID信息,到时候具体问题具体分析。
$WxPayConfig = Config::get('app.WECHAT_PAY_CONFIG');
$jsApiPayConfig = Config::get('app.WECHAT_JSAPI_PAY_CONFIG');
$title = $data['title'];
$return_url = 'http://'.$_SERVER['HTTP_HOST'].'/api/v2/payment/wechatNotify';// 异步链接
$payData['appid'] = $WxPayConfig['APPID']; //应用ID
$payData['mch_id'] = $WxPayConfig['MCHID']; //商户号
$payData['nonce_str'] = md5(rand(100000,999999)); //随机字符串
$payData['sign_type'] = 'MD5'; //签名类型
$payData['body'] = $data['body']; //商品描述
$payData['out_trade_no'] = $data['out_trade_no']; //商户订单号
//$payData['fee_type'] = 'CNY'; //货币类型
$payData['total_fee'] = $data['total_fee'] * 100; //总金额(金额计数单位为:分)
//$payData['total_fee'] = 1; //总金额(金额计数单位为:分)
$payData['spbill_create_ip'] = '127.0.0.1'; //终端IP
$payData['notify_url'] = $return_url; //通知地址
$payData['trade_type'] = $tradeType; //交易类型
$signKey = $WxPayConfig['KEY'];
if($tradeType == 'JSAPI'){
$payData['appid'] = $jsApiPayConfig['APPID'];
$payData['mch_id'] = $jsApiPayConfig['MCHID'];
$payData['openid'] = $openId;
$signKey = $jsApiPayConfig['KEY'];
}
$payData['sign'] = $this->getWeixinSign($payData,$signKey); //签名
$xml = "<xml>";
foreach ($payData as $key=>$val)
{
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
$url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
$ch = curl_init();
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch,CURLOPT_URL, $url);
// curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
// curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
//设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
//post提交方式
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
//运行curl
$data = curl_exec($ch);
//返回结果
if($data){
curl_close($ch);
$wxReturn = json_decode(json_encode(simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
$prepay_id = $wxReturn['prepay_id'];
if(!$prepay_id){
//json_echo(1,"系统错误");exit;
$json['code'] = 400;
$json['_data_pay'] = $payData;
$json['data'] = $data;
$json['tips'] = $wxReturn['return_code'].$wxReturn['return_msg'];
return $json;
}
} else {
$error = curl_errno($ch);
curl_close($ch);
$json['tips'] = $error;
$json['code'] = 400;
return $json;
}
$wechat = array();
if($tradeType == 'JSAPI'){
//重中之重!!!!!!!网页端接口请求参数列表(参数需要重新进行签名计算,参与签名的参数为:appId、timeStamp、nonceStr、package、signType,参数区分大小写。) 大小写一定要一个字母不能错
$wechat['appId'] = $wxReturn['appid'];
$wechat['timeStamp'] = time();
$wechat['nonceStr'] = $wxReturn['nonce_str'];
$wechat['package'] = 'prepay_id='.$prepay_id;
$wechat['signType'] = 'MD5';
}else{//参与签名的字段名为appid,partnerid,prepayid,noncestr,timestamp,package。注意:package的值格式为Sign=WXPay
$wechat['appid'] = $wxReturn['appid'];
$wechat['partnerid'] = $wxReturn['mch_id']; //商户号
$wechat['prepayid'] = $prepay_id;
$wechat['package'] = 'Sign=WXPay';
$wechat['noncestr'] = $wxReturn['nonce_str'];
$wechat['timestamp'] = time();
}
$wechat['sign'] = $this->getWeixinSign($wechat,$signKey);
if($tradeType == 'APP'){
unset($wechat['package']);
}
$wechat['package_value'] = 'Sign=WXPay';
$wechat['result_scheme'] = $scheme;//scheme不参与加密 加密参数为强制几个参数
$json['code']= 200;
$json['data']= $wechat;
//记录日志
save_payment_log('wechat', $title,'unifiedorder', json_encode($json, FILE_APPEND));
return $json;
tips:
微信支付,同一订单修改价格后,重新提交订单号重复错误(201 商户订单号重复 解决方案
)
举个栗子:
1、表内订单A为110110110110;
2、提交下单请求时,订单A拼接随机参数,如110110110110_0123;
3、接收返回信息,剔除订单号随机部分 _0123,得到表内订单A为110110110110;
4、该干嘛干嘛。
注意:
最好记录提交到微信的单号 110110110110_0123,可能后续要进入微信后台进行财务查对。
- 支付回调部分
微信支付回调部分传值是通过
XML类型数据传递
,所以接收到数据之后需要XML解析!
//处理微信支付回调
$testxml = file_get_contents("php://input"); //接收微信发送的支付成功信息
$result = XMLDataParse($testxml);
/*$result = array(
"appid" => "wx2421b1c4370ec43b",
"attach" => "支付测试",
"bank_type" =>"CFT",
"fee_type" => "CNY",
"is_subscribe" => "Y",
"mch_id" => "10000100",
"nonce_str" => "5d2b6c2a8db53831f7eda20af46e531c",
"openid" => "oUpF8uMEb4qRXf22hE3X68TekukE",
"out_trade_no" => "s129929113168",
"result_code" => "SUCCESS",
"return_code" => "SUCCESS",
"sign" => "B552ED6B279343CB493C5DD0D78AB241",
"sub_mch_id" => "10000100",
"time_end" => "20140903131540",
"total_fee" => "1",
"coupon_fee" => "10",
"coupon_count" => "1",
"coupon_type" => "CASH",
"coupon_id" => "10000",
"coupon_fee_0" => "100",
"trade_type" => "JSAPI",
"transaction_id" => "1004400740201409030005092168",
);*/
save_payment_log('wechat', '微信支付回调开始','weChatNotify', json_encode($result, FILE_APPEND));
$sign_return = $result['sign'];
//如果成功返回了
if($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
if($result['trade_type'] == 'JSAPI'){
$jsApiPayConfig = Config::get('app.WECHAT_JSAPI_PAY_CONFIG');
$signKey = $jsApiPayConfig['KEY'];
}else{
$WxPayConfig = Config::get('app.WECHAT_PAY_CONFIG');
$signKey = $WxPayConfig['KEY'];
}
unset($result['sign']);
$sign = $this->getWeixinSign($result, $signKey);
if ($sign == $sign_return) {
//=================================验证签名后逻辑start===================================
$payments = Db::table('payments')
->where('trade_no', $result['out_trade_no'])
->order('created_at', 'desc')
->select();
if($payments){
foreach ($payments as $payment){
if($payment['paid']){//如果 paid为1 则已经完成
save_payment_log('wechat', '微信支付回调结束', 'weChatNotify', '支付单号为' . $result['out_trade_no'] . '支付回调失败:之前 已经有交易完成记录了!');
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
echo $str;exit();
}
}
$status = $this->hadPaidChangeOrderStatus($result, 'wechat');//判断是否已经处理过(避免因为重复回调过程中出现问题)
if($status){
save_payment_log('wechat', '微信支付回调结束', 'weChatNotify', '支付单号为' . $result['out_trade_no'] . '支付回调成功:回调逻辑处理完毕!');
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
echo $str;exit();
}
save_payment_log('wechat', '微信支付回调结束', 'weChatNotify', '支付单号为' . $result['out_trade_no'] . '支付回调成功:订单状态修改失败!');
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
echo $str;exit();
}else{
save_payment_log('wechat', '微信支付回调结束', 'weChatNotify', '支付单号为' . $result['out_trade_no'] . '支付回调成功:未找到预支付单!');
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
echo $str;exit();
}
//=================================验证签名后逻辑end=====================================
}
save_payment_log('wechat', '微信支付回调结束', 'weChatNotify', '支付单号为' . $result['out_trade_no'] . '支付回调成功:sign验证失败!');
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
echo $str;exit();
}
save_payment_log('wechat', '微信支付回调结束', 'weChatNotify', '支付单号为' . $result['out_trade_no'] . '支付回调成功:支付失败!');
$str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
echo $str;exit();
tips微信加密算法
:
/**
* @do 微信签名加密
* @param 数据参数 加密key
* @return 加密完数据
*/
private function getWeixinSign($data,$key){
ksort($data);
$buff = "";
foreach ($data as $k => $v){
if($k != "sign" && $v != "" && !is_array($v)){
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&") . "&key=".$key;
$string = md5($buff);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}