Android微信支付功能集成【全攻略】


遵循:BY-SAphp

署名-相同方式共享 4.0协议java

做者:谭东android

时间:2016年10月28日算法

环境:Windows 7json

Android版微信支付官方文档和Demo问题不少,官方也没有及时更新和细化开发集成文档。api

这里分享我集成Android客户端微信支付的思路和部分代码。但愿对你们有帮助。服务器


遇到的问题无非如下几种:微信

一、提示签名不对;app

二、打包签名后的APK没法调起微信支付客户端,直接返回回调页;dom

三、不支持中文的Body;

四、支付的钱倍数不对,由于微信的单位是分;

... ...

首先,本文针对的是最新版微信Android支付SDK3.1.1写的,你们能够放心使用。


首先,把WXPayEntryActivity.java复制到咱们的包名下的.wxapi目录下,要一致。

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

 WXPayEntryActivity.java官方Demo里有,我这里也复制一份个人,仅供参考。 

public class WXPayEntryActivity extends BaseActivity implements IWXAPIEventHandler {
    private IWXAPI api;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        api = WXAPIFactory.createWXAPI(this, Conf.APP_ID);
        api.handleIntent(getIntent(), this);
        getSupportActionBar().setIcon(R.drawable.logo_gray);
        getSupportActionBar().setTitle("购买积分");
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        Drawable dw = getResources().getDrawable(R.drawable.color_bg);
        getSupportActionBar().setBackgroundDrawable(dw);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            this.finish();
        }
        return super.onOptionsItemSelected(item);
    }

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

    @Override
    public void onReq(BaseReq baseReq) {
    }

    @Override
    public void onResp(BaseResp baseResp) {
        if (baseResp.getType() == ConstantsAPI.COMMAND_PAY_BY_WX) {
            Log.d("info", "onPayFinish,errCode=" + baseResp.errCode);
            if (baseResp.errCode == 0) {
                ToastUtil.showToast(this, "支付成功");
                Intent intent = new Intent("ACTION_PAY");
                sendBroadcast(intent);
                this.finish();
            } else if (baseResp.errCode == -1) {
                ToastUtil.showToast(this, "配置错误");
                this.finish();
            } else if (baseResp.errCode == -2) {
                ToastUtil.showToast(this, "用户取消");
                this.finish();
            }
        } else {
            ToastUtil.showToast(this, baseResp.errStr);
        }
    }
}

这里说一下Android微信支付流程,这里本地客户端都处理了全部的流程。

客户端把全部须要的信息拼接为XML后,统一下单,发送给微信服务器API请求,获取订单id,也就是prepayId。获取到这个prepayId后,咱们再把必要的数据字段sign签名MD5后,调起微信支付客户端就能够了。比支付宝麻烦不少。


一、在程序的启动页,例如欢迎页就能够注册微信API的ID了。

IWXAPI msgApi = WXAPIFactory.createWXAPI(this, Conf.APP_ID);
        msgApi.registerApp(Conf.APP_ID);


完毕。

二、接下来处理微信POST请求的必要参数的拼接和加密等处理。写PayActivity.java

统一下单获取prepayId的接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder

微信文档地址:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1

把必填的参数处理下:

nonce_str:随机字符串,不长于32位。

out_trade_no:商户系统内部的订单号,32个字符内、可包含字母。

spbill_create_ip:用户端实际ip,16位,可写固定值。

notify_url:暂时写成固定的,http://www.weixin.qq.com/wxpay/pay.php。

最麻烦的,sign:32位签名,参照签名生成算法

以上这些算法我都会写成工具类,供你们参考。

签名算法:



附上整个支付代码:

PayActivity.java里关于支付的代码:

private IWXAPI api;
    private String stringA;
    private String noneString;
    private String out_trade_no;
    private String sign;
    private String prepayId;
    private String ip;
    private String body = "商品-积分充值";
    private static final String order_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//POST请求统一下单接口地址
    private String notify_url = "http://www.weixin.qq.com/wxpay/pay.php";
    private String entity;//XML形式的post请求实体

    private void payWeixin(int money) {
        api = WXAPIFactory.createWXAPI(this, Conf.APP_ID);
        try {
            body = new String(body.getBytes(), "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        noneString = Utils.createRandomString(true, 32);//32位随机字符串
        out_trade_no = Utils.createRandomString(true, 20) + Utils.timeStamp();//30位内部随机订单号
        ip = "123.123.123.123";
        stringA = "appid=" + Conf.APP_ID + "&body=" + body + "&mch_id=" + Conf.MCH_ID + "&nonce_str=" + noneString + "¬ify_url=" + notify_url + "&out_trade_no=" + out_trade_no + "&spbill_create_ip=" + ip + "&" +
                "total_fee=" + money + "&trade_type=APP";
        String stringSignTemp = stringA + "&key=" + Conf.KEY;
        sign = Utils.getMD5(stringSignTemp).toUpperCase(Locale.getDefault());

        entity = "<xml><appid>" + Conf.APP_ID + "</appid><mch_id>" + Conf.MCH_ID + "</mch_id><nonce_str>" + noneString + "</nonce_str><sign>" + sign +
                "</sign><body>" + body + "</body><out_trade_no>" + out_trade_no + "</out_trade_no><total_fee>" + money +
                "</total_fee><spbill_create_ip>" + ip + "</spbill_create_ip><notify_url>" + notify_url + "</notify_url><trade_type>APP</trade_type></xml>";

        try {
            entity = new String(entity.getBytes(), "ISO8859-1");//想要支持中文的Boby,那就要把XML转码为ISO8859-1便可
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    byte[] buf = Utils.httpPost(order_url, entity);
                    Message message = new Message();
                    message.what = 0;
                    message.obj = buf;
                    handler.sendMessage(message);
                } catch (Exception e) {
                    Log.e("info", "异常:" + e.getMessage());
                }
            }
        }).start();
    }

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    byte[] buf = (byte[]) msg.obj;
                    if (buf != null && buf.length > 0) {
                        String content = new String(buf);
                        OrderResult orderResult = Tools.parseXml(new ByteArrayInputStream(content.getBytes()));
                        if (!TextUtils.equals(orderResult.getReturnCode(), "SUCCESS")) {
                            Toast.makeText(PayActivity.this, orderResult.getReturnMsg(), Toast.LENGTH_SHORT).show();
                            return;
                        }
                        if (!TextUtils.equals(orderResult.getResultCode(), "SUCCESS")) {
                            Toast.makeText(PayActivity.this, orderResult.getErrorDesc(), Toast.LENGTH_SHORT).show();
                            return;
                        }
                        PayReq req = new PayReq();
                        req.appId = Conf.APP_ID;
                        req.partnerId = Conf.MCH_ID;
                        req.prepayId = orderResult.getPrepayId();
                        req.packageValue = "Sign=WXPay";
                        req.nonceStr = noneString;
                        String timeStamp = Utils.timeStamp();
                        req.timeStamp = timeStamp;
                        req.sign = Utils.getMD5("appid=" + Conf.APP_ID + "&noncestr=" + noneString + "&package=Sign=WXPay" +
                                "&partnerid=" + Conf.MCH_ID + "&prepayid=" + orderResult.getPrepayId() + "×tamp=" + timeStamp + "&key=" + Conf.KEY).toUpperCase(Locale.getDefault());
                        api.sendReq(req);
                    } else {
                        Log.d("PAY_GET", "服务器请求错误");
                        Toast.makeText(PayActivity.this, "服务器请求错误", Toast.LENGTH_SHORT).show();
                    }
                    break;
            }
        }
    };

Utils工具类代码:

public class Utils {

    /**
     * 产生随机字符串
     *
     * @param numberFlag 是否容许有字母
     * @param length     随机字符串长度
     * @return 随机字符串
     */
    public static String createRandomString(boolean numberFlag, int length) {
        String retStr = "";
        String strTable = numberFlag ? "1234567890" : "1234567890abcdefghijkmnpqrstuvwxyz";
        int len = strTable.length();
        boolean bDone = true;
        do {
            retStr = "";
            int count = 0;
            for (int i = 0; i < length; i++) {
                double dblR = Math.random() * len;
                int intR = (int) Math.floor(dblR);
                char c = strTable.charAt(intR);
                if (('0' <= c) && (c <= '9')) {
                    count++;
                }
                retStr += strTable.charAt(intR);
            }
            if (count >= 2) {
                bDone = false;
            }
        } while (bDone);

        return retStr;
    }

    /**
     * 获取MD5加密后的字符串
     *
     * @param val 待加密字符串
     * @return 加密后字符串
     */
    public static String getMD5(String val) {
        byte[] m = new byte[0];
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            md5.update(val.getBytes());
            m = md5.digest();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return getString(m);
    }

    private static String getString(byte[] b) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            sb.append(b[i]);
        }
        return sb.toString();
    }

    /**
     * 获取10位长度的时间戳
     *
     * @return 10位时间戳
     */
    public static String timeStamp() {
        String time = String.valueOf(System.currentTimeMillis());
        return time.substring(0, 10);
    }


    public static byte[] httpGet(final String url) {
        if (url == null || url.length() == 0) {
            return null;
        }
        HttpClient httpClient = getNewHttpClient();
        HttpGet httpGet = new HttpGet(url);
        try {
            HttpResponse resp = httpClient.execute(httpGet);
            if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                return null;
            }
            return EntityUtils.toByteArray(resp.getEntity());

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static byte[] httpPost(String url, String entity) {
        if (url == null || url.length() == 0) {
            return null;
        }
        HttpClient httpClient = getNewHttpClient();
        HttpPost httpPost = new HttpPost(url);
        try {
            httpPost.setEntity(new StringEntity(entity));
            httpPost.setHeader("Accept", "application/json");
            httpPost.setHeader("Content-type", "application/json");
            HttpResponse resp = httpClient.execute(httpPost);
            if (resp.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                return null;
            }
            return EntityUtils.toByteArray(resp.getEntity());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static HttpClient getNewHttpClient() {
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);

            SSLSocketFactory sf = new SSLSocketFactoryEx(trustStore);
            sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

            HttpParams params = new BasicHttpParams();
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
            HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            registry.register(new Scheme("https", sf, 443));

            ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

            return new DefaultHttpClient(ccm, params);
        } catch (Exception e) {
            return new DefaultHttpClient();
        }
    }

    public static OrderResult parseXml(InputStream is) {
        XmlPullParser parser = Xml.newPullParser();
        OrderResult orderResult = null;
        try {
            parser.setInput(is, "UTF-8");
            int type = parser.getEventType();
            while (type != XmlPullParser.END_DOCUMENT) {
                switch (type) {
                    case XmlPullParser.START_DOCUMENT:
                        break;
                    case XmlPullParser.START_TAG:
                        if (parser.getName().equals("xml")) {
                            orderResult = new OrderResult();
                        } else if (parser.getName().equals("return_code")) {
                            orderResult.setReturnCode(parser.nextText());
                        } else if (parser.getName().equals("return_msg")) {
                            orderResult.setReturnMsg(parser.nextText());
                        } else if (parser.getName().equals("result_code")) {
                            orderResult.setResultCode(parser.nextText());
                        } else if (parser.getName().equals("err_code_des")) {
                            orderResult.setErrorDesc(parser.nextText());
                        } else if (parser.getName().equals("prepay_id")) {
                            orderResult.setPrepayId(parser.nextText());
                        } else if (parser.getName().equals("sign")) {
                            orderResult.setSign(parser.nextText());
                        }
                        break;
                    case XmlPullParser.END_TAG:
                        break;
                }

                type = parser.next();
            }
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return orderResult;
    }
}
这样整个微信Android支付集成就完毕了。