微信小程序支付、退款

本文中使用的编程语言是PHP,PHP框架是thinkPHP3.2.3.

一、支付类,含有支付、退款等接口。

<?php
/**
 * 微信支付类
 * Created by PhpStorm.
 * User: 科技
 * Date: 2020/4/26
 * Time: 10:30
 */

namespace Api;


class WxPayController
{
    /**
     * 小程序appid
     * @var string
     */
    private $appId;

    /**
     * 小程序secret
     * @var string
     */
    private $appSecret;

    /**
     * 商户平台商户id
     * @var string
     */
    private $mchId;

    /**
     * 商户平台密钥key
     * @var string
     */
    private $key;

    /**
     * 支付回掉地址
     * @var string
     */
    private $notifyUrl;

    /**
     * 获取用户唯一标识open_id api 接口地址
     * @var string
     */
    private $code2SessionApiUrl;

    /**
     * 统一下单api 接口地址
     * @var string
     */
    private $unifiedOrderApiUrl;

    /**
     * 错误信息
     * @var string
     */
    public $errorInfo;

    /**
     * WxPay constructor.
     */
    public function __construct()
    {
        $this->appId = C('APP_ID');
        $this->appSecret = C("APP_SECRET");
        $this->mchId = C("MCH_ID");
        $this->key = C("KEY");
        $this->notifyUrl = (isset($_SERVER['REQUEST_SCHEME']) ? ($_SERVER['REQUEST_SCHEME'] . '://') : '') . $_SERVER['HTTP_HOST'].'/Api/Pay/notifyPay.html';
        $this->code2SessionApiUrl = 'https://api.weixin.qq.com/sns/jscode2session';
        $this->unifiedOrderApiUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
    }

    /**
     * 添加错误信息
     * @param $err
     */
    public function setError($err)
    {
        $this->errorInfo = $err;
    }

    /**
     * 获取错误信息
     * @return string
     */
    public function getError()
    {
        return $this->errorInfo;
    }

    /**
     * 获取支付签名
     * @param $open_id
     * @param $order_no
     * @param $money
     * @return array
     */
    public function paySign($open_id, $order_no, $money)
    {
        if (!$open_id || !$order_no || !$money) {
            $this->setError('参数错误');
            return [];
        }

        $prepay_id = $this->unifiedorder($open_id, $order_no, $money);

        if (!$prepay_id) {
            return [];
        }

        $params = array(
            'appId' => $this->appId,
            'timeStamp' => time(),
            'nonceStr' => encrypt(16),
            'package' => 'prepay_id=' . $prepay_id,
            'signType' => 'MD5',
        );

        $params['paySign'] = $this->getSign($params);

        return $params;
    }

    /**
     * 异步签名验证
     * @param $data
     * @return bool
     */
    public function checkNotifySign($data)
    {
        if (!$data) {
            return false;
        }

        $sign = $data['sign'];

        unset($data['sign']);

        if ($sign == $this->getSign($data)) {
            return true;
        }

        return false;
    }

    /**
     * 异步回调处理成功时返回内容
     * @param $msg
     * @return string
     */
    public function notifyReturnSuccess($msg = 'OK')
    {
        return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[' . $msg . ']]></return_msg></xml>';
    }

    /**
     * 异步回调处理失败时返回内容
     * @param $msg
     * @return string
     */
    public function notifyReturnFail($msg = 'FAIL')
    {
        return '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[' . $msg . ']]></return_msg></xml>';
    }

    /**
     * 统一下单
     * @param $open_id
     * @param $order_no
     * @param $money
     * @return string
     */
    private function unifiedOrder($open_id, $order_no, $money)
    {
        $params = [
            'appid' => $this->appId,
            'mch_id' => $this->mchId,
            'nonce_str' => encrypt(16),
            'body' => '食品-订单', //商品简单描述
            'out_trade_no' => $order_no, //商户系统内部订单号
            'total_fee' => priceyuantofen($money), //订单总金额,单位为分
            'spbill_create_ip' => get_client_ip(), //用户端ips
            'notify_url' => $this->notifyUrl, //通知地址
            'trade_type' => 'JSAPI', //交易类型
            'openid' => $open_id, //用户标识
        ];

        $params['sign'] = $this->getSign($params);

        $xmlData = arrayToXml($params);


        $returnXml = postXml($xmlData, $this->unifiedOrderApiUrl);

        $returnArr = xmlToArray($returnXml);


        if ($returnArr['return_code'] == 'FAIL') {

            $this->setError($returnArr['return_msg']);
            return '';
        }

        return $returnArr['prepay_id'];

    }

    /**
     * 获取签名
     * @param $params
     * @return string
     */
    private function getSign($params)
    {
        if (!$params) {
            return '';
        }

        //step1: 排序
        ksort($params);

        //step2:格式化参数
        $paramUrl = formatParams($params, false);

        //step3:追加key
        $paramUrl = $paramUrl . '&key=' . $this->key;

        //step4: md5加密
        $paramUrl = md5($paramUrl);

        //step5:转化为大写
        $sign = strtoupper($paramUrl);

        return $sign;
    }


    /*
     * 查询支付结果
     * @param $params
     * @return string
     *
     * */
    public function query($out_trade_no)
    {
        //对外暴露的退款接口
        $result = $this->wxQueryApi($out_trade_no);
        return $result;
    }


    /*
     * 查询支付结果
     * */
    private function wxQueryApi($out_trade_no)
    {
        //通过微信api进行退款流程
        $parma = array(
            'appid' => $this->appId,
            'mch_id' => $this->mchId,
            'nonce_str' => encrypt(16),
            'out_trade_no' => $out_trade_no,
        );

        $parma['sign'] = $this->getSign($parma);
        $xmldata = arrayToXml($parma);

        $returnXml = postXml($xmldata, 'https://api.mch.weixin.qq.com/pay/orderquery');
        $result = xmlToArray($returnXml);
        return $result;
    }


    /*
     * 微信退款
     * @param $params
     * @return string
     *
     * */
    public function refund($out_trade_no, $total_fee, $refund_fee, $out_refund_no)
    {
        //对外暴露的退款接口
        $result = $this->wxRefundApi($out_trade_no, $total_fee, $refund_fee, $out_refund_no);
        return $result;
    }

    /*
     * 退款接口
     * */
    private function wxRefundApi($out_trade_no, $total_fee, $refund_fee, $out_refund_no)
    {
        //通过微信api进行退款流程
        $parma = array(
            'appid' => $this->appId,
            'mch_id' => $this->mchId,
            'nonce_str' => encrypt(16),
            'out_refund_no' => $out_refund_no,
            'out_trade_no' => $out_trade_no,
            'total_fee' => priceyuantofen($total_fee),
            'refund_fee' => priceyuantofen($refund_fee),
            'notify_url' => (isset($_SERVER['REQUEST_SCHEME']) ? ($_SERVER['REQUEST_SCHEME'] . '://') : '') . $_SERVER['HTTP_HOST'].'/Api/Pay/notifyRefund.html',
        );

        $parma['sign'] = $this->getSign($parma);
        $xmldata = arrayToXml($parma);
        $xmlresult = curl_post_ssl('https://api.mch.weixin.qq.com/secapi/pay/refund', $xmldata,  '/apiclient_cert.pem', '/apiclient_key.pem');

        $result = xmlToArray($xmlresult);
        return $result;
    }


    /*
  * 查看微信退款
  * @param $params
  * @return string
  *
  * */
    public function seeRefund($order_number)
    {
        //对外暴露的退款接口
        $result = $this->wxSeeRefundApi($order_number);
        return $result;
    }


    /*
     * 查看微信退款接口
     * */
    private function wxSeeRefundApi($order_number)
    {
        //通过微信api进行退款流程
        $parma = array(
            'appid' => $this->appId,
            'mch_id' => $this->mchId,
            'nonce_str' => encrypt(16),
            'out_trade_no' => $order_number,

        );

        $parma['sign'] = $this->getSign($parma);
        $xmldata = arrayToXml($parma);

        $returnXml = postXml($xmldata, 'https://api.mch.weixin.qq.com/pay/refundquery');

        $result = xmlToArray($returnXml);
        return $result;
    }



}

二、以上方法中所用的一些工具方法。


/**
 * PHP精确计算  主要用于货币的计算用
 * @param $n1 第一个数
 * @param $symbol 计算符号 + - * / %
 * @param $n2 第二个数
 * @param string $scale 精度 默认为小数点后两位
 * @return  string
 */

function pricecalc($n1, $symbol, $n2, $scale = '2')
{
    $res = "";
    switch ($symbol) {
        case "+"://加法
            $res = bcadd($n1, $n2, $scale);
            break;
        case "-"://减法
            $res = bcsub($n1, $n2, $scale);
            break;
        case "*"://乘法
            $res = bcmul($n1, $n2, $scale);
            break;
        case "/"://除法
            $res = bcdiv($n1, $n2, $scale);
            break;
        case "%"://求余、取模
            $res = bcmod($n1, $n2, $scale);
            break;
        default:
            $res = "";
            break;
    }
    return $res;
}

/**
 * 价格由元转分(用于微信支付单位转换)
 * @param $price 金额
 * @return int
 */
function priceyuantofen($price)
{
    $price = intval(pricecalc(100, "*", $price));
    return $price;
}

/**
 * 价格由分转元
 * @param $price 金额
 * @return float
 */
function pricefentoyuan($price)
{
    $price = pricecalc(priceformat($price), "/", 100);
    return $price;
}

/**
 * 价格格式化
 *
 * @param int $price
 * @return string    $price_format
 */
function priceformat($price)
{
    $price_format = number_format($price, 2, '.', '');
    return $price_format;

}
/**
 * array to xml
 * @param $arr
 * @return string
 */
function arrayToXml($arr)
{
    $xml = "<xml>";
    foreach ($arr as $key => $val) {
        if (is_array($val)) {
            $xml .= "<" . $key . ">" . arrayToXml($val) . "</" . $key . ">";
        } else {
            $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
        }
    }
    $xml .= "</xml>";
    return $xml;
}

/**
 * xml to array
 * @param $xml
 * @return mixed
 */
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;
}

/**
 * 格式化参数 array to key1=valve1&key2=value2
 * @param $params
 * @param $url_encode
 * @return string
 */
function formatParams($params, $url_encode)
{
    if (!$params) {
        return '';
    }

    $paramUrl = "";

    ksort($params);

    foreach ($params as $k => $v) {
        if ($url_encode) {
            $v = urlencode($v);
        }

        $paramUrl .= $k . "=" . $v . "&";
    }

    if (strlen($paramUrl) > 0) {
        $paramUrl = substr($paramUrl, 0, strlen($paramUrl) - 1);
    }

    return $paramUrl;
}

/**
 * curl post xml
 * @param $xml 参数
 * @param $url 请求地址
 * @param int $second 设置超时
 * @return mixed
 */
function postXml($xml, $url, $second = 60)
{
    $ch = curl_init();
    //设置超时
    curl_setopt($ch, CURLOPT_TIMEOUT, $second);
    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);
    //post提交方式
    curl_setopt($ch, CURLOPT_POST, TRUE);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);

    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
    curl_setopt($ch, CURLOPT_TIMEOUT, 40);
    set_time_limit(0);

    //运行curl
    $data = curl_exec($ch);
    curl_close($ch);
    return $data;
}

//需要使用证书的请求
//file_cert_pem,file_key_pem两个退款必须的文件
function curl_post_ssl($url, $vars, $file_cert_pem, $file_key_pem, $second = 30, $aHeader = array())
{
    $ch = curl_init();
    //超时时间
    curl_setopt($ch, CURLOPT_TIMEOUT, $second);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    //这里设置代理,如果有的话
    //curl_setopt($ch,CURLOPT_PROXY, '10.206.30.98');
    //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);

    //以下两种方式需选择一种

    //第一种方法,cert 与 key 分别属于两个.pem文件
    //默认格式为PEM,可以注释
    curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
//        curl_setopt($ch,CURLOPT_SSLCERT,getcwd().'/cert.pem');
    curl_setopt($ch, CURLOPT_SSLCERT, __DIR__.$file_cert_pem);
    //默认格式为PEM,可以注释
    curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
//        curl_setopt($ch,CURLOPT_SSLKEY,getcwd().'/private.pem');
    curl_setopt($ch, CURLOPT_SSLKEY, __DIR__.$file_key_pem);

    //第二种方式,两个文件合成一个.pem文件
//        curl_setopt($ch, CURLOPT_SSLCERT, getcwd() . '/all.pem');
    //要求结果为字符串且输出到屏幕上
    curl_setopt($ch,CURLOPT_RETURNTRANSFER,TRUE);
    if (count($aHeader) >= 1) {
        curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
    }


    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $vars);
    $data = curl_exec($ch);
    if ($data) {
        curl_close($ch);
        return $data;
    } else {
        $error = curl_errno($ch);
//            echo "call faild, errorCode:$error\n";

//        echo "curl出错,错误码:$error";
        curl_close($ch);
        return false;
    }
}

要说明的是退款接口wxRefundApi调用的curl_post_ssl方法需要传两个以pem结尾的文件,这两个文件需要传绝对路径,根据代码中的写法,只有把这两个文件放到curl_post_ssl所在文件同一级目录下面,才能正确的找到。

三、支付回调类。

<?php
/**
 * Created by PhpStorm.
 * User: 科技
 * Date: 2020/4/26
 * Time: 10:52
 */

namespace Api\Controller;

use Api\WxPayController;
use Common\Model\OrderModel;


class PayController
{

    /**
     *   状态 0已取消 1已下单 2已支付 3用户申请退款 4订单已完成 5商家同意退款  6微信平台接受退款异步返回申请成功 7异步返回退款申请失败  8统一下单异步返回支付失败  9退款成功  10统一下单异步返回成功
     */
    const STATUS_WAITING = 1;
    const STATUS_FAILED = 2;
    const STATUS_SUCCESS = 3;
    const STATUS_LOSE = 4;
    const STATUS_OTHER = 5;
    const STATUS_PROCESSING = 6;

    /**
     * 错误信息
     * @var string
     */
    public $errorInfo;

    /**
     * 小程序支付异步回调
     *
     */
    public function notifyPay()
    {

        $xml = file_get_contents('php://input', 'r');
        $resData = xmlToArray($xml);
        $wxPay = new WxPayController();

        M('pay_log')->add(['type'=>1,'data'=>json_encode($resData),'create_time'=>time()]);

        if ($wxPay->checkNotifySign($resData)) {


            $result = $this->updateNotifyPayOrder($resData['return_code'], $resData['openid'], $resData['out_trade_no'], pricefentoyuan($resData['total_fee']), $resData['transaction_id'], $resData);

            if ($result) {
                $resStr = $wxPay->notifyReturnSuccess();
            } else {
                $resStr = $wxPay->notifyReturnFail($this->errorInfo);
            }
        } else {
            $resStr = $wxPay->notifyReturnFail('签名验证失败');
        }
        echo $resStr;
    }


    /**
     * 支付成功修改数据库
     * @param $result 支付状态
     * @param $open_id open_id
     * @param $out_trade_no 客户订单号
     * @param $money 金额
     * @param $wx_trade_no 微信支付订单号
     * @param $data
     * @return bool
     * @throws \Exception
     */
    public function updateNotifyPayOrder($result, $open_id, $out_trade_no, $money, $wx_trade_no, $data)
    {

        //记录异步通知日志


        //订单查询

        $orderInfo = (new orderModel())->getorderInfo(['order_number' => $out_trade_no], 'id,order_number,shop_id,pay_money,status,user_id');

        if (empty($orderInfo)) {
            $this->errorInfo = '订单不存在';
            return false;

        }


        //如果订单状态为成功直接返回
        if (in_array($orderInfo['status'],[2,10,4])) {
            return true;
        }


        //订单支付状态为未处理
        if ($orderInfo['status'] == 1 || $orderInfo['status'] == 8) {

            //验证金额
            if (bccomp($orderInfo['pay_money'],$money,2)) {
                $this->errorInfo = '订单金额有误';
                return false;
            }


            //判断订单支付状态
            if ($result == 'SUCCESS') {

                $model = new OrderModel();

                //开启事务处理机制
                $model->startTrans();

                //修改订单状态为成功
                $res_success = (new orderModel())->editOrder(['status' => 10], ['id' => $orderInfo['id']]);

                if ($res_success) {
                    //如果都成立就提交
                    $model->commit();

                    return true;
                }else {

                    //先将原先的预处理进行回滚
                    $model->rollback();
                    $this->errorInfo = '订单状态修改失败';
                    return false;
                }
            } else {

                //修改订单状态为支付失败
                $res = (new orderModel())->editOrder(['status' => 8], ['id' => $orderInfo['id']]);

                if (!$res) {

                    $this->errorInfo = '订单状态修改失败';
                    return false;

                }

            }
        }

        $this->errorInfo = '订单处理失败!';
        return false;
    }




    /**
     * 小程序申请退款异步回调
     *
     */
    public function notifyRefund()
    {
        $xml = file_get_contents('php://input', 'r');
        $resData = xmlToArray($xml);
        $wxPay = new WxPayController();

        M('pay_log')->add(['type'=>2,'data'=>json_encode($resData),'create_time'=>time()]);

        if ($wxPay->checkNotifySign($resData)) {


            $result = $this->updateNotifyRefundOrder($resData['return_code'], $resData['out_trade_no'], pricefentoyuan($resData['total_fee']), $resData['transaction_id'], $resData);

            if ($result) {
                $resStr = $wxPay->notifyReturnSuccess();
            } else {
                $resStr = $wxPay->notifyReturnFail($this->errorInfo);
            }
        } else {
            $resStr = $wxPay->notifyReturnFail('签名验证失败');
        }
        echo $resStr;
    }

    /**
     * 退款成功修改数据库
     * @param $result 支付状态
     * @param $open_id open_id
     * @param $out_trade_no 客户订单号
     * @param $money 金额
     * @param $wx_trade_no 微信支付订单号
     * @param $data
     * @return bool
     * @throws \Exception
     */
    public function updateNotifyRefundOrder($result, $out_trade_no, $money, $wx_trade_no, $data)
    {

        //记录异步通知日志


        //订单查询

        $orderInfo = (new orderModel())->getorderInfo(['order_number' => $out_trade_no], 'id,order_number,pay_money,status,user_id');

        if (empty($orderInfo)) {
            $this->errorInfo = '订单不存在';
            return false;

        }


        //如果订单状态为成功直接返回
        if (in_array($orderInfo['status'],[5,6,4])) {
            return true;
        }

        //订单支付状态为用户已经申请退款
        if ($orderInfo['status'] == 3 ||$orderInfo['status'] == 7) {


            //验证金额
            if (bccomp($orderInfo['pay_money'],$money,2)) {
                $this->errorInfo = '订单金额有误';
                return false;
            }


            //判断订单支付状态
            if ($result == 'SUCCESS') {

                $model = new OrderModel();

                //开启事务处理机制
                $model->startTrans();

                //修改订单状态为成功
                $res_success = (new orderModel())->editOrder(['status' => 6], ['id' => $orderInfo['id']]);

                if ($res_success) {
                    //如果都成立就提交
                    $model->commit();

                    return true;
                }else {

                    //先将原先的预处理进行回滚
                    $model->rollback();
                    $this->errorInfo = '订单状态修改失败';
                    return false;
                }


            } else {


                //修改订单状态为退款申请失败
                $res = (new orderModel())->editOrder(['status' => 7], ['id' => $orderInfo['id']]);

                if (!$res) {

                    $this->errorInfo = '订单状态修改失败';
                    return false;

                }

            }
        }

        $this->errorInfo = '订单处理失败!';
        return false;
    }


}

四、接口调用。

  /**
     * 订单支付,统一下单接口,获取支付签名
     * @access public
     * @param string $orderId 订单id
     * @param string $token
     * @return array
     */
    public function pay()
    {

        try {

            $orderInfo = $this->orderHandelCheck($this->data['orderId']);

            if ($orderInfo['status'] != 1 ) {
                $this->json(201, '订单不是待支付状态');

            }

            //调用支付类
            $wxPay = new WxPayController();

            //获取签名
            $paySign = $wxPay->paySign($this->openid, $orderInfo['order_number'], $orderInfo['pay_money']);

            if (!$paySign) {
                $this->json(201, $wxPay->getError());
            }

            $this->json(200, '请求成功', $paySign);


        } catch (\Exception $e) {
//            $this->json($e->getMessage());
            $this->json(0);
        }
    }

 /**
     * 查询订单支付结果,以进行下一步动作
     * @access public
     * @param int $orderId 订单id
     * @param string $token
     * @return array
     */
    public function payResult()
    {

        try {

            $orderInfo = $this->orderHandelCheck($this->data['orderId']);

            if (!in_array($orderInfo['status'],[1,10])) {
                if($orderInfo['status']==8){
                    $this->json(201, '统一下单接口返回失败');
                }

                $this->json(201, '订单不是待支付状态');

            }

            //调用支付类
            $wxPay = new WxPayController();

            //获取查询结果
            $query_res = $wxPay->query($orderInfo['order_number']);

            if ($query_res['return_code']=='SUCCESS') {  //请求成功
            if ($query_res['result_code']=='SUCCESS') {  //订单接收成功
            if ($query_res['trade_state']=='SUCCESS') {  //交易状态

                $model = new OrderModel();

                //开启事务处理机制
                $model->startTrans();


                $ModelThink = new \Think\Model(); // 实例化一个model对象 没有对应任何数据表


                $sql = sprintf("SELECT `id`,`order_number`,`shop_id`,`create_time`,`pay_money`,`status` FROM `pre_order` WHERE `id` = %s FOR UPDATE ", $orderInfo['id']);//查询语句

                $orderInfo=$ModelThink->query($sql)[0];


                //修改订单状态为成功
                $res_success = (new orderModel())->editOrder(['pay_status' => 1, 'status' => 2], ['id' => $orderInfo['id']]);

                if (!$res_success) {
                    //先将原先的预处理进行回滚
                    $model->rollback();
                    $this->json(201,'订单状态修改失败');

                }

                //保存账户余额表

                $walletModel = new ShopWalletModel();

                $shopWallet = $walletModel->getShopWalletInfo(['shop_id' => $orderInfo['shop_id']], 'account');

                if ($shopWallet) {

                    if ($walletModel->editShopWallet(['account' => bcadd($shopWallet['account'], $orderInfo['pay_money'], 2)], ['shop_id' => $orderInfo['shop_id']])) {   //修改钱包金额

                        if ((new ShopWalletFlowModel())->addShopWalletFlow(['shop_id' => $orderInfo['shop_id'], 'account' => $orderInfo['pay_money'], 'create_time' => time(), 'order_id' => $orderInfo['id']])) { //添加钱包流水
                            //如果都成立就提交
                            $model->commit();

                            $this->json(200, '支付成功');
                        } else {
                            //先将原先的预处理进行回滚
                            $model->rollback();
                            $this->json(201,'商户钱包流水添加失败!');

                        }


                    } else {
                        //先将原先的预处理进行回滚
                        $model->rollback();
                        $this->json(201,'账户信息保存失败!');

                    }

                } else {

                    //先将原先的预处理进行回滚

                    $model->rollback();
                    $this->json(201,'商户钱包有误!');

                }


            }elseif($query_res['trade_state']=='REFUND'){
                $this->json(201, '转入退款');
            }elseif($query_res['trade_state']=='NOTPAY'){
                $this->json(201, '未支付');
            }elseif($query_res['trade_state']=='CLOSED'){
                $this->json(201, '已关闭');
            }elseif($query_res['trade_state']=='USERPAYING'){
                $this->json(201, '用户支付中');
            }elseif($query_res['trade_state']=='PAYERROR'){
                $this->json(201, '支付失败');
            }else{
                $this->json(201, '交易状态未知');
            }

            }else{
                $this->json(201, $query_res['err_code_des']);
            }

            }else{
                $this->json(201, $query_res['return_msg']);
            }




        } catch (\Exception $e) {
            $this->json(0);
        }
    }

  /**
     * 请求退款
     * @access public
     * @param string $orderId 订单id
     * @param string $token
     * @return array
     */

public function refund(){

   $wxPay = new WxPayController();

    //申请退款
    $res_refund = $wxPay->refund($Order_info['order_number'], $Order_info['pay_money'], $Order_info['pay_money'], $Order_info['refund_number']);


    if ($res_refund['return_code'] == 'SUCCESS') { //接口请求成功
        if ($res_refund['result_code'] == 'SUCCESS') { //退款申请接受成功

            if ($model->editOrder(['status' => 5, 'Refund' => ['status' => 2, 'handle_time' => time()]], ['id' => $id], 'Refund')) {
                $this->success("提交成功,等待微信平台审核通过!", '', true);

            }else{

                $this->error("修改订单状态失败,请尽快联系管理员处理!!!", '', true);
            }


        } else {

            $this->error("申请失败,微信接受退款失败," . $res_refund['err_code_des'], '', true);
        }

    }else{
        $this->error("请求失败," . $res_refund['return_msg'], '', true);
    }

}

 /**
     * 查询订单退款结果,以进行下一步动作
     * @access public
     * @param int $orderId 订单id
     * @param string $token
     * @return array
     */
function checkRefund(){

   $wxPay = new WxPayController();

     //退款进度
     $see_res = $wxPay->seeRefund($order_info['order_number']);

     if($see_res['return_code']=='SUCCESS'){  //请求成功

         if($see_res['result_code']=='SUCCESS'){ //退款申请接收成功

         if($see_res['refund_status_0']=='SUCCESS'){ //退款状态

             $model = new OrderModel();

             //由于要计算流水,开启事务
             //开启事务处理机制
             $model->startTrans();

             $ModelThink = new \Think\Model(); // 实例化一个model对象 没有对应任何数据表


             $sql = sprintf("SELECT `id`,`order_number`,`shop_id`,`create_time`,`pay_money`,`status` FROM `pre_order` WHERE `id` = %s FOR UPDATE ", $order_info['id']);//查询语句

             $order_info=$ModelThink->query($sql)[0];

             if ($model->editOrder(['status' => 9], ['id' => $id])) {

                 $walletModel = new ShopWalletModel();

                 $shopWallet = $walletModel->getShopWalletInfo(['shop_id' => $order_info['shop_id']], 'account');


                 if ($shopWallet) {

                     if ($walletModel->editShopWallet(['account' => bcsub($shopWallet['account'], $order_info['pay_money'], 2)], ['shop_id' => $order_info['shop_id']])) {

                         if ((new ShopWalletFlowModel())->addShopWalletFlow(['shop_id' => $order_info['shop_id'], 'account' => -$order_info['pay_money'], 'create_time' => time(), 'order_id' => $id, 'scene' => 2])) {
                             //如果都成立就提交
                             $model->commit();
                             $this->success("退款成功!", '', true);

                         } else {
                             //先将原先的预处理进行回滚
                             $model->rollback();
                             $this->error("流水添加失败,请重试或尽快联系管理员处理!!!", '', true);
                         }


                     } else {
                         //先将原先的预处理进行回滚
                         $model->rollback();
                         $this->error("钱包操作失败,请重试或尽快联系管理员处理!!!", '', true);
                     }

                 } else {

                     //先将原先的预处理进行回滚
                     $model->rollback();
                     $this->error("钱包有误,请重试或尽快联系管理员处理!!!", '', true);
                 }


             } else {
                 //先将原先的预处理进行回滚
                 $model->rollback();
                 $this->error("修改订单状态失败!请重试或尽快联系管理员处理!!", '', true);

             }


         }elseif($see_res['refund_status_0']=='REFUNDCLOSE'){

             $this->success("退款关闭,请尽快联系管理员处理!!!", '', true);
         }elseif($see_res['refund_status_0']=='PROCESSING'){

             $this->success("退款处理中!", '', true);
         }elseif($see_res['refund_status_0']=='CHANGE'){

             $this->success("退款退款异常,请尽快联系管理员处理!!!", '', true);
         }


         }else{


             $this->error('退款申请接收失败,'.$see_res['err_code_des'], '', true);

         }
     }else{

         $this->error('请求失败,'.$see_res['return_msg'], '', true);

     }

}

接口使用的返回方法:

 protected function json($code=0,$message='',$data=[])
    {

        if (empty($message)) {
            //通用提示信息
            switch ($code) {
                case 200:
                    $message = '请求成功';
                    break;
                case 0:
                    $message = '请求失败';
                    break;
                case -2:
                    $message = '参数缺失';
                    break;
                default:
                    $message = $message;
                    break;

            }
        }

        return $this -> ajaxReturn(['code' => $code, 'message' => $message, 'data' => $data]);

    }

本文参考的主要文章有:

PHP 微信小程序退款

posted @ 2020-12-25 16:48  逍遥闲人  阅读(366)  评论(0编辑  收藏  举报