支付--微信公众号

时间:2022-09-24 17:48:26

公众号支付的接口文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

先写好必要的方法:

    const API_UNIFIED_ORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    const API_ORDER_QUERY   = "https://api.mch.weixin.qq.com/pay/orderquery";
    const API_CLOSE_ORDER   = "https://api.mch.weixin.qq.com/pay/closeorder";

    //签名算法

    public static  function getSign($param, $appKey){

        $param = array_filter($param);
        ksort($param);
        $stringA = "";
        foreach ($param as $key=>$value){

            if (!empty($value)){//值为空则不参与签名
                $stringA .= $key."=".$value."&";
            }
        }
        $stringSignTemp = $stringA. "key=" .$appKey;

        return strtoupper(md5($stringSignTemp));
    }

    //生成随机数
    public static  function getNonceStr(){
        $nonce_str = '';
        $max    = mt_rand(16,32);
        $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

        for ( $i = 0; $i < $max; $i++ ) {
            $nonce_str.= substr($chars, mt_rand(0, strlen($chars)-1), 1);
        }

        return $nonce_str;
    }

    //数组转XML
    public static 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;
    }

    //XML转数组
    public static function xmlToArray($xml)
    {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $values;
    }

    public static function curlPost($url, $postData) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);    // https请求 不验证证书和hosts
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $res = curl_exec($ch);
        curl_close($ch);
        return $res;
    }


    调用统一下单接口:

    public function prePay($body,$userIp,$orderNo, $price, $userOpenId){

        $postData = [
            "appid"     => self::APPID,
            "mch_id"     => self::MCHID,
            "nonce_str" => self::getNonceStr(),
            "body"       => $body,
            "out_trade_no"   => $orderNo,
            "total_fee"     => (int)($price*100),//微信以分为单位
            "spbill_create_ip"   => $userIp,
            "notify_url"         => self::notify_url,
            "trade_type"         => "JSAPI",
            "openid"             => $userOpenId,
        ];

        $postData["sign"] = self::getSign($postData, self::APPKEY);
        $postData = Func::arrayToXml($postData);

        $response = Func::xmlToArray(Func::curlPost(self::API_UNIFIED_ORDER, $postData));

        if (isset($response["return_code"]) && $response["return_code"] == "SUCCESS"){
            if ($response["result_code"] == "SUCCESS"){
                $package = "prepay_id=".$response["prepay_id"];
                $res = [
                    "appId"     => self::APPID,
                    "timeStamp" => (string)time(),
                    "nonceStr"   => self::getNonceStr(),
                    "package"   => $package,
                    "signType"   => "MD5"
                ];
                $res["paySign"] = self::getSign($res, self::APPKEY);
                return $res;//调起支付所需参数
            }
        }
        return [];
    }


    /*
     * 接收异步通知,处理订单
     * */
    public function endOrder($param){

        if (isset($param["return_code"]) && $param["return_code"] == "SUCCESS" && $param["result_code"] == "SUCCESS"){

            $resSign = $param["sign"];
            unset($param["sign"]);
            $orderInfo = "订单信息";
            if (empty($orderInfo) || $param["total_fee"] != $orderInfo["price"]*100){
                return false;
            }
            $sign = self::getSign($param, self::APPKEY);
            if ($sign != $resSign){
                return false;//签名失败
            }
            if ($param["appid"] != self::APPID){
                return false;
            }
            if ($param["mch_id"] != self::MCHID){
                return false;
            }
            if (1 == $orderInfo["pay_status"]){
                return true;
            }else{
//TODO 处理订单
                return true;
            }
        }
        return false;
    }

    /*
     * 向微信发起订单查询
     * */
    public function queryOrder($orderNO)
    {
        $nonce_str = self::getNonceStr();
        $params = [
            "appid" => self::APPID,
            "mch_id" => self::MCHID,
            "out_trade_no" => $orderNO,
            "nonce_str" => $nonce_str
        ];
        $params["sign"] = self::getSign($params, self::APPKEY);
        $params = Func::arrayToXml($params);
        $response = Func::xmlToArray(Func::curlPost(self::API_ORDER_QUERY, $params));
        if (isset($response["return_code"]) && $response["return_code"] == "SUCCESS") {
            if ($response["result_code"] == "SUCCESS" && $response["trade_state"] == "SUCCESS"){
                return true;
            }
        }
        return false;
    }

    /*
     * 关闭支付订单
     * */
    public function closeOrder($orderNo){

        $nonce_str = self::getNonceStr();
        $params = [
            "appid" => self::APPID,
            "mch_id" => self::MCHID,
            "out_trade_no" => $orderNO,
            "nonce_str" => $nonce_str
        ];

        $params["sign"] = self::getSign($params, self::APPKEY);
        $params = Func::arrayToXml($params);
        $response = Func::xmlToArray(Func::curlPost(self::API_CLOSE_ORDER, $params));
        if (isset($response["return_code"]) && $response["return_code"] == "SUCCESS") {
            $res_sign = $response["sign"];//返回的签名
            unset($response["sign"]);
            $gen_sign = self::getSign($response, self::APPKEY);
            if ($res_sign != $gen_sign){
                return false;
            }
            if ($response["appid"] != self::APPID || $response["mch_id"] != self::MCH_ID ){
                return false;
            }
            if ($response["result_code"] == "SUCCESS"){
                return true;
            }
        }
        return false;
    }

商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。

注意:订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟。调用关单或撤销接口API之前,需确认支付状态。

接收异步通知时要注意:

        $param = file_get_contents("php://input");
        $param = Func::xmlToArray($param);