微信支付之原路退款

官方文檔介紹

應用場景

當交易發生之後一段時間內,由於買家或者賣家的原因需要退款時,賣家可以通過退款接口將支付款退還給買家,微信支付將在收到退款請求並且驗證成功之後,按照退款規則將支付款按原路退到買家帳號上。

注意事項

  • 交易時間超過一年的訂單無法提交退款

  • 微信支付退款支持單筆交易分多次退款,多次退款需要提交原支付訂單的商戶訂單號和設置不同的退款單號。申請退款總金額不能超過訂單金額。 一筆退款失敗後重新提交,請不要更換退款單號,請使用原商戶退款單號

  • 請求頻率限制: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"
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章