Android 微信支付_扫码支付,我来帮你填坑!

0.概述:

本文讲的是微信支付中的扫码支付的模式二:该模式下,二维码链接由微信支付返回给商户,商户将得到的二维码链接转成二维码图片,用户通过扫码支付,此方式下生成的二维码2小时内有效。
微信支付官网地址:https://pay.weixin.qq.com/wiki/doc/api/index.html
Android 微信支付_扫码支付,我来帮你填坑!_第1张图片

1.扫码支付步骤:

  • 调用统一下单接口可获取到一个二维码链接参数code_url
  • 调用第三方库将此链接code_url 转成二维码图片,code_url链接内容为:weixin://wxpay/bizpayurl?sr=xxxx
  • 用户打开微信支付扫一扫完成支付
  • 定时调用查询订单接口确认是否支付是否完成
  • 退款接口

2.支付相关参数:

  • appid 公众号ID (微信公众平台–>开发者中心查看,商户的微信支付审核通过邮件中也会包含该字段值)
  • mch_id 商户ID 微信商户平台(帐户中心-商户信息-微信支付商户号)
  • out_trade_no 商户订单号(可以使用当前时间毫秒数)
  • key 商户支付密钥key(需要自己设置,为32位的密钥)

3.支付相关接口地址:

  • 统一支付:https://api.mch.weixin.qq.com/pay/unifiedorder
  • 查询订单:https://api.mch.weixin.qq.com/pay/orderquery
  • 申请退款:https://api.mch.weixin.qq.com/secapi/pay/refund

4.android stdio使用相关包:

网络相关

compile files('libs/org.apache.httpcomponents.httpclient_4.5.3.jar')

二维码相关:

    compile 'com.google.zxing:core:3.2.1'
    compile 'cn.bingoogolapple:bga-qrcodecore:1.1.7@aar'
    compile 'cn.bingoogolapple:bga-zxing:1.1.7@aar'

5.填坑记录:

  1. 不管是调用统一下单接口还是查询订单接口,还是退款接口,签名都是很重要的。记住,签名是大写。可以利用以下两个网站验证签名是否正确。
    微信公众平台支付接口调试工具 https://pay.weixin.qq.com/wiki/tools/signverify/
    微信支付接口签名校验工具 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=20_1
  2. 商户支付密钥key是自己设置的,一定和设置的一模一样,不要有大小写的转换。
  3. 退款接口需要用到双向证书https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3。因此在代码上需要有体现。在android中只用到4个中的apiclient_cert.p12,官网中说,【证书文件不能放在web服务器虚拟目录,应放在有访问权限控制的目录中,防止被他人下载。商户服务器要做好病毒和木马防护工作,不被非法侵入者窃取证书文件。】本文中,为了方便,我暂时将其放在assert文件夹中存放,代码中也有体现

    String result = null;
    // 证书密码(默认为商户ID)
    String password = Constent.VALUE_MCH_ID;
    // 实例化密钥库
    KeyStore ks = KeyStore.getInstance("PKCS12");
    // 获得密钥库文件流
    AssetManager am = context.getResources().getAssets();
    InputStream fis = am.open("apiclient_cert.p12");
    // 加载密钥库
    ks.load(fis, password.toCharArray());
    // 关闭密钥库文件流
    fis.close();
    // 实例化密钥库
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    // 初始化密钥工厂
    kmf.init(ks, password.toCharArray());
    // 创建SSLContext
    SSLContext sslContext = SSLContexts.custom()
            .loadKeyMaterial(ks, Constent.VALUE_MCH_ID.toCharArray())       //加载证书密码,默认为商户ID
            .build();
    sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
    // 获取SSLSocketFactory对象
    SSLSocketFactory ssf = sslContext.getSocketFactory();
    

    4.还有很多坑,待补充….

6.1.终端调用统一下单接口,得到code_url

//1 统一下单
public String unifiedOrder() {
    SortedMap parameterMap = new TreeMap();
    parameterMap.put(Constent.APPID, Constent.VALUE_APPID);//公众号ID
    parameterMap.put(Constent.MCH_ID, Constent.VALUE_MCH_ID);//商户号
    parameterMap.put(Constent.DEVICE_INFO, "");//设备号
    parameterMap.put(Constent.NONCE_STR, PayCommonUtil.getRandomString(32));//随机字符串
    parameterMap.put(Constent.BODY, "");//商品描述
    parameterMap.put(Constent.SIGN_TYPE, Constent.MD);//签名类型
    parameterMap.put(Constent.DETAIL, "");//商品详情
    parameterMap.put(Constent.ATTACH, "");//附加数据
    time = System.currentTimeMillis() + "";
    parameterMap.put(Constent.OUT_TRADE_NO, time);//商户订单号
    parameterMap.put(Constent.FEE_TYPE, Constent.CNY);//标价币种
    parameterMap.put(Constent.TOTAL_FEE, "");//标价金额
    parameterMap.put(Constent.SPBILL_CREATE_IP, "");//终端IP
    parameterMap.put(Constent.TIME_START, System.currentTimeMillis() + "");//交易起始时间
    //异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的,不能携带参数。
    parameterMap.put(Constent.NOTIFY_URL, "");//通知地址(支付结果通知)
    parameterMap.put(Constent.TRADE_TYPE, Constent.NATIVE);//交易类型
    parameterMap.put(Constent.PRODUCT_ID, "");//商品ID(和设备ID一起需知,扫码支付时必须传)
    parameterMap.put(Constent.LIMIT_PAY, Constent.NO_CREDIT);//指定支付方式
    parameterMap.put(Constent.SIGN, PayCommonUtil.createSign(Constent.UTF, parameterMap));//签名
    String requestXML = PayCommonUtil.getRequestXml(parameterMap);//将请求组装成xml形式
    String result = PayCommonUtil.httpsRequest(
            Constent.URL_TONGYI_XIADAN, Constent.POST,
            requestXML);//调用统一支付接口返回String类型字符串
    Map map = null;
    try {
        map = PayCommonUtil.xmlToMap(result);//将返回的结果转为map形式
    } catch (Exception e) {
        e.printStackTrace();
    }
    return map.get(Constent.CODE_URL);//得到code_url用来生成二维码
}

6.2.终端使用code_url生成二维码

    private void createQRCode() {
        //匿名内部类导致Activity内存泄漏的问题待解决
        new AsyncTask() {
            @Override
            protected Bitmap doInBackground(Void... params) {
                System.out.println("ds>>>  生成二维码成功");
                return QRCodeEncoder.syncEncodeQRCode(unifiedOrder(), BGAQRCodeUtil.dp2px(WechatPaymentActivity.this, 150));
            }

            @Override
            protected void onPostExecute(Bitmap bitmap) {
                if (bitmap != null) {
                    iv.setImageBitmap(bitmap);
                } else {
                    System.out.println("ds>>>  生成二维码失败");
                }
            }
        }.execute();
    }

6.3.用户打开微信支付扫一扫完成支付

无代码,不需终端操作

6.4.终端定时调用查询订单接口确认是否支付是否完成

注意:1.在主线程中不能进行网络操作 2.需要做定时操作

handler2.post(runnable2);//在main中调用

Handler handler2 = new Handler();

Runnable runnable2 = new Runnable() {
    @Override
    public void run() {
        new Thread(new Runnable() {//每次都要开一个线程去查询订单情况,直到有用户支付成功的结果。
            @Override
            public void run() {
                checkOrder();
            }
        }).start();
        handler2.postDelayed(runnable2, 5000);
    }
};

// 2 查询订单
public void checkOrder() {
    SortedMap<String, Object> parameterMap = new TreeMap<String, Object>();
    parameterMap.put(Constent.APPID, Constent.VALUE_APPID);//公众号ID
    parameterMap.put(Constent.MCH_ID, Constent.VALUE_MCH_ID);//商户号
    parameterMap.put(Constent.OUT_TRADE_NO, time);//商户订单号
    parameterMap.put(Constent.NONCE_STR, PayCommonUtil.getRandomString(32));//随机字符串
    parameterMap.put(Constent.SIGN, PayCommonUtil.createSign(Constent.UTF, parameterMap));//签名
    String requestXML = PayCommonUtil.getRequestXml(parameterMap);//将请求组装成xml形式
    String result = PayCommonUtil.httpsRequest(
            Constent.URL_CHAXUN_DINGDAN, Constent.POST,
            requestXML);//调用查询订单接口返回String类型字符串
    Map<String, String> map = null;
    try {
        if (result != null)
            map = PayCommonUtil.xmlToMap(result);//将返回的结果转为map形式
    } catch (Exception e) {
        e.printStackTrace();
    }
    if (map != null)
        packageParams = PayCommonUtil.getSortedMap(map);//将map中空的过滤掉
    if (packageParams != null && PayCommonUtil.isTenpaySign(Constent.UTF, packageParams, Constent.VALUE_API_KEY)) {
        handler.post(runnable);//在新的线程去匹配返回的信息,做相应的UI变化
    } else {
        System.out.println("通知签名验证失败");
    }
}

Handler handler = new Handler();

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        try {
            updateUI(packageParams);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
};

private void updateUI(SortedMap<Object, Object> packageParams) throws InterruptedException {
    String result_code = (String) packageParams.get(Constent.RESULT_CODE);
    String return_code = (String) packageParams.get(Constent.RETURN_CODE);
    String trade_state = (String) packageParams.get(Constent.TRADE_STATE);
    String trade_state_desc = (String) packageParams.get(Constent.TRADE_STATE_DESC);
    String error_code = (String) packageParams.get(Constent.ERROR_CODE);
    System.out.println("ds>>>  result_code=" + result_code + ", return_code=" + return_code + ", trade_state=" + trade_state);
    System.out.println("ds>>>  trade_state_desc=" + trade_state_desc + ", error_code=" + error_code);
    if (result_code.equals(Constent.SUCCESS) && return_code.equals(Constent.SUCCESS) && trade_state.equals(Constent.SUCCESS)) {
        handler2.removeCallbacks(runnable2);
        //支付成功,做相关逻辑。
    } else if (Constent.PAYERROR.equals(packageParams.get(Constent.TRADE_STATE))) {
        handler2.removeCallbacks(runnable2);
        //支付失败,做相关逻辑。
    }
}

6.5.退款接口

    //5 如果验证失败,需申请退款
    public void refundMoney() throws Exception {
    SortedMapparameterMap=new TreeMap();
    parameterMap.put(Constent.APPID, Constent.VALUE_APPID);//公众号ID
    parameterMap.put(Constent.MCH_ID, Constent.VALUE_MCH_ID);//商户号
    parameterMap.put(Constent.OUT_TRADE_NO, getIntent().getStringExtra(Constent.VALUE_OUT_TRADE_NO));//商户订单号
    parameterMap.put(Constent.NONCE_STR, PayCommonUtil.getRandomString(32));//随机字符串
    parameterMap.put(Constent.SIGN_TYPE, "MD5");
    parameterMap.put(Constent.OUT_REFUND_NO, System.currentTimeMillis() + "");//商户退款单号
    parameterMap.put(Constent.TOTAL_FEE, "1");//订单金额
    parameterMap.put(Constent.REFUND_FEE, "1");//退款金额
    parameterMap.put(Constent.REFUND_FEE_TYPE, "CNY");//货币种类
    parameterMap.put(Constent.REFUND_DESC, "商品验证失败");//退款原因
    parameterMap.put(Constent.REFUND_ACCOUNT, "refund reason");//退款资金来源
    parameterMap.put(Constent.SIGN, PayCommonUtil.createSign("UTF-8", parameterMap));//签名
    String requestXML = PayCommonUtil.getRequestXml(parameterMap);
    System.out.println("ds>>> requestXML3 = " + requestXML);
    String result = PayCommonUtil.httpsRequest(ValidateIdcardActivity.this, requestXML);
    System.out.println("ds>>> result3 = " + result);
    Map map = null;
    try {
        map = PayCommonUtil.xmlToMap(result);
    } catch (Exception e) {
        e.printStackTrace();
    }
    SortedMap packageParams = PayCommonUtil.getSortedMap(map);
    String key = Constent.VALUE_API_KEY;
    String result_code = (String) packageParams.get(Constent.RESULT_CODE);
    String mch_id = (String) packageParams.get(Constent.MCH_ID);//商户号
    String total_fee = (String) packageParams.get(Constent.TOTAL_FEE);//总金额
    String trade_state = (String) packageParams.get(Constent.TRADE_STATE);//交易状态
    System.out.println("ds>>>   trade_state:" + trade_state);
    System.out.println("ds>>>   mch_id:" + mch_id);
    System.out.println("ds>>>   total_fee:" + total_fee);
    if (PayCommonUtil.isTenpaySign("UTF-8", packageParams, key)) {
        handleResult(packageParams, result_code, mch_id, total_fee, trade_state);
    } else {
        System.out.println("通知签名验证失败");
    }
}

private void handleResult(final SortedMap packageParams, final String result_code, final String mch_id, final String total_fee, final String trade_state) {
    handler.post(new Runnable() {
        @Override
        public void run() {
            if (Constent.SUCCESS.equals(result_code)) {
                tv_validating.setText(getString(R.string.txt_validate_timeout));
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                finish();
            } else {
                System.out.println("退款失败,错误信息:" + packageParams.get("err_code"));
            }
        }
    });
}
  Runnable runnable = new Runnable() {
        @Override
        public void run() {
            try {
                refundMoney();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };
   new Thread(runnable).start();

6.6 使用到的工具类


public class PayCommonUtil {
    //判断签名是否正确
    public static boolean isTenpaySign(String characterEncoding, SortedMap packageParams, String API_KEY) {
        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=" + API_KEY);
        //算出摘要
        String mysign = MD5Encode(sb.toString(), characterEncoding).toLowerCase();
        String tenpaySign = ((String) packageParams.get("sign")).toLowerCase();
        return tenpaySign.equals(mysign);
    }

    //生成签名
    public static String createSign(String characterEncoding, SortedMap 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=" + Constent.VALUE_API_KEY);
        String sign = MD5Encode(sb.toString(), characterEncoding).toUpperCase();
        return sign;
    }

    private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};

    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n += 256;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    private static String byteArrayToHexString(byte b[]) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++)
            resultSb.append(byteToHexString(b[i]));

        return resultSb.toString();
    }

    public static String MD5Encode(String origin, String charsetname) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            resultString = byteArrayToHexString(md.digest(resultString
                    .getBytes("UTF-8")));
        } catch (Exception exception) {
        }
        return resultString;
    }

    //随机字符串生成
    public static String getRandomString(int length) { //length表示生成字符串的长度
        String base = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    //请求xml组装
    public static String getRequestXml(SortedMap parameters) {
        StringBuffer sb = new StringBuffer();
        sb.append("");
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String key = (String) entry.getKey();
            String value = (String) entry.getValue();
            sb.append("<" + key + ">" + value + " + key + ">");
        }
        sb.append("");
        return sb.toString();
    }

    //请求方法
    public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {
        try {
            URL url = new URL(requestUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            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) {
            System.out.println("连接超时");
            ce.printStackTrace();
        } catch (Exception e) {
            System.out.println("https请求异常");
            e.printStackTrace();
        }
        return null;
    }

    public static String httpsRequest(Context context, String data) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException, CertificateException, NoSuchProviderException {
        String result = null;
        // 证书密码(默认为商户ID)
        String password = Constent.VALUE_MCH_ID;
        // 实例化密钥库
        KeyStore ks = KeyStore.getInstance("PKCS12");
        // 获得密钥库文件流
        AssetManager am = context.getResources().getAssets();
        InputStream fis = am.open("apiclient_cert.p12");
        // 加载密钥库
        ks.load(fis, password.toCharArray());
        // 关闭密钥库文件流
        fis.close();
        // 实例化密钥库
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        // 初始化密钥工厂
        kmf.init(ks, password.toCharArray());
        // 创建SSLContext
        SSLContext sslContext = SSLContexts.custom()
                .loadKeyMaterial(ks, Constent.VALUE_MCH_ID.toCharArray())       //加载证书密码,默认为商户ID
                .build();
        sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
        // 获取SSLSocketFactory对象
        SSLSocketFactory ssf = sslContext.getSocketFactory();
        URL url = new URL(Constent.URL_TUIKUAN);
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        //设置当前实例使用的SSLSocketFactory
        conn.setSSLSocketFactory(ssf);
        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.connect();
        DataOutputStream out = new DataOutputStream(
                conn.getOutputStream());
        if (data != null)
            out.writeBytes(data);
        out.flush();
        out.close();
        //获取输入流
        BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        int code = conn.getResponseCode();
        if (HttpsURLConnection.HTTP_OK == code) {
            String temp = in.readLine();
            while (temp != null) {
                if (result != null)
                    result += temp;
                else
                    result = temp;
                temp = in.readLine();
            }
        }
        return result;
    }

    public static Map xmlToMap(String strXML) throws Exception {
        try {
            Map data = new HashMap();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("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());
                }
            }
            try {
                stream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return data;
        } catch (Exception ex) {
            ex.printStackTrace();
            throw ex;
        }

    }

    @NonNull
    public static SortedMap getSortedMap(Map map) {
        //过滤空 设置 TreeMap
        SortedMap packageParams = new TreeMap();
        Iterator it = map.keySet().iterator();
        while (it.hasNext()) {
            String parameter = (String) it.next();
            String parameterValue = map.get(parameter);

            String v = "";
            if (null != parameterValue) {
                v = parameterValue.trim();
            }
            packageParams.put(parameter, v);
        }
        return packageParams;
    }
}

后话:由于微信支付相关所有的代码和请求都是在android端做,因此有了这篇文章的存在,记录下来为了和我一样更多的人可以高效工作完成微信支付相关。有代码上的错误,欢迎大家及时指出。

你可能感兴趣的:(android开发)