***CodeIgniter集成微信支付(转)

  1. 云栖社区>
  2. 博客>
  3. 正文

***CodeIgniter集成微信支付(转)

suboysugar 2016-11-25 14:26:00 浏览396
展开阅读全文

微信支付Native扫码支付模式二之CodeIgniter集成篇

 http://www.cnblogs.com/24la/p/wxpay-native-qrcode-codeigniter.html
 

CI: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');
//初始化日志
$logHandlernew 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

 

如何联系我:【万里虎】www.bravetiger.cn 【QQ】3396726884 (咨询问题100元起,帮助解决问题500元起) 【博客】http://www.cnblogs.com/kenshinobiy/

网友评论

登录后评论
0/500
评论
suboysugar
+ 关注