微信支付Native扫码支付模式二之CodeIgniter集成篇
http://www.cnblogs.com/24la/p/wxpay-native-qrcode-codeigniter.htmlCI:3.0.5
微信支付API类库来自:https://github.com/zhangv/wechat-pay
请先看一眼官方场景及支付时序图:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
官方API列表:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
二维码生成类库:phpqrcode
走了几天的弯路,直到遇到Lamtin指点(热心网友),他说你既然是集成到CI为何不自己写,我想了想是啊,为什么我一直陷入官方sdk的漩涡里不能跳出来去看这件事,官方提供了API接口,你只需要去调用这些接口啊,post参数啊,是吧,后悔浪费了3天时间。为了不让你们和我一样我把我的思路及代码发布出来,有什么问题可以留言。
我们为什么使用三方支付类库?
纵观微信支付的sdk或者其他的微信支付demo,或多或少的都是围绕官方API接口来写,增加些自己用的方法方便调用之类的,而如果我自己再去写这样的一个东西,第一可能组织不好,基础弱啊,第二可能需要花费大量时间,鉴于此我去寻找比较好用的别人封装的API类库好了,终于不负所望,真有,只可以这个类库几乎没有人用,不过真不错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
class WechatPay {
const TRADETYPE_JSAPI = 'JSAPI' ,TRADETYPE_NATIVE = 'NATIVE' ,TRADETYPE_APP = 'APP' ;
const URL_UNIFIEDORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder" ;
const URL_ORDERQUERY = "https://api.mch.weixin.qq.com/pay/orderquery" ;
const URL_CLOSEORDER = 'https://api.mch.weixin.qq.com/pay/closeorder' ;
const URL_REFUND = 'https://api.mch.weixin.qq.com/secapi/pay/refund' ;
const URL_REFUNDQUERY = 'https://api.mch.weixin.qq.com/pay/refundquery' ;
const URL_DOWNLOADBILL = 'https://api.mch.weixin.qq.com/pay/downloadbill' ;
const URL_REPORT = 'https://api.mch.weixin.qq.com/payitil/report' ;
const URL_SHORTURL = 'https://api.mch.weixin.qq.com/tools/shorturl' ;
const URL_MICROPAY = 'https://api.mch.weixin.qq.com/pay/micropay' ;
/**
* 错误信息
*/
public $error = null;
/**
* 错误信息XML
*/
public $errorXML = null;
/**
* 微信支付配置数组
* appid 公众账号appid
* mch_id 商户号
* apikey 加密key
* appsecret 公众号appsecret
* sslcertPath 证书路径(apiclient_cert.pem)
* sslkeyPath 密钥路径(apiclient_key.pem)
*/
private $_config ;
/**
* @param $config 微信支付配置数组
*/
public function __construct( $config ) {
$this ->_config = $config ;
}
/**
* JSAPI获取prepay_id
* @param $body
* @param $out_trade_no
* @param $total_fee
* @param $notify_url
* @param $openid
* @return null
*/
public function getPrepayId( $body , $out_trade_no , $total_fee , $notify_url , $openid ) {
$data = array ();
$data [ "nonce_str" ] = $this ->get_nonce_string();
$data [ "body" ] = $body ;
$data [ "out_trade_no" ] = $out_trade_no ;
$data [ "total_fee" ] = $total_fee ;
$data [ "spbill_create_ip" ] = $_SERVER [ "REMOTE_ADDR" ];
$data [ "notify_url" ] = $notify_url ;
$data [ "trade_type" ] = self::TRADETYPE_JSAPI;
$data [ "openid" ] = $openid ;
$result = $this ->unifiedOrder( $data );
if ( $result [ "return_code" ] == "SUCCESS" && $result [ "result_code" ] == "SUCCESS" ) {
return $result [ "prepay_id" ];
} else {
$this ->error = $result [ "return_code" ] == "SUCCESS" ? $result [ "err_code_des" ] : $result [ "return_msg" ];
$this ->errorXML = $this ->array2xml( $result );
return null;
}
}
private function get_nonce_string() {
return substr ( str_shuffle ( "abcdefghijklmnopqrstuvwxyz0123456789" ),0,32);
}
/**
* 统一下单接口
*/
public function unifiedOrder( $params ) {
$data = array ();
$data [ "appid" ] = $this ->_config[ "appid" ];
$data [ "mch_id" ] = $this ->_config[ "mch_id" ];
$data [ "device_info" ] = (isset( $params [ 'device_info' ])&&trim( $params [ 'device_info' ])!= '' )? $params [ 'device_info' ]:null;
$data [ "nonce_str" ] = $this ->get_nonce_string();
$data [ "body" ] = $params [ 'body' ];
$data [ "detail" ] = isset( $params [ 'detail' ])? $params [ 'detail' ]:null; //optional
$data [ "attach" ] = isset( $params [ 'attach' ])? $params [ 'attach' ]:null; //optional
$data [ "out_trade_no" ] = isset( $params [ 'out_trade_no' ])? $params [ 'out_trade_no' ]:null;
$data [ "fee_type" ] = isset( $params [ 'fee_type' ])? $params [ 'fee_type' ]: 'CNY' ;
$data [ "total_fee" ] = $params [ 'total_fee' ];
$data [ "spbill_create_ip" ] = $params [ 'spbill_create_ip' ];
$data [ "time_start" ] = isset( $params [ 'time_start' ])? $params [ 'time_start' ]:null; //optional
$data [ "time_expire" ] = isset( $params [ 'time_expire' ])? $params [ 'time_expire' ]:null; //optional
$data [ "goods_tag" ] = isset( $params [ 'goods_tag' ])? $params [ 'goods_tag' ]:null;
$data [ "notify_url" ] = $params [ 'notify_url' ];
$data [ "trade_type" ] = $params [ 'trade_type' ];
$data [ "product_id" ] = isset( $params [ 'product_id' ])? $params [ 'product_id' ]:null; //required when trade_type = NATIVE
$data [ "openid" ] = isset( $params [ 'openid' ])? $params [ 'openid' ]:null; //required when trade_type = JSAPI
$result = $this ->post(self::URL_UNIFIEDORDER, $data );
return $result ;
}
private function post( $url , $data , $cert = false) {
$data [ "sign" ] = $this ->sign( $data );
$xml = $this ->array2xml( $data );
$ch = curl_init();
curl_setopt( $ch , CURLOPT_SSL_VERIFYPEER, false);
curl_setopt( $ch , CURLOPT_SSL_VERIFYHOST, false);
curl_setopt( $ch , CURLOPT_POST, 1);
curl_setopt( $ch , CURLOPT_POSTFIELDS, $xml );
curl_setopt( $ch , CURLOPT_RETURNTRANSFER, true);
curl_setopt( $ch , CURLOPT_URL, $url );
if ( $cert == true){
//使用证书:cert 与 key 分别属于两个.pem文件
curl_setopt( $ch ,CURLOPT_SSLCERTTYPE, 'PEM' );
curl_setopt( $ch ,CURLOPT_SSLCERT, $this ->_config[ 'sslcertPath' ]);
curl_setopt( $ch ,CURLOPT_SSLKEYTYPE, 'PEM' );
curl_setopt( $ch ,CURLOPT_SSLKEY, $this ->_config[ 'sslkeyPath' ]);
}
$content = curl_exec( $ch );
$array = $this ->xml2array( $content );
return $array ;
}
/**
* 数据签名
* @param $data
* @return string
*/
private function sign( $data ) {
ksort( $data );
$string1 = "" ;
foreach ( $data as $k => $v ) {
if ( $v && trim( $v )!= '' ) {
$string1 .= "$k=$v&" ;
}
}
$stringSignTemp = $string1 . "key=" . $this ->_config[ "apikey" ];
$sign = strtoupper (md5( $stringSignTemp ));
return $sign ;
}
private function array2xml( $array ) {
$xml = "<xml>" . PHP_EOL;
foreach ( $array as $k => $v ) {
if ( $v && trim( $v )!= '' )
$xml .= "<$k><![CDATA[$v]]></$k>" . PHP_EOL;
}
$xml .= "</xml>" ;
return $xml ;
}
private function xml2array( $xml ) {
$array = array ();
$tmp = null;
try {
$tmp = ( array ) simplexml_load_string( $xml );
} catch (Exception $e ){}
if ( $tmp && is_array ( $tmp )){
foreach ( $tmp as $k => $v ) {
$array [ $k ] = (string) $v ;
}
}
return $array ;
}
/**
* 扫码支付(模式二)获取支付二维码
* @param $body
* @param $out_trade_no
* @param $total_fee
* @param $notify_url
* @param $product_id
* @return null
*/
public function getCodeUrl( $body , $out_trade_no , $total_fee , $notify_url , $product_id ){
$data = array ();
$data [ "nonce_str" ] = $this ->get_nonce_string();
$data [ "body" ] = $body ;
$data [ "out_trade_no" ] = $out_trade_no ;
$data [ "total_fee" ] = $total_fee ;
$data [ "spbill_create_ip" ] = $_SERVER [ "SERVER_ADDR" ];
$data [ "notify_url" ] = $notify_url ;
$data [ "trade_type" ] = self::TRADETYPE_NATIVE;
$data [ "product_id" ] = $product_id ;
$result = $this ->unifiedOrder( $data );
if ( $result [ "return_code" ] == "SUCCESS" && $result [ "result_code" ] == "SUCCESS" ) {
return $result [ "code_url" ];
} else {
$this ->error = $result [ "return_code" ] == "SUCCESS" ? $result [ "err_code_des" ] : $result [ "return_msg" ];
return null;
}
}
/**
* 查询订单
* @param $transaction_id
* @param $out_trade_no
* @return array
*/
public function orderQuery( $transaction_id , $out_trade_no ){
$data = array ();
$data [ "appid" ] = $this ->_config[ "appid" ];
$data [ "mch_id" ] = $this ->_config[ "mch_id" ];
$data [ "transaction_id" ] = $transaction_id ;
$data [ "out_trade_no" ] = $out_trade_no ;
$data [ "nonce_str" ] = $this ->get_nonce_string();
$result = $this ->post(self::URL_ORDERQUERY, $data );
return $result ;
}
/**
* 关闭订单
* @param $out_trade_no
* @return array
*/
public function closeOrder( $out_trade_no ){
$data = array ();
$data [ "appid" ] = $this ->_config[ "appid" ];
$data [ "mch_id" ] = $this ->_config[ "mch_id" ];
$data [ "out_trade_no" ] = $out_trade_no ;
$data [ "nonce_str" ] = $this ->get_nonce_string();
$result = $this ->post(self::URL_CLOSEORDER, $data );
return $result ;
}
/**
* 申请退款 - 使用商户订单号
* @param $out_trade_no 商户订单号
* @param $out_refund_no 退款单号
* @param $total_fee 总金额(单位:分)
* @param $refund_fee 退款金额(单位:分)
* @param $op_user_id 操作员账号
* @return array
*/
public function refund( $out_trade_no , $out_refund_no , $total_fee , $refund_fee , $op_user_id ){
$data = array ();
$data [ "appid" ] = $this ->_config[ "appid" ];
$data [ "mch_id" ] = $this ->_config[ "mch_id" ];
$data [ "nonce_str" ] = $this ->get_nonce_string();
$data [ "out_trade_no" ] = $out_trade_no ;
$data [ "out_refund_no" ] = $out_refund_no ;
$data [ "total_fee" ] = $total_fee ;
$data [ "refund_fee" ] = $refund_fee ;
$data [ "op_user_id" ] = $op_user_id ;
$result = $this ->post(self::URL_REFUND, $data ,true);
return $result ;
}
/**
* 申请退款 - 使用微信订单号
* @param $out_trade_no 商户订单号
* @param $out_refund_no 退款单号
* @param $total_fee 总金额(单位:分)
* @param $refund_fee 退款金额(单位:分)
* @param $op_user_id 操作员账号
* @return array
*/
public function refundByTransId( $transaction_id , $out_refund_no , $total_fee , $refund_fee , $op_user_id ){
$data = array ();
$data [ "appid" ] = $this ->_config[ "appid" ];
$data [ "mch_id" ] = $this ->_config[ "mch_id" ];
$data [ "nonce_str" ] = $this ->get_nonce_string();
$data [ "transaction_id" ] = $transaction_id ;
$data [ "out_refund_no" ] = $out_refund_no ;
$data [ "total_fee" ] = $total_fee ;
$data [ "refund_fee" ] = $refund_fee ;
$data [ "op_user_id" ] = $op_user_id ;
$result = $this ->post(self::URL_REFUND, $data ,true);
return $result ;
}
/**
* 下载对账单
* @param $bill_date 下载对账单的日期,格式:20140603
* @param $bill_type 类型
* @return array
*/
public function downloadBill( $bill_date , $bill_type = 'ALL' ){
$data = array ();
$data [ "appid" ] = $this ->_config[ "appid" ];
$data [ "mch_id" ] = $this ->_config[ "mch_id" ];
$data [ "bill_date" ] = $bill_date ;
$data [ "bill_type" ] = $bill_type ;
$data [ "nonce_str" ] = $this ->get_nonce_string();
$result = $this ->post(self::URL_DOWNLOADBILL, $data );
return $result ;
}
/**
* 获取js支付使用的第二个参数
*/
public function get_package( $prepay_id ) {
$data = array ();
$data [ "appId" ] = $this ->_config[ "appid" ];
$data [ "timeStamp" ] = time();
$data [ "nonceStr" ] = $this ->get_nonce_string();
$data [ "package" ] = "prepay_id=$prepay_id" ;
$data [ "signType" ] = "MD5" ;
$data [ "paySign" ] = $this ->sign( $data );
return $data ;
}
/**
* 获取发送到通知地址的数据(在通知地址内使用)
* @return 结果数组,如果不是微信服务器发送的数据返回null
* appid
* bank_type
* cash_fee
* fee_type
* is_subscribe
* mch_id
* nonce_str
* openid
* out_trade_no 商户订单号
* result_code
* return_code
* sign
* time_end
* total_fee 总金额
* trade_type
* transaction_id 微信支付订单号
*/
public function get_back_data() {
$xml = file_get_contents ( "php://input" );
$data = $this ->xml2array( $xml );
if ( $this ->validate( $data )) {
return $data ;
} else {
return null;
}
}
/**
* 验证数据签名
* @param $data 数据数组
* @return 数据校验结果
*/
public function validate( $data ) {
if (!isset( $data [ "sign" ])) {
return false;
}
$sign = $data [ "sign" ];
unset( $data [ "sign" ]);
return $this ->sign( $data ) == $sign ;
}
/**
* 响应微信支付后台通知
* @param $return_code 返回状态码 SUCCESS/FAIL
* @param $return_msg 返回信息
*/
public function response_back( $return_code = "SUCCESS" , $return_msg =null) {
$data = array ();
$data [ "return_code" ] = $return_code ;
if ( $return_msg ) {
$data [ "return_msg" ] = $return_msg ;
}
$xml = $this ->array2xml( $data );
print $xml ;
}
}
|
一、注意:此类库集成到ci我们要改名WechatPay改为Wechatpay让他符ci类库规范,而且文件名也要改保持统一性
二、把Wechatpay.php放在application\libraries文件夹内,将证书之类的,日志文件之类的放置在和wechatpay.php同级目录下即可,当然可以随便放
三、将微信配置信息,商户号、appid、AppSecret、API key、证书位置等信息放在wxpay_config.php文件中,放在application\config目录中
wxpay_config.php代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php defined( 'BASEPATH' ) OR exit ( 'No direct script access allowed' );
/**
* Created by PhpStorm.
* User: sxq
* Date: 2016-04-20
* Time: 16:59
*/
$config [ 'appid' ] = '你的公众号appid' ;
$config [ 'mch_id' ] = '你的商户号' ;
$config [ 'apikey' ] = '你的APIkey' ;
$config [ 'appsecret' ] = "你的AppSecret" ;
$config [ 'sslcertPath' ] = APPPATH. 'libraries/cert/apiclient_cert.pem' ;
$config [ 'sslkeyPath' ] = APPPATH. 'libraries/cert/apiclient_key.pem' ;
|
四、phpqrcode文件,这份文件在微信官方sdk中,使用文件有phpqrcode文件夹和qrcode.php也一同放置在application\libraries文件夹内
五、日志文件log.php,这份文件在微信官方sdk中也一同放置在application\libraries文件夹内
1 2 3 4 5 6 |
require_once (APPPATH. 'libraries/log.php' );
//初始化日志
$logHandler = new CLogFileHandler(APPPATH. "logs/" . date ( 'Y-m-d' ). '.log' );
Log::Init( $logHandler , 15);
//我在控制器最顶部加了这个实例化,日志文件放在了application/logs文件夹
//调用方式:log::debug("输出信息");简单记录执行信息方便调试
|
六、配置信息写完后,那么在控制器里调用吧(满满的全是干货)
我们首先按照常规的加载配置信息代码一样去加载微信配置信息,最后再加载三方类库wechatpay.php
1 2 3 4 5 6 7 8 9 |
$this ->load->config( 'wxpay_config' );
$wxconfig [ 'appid' ]= $this ->config->item( 'appid' );
$wxconfig [ 'mch_id' ]= $this ->config->item( 'mch_id' );
$wxconfig [ 'apikey' ]= $this ->config->item( 'apikey' );
$wxconfig [ 'appsecret' ]= $this ->config->item( 'appsecret' );
$wxconfig [ 'sslcertPath' ]= $this ->config->item( 'sslcertPath' );
$wxconfig [ 'sslkeyPath' ]= $this ->config->item( 'sslkeyPath' );
//由于此类库构造函数需要传参,我们初始化类库就传参数给他吧
$this ->load->library( 'Wechatpay' , $wxconfig );
|
这步基础信息配置完毕,接下来我们需要构造统一下单API接口参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$param [ 'body' ]= "商品名称(自行看文档具体填什么)" ;
$param [ 'attach' ]= "我有个参数要传我就穿了个id过来,这里不要有空格避免出错" ;
$param [ 'detail' ]= "我填了商品名称加订单号" ;
$param [ 'out_trade_no' ]= "商户订单号" ;
$param [ 'total_fee' ]= "金额,记得乘以100,微信支付单位默认分" ; //如$total_fee*100
$param [ "spbill_create_ip" ] = $_SERVER [ 'REMOTE_ADDR' ]; //客户端IP地址
$param [ "time_start" ] = date ( "YmdHis" ); //请求开始时间
$param [ "time_expire" ] = date ( "YmdHis" , time() + 600); //请求超时时间
$param [ "goods_tag" ] = urldecode( $productname ); //商品标签,自行填写
$param [ "notify_url" ] = base_url(). "home/wxnotify" ; //自行定义异步通知url
$param [ "trade_type" ] = "NATIVE" ; //扫码支付模式二
$param [ "product_id" ] = $order ->productid; //正好有产品id就传了个,看文档说自己定义
//调用统一下单API接口
$result = $this ->wechatpay->unifiedOrder( $param ); //这里可以加日志输出,log::debug(json_encode($result));
//成功(return_code和result_code都为SUCCESS)就会返回含有带支付二维码链接的数据
if (isset( $result [ "code_url" ]) && ! empty ( $result [ "code_url" ])) { /> //二维码图片链接
$data [ 'wxurl' ] = $result [ "code_url" ];
//这里传递商户订单号到扫码视图,是因为我想做跳转,根据商户号去查询订单是否支付成功,如果成功了就跳转,定时轮询微信服务器(这个谁有好的方法可以分享给我啊,表示感谢啦)
$data [ 'orderno' ] = $out_trade_no ;
$this ->load->view( 'home/pay' , $data );
}
|
pay.php扫码视图页面代码如下:这部分代码来自(https://github.com/Alpha2016/wxpay)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<?php if (isset( $wxurl )&&! empty ( $wxurl )){?>
<div class = "bgcolor" >
<div class = "container" >
<div class = "panel" >
<div class = "panel-heading" >
<ol class = "breadcrumb" >
<li><a href= "<?php echo base_url().'home';?>" >首页</a><span class = 'divider' >></span></li>
<li class = "active active-tab" ><span><?php echo "二维码支付" ;?></span></li>
</ol>
</div>
<div class = "panel-body" >
<div class = "page-header" >二维码支付</div>
<div class = "text-danger center-block text-center" >
<input type= "hidden" id= "orderno" value= "<?php echo $orderno;?>" />
<img alt= "扫码支付" src= "<?php echo base_url().'home/qrcode?data='.urlencode($wxurl);?>" style= "width:200px;height:200px;" />
</div>
</div>
</div>
</div>
</div>
<?php }?>
<script>
// 每半秒请求一次数据,然后判断,跳转,增加用户友好性
$( function (){
orderno = $( '#orderno' ).val();
start = self.setInterval( "checkstatus(orderno)" , 500);
});
function checkstatus(order_no){
if (order_no == undefined || order_no == '' ){
window.clearInterval(start);
}
else {
$.ajax({
url: "<?php echo base_url().'home/queryorder';?>" ,
type: 'POST' ,
dataType: 'json' ,
data:{orderno:orderno},
success: function (msg){
if (msg.trade_state == "SUCCESS" ) {
window.clearInterval(start);
alert( '支付成功' );
location.href = "<?php echo base_url().'home/myorder';?>" ;
}
}
});
}
}
</script>
|
其实核心在二维码链接如何转换成二维码图片和如何定时轮询支付结果
1 |
<?php echo base_url(). 'home/qrcode?data=' .urlencode( $wxurl );?>这句是调用phpqrcode类库<br>轮询方法代码:<br>该部分在home控制器下
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function queryorder()
{
$this ->load->config( 'wxpay_config' );
$wxconfig [ 'appid' ]= $this ->config->item( 'appid' );
$wxconfig [ 'mch_id' ]= $this ->config->item( 'mch_id' );
$wxconfig [ 'apikey' ]= $this ->config->item( 'apikey' );
$wxconfig [ 'appsecret' ]= $this ->config->item( 'appsecret' );
$wxconfig [ 'sslcertPath' ]= $this ->config->item( 'sslcertPath' );
$wxconfig [ 'sslkeyPath' ]= $this ->config->item( 'sslkeyPath' );
$this ->load->library( 'Wechatpay' , $wxconfig );
$out_trade_no = $_POST [ 'orderno' ]; //调用查询订单API接口
$array = $this ->wechatpay->orderQuery( '' , $out_trade_no );
echo json_encode( $array );
}
|
那么二维码类库调用在这里
1 2 3 4 5 6 |
function qrcode()
{
require_once (APPPATH. 'libraries/phpqrcode/phpqrcode.php' );
$url = urldecode( $_GET [ "data" ]);
QRcode::png( $url );
}
|
那么二维码生成支付图片完成,支付轮询也完成了,该如何去处理业务逻辑呢?
先说明下,这部分有个弊端,如果客户一直不支付那么他就一直轮询,可以自行设置个有效期,我没有设置。如果在轮询到处理业务逻辑怎么样?可以的,但是也有个问题如果客户直接关掉了,你来不及处理的业务怎么办?所以还要确保不掉单,还需要再微信异步通知url那里处理下业务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
//微信异步通知
function wxnotify()
{
//$postStr = file_get_contents("php://input");//因为很多都设置了register_globals禁止,不能用$GLOBALS["HTTP_RAW_POST_DATA'] //这部分困扰了好久用上面这种一直接受不到数据,或者接受了解析不正确,最终用下面的正常了,有哪位愿意指点的可以告知一二
$xml = $GLOBALS [ 'HTTP_RAW_POST_DATA' ]; //这个需要开启;always_populate_raw_post_data = On
$this ->load->config( 'wxpay_config' );
$wxconfig [ 'appid' ]= $this ->config->item( 'appid' );
$wxconfig [ 'mch_id' ]= $this ->config->item( 'mch_id' );
$wxconfig [ 'apikey' ]= $this ->config->item( 'apikey' );
$wxconfig [ 'appsecret' ]= $this ->config->item( 'appsecret' );
$wxconfig [ 'sslcertPath' ]= $this ->config->item( 'sslcertPath' );
$wxconfig [ 'sslkeyPath' ]= $this ->config->item( 'sslkeyPath' );
$this ->load->library( 'Wechatpay' , $wxconfig );
libxml_disable_entity_loader(true);
$array = json_decode(json_encode(simplexml_load_string( $xml , 'SimpleXMLElement' , LIBXML_NOCDATA)), true);
log::debug( $xml );
log::debug(json_encode( $array ));
if ( $array !=null)
{
$out_trade_no = $array [ 'out_trade_no' ];
$trade_no = $array [ 'transaction_id' ];
$data [ 'orderid' ]= $array [ 'attach' ];
$this ->load->model( 'payorder' );
$payinfo = $this ->payorder->GetPayorder( array ( 'orderno' => $out_trade_no ));
if (! $payinfo ) {
$data [ 'orderno' ] = $out_trade_no ;
$data [ 'money' ] = $array [ 'total_fee' ];
$data [ 'tradeno' ] = $trade_no ;
$rs = $this ->payorder->AddPayorder( $data );
if ( $rs >0)
{ //告知微信我成功了
$this ->wechatpay->response_back();
} else { //告知微信我失败了继续发
$this ->wechatpay->response_back( "FAIL" );
}
} else {
$this ->wechatpay->response_back();
}
}
}
|
花了5天的时间去研究这个类型的微信支付,花了半天的时间去梳理知识点,整体感觉就是如果API接口少,又有成熟类库自己去集成吧
微信支付JSAPI模式及退款CodeIgniter集成篇
http://www.cnblogs.com/24la/p/wxpay-jsapi-refund.html微信支付接口文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1
首先你得知道这个jsapi是不能离开微信进行调用支付的,明白了这个道理我们好下手,页面是在微信内显示并通过jsapi调用微信支付组件进行支付。
可以看看我们上一篇文章,主要是Native扫码支付模式二
我们仍然继续使用wechatpay.php这个支付集成类,简单方便好理解,不过如果应用jsapi的话这个类有个bug
在我们构造jsapi需要的参数时有个时间戳,我们用time()生成的,会报微信支付调用JSAPI缺少参数:timeStamp
修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/**
* 获取js支付使用的第二个参数
*/
public function get_package( $prepay_id ) {
$data = array ();
$data [ "appId" ] = $this ->_config[ "appid" ];
//改动地方,把它变成字符串
$time =time();
$data [ "timeStamp" ] = "\"" . $time . "\"" ;
$data [ "nonceStr" ] = $this ->get_nonce_string();
$data [ "package" ] = "prepay_id=$prepay_id" ;
$data [ "signType" ] = "MD5" ;
$data [ "paySign" ] = $this ->sign( $data );
return $data ;
}
|
其实这个方法就是获取jsapi的支付参数了
一、微信JSAPI支付
不能忘记配置授权目录,调用jsapi我是在http://xxx.com/index.php/home下我配置了这个
首先我们还是要调用统一下单接口,获取我们要的参数(如果此类的配置放置位置等不会的请参考上篇文章),此为pay方法,在调用统一下单接口的时候我们需要知道需要哪些参数
1、要获取openid,这个我是项目用了一个微信API的类库,https://github.com/dodgepudding/wechat-php-sdk,主要是用了这里面的方法
此项目有朋友专门的对接了CodeIgniter框架的扩展类库,可以直接用,目录结构,我们直接上代码吧
1 2 3 4 5 6 |
public function __construct()
{
parent::__construct();
$this ->load->library( 'CI_Wechat' ); //由于我的项目是时刻都跟微信绑在一起,所以直接加载在构造函数里了,不用每个方法都加载了。
$this ->load->library( 'pagination' );
}
|
CI_Model内容大家看下上面的类库源码,还有里面如何配置的,下面我们看看如何获取openid
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
function oauthurl()
{
$oauth_url = $this ->ci_wechat->getOauthRedirect(base_url() . 'index.php/home/oauth' , 1);
header( 'Location: ' . $oauth_url );
exit ();
}
function oauth()
{
if (!isset( $_GET [ 'code' ])) {
//触发微信返回code码
$baseUrl = urlencode( 'http://' . $_SERVER [ 'HTTP_HOST' ] . $_SERVER [ 'PHP_SELF' ] . $_SERVER [ 'QUERY_STRING' ]);
$url = $this ->__CreateOauthUrlForCode( $baseUrl );
Header( "Location: $url" );
exit ();
} else {
$json = $this ->ci_wechat->getOauthAccessToken();
$openid = $json [ 'openid' ];
//注册用户,成功后可以抢单
//return $this->_isRegistered($_SESSION['user']['openid']);
return $openid ;
}
}
|
以上两个方法就是获取openid的,获取之后我是保存在session里的,我每个页面都判断是否获取了openid如果没有获取直接
1 |
$this ->session->set_userdata( 'openid' , $this ->oauth());
|
这样保证一直能得到openid
2、构造JSAPI支付所需参数(统一下单的参数构造)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
$this ->load->model( 'publist' ); //获取订单信息
$pub = $this ->publist->GetList( array ( 'id' => $_SESSION [ 'orderid' ]));
//微信支付配置的参数配置读取
$this ->load->config( 'wxpay_config' );
$wxconfig [ 'appid' ]= $this ->config->item( 'appid' );
$wxconfig [ 'mch_id' ]= $this ->config->item( 'mch_id' );
$wxconfig [ 'apikey' ]= $this ->config->item( 'apikey' );
$wxconfig [ 'appsecret' ]= $this ->config->item( 'appsecret' );
$wxconfig [ 'sslcertPath' ]= $this ->config->item( 'sslcertPath' );
$wxconfig [ 'sslkeyPath' ]= $this ->config->item( 'sslkeyPath' );
$this ->load->library( 'Wechatpay' , $wxconfig );
//商户交易单号
$out_trade_no = $pub ->listno;
$total_fee = $pub ->fee;
$openid = $_SESSION [ 'openid' ];
$param [ 'body' ]= "黑人牙膏" ;
$param [ 'attach' ]= $pub ->id;
$param [ 'detail' ]= "黑人牙膏-" . $out_trade_no ;
$param [ 'out_trade_no' ]= $out_trade_no ;
$param [ 'total_fee' ]= $total_fee *100;
$param [ "spbill_create_ip" ] = $_SERVER [ 'REMOTE_ADDR' ];
$param [ "time_start" ] = date ( "YmdHis" );
$param [ "time_expire" ] = date ( "YmdHis" , time() + 600);
$param [ "goods_tag" ] = "黑人牙膏" ;
$param [ "notify_url" ] = base_url(). "index.php/home/notify" ;
$param [ "trade_type" ] = "JSAPI" ;
$param [ "openid" ] = $openid ;
//统一下单,获取结果,结果是为了构造jsapi调用微信支付组件所需参数
$result = $this ->wechatpay->unifiedOrder( $param );
//如果结果是成功的我们才能构造所需参数,首要判断预支付id
if (isset( $result [ "prepay_id" ]) && ! empty ( $result [ "prepay_id" ])) {
//调用支付类里的get_package方法,得到构造的参数
$data [ 'parameters' ]=json_encode( $this ->wechatpay->get_package( $result [ 'prepay_id' ]));
$data [ 'notifyurl' ]= $param [ "notify_url" ];
$data [ 'fee' ]= $total_fee ;
$data [ 'pubid' ]= $_SESSION [ 'orderid' ];
$this ->load->view( 'home/header' );
//要有个页面将以上数据传递过去并展示给用户
$this ->load->view( 'home/pay' , $data );
$this ->load->view( 'home/footer' );
}
|
3、支付页面,views视图pay.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<?php
$jsApiParameters = $parameters ; //参数赋值
?>
<script type= "text/javascript" >
//调用微信JS api 支付
function jsApiCall()
{
WeixinJSBridge.invoke(
'getBrandWCPayRequest' ,
<?php echo $jsApiParameters ; ?>,
function (res){
WeixinJSBridge.log(res.err_msg);
if (res.err_msg == "get_brand_wcpay_request:ok" ){
$.alert( '支付成功' );
//我在这里选择了前台只要支付成功将单号传递更新数据
$.ajax({
url: '<?php echo $notifyurl.' / '.$pubid;?>' ,
dataType: 'json' ,
success : function (ret){
if (ret==1){
//成功后返回我的订单页面
location.href= "<?php echo base_url().'index.php/home/myorder';?>" ;
}
}
});
} else
{
//$.alert('支付失败');
}
//alert(res.err_code+res.err_desc+res.err_msg);
}
);
}
function callpay()
{
if (typeof WeixinJSBridge == "undefined" ){
if ( document.addEventListener ){
document.addEventListener( 'WeixinJSBridgeReady' , jsApiCall, false);
} else if (document.attachEvent){
document.attachEvent( 'WeixinJSBridgeReady' , jsApiCall);
document.attachEvent( 'onWeixinJSBridgeReady' , jsApiCall);
}
} else {
jsApiCall();
}
}
</script>
<div class = "hd" >
<h1 class = "page_title" >支付佣金</h1>
<p class = "page_desc" >请认真核对佣金金额</p>
</div>
<div class = "weui_cells" >
<div class = "weui_cell" >
<div class = "weui_cell_hd weui_cell_primary" >
该笔订单支付金额为<span style= "color:#f00;font-size:50px" ><?php echo $fee ; ?></span>元钱
</div>
</div>
</div>
<button class = "weui_btn weui_btn_primary" type= "button" onclick= "callpay()" >立即支付</button>
|
以上代码可以用微信web开发者工具,使用方式自己看看吧,有了这个工具调试不再难
4、支付成功跳转页面,我们看notify方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function notify()
{
$id = $this ->uri->segment(3);
if (isset( $_SESSION [ 'openid' ])) {
$this ->load->model( 'publist' ); //更新业务逻辑
$rs = $this ->publist->UpdateList( array ( 'id' => $id , 'feestatus' => 1));
if ( $rs > 0) {
echo 1;
exit ;
} else {
echo 0;
exit ;
}
}
}
|
这样我们的支付流程就彻底走完了。
二、当我们支付完之后,有些单子可以退单的,如何将款项也退回呢
以上场景要弄明白了
我们申请退款需要参数有哪些?我们看看支付类里的退款方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/**
* 申请退款 - 使用商户订单号
* @param $out_trade_no 商户订单号
* @param $out_refund_no 退款单号
* @param $total_fee 总金额(单位:分)
* @param $refund_fee 退款金额(单位:分)
* @param $op_user_id 操作员账号
* @return array
*/
public function refund( $out_trade_no , $out_refund_no , $total_fee , $refund_fee , $op_user_id ){
$data = array ();
$data [ "appid" ] = $this ->_config[ "appid" ];
$data [ "mch_id" ] = $this ->_config[ "mch_id" ];
$data [ "nonce_str" ] = $this ->get_nonce_string();
$data [ "out_trade_no" ] = $out_trade_no ;
$data [ "out_refund_no" ] = $out_refund_no ;
$data [ "total_fee" ] = $total_fee ;
$data [ "refund_fee" ] = $refund_fee ;
$data [ "op_user_id" ] = $op_user_id ;
$result = $this ->post(self::URL_REFUND, $data ,true);
return $result ;
}
|
商户订单号,商户提供的退单号,付款金额,退款金额(不能退的比实际付款的多),操作员(一般商户号)
控制器内写退款方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
//申请退款
function refund( $id = "" )
{
if ( $id == "" ){
//方便我手动调用退单
$id = $this ->uri->segment(3);
}
if (isset( $id ) && $id != "" ) {
$this ->load->model( 'publist' );
//1、取消订单可以退款。2、失败订单可以退款
$pub = $this ->publist->GetList( array ( 'id' => $id ));
if ( $pub ->liststatus == 3 || $pub ->liststatus == 4) {
$listno = $pub ->listno;
$fee = $pub ->fee * 100;
$this ->load->config( 'wxpay_config' );
$wxconfig [ 'appid' ]= $this ->config->item( 'appid' );
$wxconfig [ 'mch_id' ]= $this ->config->item( 'mch_id' );
$wxconfig [ 'apikey' ]= $this ->config->item( 'apikey' );
$wxconfig [ 'appsecret' ]= $this ->config->item( 'appsecret' );
$wxconfig [ 'sslcertPath' ]= $this ->config->item( 'sslcertPath' );
$wxconfig [ 'sslkeyPath' ]= $this ->config->item( 'sslkeyPath' );
$this ->load->library( 'Wechatpay' , $wxconfig );
if (isset( $listno ) && $listno != "" ) {
$out_trade_no = $listno ;
$total_fee = $fee ;
$refund_fee = $fee ;
//自定义商户退单号
$out_refund_no = $wxconfig [ 'mch_id' ]. date ( "YmdHis" );
$result = $this ->wechatpay->refund( $out_trade_no , $out_refund_no , $total_fee , $refund_fee , $wxconfig [ 'mch_id' ]);
log::DEBUG(json_encode( $result ));
if (isset( $result [ "return_code" ]) && $result [ "return_code" ]= "SUCCESS" &&isset( $result [ "result_code" ]) && $result [ "result_code" ]= "SUCCESS" ) {
echo "<script>$.toast('退款成功')</script>" ;
}
//佣金状态更改为已退款
$this ->publist->UpdateList( array ( 'id' => $id , 'liststatus' =>3, 'listoutno' => $out_refund_no ));
redirect( 'home/myorder' );
}
}
}
}
|
试试就好了,很快就可以接到退款消息
以上是这几天摸索出来的东西,分享给大家。
其他参考文章:
CI集成JSApi微信支付
http://blog.sina.com.cn/s/blog_14955e3b70102wvmj.html