微信支付之原路退款

官方文档介绍

应用场景

当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。

注意事项

  • 交易时间超过一年的订单无法提交退款

  • 微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号

  • 请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次
    错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次

  • 每个支付订单的部分退款次数不能超过50次

  • 为保证支付安全需要下载支付api签名证书

实现

  • 发送退款申请
    /**
     * @do 自动退款问题
     * @parm order_uuid
     * @return status
    */
    public function wechatRefund()
    {
        //todo 自己的数据验证
        $input = file_get_contents('php://input');
        $input = json_decode($input, true);
        $payment = Db::table('payment')
            ->alias('p')
            ->join('mall_orders o', 'o.id = p.oid')
            ->field('p.transaction_id, p.total_fee')
            ->where('o.uuid', $input['uuid'])
            ->find();
        if($input['type'] == 1)
        {
            $payConfig = Config::get('app.WECHAT_PAY_CONFIG');
        }else{
            $payConfig = Config::get('app.WECHAT_JSAPI_PAY_CONFIG');
        }

        $refundData = array(
            'appid' => $payConfig['appid'],
            'mch_id' => $payConfig['mch_id'],
            'nonce_str' => '13vssdfsfsfewffffwfw',//随机字符串
//            'out_trade_no' => '',//和 transaction_id 二选一
            'transaction_id' => $payment['transaction_id'],//微信支付流水号(微信交易单号)
            'out_refund_no' => 'r242668049786'.rand(100,999),//todo 商家退款单号 自己生成自己的规则
            'refund_fee' => $payment['total_fee'],//这里可以少于总额
            'total_fee' => $payment['total_fee'],
            'notify_url' => 'http://'.$_SERVER['HTTP_HOST'].'/api/v2/payment/refundNotify'// 异步链接
        );

        $refundData['sign'] = $this->getWeixinSign($refundData, $payConfig['key']); //签名
        $xml = "<xml>";
        foreach ($refundData as $key=>$val)
        {
            $xml.="<".$key.">".$val."</".$key.">";
        }
        $xml.="</xml>";
        $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';

        $data = $this->curlClient($url, $xml, $input['type']);
        if($data)
        {
            $wxReturn = json_decode(json_encode(simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA)), true);

            return json($wxReturn);
        }else{
            //请求失败处理
        }
    }

    /**
     * @do 发布请求
     * @param $url 请求连接
     * @param $xml 请求XML数据
     * @param $type 1-app 2-jsapi (根据不同渠道配置 不同api证书)
     * @return xml数据
    */
    private function curlClient($url, $xml, $type = 1)
    {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch,CURLOPT_URL, $url);

        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLCERT, getcwd().'/../extend/wechat/apiclient_cert.pem');
        //默认格式为PEM,可以注释
        curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
        curl_setopt($ch,CURLOPT_SSLKEY,getcwd().'/../extend/wechat/apiclient_key.pem');

        //设置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
        $data = curl_exec($ch);
        curl_close($ch);

        return $data;
    }

    /**
     * @do 微信签名加密
     * @param 数据参数  加密key
     * @return 加密完数据
    */
    private function getWeixinSign($data,$key){
        ksort($data);
        $buff = "";
        foreach ($data as $k => $v)
        {
            if($k != "sign" && $v != "" && !is_array($v)){
                $buff .= $k . "=" . $v . "&";
            }
        }
        $buff = trim($buff, "&") . "&key=".$key;
        $string = md5($buff);
        //签名步骤四:所有字符转为大写
        $result = strtoupper($string);
        
        return $result;
    }
  • 示例展示
 失败或者错误
 {
     "return_code": "SUCCESS",
     "return_msg": "OK",
     "appid": "wx2decb7568432132",
     "mch_id": "150133123213",
     "nonce_str": "899bAeRBHvKzBiVG",
     "sign": "B47634E491630AB2C93AA3BE35DE960A",
     "result_code": "FAIL",
     "err_code": "REFUND_FEE_MISMATCH",
     "err_code_des": "订单金额或退款金额与之前请求不一致,请核实后再试"
 }

 {
     "return_code": "SUCCESS",
     "return_msg": "OK",
     "appid": "wx2decb7568411323",
     "mch_id": "15013312313",
     "nonce_str": "iuedVg1fXt3Khhyg",
     "sign": "1396FF43DADDC5EBDBE7756A2747C485",
     "result_code": "FAIL",
     "err_code": "ERROR",
     "err_code_des": "订单已全额退款"
 }


 成功事例
 {
     "return_code": "SUCCESS",
     "return_msg": "OK",
     "appid": "wx2decb75684533321326",
     "mch_id": "15013211312312",
     "nonce_str": "MEGUyLDW1jvI8lH8",
     "sign": "43BE291FC639D6D9EFC9FE72E6038E44",
     "result_code": "SUCCESS",
     "transaction_id": "4200000443201911147320071718",
     "out_trade_no": "s242933595518",
     "out_refund_no": "r242668049786143",
     "refund_id": "50300602272019111413238713216",
     "refund_channel": [],
     "refund_fee": "1",
     "coupon_refund_fee": "0",
     "total_fee": "1",
     "cash_fee": "1",
     "coupon_refund_count": "0",
     "cash_refund_fee": "1"
 }    
  • 上面只是申请了退款申请,至于成功与否还是根据回调的结果来看
/**
 * @do 退款结果通知
 * @return success
*/
public function refundNotify()
{
    //处理微信支付回调
    $testxml  = file_get_contents("php://input");  //接收微信发送的支付成功信息
    $result = XMLDataParse($testxml);
    if($result)
    {
        $payConfig = Config::get('app.WECHAT_PAY_CONFIG');
        if($result['appid'] != $payConfig['appid'])
        {
            $payConfig = Config::get('app.WECHAT_JSAPI_PAY_CONFIG');
        }

        $info = $this->refund_decrypt($result['req_info'], $payConfig['key']);
        $result['req_info'] = $info;
        save_payment_log('wechat', '微信退款回调开始','weChatNotify', json_encode($result, FILE_APPEND));
        //todo 根据回调信息处理后续逻辑
    }
}

private function refund_decrypt($str, $key) {
    $decrypt = base64_decode($str, true);
    $info = $this->xmlToArray(openssl_decrypt($decrypt , 'aes-256-ecb', $key, OPENSSL_RAW_DATA));

    return $info;
}

private function xmlToArray($xml)
{
    libxml_disable_entity_loader(true);     // 禁止引用外部xml实体
    $jsonxml = json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA));
    $result = json_decode($jsonxml, true);

    return $result;
}
  • 返回示例
{
    "return_code":"SUCCESS",
    "appid":"wx2decb75684323232",
    "mch_id":"15013232323",
    "nonce_str":"c45de72094424945a7f40ab878918d24",
    "req_info":{
        "out_refund_no":"r242668049786278",
        "out_trade_no":"s242988257840",
        "refund_account":"REFUND_SOURCE_RECHARGE_FUNDS",
        "refund_fee":"1",
        "refund_id":"50300602332019111413219448409",
        "refund_recv_accout":"支付用户零钱",
        "refund_request_source":"API",
        "refund_status":"SUCCESS",
        "settlement_refund_fee":"1",
        "settlement_total_fee":"1",
        "success_time":"2019-11-14 14:48:06",
        "total_fee":"1",
        "transaction_id":"4200000451201911142685392734"
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值