精华内容
下载资源
问答
  • QQ小程序支付

    千次阅读 2020-02-14 20:15:43
    QQ小程序支付 Java后端 同学折腾QQ小程序的支付折腾了好几天,没有完成统一下单,因为我做过微信和支付宝支付,他就让我帮忙搞 我搞了好两三个小时,也没搞出来,最终我觉得问题在商户key那里,问了几次甲方,他说...

    QQ小程序支付 Java后端

    同学折腾QQ小程序的支付折腾了好几天,没有完成统一下单,因为我做过微信和支付宝支付,他就让我帮忙搞
    我搞了好两三个小时,也没搞出来,最终我觉得问题在商户key那里,问了几次甲方,他说key没问题
    我仍然觉得问题很有可能在key,就去直接给他重置了key,然后,就成功完成了支付…
    总结,永远不要相信甲方

    QQ小程序支付与微信小程序支付类似,签名方式完全相同,提交的xml有些不同

    QQ小程序统一下单文档
    微信小程序验签工具(QQ小程序适用)

    首先是配置类,设置为包内访问权限,其实应该放于properties文件,或者直接配置在xml中,偷了个懒直接写在了代码中

    public class PayConfigs {
    
        final static String appid="";
    
        final static String mchid="";
    
        final static String key="";
    
        final static String reqAd="https://qpay.qq.com/cgi-bin/pay/qpay_unified_order.cgi";
    }
    

    小程序支付需要首先发起一个request到后端并携带一些商品信息,后端提交XML然后返回一个prepay_id到前端,小程序提供唤醒支付API调用

    qq.request({
          url: "请求地址",
          data: { /* 数据 */ },
          success: function(result) {
            if (result.data) {
              qq.requestPayment({
    			  package: "prepay_id=" + result.data.prepay_id,
    			  bargainor_id: "", //商户号
    			  success(res) { },
    			  fail(res) { }
    			})
            }
          }
        })
    

    发起支付的Java方法,需要用到一个工具类,在文末写明

    public Map<String,String> qqPay() throws Exception{
            String mchid = PayConfigs.mchid;
            String nonce_str = PayUtil.getRandomStringByLength(16);
            String body = "测试";
            String out_trade_no = "OTS"+ PayUtil.getRandomStringByLength(12); //商户订单号
            String fee_type = "CNY";
            String total_fee = "100"; //自定义货币总额,单位为分
            String spbill_create_ip = ""; // 用户客户端ip
            String trade_type = "JSAPI"; //小程序默认为JSAPI
            String notify_url = "http://www.baidu.com"; //回调地址
    
            Map<String, String> packageParams = new HashMap<>();
            packageParams.put("mch_id", mchid);
            packageParams.put("nonce_str", nonce_str);
            packageParams.put("body", body);
            packageParams.put("out_trade_no", out_trade_no + ""); //商户订单号
            packageParams.put("total_fee", total_fee + ""); //支付金额,需要转成字符串
            packageParams.put("spbill_create_ip", spbill_create_ip);
            packageParams.put("notify_url", notify_url); //支付成功后的回调地址
            packageParams.put("trade_type", trade_type); //支付方式
    
            String result = PayUtil.exec(packageParams,PayConfigs.key,PayConfigs.reqAd);
            System.out.println(result);
    
            // 业务逻辑
    
            return PayUtil.xmlToMap(result);
        }
    

    当用户支付成功后腾讯服务器会访问提交的notify_url即回调地址,并携带XML提供订单号与签名验证等

    public String acceptPay(HttpServletRequest request) throws Exception{
            BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
            String line;
            StringBuilder stringBuilder = new StringBuilder();
            while ((line = br.readLine()) != null) {
                stringBuilder.append(line);
            }
            br.close();
            String notityXml = stringBuilder.toString();
            Map<String,String> acceptParam = PayUtil.xmlToMap(notityXml);
            if (acceptParam.get("trade_state").equals("SUCCESS") && PayUtil.verifySign(acceptParam,PayConfigs.key)){
    
                // 注意,在QQ服务器收到Accept之前可能会产生多次回调。需要有处理多次回调的代码
                // 业务逻辑
    
                System.out.println(PayUtil.acceptXML());
            }
            return PayUtil.acceptXML();
        }
    

    依赖以及工具类 文章提及的所有代码

    <dependencies>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.6</version>
        </dependency>
    </dependencies>
    
    package com.utils;
    
    import org.apache.commons.codec.digest.DigestUtils;
    
    import java.io.*;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.nio.charset.StandardCharsets;
    import java.util.*;
    import org.w3c.dom.Node;
    import org.w3c.dom.NodeList;
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    
    
    public class PayUtil {
    
        public static String exec(Map<String, String> map, String key, String gateway) {
            Map<String, String> sortedMap = sortMapByKey(map);
            String sign = getLinkToSign(sortedMap, key);
            String xml = mapToXml(sortedMap, sign);
            String result = PayUtil.httpRequest(gateway, "POST", xml);
            return result;
        }
    
        public static String getRandomStringByLength(int length) {
            String base = "abcdefghijklmnopqrstuvwxyz0123456789";
            Random random = new Random();
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < length; i++) {
                int number = random.nextInt(base.length());
                stringBuilder.append(base.charAt(number));
            }
            return stringBuilder.toString();
        }
    
         public static Map<String, String> xmlToMap(String strXML) throws Exception {
            Map<String, String> data = new HashMap<>();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes(StandardCharsets.UTF_8));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            return data;
        }
    
        public static boolean verifySign(Map<String, String> map, String key){
            String sign = map.get("sign");
            map.remove("sign");
            Map<String, String> sortedMap = sortMapByKey(map);
            String xmlSign = getLinkToSign(sortedMap, key);
            return xmlSign.equals(sign);
        }
    
        public static String acceptXML(){
            return "<xml><return_code>SUCCESS</return_code></xml>";
        }
    
    
        private static String sign(String text, String key) {
            text = text + "key=" + key;
    //        System.out.println("Sign Url: " + text);
            return DigestUtils.md5Hex(getContentBytes(text)).toUpperCase();
        }
    
    
        private static byte[] getContentBytes(String content) {
            return content.getBytes(StandardCharsets.UTF_8);
        }
    
    
        private static String getLinkToSign(Map<String, String> map, String payKey) {
            StringBuilder preStr = new StringBuilder();
            for (Map.Entry<String, String> m : map.entrySet()) {
                String key = m.getKey();
                String value = m.getValue();
                preStr.append(key).append("=").append(value).append("&");
            }
            String link = preStr.toString();
            return sign(link, payKey);
        }
    
        private static String httpRequest(String requestUrl, String requestMethod, String outputStr) {
            StringBuilder stringBuilder = new StringBuilder();
            try {
                URL url = new URL(requestUrl);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod(requestMethod);
                conn.setDoOutput(true);
                conn.setDoInput(true);
                conn.connect();
                if (null != outputStr) {
                    OutputStream os = conn.getOutputStream();
                    os.write(outputStr.getBytes(StandardCharsets.UTF_8));
                    os.close();
                }
                InputStream is = conn.getInputStream();
                InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);
                String line;
                while ((line = br.readLine()) != null) {
                    stringBuilder.append(line);
                }
                br.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return stringBuilder.toString();
        }
    
    
        private static Map<String, String> sortMapByKey(Map<String, String> map) {
            List<String> keys = new ArrayList<>(map.keySet());
            Collections.sort(keys);
            // HashMap底层是数组加链表,会把key的值放在通过哈希算法散列后的对象的数组坐标上,
            // 所以取得的值是按哈希表来取的,所以和放入的顺序无关
            // 保持有序需要用LinkedHashMap
            Map<String, String> m = new LinkedHashMap<>();
            for (String key : keys) {
                m.put(key, map.get(key));
            }
            map.clear();
            return m;
        }
    
        private static String mapToXml(Map<String, String> map, String sign) {
            StringBuilder stringBuilder = new StringBuilder().append("<xml>");
            for (Map.Entry<String, String> m : map.entrySet()) {
                stringBuilder.append("<").append(m.getKey()).append(">")
                        .append(m.getValue()).append("</").append(m.getKey()).append(">");
            }
            stringBuilder.append("<sign>").append(sign).append("</sign>").append("</xml>");
            return stringBuilder.toString();
        }
    
    }
    
    展开全文
  • 由于公司业务需要,最近这段时间对接了QQ小程序支付【包括QQ钱包支付 和 QQ小程序内微信支付】,由于网络上相关的资料很少,遂留此文,以备后用。【顺便吐槽一下,官方文档不可全信】 由于业务关系,此处将 QQ钱包...

    前言

    由于公司业务需要,最近这段时间对接了QQ小程序支付【包括QQ钱包支付 和 QQ小程序内微信支付】,由于网络上相关的资料很少,遂留此文,以备后用。【顺便吐槽一下,官方文档不可全信】

    由于业务关系,此处将 QQ钱包支付 和 QQ小程序内微信支付 两种支付放在一起,通过条件选择相应支付方式。如你的业务不需要同时接入两种支付方式,可自由拆分

    准备工作

    语言:PHP

    1. QQ钱包支付

    官方流程图:QQ钱包支付

    必要步骤:先开通QQ钱包的商户号,然后和QQ小程序进行绑定。

    后端流程:

    • 接收前端参数,如用户ID,商品ID…
    • 根据自有规则生成订单
    • 组装参数,调用QQ 统一下单接口,生成预付单
    • 将QQ后台返回的参数 pre_payid 返回给前端
    • 前端支付成功,QQ后台会异步回调我们的回调接口(调用QQ统一下单接口时传入)
    • 回调接口里根据接收参数处理订单逻辑,如标记订单已完成支付、通知商品购买成功、消费记录…
    1. 微信支付

    官方流程图:微信支付

    必要步骤:在QQ小程序开发者管理端绑定一个微信支付商户号【这意味着你首先要开通微信支付】

    后端流程:

    • 后端流程基本和QQ钱宝支付差不多,不同的是QQ后台不对预付单请求做处理,只是充当一个代理转发的角色-------将请求转发至微信后台【使用微信H5统一下单方式】,在微信后台生成预付单信息
    • 微信H5支付有两个版本,代号 V2V3区别和文档地址。此文使用的是 V2版本,签名采用的加密方式为 MD5

    PHP服务端代码

    支付类规范接口

    <?php
    
    
    namespace xxxx;
    
    
    interface PayInterface
    {
        /**
         * @desc 支付  Interface
         */
    
    
        /**
         * @desc 用户下单
         * @return mixed
         */
        public function createOrder();
    
    
        /**
         * @desc 支付回调
         * @return mixed
         */
        public function payNotify();
    
    
        /**
         * @desc 支付订单查询
         * @return mixed
         */
        public function queryPayOrder();
    
    
        /**
         * @desc 申请退款
         * @return mixed
         */
        public function payRefund();
    }
    

    QQ小程序支付类

    <?php
    
    
    namespace xxxx;
    
    
    use xxxx;
    
    
    class QqPay implements PayInterface
    {
    
        /**************************************  QQ参数 【QQ支付相关】    ***************************************/
    
        //QQ小程序 appID  
        private $appId = '';
    
        //QQ小程序 secret
        private $appSecret = '';
    
        //QQ小程序 商户号
        private $mchId = '';
    
        //支付秘钥---需在QQ商户后台设置
        private $key = '';
    
        //获取access token url
        private $getTokenUrl = 'https://api.q.qq.com/api/getToken';
    
        //QQ钱包支付回调地址---填写处理QQ支付完成逻辑地址(需要公网能访问,不能带参数)
        private $notifyUrl = '';
    
        //接口API URL base(QQ支付接口基地址)
        private $apiUrlPrefix = 'https://qpay.qq.com';
    
        //下单地址URL (QQ钱包支付--生成预付单地址)
        private $unifiedOrderUrl = "/cgi-bin/pay/qpay_unified_order.cgi";
    
    
        //查询订单URL(QQ钱包支付)
        private $orderQueryUrl = "/cgi-bin/pay/qpay_order_query.cgi";
        //关闭订单URL (QQ钱包支付)
        private $closeOrderUrl = "/cgi-bin/pay/qpay_close_order.cgi";
    
        /**************************************  微信参数 【用于QQ小程序调用微信支付】    ***************************************/
    
        //wx key
        private $wxKey = '';
    
        //微信公众号appId
        private $wxAppId = '';
    
        //微信商户号
        private $wxMchId = '';
    
        //QQ 小程序平台 V2 版 支付回调代理地址
        private $notifyUrlQqAgent = 'https://api.q.qq.com/wxpay/notify';
    
        //wap_url WAP网站URL地址 【微信H5下单必填参数,详见:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1】
        private $wapUrl = '';
    
        //wap_name WAP 网站名 【微信H5下单必填参数】
        private $wapName = '';
    
        //QQ小程序内微信支付回调url---填写处理微信支付完成逻辑地址(需要公网能访问,不能带参数)【微信H5支付成功通知地址,微信回调的实际是QQ后台地址,然后QQ后台转发到此地址】
        private $wxNotifyUrl = '';
    
        //H5下单代理url base
        private $qqAgentUrl = 'https://api.q.qq.com/wxpay/unifiedorder';
    
    
        /**
         * @desc 用户下单  创建预支付订单
         * @return array|mixed|string[]
         * @author BaTianHu
         */
        public function createOrder()
        {
            //校验用户登录态--如无特殊原因,此步骤强烈建议在逻辑层之前进行(如:鉴权层),如未登陆,或者权限不够,直接返回
    
            //接收参数,如用户ID,商品ID...
    
    		//查询相应信息,并验证信息的有效性,如商品信息,如无效,直接返回
    		
    
    		//todo 如有需要,可将订单逻辑拆分出去,会显得数据层级更合理,更易维护
            try {
                //生成订单号
                $orderNo = Pay::createOrderNo();
                //应付价格 单位 /分
                $totalFee = $price * 100;
    
    			//赋值
                $pay = new Pay();
                $pay->order_no = $orderNo;
    			......
    			$pay->created_at = time();
    
    			//保存订单
                if ($pay->save(false)) {
                    //预下单
                    $retInfo = $this->unifiedOrder($orderNo, $totalFee, $isWx);
    
                    //判断预字符订单是否生成成功
                    if ($retInfo['return_code'] === 'SUCCESS' && $retInfo['result_code'] === 'SUCCESS') {
                        //QQ钱包支付需返回参数
                        $data['prepay_id'] = $retInfo['prepay_id'];
    
                        if ($isWx) {
                        	//微信支付需返回参数-----用于前端跳转微信支付
                            $data['mweb_url'] = $retInfo['mweb_url'];
                            //用于前端查询订单状态
                            $data['order_no'] = $orderNo;
                        }
                        return ['errcode' => 'ok', 'errmsg' => '创建订单成功', 'data' => $data];
                    }
    
                    //todo 如果需要,这里可以存一个日志,保存失败原因
    
                    return ['errcode' => 'fail', 'errmsg' => '预下单失败'];
    
                } else {
                    return ['errcode' => 'fail', 'errmsg' => '创建订单失败'];
                }
    
            } catch (\Exception $exception) {
                //todo 记录错误信息
                
                return ['errcode' => 'fail', 'errmsg' => '创建订单失败'];
            }
        }
    
    
        /**
         * @desc 支付回调【QQ钱包支付】
         * @return array|false|mixed
         * @throws Exception
         * @author BaTianHu
         */
        public function payNotify()
        {
            $notifyDataXml = file_get_contents("php://input");
    
            $data = OtherCommon::xml_to_data($notifyDataXml);
    
            $sign = $data['sign'];
            unset($data['sign']);
    
            if ($sign <> $this->sign($data)) {
                // 验签失败
                return $this->setRetInfo('签名失败');
            }
    
            // 如果订单已支付,进行业务处理并返回核销信息
            if(isset($data['trade_state']) && $data['trade_state'] == 'SUCCESS') {
    
                //处理订单支付逻辑---此处调用处理订单逻辑方法(根据各自业务场景或有不同)
                $notifyDealInfo = Pay::payNotifyPro($data['out_trade_no'], $data['total_fee'], $data['transaction_id']);
    
                if ($notifyDealInfo === true) {
                    //逻辑处理成功
                    return $this->setRetInfo();
                }
                //逻辑处理失败
                return $this->setRetInfo('逻辑处理失败');
            }
    
            //参数格式错误
            return $this->setRetInfo('参数格式校验错误');
        }
    
    
        /**
         * @desc QQ预下单  【QQ钱包支付  or  QQ内微信支付】
         * @param string $orderNo
         * @param int $totalFee
         * @param bool $isWx
         * @return array
         * @throws LocalRedisException
         * @author BaTianHu
         */
        private function unifiedOrder(string $orderNo, int $totalFee, $isWx = false) :array
        {
    
            $params['nonce_str'] = 'hdakhgdjsa';  //随机数---生成一个随机数
            $params['body'] = '测试---不可描述';  //商品描述
            $params['out_trade_no'] = $orderNo;  //订单号--商户平台订单号
            $params['total_fee'] = $totalFee;    //总金额 单位 /分
            $params['spbill_create_ip'] = OtherCommon::getUserIp();  //终端IP---用户端实际IP
    
            if ($isWx) {
                //QQ内微信支付
    
                $params['appid'] = $this->wxAppId;
                $params['mch_id'] = $this->wxMchId;
                $params['sign_type'] = 'MD5';  //签名方式
                $params['notify_url'] = $this->notifyUrlQqAgent;
                $params['trade_type'] = 'MWEB';  //交易类型
                $params['scene_info'] = '{"h5_info": {"type":"Wap","wap_url": '.$this->wapUrl.',"wap_name": "'.$this->wapName.'"}}';
    
                //签名
                $params['sign'] = $this->sign($params, $this->wxKey);
    
                //获取代理支付地址
                $url = $this->getQqAgentUrl($this->qqAgentUrl);
    
            } else {
                //QQ钱包支付
    
                $params['appid'] = $this->appId;
                $params['mch_id'] = $this->mchId;
                $params['fee_type'] = 'CNY';  //货币类型  人民币
                $params['notify_url'] = OtherCommon::getBaseUrl($this->notifyUrl, 'https://');  //回调地址
                $params['trade_type'] = 'MINIAPP';  //支付场景
    
                //签名
                $params['sign'] = $this->sign($params);
    
                //支付地址
                $url = $this->getQqUrl($this->unifiedOrderUrl);
    
            }
    
            //数组转xml
            $xml = OtherCommon::data_to_xml($params);
    
            //请求QQ预下单接口
            $response = OtherCommon::postXmlCurl($xml, $url);
    
            //返回数组结果
            return OtherCommon::xml_to_data($response);
        }
    
    
        /**
         * @desc 支付订单查询
         * @return mixed
         */
        public function queryPayOrder()
        {
            // TODO: Implement queryPayOrder() method.
        }
    
        /**
         * @desc 申请退款
         * @return mixed
         */
        public function payRefund()
        {
            // TODO: Implement payRefund() method.
        }
    
    
        /**
         * @desc 生成|校验 签名 
         * @param array $signParam  参与签名的参数
         * @param string $key 默认为QQ支付 key
         * @return string
         * @author BaTianHu
         */
        private function sign(array $signParam, string $key = '')
        {
            $sign = '';
            if (empty($signParam)) {
                return $sign;
            }
    
            //按字母升序排序
            ksort($signParam);
    
            $parts = array();
            foreach ($signParam as $k => $v) {
                $parts[] = $k . '=' . $v;
            }
            $sign = implode('&', $parts);
    
            if (empty($key)) {
                $key = $this->key;
            }
            $sign = $sign . "&key=".$key;
    
            return strtoupper(md5($sign));
        }
    
    
        /**
         * @desc 组装请求目的地址
         * @param $relativeUrl
         * @return string
         * @author BaTianHu
         */
        private function getQqUrl($relativeUrl)
        {
            return $this->apiUrlPrefix.$relativeUrl;
        }
    
    
        /**
         * @desc 设置返回信息
         * @param string $errMsg 错误信息
         * @return array|false|mixed
         * @author BaTianHu
         */
        private function setRetInfo(string $errMsg = '')
        {
            if (empty($errMsg)) {
                //处理成功
                $data['return_code'] = 'SUCCESS';
            } else {
                //处理失败
                $data['return_code'] = 'FAIL';
                //失败原因
                $data['return_msg'] = $errMsg;
            }
    
            return OtherCommon::data_to_xml($data);
        }
    
    
        /**
         * @desc 组装支付代理地址【QQ小程序平台-代理微信H5支付】
         * @param $baseAgentUrl
         * @return string
         * @throws LocalRedisException
         * @author BaTianHu
         */
        private function getQqAgentUrl($baseAgentUrl)
        {
            //获取access token
            $accessToken = $this->getAccessToken();
    
            //组装真实回调url
            $UrlEncodedNotifyUrl = urlencode(OtherCommon::getBaseUrl($this->wxNotifyUrl, 'https://'));
    
            //组装返回QQ代理微信支付URL
            return $baseAgentUrl.'?appid='.$this->appId.'&access_token='.$accessToken.'&&real_notify_url='.$UrlEncodedNotifyUrl;
        }
    
        /**
         * @desc 获取AccessToken
         * @return bool|mixed|string
         * @throws LocalRedisException
         * @author BaTianHu
         */
        private function getAccessToken()
        {
            //先尝试redis获取AccessToken
            $accessToken = $redis->get(ConstKeyHelpers::QQ_ACCESS_TOKEN_PRO);
            if (empty($accessToken)) {
                $param = [
                    'grant_type' => 'client_credential',
                    'appid'      => $this->appId,
                    'secret'      => $this->appSecret,
                ];
    
    			//请求QQ接口,获取AccessToken
                $retInfo = OtherCommon::curlGet($this->getTokenUrl, $param);
                $retInfo = json_decode($retInfo, true);
    
                if ($retInfo['errcode'] === 0) {
                    $accessToken = $retInfo['access_token'];
                    $expressTime = $retInfo['expires_in'] - 100;
                    //将AccessToken存入redis
                    $redis->set(ConstKeyHelpers::QQ_ACCESS_TOKEN_PRO, $accessToken, $expressTime);
                }
            }
    
            return $accessToken;
        }
    
    
    }
    

    公共方法文件

    <?php
    
    namespace xxx;
    
    use xxxxx;
    
    class OtherCommon
    {
        /**
         * @desc 组装绝对地址 || 获取网站基地址
         * @param string $relativeUrl
         * @param string $protocolType  示例:https://
         * @return string
         * @author BaTianHu
         */
        public static function getBaseUrl($relativeUrl = '', string $protocolType = '') :string
        {
            if (empty($protocolType)) {
                $protocolType = self::getHttpType();
            }
            return $protocolType.self::getHostDomain().$relativeUrl;
        }
    
        /**
         * @desc 获取当前网址协议(HTTP/HTTPS)
         * @return string
         * @author BaTianHu
         */
        public static function getHttpType() :string
        {
            return ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://';
        }
    
        //获取host信息
        public static function getHostDomain()
        {
            return $_SERVER['HTTP_HOST'] ?? '';
        }
    
        /**
         * @desc 获取用户的 IP 地址
         * @return mixed|string
         * @author BaTianHu
         */
        public static function getUserIp()
        {
            return $_SERVER['REMOTE_ADDR'] ?? '';
        }
    
        /**
         * @param $stringOne
         * @param $stringTwo
         * @return float|int
         * 返回两个字符串的相似度
         * vine 2019年4月22日10:10:57
         */
        public static function diffWords($stringOne, $stringTwo)
        {
            $sameNumber = similar_text($stringOne, $stringTwo);
            return $sameNumber * 2 / (strlen($stringOne) + strlen($stringTwo));
        }
    
        /**
         * @desc curl get
         * @param string $url
         * @param array $arr
         * @return bool|string
         * @author BaTianHu
         */
        public static function curlGet(string $url, array $arr = [])
        {
            //组装get参数
            if (!empty($arr)) {
                $tempArr = [];
                foreach ($arr as $k => $v) {
                    $tempArr[] = $k.'='.$v;
                }
                $str = implode('&',$tempArr);
                $url = $url.'?'.$str;
            }
    
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    
            $output = curl_exec($ch);
            curl_close($ch);
            return $output;
        }
    
    
        /**
         * @desc post curl
         * @param string $url
         * @param array $data
         * @return bool|string
         * @author BaTianHu
         */
        public static function postJsonCurl(string $url, array $data)
        {
            $data_string = json_encode($data);
    
            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
            curl_setopt($ch, CURLOPT_POSTFIELDS,$data_string);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                    'Content-Type: application/json',
                    'Accept: application/json',
                    'User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1)',
                    'Content-Length: ' . strlen($data_string))
            );
    
    		//忽略ssl检查
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    
            $data = curl_exec($ch);
            if (curl_errno($ch)) {
                $data = curl_error($ch);
            }
    
            curl_close($ch);
            return $data;
        }
    
    
        /**
         * @desc 以post方式提交xml到对应的接口url
         *
         * @param string $xml 需要post的xml数据
         * @param string $url url
         * @param bool $useCert 是否需要证书,默认不需要
         * @param int $second url执行超时时间,默认30s
         * @return bool|string
         * @author BaTianHu
         */
        public static function postXmlCurl(string $xml, string $url, $useCert = false, $second = 30)
        {
            $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,2);
            //设置header
            curl_setopt($ch, CURLOPT_HEADER, FALSE);
            //要求结果为字符串且输出到屏幕上
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
            if($useCert == true){
                //设置证书
                //使用证书:cert 与 key 分别属于两个.pem文件
                curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
                //curl_setopt($ch,CURLOPT_SSLCERT, WxPayConfig::SSLCERT_PATH);
                curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
                //curl_setopt($ch,CURLOPT_SSLKEY, WxPayConfig::SSLKEY_PATH);
            }
            //post提交方式
            curl_setopt($ch, CURLOPT_POST, TRUE);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
            //运行curl
            $data = curl_exec($ch);
    
            curl_close($ch);
            return $data;
        }
    
        /**
         * @desc 数组 转 XML
         * @param array $params 参数名称
         * @return false|string
         * @author BaTianHu
         */
        public static function data_to_xml(array $params)
        {
            if (count($params) <= 0) {
                return false;
            }
    
            $xml = "<xml>";
            foreach ($params as $key=>$val) {
                if (is_numeric($val)){
                    $xml.="<".$key.">".$val."</".$key.">";
                }else{
                    $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
                }
            }
            $xml.="</xml>";
            return $xml;
        }
    
        /**
         * @desc 将xml转为array
         * @param string $xml
         * @return false|mixed
         * @author BaTianHu
         */
        public static function xml_to_data(string $xml = '')
        {
            if (empty($xml)) {
                return [];
            }
            //将XML转为array 禁止引用外部xml实体
            libxml_disable_entity_loader(true);
            return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        }
    }
    

    值得注意的事

    I. QQ钱包支付通知文档中,trade_state 参数写的是 首字母大写 Success,但其实是全大写 SUCCESS

    在这里插入图片描述

    II. QQ商户后台无法登陆,下载安装安全控件后依然无法登陆

    若安装安全控件后不生效,页面一直提示未安装的情况,可安装如下浏览器操作。(适用于Windows及Mac OS等系统)

    1、Chrome浏览器

    若浏览器已升级到Chrome 76.0.3809.87(简称Chrome 76)及以上版本,请查看下述指引:

    (1)在浏览器的地址栏输入 chrome://flags/#enable-nacl

    在这里插入图片描述

    (2)找到Native Client插件,将Native Client的状态改为Enabled

    在这里插入图片描述

    (3)重启浏览器,再重新登录QQ钱包商户平台,尝试安装安全控件

    2、Internet Explorer 11浏览器

    请打开如下链接:https://support.microsoft.com/zh-cn/help/17621/internet-explorer-downloads安装 Internet Explorer 11浏览器后,再尝试安装安全控件。

    展开全文
  • 由于公司业务需要,最近这段时间对接了QQ小程序支付QQ小程序内微信支付,在微信H5统一下单和qq小程序当中反复横跳,求助官方、论坛里面遨游、发送邮件(邮件已经发送三天,但是没有任何回复)。 准备工作 语言java ...

    前言(如果做过微信小程序支付对接其实就是修改参数)

    由于公司业务需要,最近这段时间对接了QQ小程序支付QQ小程序内微信支付,在微信H5统一下单和qq小程序当中反复横跳,求助官方、论坛里面遨游、发送邮件(邮件已经发送三天,但是没有任何回复)。

    准备工作

    语言java

    # 微信支付配置
    wxpay:
      config:
        # 微信公众号身份的唯一标识
        wechatAppid: 
        # 微信公钥
        wechatMchid: 
        # 商户支付密钥Key
        wechatAppkey: 
        # JSAPI接口中获取openid
        wechatAppsecret: 
        # 微信通讯token值
        wechatToken: 
        # 回调地址
        notifyUrl: 
        # domain地址
        domain: 
    https://api.q.qq.com/wxpay/unifiedorder?appid=APPID6&access_token=access_token&&real_notify_url=wxpayCallback

    domain参数说明:

    1、access_token可以通过获取小程序全局唯一后台接口调用凭据(access_token)。调调用绝大多数后台接口时都需使用 access_token,开发者需要进行妥善保存

    GET https://api.q.qq.com/api/getToken?grant_type=client_credential&appid=APPID&secret=APPSECRET

    2、real_notify_url 支付成功后 qq小程序异步回调的地址 (记住使用 URLEncoder.encode()方法转化url地址)


    wx:
      open:
        # 微信开放平台 appid
        app_id: 
        # 微信开放平台 appsecret
        app_secret: 
        # 微信开放平台 重定向url
        redirect_url:

    代码

    @Override
    public ApiResponse<String> createPayQrCode(String orderNo, String subject, String amount, String payType, int bizType, String ip, String hbFqNum, String isBalance) {
        final String huabei = "huabei";
        if (StrUtil.isBlank(orderNo)) {
            return ApiResponse.fail("订单不存在");
        }
        PayBizDataParam payBizDataParam = new PayBizDataParam(bizType);
        payBizDataParam.setIsBalance(isBalance);
        payBizDataParam.setPayType(payType);
        ApiResponse<String> payQrCodeResponse = null;
    //支付宝支付
        if (OrderPayTypeEnum.ALIPAY.getCode().equals(payType)) {
            payQrCodeResponse = alipayConsumerService.getPayQrCode(subject, orderNo,
                    amount, null, new HashMap<>(1), payBizDataParam);
        } else if (huabei.equals(payType)) {
    //支付宝花呗支付
            Map<String, String> params = new HashMap<>(1);
            params.put("hbFqNum", hbFqNum);
            payQrCodeResponse = huabeiConsumerService.getPayQrCode(subject, orderNo,
                    amount, null, params, payBizDataParam);
        } else if (OrderPayTypeEnum.WXPAY.getCode().equals(payType)) {
    //微信扫码支付
            Map<String, String> params = new HashMap<>(1);
            params.put("spbill_create_ip", ip);
            params.put("trade_type", "NATIVE");
            payQrCodeResponse = wxpayConsumerService.getPayQrCode(subject, orderNo, amount, null, params, payBizDataParam);
        }else if (OrderPayTypeEnum.WXH5.getCode().equals(payType)) {
    //微信H5 qq小程序调用
            Map<String, String> params = new HashMap<>(2);
            params.put("spbill_create_ip", ip);
            params.put("trade_type", "MWEB");
            payQrCodeResponse = wxpayConsumerService.getPayQrCode(subject, orderNo, amount, null, params, payBizDataParam);
        }
        return payQrCodeResponse;
    }
    
    (二维码支付和H5都在里面 extMap里面还设置了不同的参数)
    public ApiResponse<String> getPayQrCode(String subject, String outTradeNo, String totalAmount, String notifyUrl, Map<String, String> extMap, PayBizDataParam payBizDataParam) {
        // 元转分
        int totalFee = PriceUtil.yuanToFen(totalAmount).intValue();
        Map<String, String> reqData = new HashMap<>(8);
        reqData.put("body", subject);
        outTradeNo = outTradeNo + "XCY" + OrderUtil.getNumberRandomStr(4);
        reqData.put("out_trade_no", outTradeNo);
        reqData.put("total_fee", totalFee + "");
        reqData.put("attach", payBizDataParam.toParams());
    
        if (OrderPayTypeEnum.WXH5.getCode().equals(payBizDataParam.getPayType())) { 
             //获取access_token
            Map<String, Object> accessTokenMap = JSON.parseObject(HttpUtil.get("https://api.q.qq.com/api/getToken?grant_type=client_credential&appid=" + appId + "&secret=" + secret), Map.class);
            //判断是否获取成功
       if (!((Integer) accessTokenMap.get("errcode") == 0)) {
                return ApiResponse.fail(RespCodeConstant.PAY_WXPAY_ERROR);
            }
            String accessToken = (String) accessTokenMap.get("access_token");
            Map<String, String> h5Map = new HashMap<>(3);
            Map<String, String> h5InfoMap = new HashMap<>(5);
            h5Map.put("payer_client_ip", extMap.get("spbill_create_ip"));
            h5InfoMap.put("type", "Wap");
            h5InfoMap.put("wap_url", "开通H5支付绑定的IP地址");
            h5InfoMap.put("wap_name", "绑定应用的名称");
            h5Map.put("h5_info", JSONObject.toJSONString(h5InfoMap));
            reqData.put("scene_info", JSONObject.toJSONString(h5Map));
            reqData.putAll(extMap);
            try {
                Map<String, String> respData = unifiedOrderH5(reqData, WxPayConstants.H5NOTIFY_URL, accessToken);
                log.info("微信H5支付信息{}", JSONObject.toJSONString(respData));
                String returnCode = respData.get(RETURN_CODE);
                String resultCode = respData.get(RESULT_CODE);
                if (returnCode.equals(WxPayConstants.SUCCESS) && resultCode.equals(WxPayConstants.SUCCESS)) {
                    String h5Url = respData.get("mweb_url");
                    return ApiResponse.ok(h5Url);
                } else if (returnCode.equals(WxPayConstants.FAIL)) {
                    return ApiResponse.fail(RespCodeConstant.PAY_WXPAY_ERROR);
                }
            } catch (Exception e) {
                log.error("微信支付异常", e);
            }
            return ApiResponse.fail(RespCodeConstant.PAY_WXPAY_ERROR);
    
        } else {
            reqData.putAll(extMap);
            try {
                Map<String, String> respData = unifiedOrder(reqData, notifyUrl);
                log.info(JSONObject.toJSONString(respData));
                String returnCode = respData.get(RETURN_CODE);
                String resultCode = respData.get(RESULT_CODE);
                if (returnCode.equals(WxPayConstants.SUCCESS) && resultCode.equals(WxPayConstants.SUCCESS)) {
                    String codeUrl = respData.get("code_url");
                    byte[] qrCodes = QrCodeUtil.generatePng(codeUrl, 200, 200);
                    return ApiResponse.ok(Base64.encode(qrCodes));
                } else if (returnCode.equals(WxPayConstants.FAIL)) {
                    return ApiResponse.fail(RespCodeConstant.PAY_WXPAY_ERROR);
                }
            } catch (Exception e) {
                log.error("微信支付异常", e);
            }
            return ApiResponse.fail(RespCodeConstant.PAY_WXPAY_ERROR);
        }
    }

    如果想学习支付调用可以访问gitee上的一个开源项目:https://gitee.com/javen205/IJPay?_from=gitee_search

    聚合支付,IJPay 让支付触手可及,封装了微信支付、QQ支付、支付宝支付、京东支付、银联支付、PayPal支付等常用的支付方式以及各种常用的接口。不依赖任何第三方 mvc 框架,仅仅作为工具使用简单快速完成支付模块的开发,可轻松嵌入到任何系统里

     

     

    展开全文
  • 小程序支付

    2018-11-29 15:45:53
    微信小程序开发----基于JAVA实现微信支付过程(小程序支付JSAPI) 项目需求:小程序中带支付功能(刚刚做完带微信支付功能的小程序项目,将自己做过的项目中用到的知识进行梳理、总结)  微信支付其实就是调用...

    微信小程序开发----基于JAVA实现微信支付过程(小程序支付JSAPI)

    项目需求:小程序中带支付功能(刚刚做完带微信支付功能的小程序项目,将自己做过的项目中用到的知识进行梳理、总结)

                       微信支付其实就是调用官方文档的“统一下单”,然后传入相应的参数的过程。

    一、仔细阅读官方文档:

    https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1

    二、小程序支付流程图:

    三、必填参数

    字段名 变量名 必填 类型 示例值 描述
    小程序ID appid String(32) wxd678efh567hg6787 微信分配的小程序ID
    商户号 mch_id String(32) 1230000109 微信支付分配的商户号
    随机字符串 nonce_str String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,长度要求在32位以内。推荐随机数生成算法
    签名 sign String(32) C380BEC2BFD727A4B6845133519F3AD6 通过签名算法计算得出的签名值,详见签名生成算法
    商品描述 body String(128) 腾讯充值中心-QQ会员充值

    商品简单描述,该字段请按照规范传递,具体请见参数规定

    商户订单号 out_trade_no String(32) 20150806125346 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一。详见商户订单号
    标价金额 total_fee Int 88 订单总金额,单位为分,详见支付金额
    终端IP spbill_create_ip String(16) 123.12.12.123 APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
    通知地址 notify_url String(256) http://www.weixin.qq.com/wxpay/pay.php 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
    交易类型 trade_type String(16) JSAPI 小程序取值如下:JSAPI,详细说明见参数规定
    用户标识 openid String(128) oUpF8uMuAJO_M2pxb1Q9zNjWeS6o trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。openid如何获取,可参考【获取openid

    ****** 在这里重要说明一下,因为在交易类型是JSAPI(小程序)时,用户标识openid参数是必填必填的。*****

    四、开发流程

    1、准备工作:

    1)从小程序前端获取       openid-----用户标识
    2)配置文件中配置的固定参数: 

    字段名 描述
    appid 小程序id(以wx开头的一串数字)
    mchId 商户号(一串10位数字)
    notify_url 支付成功以后回调的我们写的接口地址,通知url必须为外网可访问的url,不能携带参数。
    partnerKey 商户平台设置的密钥key (32位的字串)

    2、后台代码(利用nutz框架编写)

    (1)PayCPayCommonUtil类

        /**
         * 创建微信交易对象
         */
        @SuppressWarnings("unused")
        public static SortedMap<Object, Object> getWXPrePayID()
        {
            
            SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
            
            parameters.put("nonce_str", PayCommonUtil.CreateNoncestr());//随机字符串
            parameters.put("fee_type", "CNY");//标价币种
            parameters.put("notify_url",Constants.GET_WXPAY_TOKEN_URL);//微信回调地址
            parameters.put("trade_type", "JSAPI");//交易类型
            return parameters;
        }
    
    
        /**
         * @Description:创建sign签名
         * @param characterEncoding
         *            编码格式
         * @param parameters
         *            请求参数
         * @return
         */
        public static String createSign(String characterEncoding, SortedMap<Object, Object> parameters)
        {
            StringBuffer sb = new StringBuffer();
            Set es = parameters.entrySet();
            Iterator it = es.iterator();
            while (it.hasNext())
            {
                Map.Entry entry = (Map.Entry) it.next();
                String k = (String) entry.getKey();
                Object v = entry.getValue();
                if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k))
                {
                    sb.append(k + "=" + v + "&");
                }
            }
            sb.append("key=" +Constants.GET_WXPAY_KEY);
            String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
        
            return sign;
        }
    
        /**
         * @Description:将请求参数转换为xml格式的string
         * @param parameters
         *            请求参数
         * @return
         */
        public static String getRequestXml(SortedMap<Object, Object> parameters)
        {
            StringBuffer sb = new StringBuffer();
            sb.append("<xml>");
            Set es = parameters.entrySet();
            Iterator it = es.iterator();
            while (it.hasNext())
            {
                Map.Entry entry = (Map.Entry) it.next();
                String k = (String) entry.getKey();
                String v = (String) entry.getValue();
                if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k))
                {
                    sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
                } else
                {
                    sb.append("<" + k + ">" + v + "</" + k + ">");
                }
            }
            sb.append("</xml>");
            return sb.toString();
        }
    
        /**
         * 发送https请求
         *
         * @param requestUrl
         *            请求地址
         * @param requestMethod
         *            请求方式(GET、POST)
         * @param outputStr
         *            提交的数据
         * @return 返回微信服务器响应的信息
         */
        public static String httpsRequest(String requestUrl, String requestMethod, String outputStr)
        {
            try
            {
                // 创建SSLContext对象,并使用我们指定的信任管理器初始化
                /*TrustManager[] tm ={ new TrustManagerUtil() };
            
                SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
                sslContext.init(null, tm, new java.security.SecureRandom());
                // 从上述SSLContext对象中得到SSLSocketFactory对象
                SSLSocketFactory ssf = sslContext.getSocketFactory();*/
                URL url = new URL(requestUrl);
                HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
                // conn.setSSLSocketFactory(ssf);
                conn.setDoOutput(true);
                conn.setDoInput(true);
                conn.setUseCaches(false);
                // 设置请求方式(GET/POST)
                conn.setRequestMethod(requestMethod);
                conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
                // 当outputStr不为null时向输出流写数据
                if (null != outputStr)
                {
                    OutputStream outputStream = conn.getOutputStream();
                    // 注意编码格式
                    outputStream.write(outputStr.getBytes("UTF-8"));
                    outputStream.close();
                }
                // 从输入流读取返回内容
                InputStream inputStream = conn.getInputStream();
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                String str = null;
                StringBuffer buffer = new StringBuffer();
                while ((str = bufferedReader.readLine()) != null)
                {
                    buffer.append(str);
                }
                // 释放资源
                bufferedReader.close();
                inputStreamReader.close();
                inputStream.close();
                inputStream = null;
                conn.disconnect();
                return buffer.toString();
            } catch (ConnectException ce)
            {
                // log.error("连接超时:{}", ce);
            } catch (Exception e)
            {
                // log.error("https请求异常:{}", e);
            }
            return null;
        }
    
    
        /**
         * 再次签名,支付
         */
        public static SortedMap<Object, Object> startWXPay(Map result)
        {
            try
            {
                //Map<Object, Object> map = XMLUtil.doXMLParse(result);
                SortedMap<Object, Object> parameterMap = new TreeMap<Object, Object>();
                parameterMap.put("appId", Constants.GET_WXS_APPID);//小程序ID
                parameterMap.put("package", "prepay_id="+result.get("prepay_id"));
                parameterMap.put("signType", "MD5");
                parameterMap.put("nonceStr", PayCommonUtil.CreateNoncestr());//创建随机数
                // 生成的时间戳是13位,但是ios必须是10位,所以截取了一下
                parameterMap.put("timeStamp",Long.parseLong(String.valueOf(System.currentTimeMillis()).toString().substring(0, 10)));
                String sign = PayCommonUtil.createSign("UTF-8", parameterMap);
                parameterMap.put("sign", sign);//签名
                return parameterMap;
            } catch (Exception e)
            {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 接收微信的异步通知
         *
         * @throws IOException
         */
        public static String reciverWx(HttpServletRequest request) throws IOException
        {
            InputStream inputStream;
            StringBuffer sb = new StringBuffer();
            inputStream = request.getInputStream();
            String s;
            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            while ((s = in.readLine()) != null)
            {
                sb.append(s);
            }
            in.close();
            inputStream.close();
            return sb.toString();
        }
    
        /**
         * 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
         *
         * @return boolean
         */
        public static boolean isTenpaySign(String characterEncoding, SortedMap<Object, Object> packageParams)
        {
            StringBuffer sb = new StringBuffer();
            Set es = packageParams.entrySet();
            Iterator it = es.iterator();
            while (it.hasNext())
            {
                Map.Entry entry = (Map.Entry) it.next();
                String k = (String) entry.getKey();
                String v = (String) entry.getValue();
                if (!"sign".equals(k) && null != v && !"".equals(v))
                {
                    sb.append(k + "=" + v + "&");
                }
            }
            sb.append("key=" + Constants.GET_WXPAY_KEY);
            // 算出摘要
            String mysign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toLowerCase();
            String tenpaySign = ((String) packageParams.get("sign")).toLowerCase();
            return tenpaySign.equals(mysign);
        }

     

    (2)XMLUtil类(用到jdom.jar包,下载地址:http://www.jdom.org/dist/binary/archive/

    /**
         * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
         *
         * @param strxml
         * @return
         * @throws JDOMException
         * @throws IOException
         */
        public static SortedMap<Object, Object> doXMLParse(String strxml) throws JDOMException, IOException {
            if (null == strxml || "".equals(strxml)) {
                return null;
            }
            SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
            InputStream in = String2Inputstream(strxml);
            SAXBuilder builder = new SAXBuilder();
            Document doc = builder.build(in);
            Element root = doc.getRootElement();
            List list = root.getChildren();
            Iterator it = list.iterator();
            while (it.hasNext()) {
                Element e = (Element) it.next();
                String k = e.getName();
                String v = "";
                List children = e.getChildren();
                if (children.isEmpty()) {
                    v = e.getTextNormalize();
                } else {
                    v = XMLUtil.getChildrenText(children);
                }
                parameters.put(k, v);
            }
            //关闭流
            in.close();
            return parameters;
        }
    

    (3)PayController(后台支付)

     /* 防止输入错误金额 */
                    String price = data.getTotalPrice();
                    if (Double.parseDouble(price) <= 0) // 防止抓包修改订单金额造成损失
                    {
                        baseResult.setState(-999);
                        baseResult.setMsg("付款金额错误!");
                        baseResult.setSuccess(false);
                        return baseResult;
                    }
                    try {
                        SortedMap<Object, Object> parameters = PayCommonUtil.getWXPrePayID(); // 获取预付单
                        parameters.put("appid", Constants.GET_WXS_APPID);// 小程序ID
                        parameters.put("mch_id", Constants.GET_WXPAY_MCHID);// 商户号
                        parameters.put("body", "商品");// 产品描述
                        parameters.put("spbill_create_ip", request.getRemoteAddr());// 终端IP
                        // 商户订单号
                        parameters.put("out_trade_no", ((Map) mp.get("data")).get("orderId"));// 订单id
                        parameters.put("openid", ((Map) mp.get("data")).get("openid"));// openid
                        parameters.put("total_fee", data.getTotalPrice()); // 订单金额,String.valueOf((int)(data.getTotalPrice()*100))
                        // 测试时,每次支付一分钱,微信支付所传的金额是以分为单位的,因此实际开发中需要x100
                        // 设置签名
                        String sign = PayCommonUtil.createSign("UTF-8", parameters);
                        parameters.put("sign", sign);
                        // 封装请求参数结束
                        String requestXML = PayCommonUtil.getRequestXml(parameters); // 获取xml结果
                        // 调用统一下单接口
                        String result = PayCommonUtil.httpsRequest(Constants.GET_WXPAY_UNIFIEDORDER_URL, "POST",
                                requestXML);
                        Map<Object, Object> map = XMLUtil.doXMLParse(result);
                        SortedMap<Object, Object> parMap = PayCommonUtil.startWXPay(map);
    
                        if (parMap != null) {
                            map.put("createtime", CommonUtil.getSystemTime());
                            map.put("out_trade_no", ((Map) mp.get("data")).get("orderId"));
                            map.put(".table", "nz_pay_info");
                            map.put("timeStamp", parMap.get("timeStamp"));
                            map.put("package", parMap.get("package"));
                            map.put("sign", parMap.get("sign"));
                            map.put("nonce_str", parMap.get("nonceStr"));
                            dao.insert(map);
                            baseResult.setData(parMap);
                            baseResult.setSuccess(true);
                            baseResult.setMsg("成功");
                        } else {
                            baseResult.setState(-999);
                            baseResult.setMsg("支付出现异常,请稍后重试!");
                            baseResult.setSuccess(false);
    
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        baseResult.setState(-999);
                        baseResult.setMsg("程序异常!");
                        baseResult.setSuccess(false);
                    }

    (4)微信异步通知

    
    	/**
    	 * 微信异步通知
    	 */
    
    	@At("/notice")
    	@POST
    	public void wxNotify(HttpServletRequest request, HttpServletResponse response) throws IOException, JDOMException {
    		String result = PayCommonUtil.reciverWx(request); // 接收到异步的参数
    		Map<Object, Object> m = new HashMap<Object, Object>();// 解析xml成map
    		if (m != null && !"".equals(m)) {
    			m = XMLUtil.doXMLParse(result);
    		}
    		SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
    		Iterator it = m.keySet().iterator();
    		while (it.hasNext()) {
    			String parameter = (String) it.next();
    			String parameterValue = (String) m.get(parameter);
    			String v = "";
    			if (null != parameterValue) {
    				v = parameterValue.trim();
    			}
    			packageParams.put(parameter, v);
    		}
    
    		// 判断签名是否正确
    		String resXml = "";
    		if (PayCommonUtil.isTenpaySign("UTF-8", packageParams)) {
    			;
    			if ("SUCCESS".equals((String) packageParams.get("return_code"))
    					&& "SUCCESS".equals((String) packageParams.get("result_code"))) {
    				// 如果返回成功
    				String mch_id = (String) packageParams.get("mch_id"); // 商户号
    				String out_trade_no = (String) packageParams.get("out_trade_no"); // 商户订单号
    				String total_fee = (String) packageParams.get("total_fee");
    				// 验证商户ID 和 价格 以防止篡改金额
    				if (Constants.GET_WXPAY_MCHID.equals(mch_id)
    			
    				) {
    					
    					// 变更数据库中该订单状态(修改订单的待付款状态,修改订单号id为“”的表中的数据)
    					orderService.updatePayment(out_trade_no);
    
    					resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
    							+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
    				} else {
    					resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
    							+ "<return_msg><![CDATA[参数错误]]></return_msg>" + "</xml> ";
    				}
    			} else // 如果微信返回支付失败,将错误信息返回给微信
    			{
    				resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
    						+ "<return_msg><![CDATA[交易失败]]></return_msg>" + "</xml> ";
    			}
    		} else {
    			resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
    					+ "<return_msg><![CDATA[通知签名验证失败]]></return_msg>" + "</xml> ";
    		}
    
    		// 处理业务完毕,将业务结果通知给微信
    		// ------------------------------
    		BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
    		out.write(resXml.getBytes());
    		out.flush();
    		out.close();
    	}

    3、前端代码

    wx.request({
          url: api.goodsOrderUrl,
          method: "post",
          data: orderData,
          header: {
            'Authorization': 'Bearer ' + skey
          },
          dataType: "json",
          success: function (res) {
            wx.hideLoading()
            if (0 == res.data.state) {
              var pack = res.data.data.package
              app.globalData.payOk = true;
              wx.requestPayment({
                'timeStamp': res.data.data.timeStamp.toString(),
                'nonceStr': res.data.data.nonceStr,
                'package': res.data.data.package,
                'signType': 'MD5',
                'paySign': res.data.data.sign,
                'success': function (res) {
                  console.log('支付成功返回res', res)
                  console.log('pack222', pack)
                  that.sendMsg(pack)
                },
                'fail': function (res) {
                  wx.redirectTo({
                    url: '/pages/goods/pay/pay?resultType=warn'
                  })
                }
    
                
              })
            }
          },
       
        })

    五、开发中注意事项

    1、保证小程序appid正确、商户平台设置的密钥key、商户号id正确

    2、微信通知地址必须外网地址

    至此,一套完整的支付流程已经全部搞定,希望能够帮助到大家!!!!本人写作水平有限,欢迎广大读者指正,如有问题,可在博客中给出评论。

    展开全文
  • 微信小程序支付

    2018-04-26 09:04:30
    微信小程序支付总结微信小程序支付总结已关闭评论 该文章纪录了我在开发小程序支付过程中的具体流程1. 申请微信支付小程序认证后进入微信支付申请小程序的微信支付2.配置商户信息申请微信支付成功后,登陆商户平台...
  • 代理商功能修复H5预览修复增加了:抖音/头条小程序,百度小程序,支付宝小程序,qq小程序,H5端安卓APP/苹果app即将上线 DIY功能优化 拼团小程序BUG修复 多商户栏目调用错误修复 会员功能充值问题修
  • 微信前端+QQ小程序前端代码+百度小程序前端代码+支付宝小程序前端代码+字节跳动小程序前端代码 1、【优化】分销商支持等级设置; 2、【优化】订单支持十万级数据量导出; 3、【优化】礼品卡导出增加会员昵称和激活...
  • TaroCropper 是Taro小程序框架下使用的图片裁剪,基于canvasAPI进行实现,支持滑动和缩放,目前测试在微信小程序端, QQ小程序端, 支付宝小程序端, 字节跳动小程序端, 百度小程序端和H5端可以正常使用。 关于Taro不同...
  • 最近有个需要由于H5页面需要嵌套在微信小程序里面,所以H5的支付在小程序里面就行不通了。...注释:视频中的大佬使用的小程序支付,而我下面介绍的封装过的小程序支付。所以后面代码会有些许出入,可根据自己的..
  • 国内领先企业级B2C开源电商系统,包含PC、h5、微信小程序、支付宝小程序、百度小程序、头条&抖音小程序、QQ小程序,遵循Apache2开源协议发布、基于ThinkPHP5.1框架研发
  • 获取到用户的openid,api参见公共api【小程序登录API】 上面已经说过要前后台结合,所以开发小程序的你这时就要做第一步了,文档在:https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-login.html#wxloginobject...
  • 小程序支付系列

    2019-10-06 08:52:17
    小程序支付,涉及一些知识。 在微信提供的接口文档中提供了一个微信支付接口,应该是直接调用这个接口就可以发起微信支付 文档路径:...
  • 1.帮助文档地址:https://pay.weixin.qq.com/static/help_guide/help_index.shtml 2.开发者文档地址:...3.小程序支付与JSAPI支付区别:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api...
  • 小程序微信支付 一、前置工作(微信公众平台申请https://mp.weixin.qq.com/)问老大|| 产品 || 财务 || 自己申请 要 申请商户号关联APPID 1、APPID(小程序ID) 2、AppSecret(小程序密钥) 3、商户号 4、商户...
  • 微信小程序支付开发

    2020-04-16 13:38:16
    小程序支付开发步骤:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3 微信小程序支付前端源码:https://www.cnblogs.com/oneall/p/9548448.html 微信小程序支付C#后端源码:...
  • Php微信小程序支付

    2020-03-23 11:07:34
    微信小程序支付官方参数小程序中代码后端发起支付代码支付回调 官方参数 文档地址:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/payment/wx.requestPayment.html 小程序中代码 如果说你的小...
  • 小程序支付流程

    2019-05-09 08:18:49
    微信支付 做微信支付有一段时间了,在做微信支付时遇到很多坑,比如code失效,code已...1、首先开通微信支付,即申请或复用微信支付商户号 申请完小程序后,登录小程序后台(mp.weixin.qq.com)。点击左侧导航栏的...
  • 小程序 支付

    2019-08-08 10:43:14
    请戳此链接:https://developers.weixin.qq.com/miniprogram/dev/framework/plugin/functional-pages/request-payment.html
  • 本文实例为大家分享了微信小程序支付PHP具体代码,供大家参考,具体内容如下 服务器端获取 openid Getopenid.php <?php header('Content-type: application/json; charset=UTF-8'); $APPID=;//填写小程序...
  • 微信小程序调用支付接口支付

    万次阅读 多人点赞 2018-06-25 17:32:14
    我前段时间做微信支付,遇到了很多坑,网上也没有讲解的特别明白的,通过借鉴各路人才的经验,最后也完成了,网上有很多讲解,我在这只讲一些注意点和解决的方法。我就讲讲我从完全懵到完成的过程吧。 在微信提供的...
  • 小程序支付签名

    2018-10-15 19:35:59
    注意:小程序支付需要使用再次签名 MD5Encode.js文件下载地址:https://download.csdn.net/download/qq_33040483/10722234 引入 var MD5Encode = require("../../utils/MD5Encode.js"); res是接口...
  • PC+H5、支付宝小程序、微信小程序、百度小程序、头条&抖音小程序、QQ小程序。 传送门 官网地址: 社区交流: 应用商店: 演示地址: (管理账户/密码可进入官网查看) 支付宝小程序: 微信小程序: 百度小程序: ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,361
精华内容 544
关键字:

qq小程序支付