首页 > PHP > 微信小程序+ThinkPHP3.2/TP6实现支付功能

微信小程序+ThinkPHP3.2/TP6实现支付功能

时间:2024-12-04浏览次数:132

ThinkPhP代码部分:

后端部分pay方法仅仅是发起微信统一下单,并没有实际发起支付,我们需要拿到统一下单微信返回的参数返还给小程序,小程序执行wx.requestPayment才是真正的发起支付。

//微信支付
    public function pay(){
        $fee = 1;//举例支付0.01,单位是分
        $appid = "你的小程序appid";//appid.如果是公众号 就是公众号的appid
        $body = "你的商品名称";
        $mch_id = '你的商户号';//商户号
        $nonce_str = $this->nonce_str();//随机字符串
        $notify_url = '你的支付成功回调url'; //回调的url【自己填写】,注:此处url必须是外网可访问地址才可以,如果是自己内网服务器不行。
        $openid = "支付用户的openid";  //支付用户的openid
        $out_trade_no = "此处是你生成的订单号";//商户订单号:需要保证随机生成不重复,建议生成20位
        $spbill_create_ip = '127.0.0.1';//服务器的ip【自己填写】;
        $total_fee = $fee*100;// 微信支付单位是分,所以这里需要*100
        $trade_type = 'JSAPI';//交易类型 默认
        //这里是按照顺序的 因为下面的签名是按照顺序 排序错误 肯定出错
        $post['appid'] = $appid;
        $post['body'] = $body;
        $post['mch_id'] = $mch_id;
        $post['nonce_str'] = $nonce_str;//随机字符串
        $post['notify_url'] = $notify_url;
        $post['openid'] = $openid;
        $post['out_trade_no'] = $out_trade_no;
        $post['spbill_create_ip'] = $spbill_create_ip;//终端的ip
        $post['total_fee'] = $total_fee;//总金额 
        $post['trade_type'] = $trade_type;
        $sign = $this->sign($post);//签名
        $post_xml = '<xml>
           <appid>'.$appid.'</appid>
           <body>'.$body.'</body>
           <mch_id>'.$mch_id.'</mch_id>
           <nonce_str>'.$nonce_str.'</nonce_str>
           <notify_url>'.$notify_url.'</notify_url>
           <openid>'.$openid.'</openid>
           <out_trade_no>'.$out_trade_no.'</out_trade_no>
           <spbill_create_ip>'.$spbill_create_ip.'</spbill_create_ip>
           <total_fee>'.$total_fee.'</total_fee>
           <trade_type>'.$trade_type.'</trade_type>
           <sign>'.$sign.'</sign>
        </xml> ';
//        print_r($post_xml);die;
        //统一接口prepay_id
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        $xml = $this->http_request($url,$post_xml);
        $array = $this->xmlToArray($xml);
//        print_r($xml);die;
        if($array['return_code'] == 'SUCCESS' && $array['result_code'] == 'SUCCESS'){
            $time = time();
            $tempArr=array(
                'appId' => $appid,
                'nonceStr' => $nonce_str,
                'package' => 'prepay_id='.$array['prepay_id'],
                'signType' => 'MD5',
                'timeStamp' => "$time"
            );
            $data['state'] = 200;
            $data['timeStamp'] = "$time";//时间戳
            $data['nonceStr'] = $nonce_str;//随机字符串
            $data['signType'] = 'MD5';//签名算法,暂支持 MD5
            $data['package'] = 'prepay_id='.$array['prepay_id'];//统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
            $data['paySign'] = $this->sign($tempArr);//签名,具体签名方案参见微信公众号支付帮助文档;
            $data['out_trade_no'] = $out_trade_no;
            $data['orderNumber'] = $orderNumber;
            $data['sumPrice'] = $sumPrice;
 
        }else{
            $data['state'] = 0;
            $data['text'] = "错误";
            $data['returnArr'] = $array;
        }
        //将此处的$data返回给小程序即可,小程序端发起支付需要用到:timeStamp、nonceStr、package、signType、paySign等参数,我们此处并没有发起真正的支付,仅是向微信发起了统一下单拿到微信返回的相关参数返回给小程序,然后由小程序发起支付
        return $data;
    }
    //随机32位字符串
    private function nonce_str(){
        $result = '';
        $str = 'QWERTYUIOPASDFGHJKLZXVBNMqwertyuioplkjhgfdsamnbvcxz';
        for ($i=0;$i<32;$i++){
            $result .= $str[rand(0,48)];
        }
        return $result;
    }
    //签名 $data要先排好顺序
    private function sign($data){
        $stringA = '';
        foreach ($data as $key=>$value){
            if(!$value) continue;
            if($stringA) $stringA .= '&'.$key."=".$value;
            else $stringA = $key."=".$value;
        }
        $wx_key = '商户key';//申请支付后有给予一个商户账号和密码,登陆后自己设置的key
        $stringSignTemp = $stringA.'&key='.$wx_key;
        return strtoupper(md5($stringSignTemp));
    }
    //curl请求
    public function http_request($url,$data = null,$headers=array()){
        $curl = curl_init();
        if( count($headers) >= 1 ){
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        }
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        if (!empty($data)){
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($curl);
        curl_close($curl);
        return $output;
    }
    //xml转换成数组
    private function xmlToArray($xml) {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        $val = json_decode(json_encode($xmlstring), true);
        return $val;
    }


notify小程序支付成功回调函数:

此处需要注意:只有当notify异步回调成功才算是支付成功,此时可以修改订单状态等操作。小程序wx.requestPayment中的success执行成功并不能进行修改订单状态等操作,但我们可以在success中进行跳转页面等操作。

//小程序支付成功回调函数
    public function notify(){
        //获取接口数据,如果$_REQUEST拿不到数据,则使用file_get_contents函数获取
        $post = $_REQUEST;
        if ($post == null) {
            $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><appid><![CDATA[wx42e35e9cba15a5fc]]></appid>
        <bank_type><![CDATA[CFT]]></bank_type>
        <cash_fee><![CDATA[1]]></cash_fee>
        <fee_type><![CDATA[CNY]]></fee_type>
        <is_subscribe><![CDATA[N]]></is_subscribe>
        <mch_id><![CDATA[1509441511]]></mch_id>
        <nonce_str><![CDATA[UtpvtBhXIqpROhTXNpJSafbcFuPXTprt]]></nonce_str>
        <openid><![CDATA[oYlJJ5Fs8VzKYE3xOKvKn-IuuMLM]]></openid>
        <out_trade_no><![CDATA[20190516155797735964]]></out_trade_no>
        <result_code><![CDATA[SUCCESS]]></result_code>
        <return_code><![CDATA[SUCCESS]]></return_code>
        <sign><![CDATA[8DBFF1A0922A35B081CD09E59FB21EDF]]></sign>
        <time_end><![CDATA[20190516112925]]></time_end>
        <total_fee>1</total_fee>
        <trade_type><![CDATA[JSAPI]]></trade_type>
        <transaction_id><![CDATA[4200000296201905161862992531]]></transaction_id>
        </xml>';
         *************************微信回调返回*****************/
        libxml_disable_entity_loader(true); //禁止引用外部xml实体
        $xml = simplexml_load_string($post, 'SimpleXMLElement', LIBXML_NOCDATA);//XML转数组
        $post_data = (array)$xml;
        //将用户支付信息记录日志文件
        \Think\Log::record("用户openid:".$post_data['openid']);
        \Think\Log::record("appId:".$post_data['appid']);
        \Think\Log::record("订单编号:".$post_data['out_trade_no']);
        \Think\Log::record("支付金额:".$post_data['total_fee']/100);
        //此时你就可以进行修改订单状态以及其他的操作了...
        $out_trade_no=$post_data['out_trade_no'];  //订单编号
           
        //阻止微信接口反复回调接口  文档地址 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;
    }


微信小程序代码部分:

此处发起支付所用到的timeStamp、nonceStr、package、signType、paySign等相关参数是由后端pay方法返回而来。

// pages/pay/pay.js
Page({
  data: {
    orderId: '', // 订单ID
  },
  onLoad: function (options) {
    // 从页面参数中获取订单ID
    this.setData({
      orderId: options.orderId
    });
  },
  initiatePayment: function () {
    const that = this;
    wx.request({
      url: 'https://your-backend.com/api/getPayParams', // 后端接口地址
      method: 'POST',
      data: {
        orderId: that.data.orderId,
      },
      success: function (res) {
        const payParams = res.data;
        that.wxPay(payParams);
      },
      fail: function (error) {
        console.error('Failed to get payment parameters:', error);
      }
    });
  },
  wxPay: function (payParams) {
    wx.requestPayment({
      timeStamp: payParams.timeStamp,
      nonceStr: payParams.nonceStr,
      package: payParams.package,
      signType: payParams.signType,
      paySign: payParams.paySign,
      success: function (res) {
        console.log('Payment success:', res);
        // 处理支付成功的逻辑
      },
      fail: function (error) {
        console.error('Payment failed:', error);
        // 处理支付失败的逻辑
      }
    });
  }
});


页面代码部分:

<!-- pages/pay/pay.wxml -->
<view class="container">
  <button bindtap="initiatePayment">支付</button>
</view>



TP6:

1. 微信支付配置

首先,在config目录下创建wechat.php配置文件,用于存放微信支付的相关参数:

<?php
// config/wechat.php

return [
    'appid' => 'your_appid', // 公众号ID
    'appsecret' => 'your_appsecret', // 公众号秘钥
    'mch_id' => 'your_mch_id', // 商户ID
    'key' => 'your_pay_key', // 支付密钥
    'notify_url' => 'https://your-backend.com/api/payNotify', // 支付结果通知地址
];

2. 安装EasyWeChat库

使用Composer安装EasyWeChat库,用于微信支付:

composer require overtrue/easywechat:~4.0

3. 创建微信支付控制器

application/controller目录下创建WechatPayController.php文件,并添加以下代码:

<?php
// application/controller/WechatPayController.php

namespace app\controller;

use think\Controller;
use EasyWeChat\Factory;
use think\facade\Config;

class WechatPayController extends Controller
{
    public function pay()
    {
        $config = Config::get('wechat');
        $app = Factory::payment($config);

        $params = [
            'body' => '订单描述', // 商品描述
            'out_trade_no' => '订单号', // 自定义的订单号
            'total_fee' => '订单金额(单位:分)',
            'notify_url' => $config['notify_url'],
            'trade_type' => 'JSAPI',
            'openid' => '用户的openid', // 用户的openid
        ];

        $result = $app->order->unify($params);

        if ($result['return_code'] === 'SUCCESS' && $result['result_code'] === 'SUCCESS') {
            $prepay_id = $result['prepay_id'];
            $data = [
                'timeStamp' => strval(time()),
                'nonceStr' => $app->utils->createNonceStr(),
                'package' => "prepay_id={$prepay_id}",
                'signType' => 'MD5',
                'paySign' => $app->utils->getPaySign([
                    'timeStamp' => $data['timeStamp'],
                    'nonceStr' => $data['nonceStr'],
                    'package' => $data['package'],
                    'signType' => $data['signType'],
                ], $config['key']),
            ];
            return json($data);
        } else {
            return json(['error' => '支付参数生成失败']);
        }
    }

    public function notify()
    {
        $config = Config::get('wechat');
        $app = Factory::payment($config);
        $response = $app->handlePaidNotify(function ($message, $fail) {
            // 根据$message中的参数进行验证和处理
            // 验证通过后,处理订单状态等业务逻辑
            return true; // 返回true表示处理成功
        });

        return $response;
    }
}

4. 路由配置

route目录下创建route.php文件,并添加支付接口的路由规则:

<?php
// route/route.php

use think\facade\Route;

Route::post('pay', 'WechatPayController/pay'); // 发起支付
Route::any('notify', 'WechatPayController/notify'); // 支付回调


参考原文:https://blog.csdn.net/weixin_42028285/article/details/93483574