微信支付接入总结


Android Studio,集成微信支付通道
一、支付说明:
1.引用微信支付官方交互时序图,统一下单API、支付结果通知API和查询订单API等都涉及签名过程,调用都必须在商户服务器端完成。


商户系统和微信支付系统主要交互说明:
步骤1: 用户在商户APP中选择商品,提交订单,选择微信支付。
步骤2: 商户后台收到用户支付单,调用微信支付统一下单接口。参见【 统一下单API 】。
步骤3: 统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。参与签名的字段名为appid,partnerid,prepayid,noncestr,timestamp,package。注意:package的值格式为Sign=WXPay
步骤4: 商户APP调起微信支付。api参见本章节【 app端开发步骤说明
步骤5: 商户后台接收支付通知。api参见【 支付结果通知API
步骤6: 商户后台查询支付结果。,api参见【 查询订单API

二、APP端集成
步骤:
1:集成环境(已改用gradle形式,发布到jcenter( http://jcenter.bintray.com/,在build.gradle文件中,添加如下依赖即可)
compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'

2:清单文件

     (1)权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

    (2)回调(要在自己包名目录下新建目录wxapi,然后新建WXPayEntryActivity,下面会贴出实现类,注意这个类和微信登录回调的是不一样的,红色的部分)

<activity
    android:name=".wxapi.WXPayEntryActivity"
    android:exported="true"
    android:launchMode="singleTop"
    android:screenOrientation="portrait" />

3:微信注册在调用支付之前一定要进行注册

    IWXAPI mWxApi = WXAPIFactory.createWXAPI(this, ShareKey.WX_ID, false);
    // 将该app注册到微信
    mWxApi.registerApp(ShareKey.WX_ID);
4:调起支付
/**
* 微信支付
* @param infodata
* @param checkmannery
*/
public void doWeChatPayment(PaymentPlanAggBean infodata, String checkmannery, String checkdueperiod) {
//首先在调用之前,需要先在代码中进行微信API注册,并将该app注册到微信
api = WXAPIFactory.createWXAPI(activity, AppConstant.WX_APP_ID, false);

//查询后台得到对应用户的缴费计划
GenWXOrderInfoRequest orderRequest = new GenWXOrderInfoRequest(activity, new RequestListener() {
     @Override
     public void successBack(Object object) {
         UIUtil.dismissProgressDialog();
         if (null == object) {
             showErrorMsg("警告", "获取支付订单信息错误");
             return;
          } else {
              WeChatResultBean wxbean = (WeChatResultBean) object;
              corderid = wxbean.getCorderid();
              try {
                  PayReq req = new PayReq();
     
                  req.appId = AppConstant.WX_APP_ID;
                  req.partnerId = wxbean.getPartnerid();//商户号
                  req.prepayId = wxbean.getPrepayid();//预支付交易会话ID
                  req.nonceStr = wxbean.getNoncestr();//随机字符串随机字符串,不长于32位
                  req.timeStamp = wxbean.getTimestamp();//时间戳
                  req.packageValue = wxbean.getPackagevalue();//扩展字段暂填写固定值Sign=WXPay
                  req.sign = wxbean.getSign();
                  req.extData = "app data"; // optional
                  // 在支付之前,如果应用没有注册到微信,应该先调用IWXMsg.registerApp将应用注册到微信
                  boolean flag =  api.sendReq(req);
                  Log.e("PAY_GET", "异常:" +flag);
              } catch (Exception e) {
                  Log.e("PAY_GET", "异常:" + e.getMessage());
              }
          }
      }
       @Override
       public void failBack(Object object) {
           UIUtil.dismissProgressDialog();
       }
   });

//根据界面信息生成预订单数据对象
String strjson = chgPlanToOrder(infodata, checkmannery, checkdueperiod);

//支付前需要
orderRequest.setJsondata(strjson);
orderRequest.startRequest();
}
5:支付回调
/**
 * 微信支付回调页面
 */
public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler {

    private static final String TAG = "MicroMsg.SDKSample.WXPayEntryActivity";

    private IWXAPI api;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pay_result);
        //回调的关键,不写的话可能回调失败
        api = WXAPIFactory.createWXAPI(this, AppConstant.WX_APP_ID);
        api.handleIntent(getIntent(), this);

        finish();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        setIntent(intent);
        api.handleIntent(intent, this);
    }

    @Override
    public void onReq(BaseReq req) {
    }

    @SuppressLint("LongLogTag")
    @Override
    public void onResp(BaseResp resp) {
        Log.d(TAG, "onPayFinish, errCode = " + resp.errCode);

        if (resp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {

            int code = resp.errCode;

            switch (code) {
                case 0://支付成功后的界面
                    break;
                case -1:
                    UIUtils.showToast(getString(R.string.pay_result_callback_msg, String.valueOf(resp.errCode)) + "签名错误、未注册APPID、项目设置APPID不正确、注册的APPID与设置的不匹配、您的微信账号异常等。");
                    break;
                case -2://用户取消支付后的界面
                    break;
            }
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle(R.string.app_tip);
            builder.setMessage(getString(R.string.pay_result_callback_msg, String.valueOf(resp.errCode)));
            builder.show();
        }

        //微信支付后续操作,失败,成功,取消
    }
}
、后台集成
微信支付功能除了前台的微信调用还需要后台相关代码功能的支持,光有APP端功能是跑不起来的。
1、统一下单接口集成。
微信支付需要手动调用微信提供的统一下单接口生成支付预订单,并根据返回的预订单id标识进行后续的操作,关键工具类代码:
package com.tenpay.util;


import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import com.bean.wechatBean.WeChatPreOrderBean;

import nc.util.iface.pub.XStreamUtil;


public class PayCommonUtil {

    //微信参数配置  
    public static String APPID = "wx8dd63af172e40469";
    //注意这个key必须从https://pay.weixin.qq.com/index.php/core/cert/api_cert,重新生成,一次生成后不宜修改
    public static String API_KEY = "6E0BB9100C9AF7384EB0280EB56F84AE";
    //商户号微信注册的时候会发送到账户
    public static String MCH_ID = "1494998152";

    @SuppressWarnings("unchecked")
    public static Map<String, String> weixinPrePay(String orderno,String totalAmount,
                                                   String description, InetAddress request) {

        SortedMap<String, Object> parameterMap = new TreeMap<String, Object>();
        parameterMap.put("appid", PayCommonUtil.APPID);//应用ID  
        parameterMap.put("mch_id", PayCommonUtil.MCH_ID);//商户号   
        parameterMap.put("nonce_str", PayCommonUtil.getRandomString(32));//随机字符串  
        parameterMap.put("body", description);//商品描述     
        parameterMap.put("out_trade_no", orderno); //商户订单号  
        parameterMap.put("fee_type", "CNY");//币种     
        parameterMap.put("total_fee", totalAmount);  //总金额
        parameterMap.put("spbill_create_ip", request.getHostAddress());  //终端IP    
        parameterMap.put("notify_url", "http://xxx.com"); //通知地址 
        parameterMap.put("trade_type", "APP");//交易类型  
        String sign = PayCommonUtil.createSign("UTF-8", parameterMap,API_KEY);
        parameterMap.put("sign", sign);  //签名

        String requestXML = PayCommonUtil.getRequestXml(parameterMap);
        System.out.println(requestXML);
        String result = PayCommonUtil.httpsRequest(
                "https://api.mch.weixin.qq.com/pay/unifiedorder", "POST",
                requestXML);
        System.out.println(result);
        Map<String, String> map = null;
        try {
            map = PayCommonUtil.doXMLParse(result);
        } catch (JDOMException e) {
            // TODO Auto-generated catch block  
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block  
            e.printStackTrace();
        }
        return map;
    }

    /**
     * 随机字符串生成  
     * @param length
     * @return
     */
    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组装  
     * @param parameters
     * @return
     */
    public static String getRequestXml(SortedMap<String, 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 key = (String) entry.getKey();
            String value = (String) entry.getValue();
            if ("attach".equalsIgnoreCase(key) || "body".equalsIgnoreCase(key) || "sign".equalsIgnoreCase(key)) {
                sb.append("<" + key + ">" + "<![CDATA[" + value + "]]></" + key + ">");
            } else {
                sb.append("<" + key + ">" + value + "</" + key + ">");
            }
        }
        sb.append("</xml>");
        return sb.toString();
    }

    /**
     * 生成签名  
     * @param characterEncoding
     * @param parameters
     * @param apiKey
     * @return
     */
    public static String createSign(String characterEncoding, SortedMap<String, Object> parameters, String apiKey) {
        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=" + apiKey);
        System.out.println(sb.toString());
        String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
        return sign;
    }

    /**
     * 验证回调签名
     * @param packageParams
     * @param key
     * @param charset
     * @return
     */
    public static boolean isTenpaySign(Map<String, String> map, String apiKey) throws UnsupportedEncodingException {
        String charset = "utf-8";
        String signFromAPIResponse = map.get("sign");
        if (signFromAPIResponse == null || signFromAPIResponse.equals("")) {
            System.out.println("API返回的数据签名数据不存在,有可能被第三方篡改!!!");
            return false;
        }
        System.out.println("服务器回包里面的签名是:" + signFromAPIResponse);
        //过滤空 设置 TreeMap
        SortedMap<String, String> packageParams = new TreeMap<>();
        for (String parameter : map.keySet()) {
            String parameterValue = map.get(parameter);
            String v = "";
            if (null != parameterValue) {
                v = parameterValue.trim();
            }
            packageParams.put(parameter, v);
        }

        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=" + apiKey);

        //将API返回的数据根据用签名算法进行计算新的签名,用来跟API返回的签名进行比较

        //算出签名
        String resultSign = "";
        String tobesign = sb.toString();
        if (null == charset || "".equals(charset)) {
            resultSign = MD5Util.MD5Encode(tobesign, charset).toUpperCase();
        } else {
            resultSign = MD5Util.MD5Encode(tobesign, charset).toUpperCase();
        }
        String tenpaySign = packageParams.get("sign").toUpperCase();
        return tenpaySign.equals(resultSign);
    }

    /**
     * 请求方法  
     * @param requestUrl
     * @param requestMethod
     * @param outputStr
     * @return
     */
    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);
        } catch (Exception e) {
            System.out.println("https请求异常:{}" + e);
        }
        return null;
    }

//    //退款的请求方法  
//    public static String httpsRequest2(String requestUrl, String requestMethod, String outputStr) throws Exception {
//        KeyStore keyStore = KeyStore.getInstance("PKCS12");
//        StringBuilder res = new StringBuilder("");
//        FileInputStream instream = new FileInputStream(new File("/home/apiclient_cert.p12"));
//        try {
//            keyStore.load(instream, "".toCharArray());
//        } finally {
//            instream.close();
//        }
//
//        // Trust own CA and all self-signed certs  
//        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, "1313329201".toCharArray()).build();
//        // Allow TLSv1 protocol only  
//        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
//        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
//        try {
//
//            HttpPost httpost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund");
//            httpost.addHeader("Connection", "keep-alive");
//            httpost.addHeader("Accept", "*/*");
//            httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
//            httpost.addHeader("Host", "api.mch.weixin.qq.com");
//            httpost.addHeader("X-Requested-With", "XMLHttpRequest");
//            httpost.addHeader("Cache-Control", "max-age=0");
//            httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
//            StringEntity entity2 = new StringEntity(outputStr, Consts.UTF_8);
//            httpost.setEntity(entity2);
//            System.out.println("executing request" + httpost.getRequestLine());
//
//            CloseableHttpResponse response = httpclient.execute(httpost);
//
//            try {
//                HttpEntity entity = response.getEntity();
//
//                System.out.println("----------------------------------------");
//                System.out.println(response.getStatusLine());
//                if (entity != null) {
//                    System.out.println("Response content length: " + entity.getContentLength());
//                    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));
//                    String text = "";
//                    res.append(text);
//                    while ((text = bufferedReader.readLine()) != null) {
//                        res.append(text);
//                        System.out.println(text);
//                    }
//
//                }
//                EntityUtils.consume(entity);
//            } finally {
//                response.close();
//            }
//        } finally {
//            httpclient.close();
//        }
//        return res.toString();
//
//    }

    /**
     * xml解析  
     * @param strxml
     * @return
     * @throws IOException
     * @throws JDOMException
     */
    public static Map doXMLParse(String strxml) throws  IOException, JDOMException {
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");

        if (null == strxml || "".equals(strxml)) {
            return null;
        }

        Map m = new HashMap();

        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
        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 = getChildrenText(children);
            }
            System.out.println(k+"-"+v);
            m.put(k, v);
        }

        //关闭流  
        in.close();

        return m;
    }

    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if (!children.isEmpty()) {
            Iterator it = children.iterator();
            while (it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if (!list.isEmpty()) {
                    sb.append(getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }

        return sb.toString();
    }
}

2、根据下单返回单据,生成支付命令

/**
 * 创建微信预订单信息,并返回
 *
 * @param aggvo:APP端返回的预订单数据VO
 * @return
 * @throws UnknownHostException
 */
private WeChatResultBean getOrderInfo(OrderDetailAggBean aggvo) throws UnknownHostException {

    OrderDetailHeaderBean header = aggvo.getHeader();

    InetAddress address = InetAddress.getLocalHost();
    String orderno =  getOutTradeNo();
    String ordername = "党费缴纳";// 订单名称-固定党费缴纳
    String orderinfo = ordername + "-支付人:" + header.getUsername();// 订单详情
    //微信接口要求金额转换成分进行传递
    double  dorderprice =Double.parseDouble(header.getNpaymentprice())*100;// 订单支付金额

    String orderprice = (int)dorderprice+"";
    //调用微信生成预订单:订单号,价格,网络端口号
    Map<String,String> maps =PayCommonUtil.weixinPrePay(orderno, orderprice, orderinfo, address );

    //将返回值封装成json对象
    WeChatResultBean bean = new WeChatResultBean();
    bean.setReturn_code(maps.get("return_code"));// 返回状态码SUCCESS/FAIL
    bean.setReturn_msg(maps.get("return_msg"));// 返回信息,如非空,为错误原因
    bean.setAppid(maps.get("appid"));// 调用接口提交的应用ID
    bean.setPartnerid(maps.get("mch_id"));// 调用接口提交的商户号
    bean.setNoncestr(maps.get("nonce_str"));// 微信返回的随机字符串
    bean.setPreordersign(maps.get("sign"));// 微信返回的签名
    bean.setResult_code(maps.get("result_code"));// 业务返回结果
    bean.setPrepayid(maps.get("prepay_id"));// 预支付交易会话标识
    bean.setTrade_type(maps.get("trade_type"));// 交易类型:调用接口提交的交易类型,取值如下:JSAPI,NATIVE,APP
    bean.setErr_code(maps.get("err_code"));// 错误代码
    bean.setErr_code_des(maps.get("err_code_des"));// 错误代码描述
    bean.setTimestamp(System.currentTimeMillis()+"");//时间戳(毫秒)

    //重新生成微信APP支付使用的签名
    //(微信支付用的签名跟预订单不是一回事,需要重新按下面的格式进行生成,否则在前台支付会报-1错误)
    SortedMap<String,Object> signmaps = new TreeMap<String, Object>();

    signmaps.put("appid", bean.getAppid());
    signmaps.put("noncestr", bean.getNoncestr());
    signmaps.put("package", bean.getPackagevalue());
    signmaps.put("partnerid", bean.getPartnerid());
    signmaps.put("prepayid", bean.getPrepayid());
    signmaps.put("timestamp",bean.getTimestamp() );

    String sign = PayCommonUtil.createSign("UTF-8", signmaps,PayCommonUtil.API_KEY);
    //签名
    bean.setSign(sign);

    return bean;
}
、支付错误分析及解决,参照下列文章

Android微信支付流程及返回码-1之坑

Android微信支付直接跳转WXPayEntryActivity

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值