美文网首页初识小程序
微信支付与退款

微信支付与退款

作者: 洗耳恭听_kai | 来源:发表于2020-10-12 13:11 被阅读0次

微信支付和微信退款大致相同,只不过微信支付有一个回调的过程,而退款只需要看返回的状态就可以了。

一、微信小程序支付

主要的步骤:

  • 传递相关参数,后台获取预支付id:prepay_id和签名
  • 小程序端使用wx.requestPayment来进行支付操作
  • 根据后台回调地址中接受的数据进行判断是否支付成功
  • 消息订阅,推送支付成功消息
pay: function () {
    //尚未开通支付
    // wx.showModal({
    //   title: '提示',
    //   content: '尚未开通支付',
    //   success: function (res) {
    //     wx.switchTab({
    //       url:'../../index'
    //     })
    //   }
    // })
    // return false
    var _this = this
    var PHPSESSID = wx.getStorageSync('PHPSESSID')
    var orderIds = this.data.orderid
    var amount = this.data.money
    if (!orderIds || !amount){
      app.openPage('member/member?type=1')
    }
    if (PHPSESSID){
      app.reqPost("wxpay", "pay", {
        orderIds: orderIds,
        order_amount: amount,
        appID: config.AppID,
        appSecret: config.AppSecret,
        PHPSESSID: PHPSESSID
      }, function (res) {
        console.log('支付完成:')
        console.log(res)
        var prepay_id = res.data.data.prepay_id
        var uid = res.data.data.uid
        var pay_time = res.data.data.pay_time
        var out_trade_no = res.data.data.out_trade_no  //商户订单号,退款需要用到
        wx.requestPayment({
          'timeStamp': res.data.data.response.timeStamp,
          'nonceStr': res.data.data.response.nonceStr,
          'package': res.data.data.response.package,
          'signType': res.data.data.response.signType,
          'paySign': res.data.data.response.paySign,
          'success': function (res) {
            console.log('下面是支付成功后的打印')
            console.log(res)
            console.log(prepay_id)
            _this.setData({
              prepay_id: prepay_id
            })
            if (prepay_id){
              //更新订单状态
              app.reqPost("buy", "payresult1", {
                out_trade_no: out_trade_no
              },function(res){
                 //_this.submitForm(orderIds, prepay_id)
                 console.log('更新订单状态后:')
                 console.log(res)
                 if(res.data.code == 1000){
                  wx.showModal({
                    title: '提示',
                    content: '支付成功',
                    success: function (res) {
                      //消息订阅
                      var touser = uid
                      var tmplId = 'xxxx'
                      var page = 'pages/index/index'
                      var content = _this.data.orderid
                      console.log('打印消息订阅参数:')
                      console.log(touser)
                      console.log(tmplId)
                      console.log(page)
                      console.log(content)
                      app.toggleSubscribe(touser,tmplId,page,content)
                      app.openPage('member/mine_order?type=2')
                    }
                  })
                 }else{
                  wx.showModal({
                    title: '提示',
                    content: '支付失败',
                    success: function (res) {
                      app.openPage('member/mine_order?type=1')
                    }
                  })
                 }
              })
            }
          },
          'fail': function (res) {
            wx.showModal({
              title: '提示',
              content: '支付失败',
              success: function (res) {
                app.openPage('member/mine_order?type=0')
              }
            })
          }
        })
      })
    }
  },

后端获取签名和预支付id

  public function pay(){
        $app_id=empty($_REQUEST['appID'])?"wx5cf75606efxxx":$_REQUEST['appID'];
        $mch_id='xxx';  //商户号
        $app_key='xxxx';
        $sessionid=$_REQUEST['PHPSESSID'];
        $userinfo = Db::name('users')->where('sessionid',$sessionid)->find();
        $uid = $userinfo['id'];
        $openid = $userinfo['openid'];
        // order_info
        $orderIds=$_REQUEST['orderIds'];
        if(empty($orderIds)){
            return json("网络繁忙");
        }
        $out_trade_no=$this->getRandChar(5)."_".$orderIds; //商户订单号,退款需要用到
        //存储到数据库
        Db::name('order')->where('id',$orderIds)->update(['out_trade_no'=>$out_trade_no]);
        $order_amount=(float)$_REQUEST['order_amount'];
        // get prepay id
        $prepay_id = $this->generatePrepayId($app_id, $mch_id, $app_key,$out_trade_no,$order_amount,$openid);
        $response = array(
            'appId' => $app_id,
            'timeStamp' => ''.time(),
            'nonceStr' => $this->generateNonce(),
            'package' => 'prepay_id='.$prepay_id,
            'signType' => 'MD5'
        );
        $response['paySign'] = $this->calculateSign($response, $app_key);
        return json(['code'=>'1000','msg'=>'success','data'=>['response'=>$response,'prepay_id'=>$prepay_id,'uid'=>$uid,'pay_time'=>date('Y-m-d H:i:s',time()),'out_trade_no'=>$out_trade_no]]);
    }

  public function getRandChar($length){
        $str = "";
        $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
        $max = strlen($strPol)-1;

        for($i=0;$i<$length;$i++){
            $str.=$strPol[rand(0,$max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数
        }

        return $str;
    }

  public function generatePrepayId($appId, $mchId, $appKey, $out_trade_no,$order_amount,$openid){
        // add sign
        $params = array(
            'appid'            => $appId,
            'mch_id'           => $mchId,
            'openid'           => $openid,
            'nonce_str'        => $this->generateNonce(),
            'body'             => 'xx小程序商品',
            'detail'           => "微信支付",
            'out_trade_no'     => $out_trade_no,
            'total_fee'        => $order_amount*100,
            'spbill_create_ip' => $this->getIP(),
            'notify_url'       => 'https://xxx/api/Wxpaynotify/index',
            'trade_type'       => 'JSAPI'
        );
        $params['sign'] = $this->calculateSign($params, $appKey);
        // create xml
        $xml = $this->getXMLFromArray($params);
        // send request
        $ch = curl_init();
        curl_setopt_array($ch, array(
            CURLOPT_URL            => "https://api.mch.weixin.qq.com/pay/unifiedorder",
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_POST           => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER         => false,
            CURLOPT_POSTFIELDS     => $xml,
        ));
        $result = curl_exec($ch);
        curl_close($ch);
        // get the prepay id from response
        $xml = simplexml_load_string($result);

        return (string)$xml->prepay_id;
    }

/**
     * Generate a nonce string
     *
     * @link https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=4_3
     */
    public function generateNonce(){
        return md5(uniqid('', true));
    }
/**
     * Get a sign string from array using app key
     *
     * @link https://pay.weixin.qq.com/wiki/doc/api/app.php?chapter=4_3
     */
    public function calculateSign($arr, $key){
        ksort($arr);
        $buff = "";
        foreach ($arr as $k => $v) {
            if ($k != "sign" && $k != "key" && $v != "" && !is_array($v)){
                $buff .= $k . "=" . $v . "&";
            }
        }
        $buff = trim($buff, "&");
        return strtoupper(md5($buff . "&key=" . $key));
    }
    /**
     * Get xml from array
     */
   public function getXMLFromArray($arr){
        $xml = "<xml>";
        foreach ($arr as $key => $val) {
            if (is_numeric($val)) {
                $xml .= sprintf("<%s>%s</%s>", $key, $val, $key);
            } else {
                $xml .= sprintf("<%s><![CDATA[%s]]></%s>", $key, $val, $key);
            }
        }
        $xml .= "</xml>";
        return $xml;
    }
public function getIP() {
        if (getenv('HTTP_CLIENT_IP')) {
            $ip = getenv('HTTP_CLIENT_IP');
        }
        elseif (getenv('HTTP_X_FORWARDED_FOR')) {
            $ip = getenv('HTTP_X_FORWARDED_FOR');
        }
        elseif (getenv('HTTP_X_FORWARDED')) {
            $ip = getenv('HTTP_X_FORWARDED');
        }
        elseif (getenv('HTTP_FORWARDED_FOR')) {
            $ip = getenv('HTTP_FORWARDED_FOR');

        }
        elseif (getenv('HTTP_FORWARDED')) {
            $ip = getenv('HTTP_FORWARDED');
        }
        else {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
        return $ip;
    }

回调

public function index(){
        $post = file_get_contents("php://input");

        if ($post == null) {
            $post = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? $GLOBALS['HTTP_RAW_POST_DATA'] : '';
        }

        if (empty($post) || $post == null || $post == '') {
            //阻止微信接口反复回调接口  文档地址 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=7,下面这句非常重要!!!
            $str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
            echo $str;
            exit('Notify 非法回调');
        }

        /*****************微信回调返回数据样例*******************
        $post = '<xml>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <return_msg><![CDATA[OK]]></return_msg>
        <appid><![CDATA[wx2421b1c4370ec43b]]></appid>
        <mch_id><![CDATA[10000100]]></mch_id>
        <nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
        <sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id>
        <trade_type><![CDATA[APP]]></trade_type>
        </xml>';
         *************************微信回调返回*****************/

        libxml_disable_entity_loader(true); //禁止引用外部xml实体

        $xml = simplexml_load_string($post, 'SimpleXMLElement', LIBXML_NOCDATA);//XML转数组

        $post_data = (array)$xml;

        //打印
        $this->print_back_pay_result(['msg'=>'打印开始:回调的参数']);
        $this->print_back_pay_result($post_data);
        $this->print_back_pay_result(['msg'=>'打印结束']);

        /** 解析出来的数组
         *Array
         * {
        "appid": "wx5xxxxxx20c9b",
        "bank_type": "OTHERS",
        "cash_fee": "1",
        "fee_type": "CNY",
        "is_subscribe": "N",
        "mch_id": "1587530151",
        "nonce_str": "4f28da5dcf4d91e2c27ec8685f53978d",
        "openid": "oZUGc5eMQWLLJ4nYIHV9AEGn4llk",
        "out_trade_no": "iL3tS_97",
        "result_code": "SUCCESS",
        "return_code": "SUCCESS",
        "sign": "16B463A5CE8A467F98E0DAFD80510618",
        "time_end": "20201012092037",
        "total_fee": "1",
        "trade_type": "JSAPI",
        "transaction_id": "420000072xxx1xxx25776254821"
        }
         **/
        //订单号
        $out_trade_no = isset($post_data['out_trade_no']) && !empty($post_data['out_trade_no']) ? $post_data['out_trade_no'] : 0;

        //$this->print_back_pay_result($out_trade_no);

        //查询订单信息
        $order_info = Db::name('order')->where('out_trade_no',$out_trade_no)->find();

        $productid = $order_info['class_id'];
        $productinfo = Db::name('product')->where('id',$productid)->find();

        $this->print_back_pay_result(['msg'=>'打印开始:获取订单信息']);
        $this->print_back_pay_result($order_info);
        $this->print_back_pay_result(['msg'=>'打印结束']);


        if(count($order_info) > 0){
            //查询平台信息
            //$platform_info = DB::fetch_first("SELECT * FROM pingtaiInfo WHERE `open_pid`= {$order_info['part1']}");

            //平台支付key
            $wxpay_key = 'xxx';

            //接收到的签名
            $post_sign = $post_data['sign'];
            unset($post_data['sign']);

            //重新生成签名
            $newSign = $this->MakeSign($post_data,$wxpay_key);

            $this->print_back_pay_result(['msg'=>'打印开始:生成签名']);
            $this->print_back_pay_result(['new'=>$newSign,'old'=>$post_sign]);
            $this->print_back_pay_result(['msg'=>'打印结束']);

            //签名统一,则更新数据库
            if($post_sign == $newSign){

                $updateData = array();
                $updateData['pay_time'] = time(); //支付时间
                $updateData['status'] = 2; //订单状态-支付完成

                //购买的课程数量加一
                $updatenum = $productinfo['num'] + 1;
                Db::name('product')->where('id',$order_info['class_id'])->update(['num'=>$updatenum]);

                //更新order数据库
                Db::name('order')->where('out_trade_no',$out_trade_no)->update($updateData);

                $this->print_back_pay_result(['msg'=>'打印开始:签名成功']);
                $this->print_back_pay_result($updateData);
                $this->print_back_pay_result(['msg'=>'打印结束']);
            }
        }

//阻止微信接口反复回调接口  文档地址 https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=7,下面这句非常重要!!!
        $str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
        echo $str;
    }

    public function MakeSign($params,$key){
        //签名步骤一:按字典序排序数组参数
        ksort($params);
        $string = $this->ToUrlParams($params);  //参数进行拼接key=value&k=v
        //签名步骤二:在string后加入KEY
        $string = $string . "&key=".$key;
        //签名步骤三:MD5加密
        $string = md5($string);
        //签名步骤四:所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    public function ToUrlParams( $params ){
        $string = '';
        if( !empty($params) ){
            $array = array();
            foreach( $params as $key => $value ){
                $array[] = $key.'='.$value;
            }
            $string = implode("&",$array);
        }
        return $string;
    }

    //写日志-返回码-交易结果
    public function print_back_pay_result($data){
        //var_dump(self::payback('PAY'));die;
        //$data = json_decode($data,true);
        //$data = ['date'=>time(),'name'=>'yk','age'=>'27'];
        $params = json_encode($data,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
        $time = date('Y-m-d h:i:s',time());
        $address = self::payback('PAY');
        $file_size = filesize($address);
        if($file_size > 2097152){
            file_put_contents($address,'');
            $data = $time.$params;
        }
        $data = $time.$params;
        file_put_contents($address,$data."\r\n",FILE_APPEND);
    }

    static public function payback($option){
        switch ($option){
            case 'ROOT_PATH' : return dirname(realpath($_SERVER['SCRIPT_FILENAME'])) . DIRECTORY_SEPARATOR;break;
            case 'EXTEND_PATH' : return ROOT_PATH.'extend' . DIRECTORY_SEPARATOR;break;
            case 'PAY' : return EXTEND_PATH.'payback/payback.txt';break;
        }
        return $option;
    }

二、退款

具体步骤:

  • 小程序端申请退款
  • 后台进行退款

小程序申请退款

这里可以有多种写法,主要就是给这个订单的状态改变,然后后台可以接受到退款的申请就行了。

后台

1、证书在微信商户号平台获取,必须是在申请商户号的电脑上下载,否则更改证书,就可以在自己电脑这边下载
2、证书的路径需要写绝对路径,否则会报:58的错误
3、需要传递退款者的openid,这样才能知道退给谁

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2020/10/7 0007
 * Time: 15:56
 */

namespace app\admin\controller;
use think\Db;

class WxRefund extends Wxpay
{
    protected $SSLCERT_PATH = '/www/wwwroot/text.jcy.com/public/cert/apiclient_cert.pem';//证书路径

    protected $SSLKEY_PATH = '/www/wwwroot/text.jcy.com/public/cert/apiclient_key.pem';//证书路径

    protected $MCHID = 'xxx';//商户号

    protected $APPID = 'xxx';

    protected $APPKEY='xxx';

    public function refund($orderid){

        //对外暴露的退款接口

        $result = $this->wxrefundapi($orderid);

        return $result;

    }

    private function wxrefundapi($orderid){

        $orderinfo = Db::name('order')->where('id',$orderid)->find();

        //通过微信api进行退款流程

        $params = array(

            'appid'=> $this->APPID,

            'mch_id'=> $this->MCHID,

            'nonce_str'=> $this->generateNonce(),

            'out_refund_no'=> $this->getRandChar(5)."_".$orderid,

            'out_trade_no'=> $orderinfo['out_trade_no'],

            'total_fee'=> (float)$orderinfo['money'] * 100,

            'refund_fee'=> (float)$orderinfo['money'] * 100,

            'op_user_id' => $this->MCHID,

        );
        $params['sign'] = $this->calculateSign($params, $this->APPKEY);

        $xmldata = $this->getXMLFromArray($params);

        $xmlresult = $this->postXmlSSLCurl($xmldata,'https://api.mch.weixin.qq.com/secapi/pay/refund');

        $result = $this->xmlToArray($xmlresult);

        return $result;

    }

    //数组转字符串方法

    protected function arrayToXml($arr){

        $xml = "<xml>";

        foreach ($arr as $key=>$val)

        {

            if (is_numeric($val)){

                $xml.="<".$key.">".$val."</".$key.">";

            }else{

                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";

            }

        }

        $xml.="</xml>";

        return $xml;

    }
    protected function xmlToArray($xml){

        $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);

        return $array_data;

    }

    //需要使用证书的请求
    function postXmlSSLCurl($xml,$url,$second=30)

    {

        $ch = curl_init();

        //超时时间

        curl_setopt($ch,CURLOPT_TIMEOUT,$second);

        //这里设置代理,如果有的话

        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');

        //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);

        //设置header

        curl_setopt($ch,CURLOPT_HEADER,FALSE);

        //要求结果为字符串且输出到屏幕上

        curl_setopt($ch,CURLOPT_RETURNTRANSFER,TRUE);

        //设置证书

        //使用证书:cert 与 key 分别属于两个.pem文件

        //默认格式为PEM,可以注释

        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');

        curl_setopt($ch,CURLOPT_SSLCERT, $this->SSLCERT_PATH);

        //默认格式为PEM,可以注释

        curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');

        curl_setopt($ch,CURLOPT_SSLKEY, $this->SSLKEY_PATH);

        //post提交方式

        curl_setopt($ch,CURLOPT_POST, true);

        curl_setopt($ch,CURLOPT_POSTFIELDS,$xml);

        $data = curl_exec($ch);

        //返回结果

        if($data){

            curl_close($ch);

            return $data;

        }

        else {

            $error = curl_errno($ch);

            echo "curl出错,错误码:$error"."<br>";

            curl_close($ch);

            return false;

        }

    }
}

相关文章

  • 对接微信和支付宝退款api

    1、微信退款 参考:(41条消息) weixin-java-pay实现公众号微信支付与退款_earthhour的博...

  • 微信支付与退款

    微信支付和微信退款大致相同,只不过微信支付有一个回调的过程,而退款只需要看返回的状态就可以了。 一、微信小程序支付...

  • vue实现webapp支付

    最近在使用vue写webapp,app中要求可以实现线上支付,研究了微信H5支付与支付宝H5支付。其中微信H5支付...

  • 微信APP支付和退款(JAVA)

    微信APP支付和退款 微信支付流程说明 Java demo实例 退款 转账 1、微信支付 1 微信支付流程说明 本...

  • 微信H5支付

    H5支付主要用于手机浏览器请求微信支付的场景。可以方便的从外部浏览器唤起微信支付。H5支付与JSAPI支付不同的是...

  • 微信退款

    微信退款 /************************wechat.properties**********...

  • 微信退款

    /** * 微信退款 * * @access public * @param string $total_a...

  • 微信退款

  • 微信公众号内退款功能的实现

    应用场景 当交易生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支...

  • 微信支付--PHP 封装

    微信支付开发文档 类的使用方法 笔者阅读微信支付SDK里面的example之后,把下单,查询订单,退款,查询退款等...

网友评论

    本文标题:微信支付与退款

    本文链接:https://www.haomeiwen.com/subject/gujlpktx.html