Android微信支付详解

本文详细介绍了Android上实现微信支付的全过程,包括调用前准备、SDK下载与依赖、权限设置、注册到微信、统一下单及调起支付接口的步骤。强调了统一下单时参数的生成和签名的重要性,以及在调试过程中可能出现的签名错误和应用签名不一致导致的支付失败问题。同时提供了检查应用签名的方法和解决策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(一)调用前准备

  • 申请微信开发者账号,添加应用及申请开通微信支付功能,如
    查看开通流程

    • 注意

      • 在申请支付功能时会开通一个微信商户号,请注意是app的商户号,不是公众号的商户号,审核成功后会发到你邮箱里,微信支付商户号(不是商户平台登录号)后面会用到,另外,申请开通微信支付功能需要300元/年(土豪请忽略)。

      • 添加的应用填写的包名根据manifests的package上写;

(二)开发步骤:

1 、SDK下载

 ①方法:
Android Studio环境下:
在build.gradle文件中,添加如下依赖即可:
dependencies {
    compile 'com.tencent.mm.opensdk:wechat-sdk-android-with-mta:+'
}
或
dependencies {
    compile 'com.tencent.mm.opensdk:wechat-sdk-android-without-mta:+'
}
(其中,前者包含统计功能)
②方法:
    依赖官网里的微信Demo里的libs的SDK文件,
    注意:此方法可能会有出现警告并且无法在模拟器运行的情况 (sdk太老,不兼容Android,或者前面你已经导入的第三方Jar包和微信支付jar冲突)

此时可以尝试使用真机运行;不过,最好的办法就是找到最新版的微信demo里的SDK(如:微信开发者平台-->资源中心-->移动应用-->android 开发文档-->SDK下载 )

因为Demo文件的sdk更新不太及时,最好还是使用build.gradle文件添加依赖方法好。

2、添加权限

  • AndroidManifest.xml 设置
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

3、注册到微信

4、开始调用微信支付

第一步,统一下单(一般是服务器做好的) 下单接口文档

  1. 注意事项:

    • 务必提交必须的字段:appid,body,mch_id,nonce_str,notify_url,
      out_trade_no,spbill_create_ip,total_fee,trade_type,sign(都是小写);提交到微信接口时以xml格式提交

    • sign为前面提交的参数按照参数名ASCII码从小到大排序签名拼接起来然后进行MD5运算,再将得到的字符串所有字符转换为大写得到的,如签名生成算法

    • 参与生成sign的key为商户账号的密钥,key设置路径如下:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置

  2. 下面是具体代码(如若查看你的sign生成及提交的xml是否正确可以点击如下:签名生成工具(不要用QQ浏览器看,打不开的)
   //拼接字段,顺序不能变
                String A = "appid=你的appID" +
                        "&body=jinshi" +
                        "&mch_id=你的商户号" +
                        "&nonce_str=" + nonce_str +
                        "&notify_url=http://www.szgsip.com/" +
                        "&out_trade_no=" + trade_no +
                        "&spbill_create_ip=192.168.1.1" +
                        "&total_fee=1" +
                        "&trade_type=APP";
                String key = "你的密钥";
                String temp = A + "&key=" + key;
  // 生成sign
               String sign = MD5.md5(temp).toUpperCase();
  • 其中nonce_str为随机字符串,trade_no 为订单号,可以用下面方法模拟生成
    (其实可以写固定的,不过只能下单一次)

String nonce_str = getNonce_str(5);//5为填多长的字符串,不长于32位即可
String trade_no = getNonce_str(8);

public static String getNonce_str(int length) {
        String base = "QWERTYUIOPLKJHGFDSAZXCVBNM0123456789";
        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();
    }
  • 接下来提交到微信的接口上

                StringBuilder xml = new StringBuilder();//组建xml数据,请保持和上面参与生成算法的参数一致
                xml.append("<xml>\n");
                xml.append("<appid>你的appID</appid>\n");
                xml.append("<body>jinshi</body>\n");
                xml.append("<mch_id>你的商户号</mch_id>\n");
                xml.append("<nonce_str>" + nonce_str + "</nonce_str>\n");
                xml.append("<notify_url>http://www.szgsip.com/</notify_url>\n");
                xml.append("<out_trade_no>" + trade_no + "</out_trade_no>\n");
                xml.append("<spbill_create_ip>192.168.1.1</spbill_create_ip>\n");
                xml.append("<total_fee>1</total_fee>\n");
                xml.append("<trade_type>APP</trade_type>\n");
                xml.append("<sign>" + sign + "</sign>\n");
                xml.append("</xml>");

                byte[] xmlbyte = new byte[0];
                try {
                    xmlbyte = xml.toString().getBytes("UTF-8");
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                Log.e("-----xml------", xml + "");
                try {

                    URL url = new URL(BaseApp.uri);  //提交到微信服务器的接口https://api.mch.weixin.qq.com/pay/unifiedorder

                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setConnectTimeout(5000);
                    conn.setDoOutput(true);// 允许输出
                    conn.setDoInput(true);
                    conn.setUseCaches(false);// 不使用缓存
                    conn.setRequestMethod("POST");
                    conn.setRequestProperty("Connection", "Keep-Alive");// 维持长连接
                    conn.setRequestProperty("Charset", "UTF-8");//如果上面参数body是中文,就不要用UTF—8
                    conn.setRequestProperty("Content-Length",
                            String.valueOf(xmlbyte.length));
                    conn.setRequestProperty("Content-Type", "text/xml; charset=UTF-8");
//                    conn.setRequestProperty("X-ClientType", "2");//发送自定义的头信息
                    conn.getOutputStream().write(xmlbyte);
                    conn.getOutputStream().flush();
                    conn.getOutputStream().close();
                    if (conn.getResponseCode() != 200) {
                        throw new RuntimeException("请求url失败");
                    } else {
                    }

//也可以用 HttpClient类提交网络请求,不过Android6.0及以上已经删除这个类了
//两个return_code 为SUCCESS即为正确
<xml>
   <return_code><![CDATA[SUCCESS]]></return_code>
   <return_msg><![CDATA[OK]]></return_msg>
   <appid><![CDATA[wx2421b1c4370ec43b]]></appid>
   <mch_id><![CDATA[2434546]]></mch_id>
   <nonce_str><![CDATA[IITRi8Iabbblz1Jc]]></nonce_str>
   <sign><![CDATA[7921E432F65EB8ED0CE9755F0E86D72F]]></sign>
   <result_code><![CDATA[SUCCESS]]></result_code>
   <prepay_id><![CDATA[wx201411101639507cbf6ffd8b0779950874]]></prepay_id>
   <trade_type><![CDATA[APP]]></trade_type>
</xml> 
  • 解析xml,其中上面的appid,mch_id,prepay_id是我们需要的,nonce_str可以按照上面getNonce_str()得到,sign值我也不知道有什么卵用,(注意:千万不要使用这个sign到下一个接口中,下面接口的sign要重新生成,不是用这个)
//第一步
//使用dom4j类解析,先在build.gradle添加依赖
  compile 'org.dom4j:dom4j:2.0.0'
//第二步
public void dom(String xml) {
        Document doc = null;
        try {
            doc = DocumentHelper.parseText(xml);
            Element root = doc.getRootElement();// 指向根节点
            // 解析
            Element appid = root.element("appid");
            Element mch_id = root.element("mch_id");
            Element prepay_id = root.element("prepay_id");
            final String appidName = appid.getStringValue();
            final String mch_idName = mch_id.getStringValue();
            final String prepay_idName = prepay_id.getStringValue();

        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

第二步,调起支付接口(也是服务器做好的)最坑爹的一个接口文档
这里写图片描述
注意事项:

文档中的参数sign也是拼接该接口字段重新生成的,绝对不是上个接口返回的sign,这点要注意
  • 在调用该接口之前还需要做一步:

    • 找到微信Demo文件的WXPayEntryActivity类,连同wxapi包一起复制,其中不需要WXEntryActivity类;

    • WXPayEntryActivity.java路径必须要放到“App包名.wxapi”的package中,否则无法响应回调(包名可以在manifests中package查看);

    • WXPayEntryActivity 在manifests配置为:

  <activity
            android:name=".wxapi.WXPayEntryActivity"
            android:exported="true"
            android:launchMode="singleTop">
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="你的appid"/>
            </intent-filter>
        </activity>
  • 接下来可以调用支付接口了
//生成时间戳,部分系统取到的值为毫秒级,需要转换成秒(10位数字)。 
  String time_str = System.currentTimeMillis() / 1000 + "";

 //生成sign
 String finalSign = getSign(mch_idName, prepay_idName, "Sign=WXPay",      nonce_str, time_str, BaseApp.key);

 //提交参数
            PayReq payReq = new PayReq();
              payReq.appId = "你的appID";
              payReq.partnerId = "你的mch_id";
              payReq.prepayId = "下单接口返回的prepayId";
              payReq.nonceStr = nonce_str;
              payReq.timeStamp = time_str;//时间戳
              payReq.packageValue = "Sign=WXPay";//微信官方固定写法
              payReq.sign = finalSign;
 //发送请求
            api.sendReq(payReq);




 //该方法生成sign
 private String getSign(String partnerId, String prepayId, String packageValue, String nonceStr, String timeStamp, String key) {
        String stringA =
                "appid=" + BaseApp.app_ID
                        + "&noncestr=" + nonceStr
                        + "&package=" + packageValue
                        + "&partnerid=" + partnerId
                        + "&prepayid=" + prepayId
                        + "&timestamp=" + timeStamp;
        String stringSignTemp = stringA + "&key=" + key;
        String sign = MD5.md5(stringSignTemp).toUpperCase();
        return sign;
    }
  • 然后,在WXPayEntryActivity类中会有回调

    • 在这个类中需要注意的地方有三个地方:
      • 这个类中的布局是可以自定义的,如果你不需要展示什么布局,删了即可

      • 这里写图片描述
    • 回调结果的处理,下面是官方的处理方式,直接给了一个dialog,很多人会摸不着头脑,如果你不需要这个dialog,直接删除就好了,不需要把官方demo中的布局和资源都复制过来
      这里写图片描述

    • 最后这个才是我们需要关注的地方:

    @Override
    public void onResp(BaseResp resp) {
        if (!api.isWXAppInstalled()) {
            Toast.makeText(this, "您没有安装微信", Toast.LENGTH_SHORT).show();
        }
        if (!api.isWXAppSupportAPI()) {
            Toast.makeText(this, "当前版本不支持支付", Toast.LENGTH_SHORT).show();
        }

        Log.e("---resp.errStr------", resp.errStr + "" + "-----------------------");
        Log.e("回调微信支付结果", "errCode=" + resp.errCode);
        if (resp.errCode == 0) {
            Toast.makeText(this, "支付成功!", Toast.LENGTH_SHORT).show();

        } else if (resp.errCode == -1) {
            Toast.makeText(this, "支付失败!", Toast.LENGTH_SHORT).show();
            finish();

        } else if (resp.errCode == -2) {
            Toast.makeText(this, "取消支付!", Toast.LENGTH_SHORT).show();
            finish();
        }  

很多人都会遇到resp.errCode == -1支付失败的问题,官方只有这么一句话告诉你
这里写图片描述
这才是最坑的地方,这么大一个网络公司,就不能多写几句。。。还有,打印resp.errStr=null,这真TM。。。

- 支付失败最主要的原因其实有两个,

  • 一个就是参数sign生成出错,拼接字符串时参数名没有小写,参数没有按AscII码顺序排序,而且两个接口都要重新生成Sign,这个问题认真按照官方写的步骤就不会出错了;

  • 第二个问题,其实是没有打包,或者打包后app的签名和官方注册的不一致,yi

    • 查看app签名步骤如下:

      • 运行进入控制台

      这里写图片描述

    • 在弹出的控制台窗口中输入 cd .android 定位到 .android 文件夹。

    这里写图片描述

    • 继续在控制台输入命令。
      • 开发模式使用 debug.keystore,(使用eclipse)命令为:keytool -list -v -keystore debug.keystore(或者keytool -list -v -keystore debug.jks 使用studio)
      • 发布模式使用 apk 对应的 keystore,命令为:keytool -list -v -keystore apk的keystore

具体路径的情况下:

- 提示输入密钥库密码,开发模式默认密码是 android,发布模式的密码是为 apk 的 keystore 设置的密码。输入密钥后回车(如果没设置密码,可直接回车),此时可在控制台显示的信息中获取 MD5值(微信是需要MD5值,像百度地图是要SHA1值),如下图所示:(右击标记即可复制MD5值)

讲了,那么多,其实最简单的方法就是用微信的签名生成工具

还有个问题,为了保证debug测试运行和打包签名后运行时签名一致呢?其实可以 这样:

  1. 首先 ,打开如下

  2. 添加签名文件

3.设置debug和release的默认签名

4.最后,在gradle查看是否设置成功

这里写图片描述

另外,可以查看debug运行时签名:

md5就是微信支付的签名信息(百度地图则是SHA1)

以上是微信支付最主要的问题,如果还有解决不了的,直接发邮件给微信邮箱吧
(记得写上你的商户号+用户名称和详细的开发问题,一般一天内回复你的)

wepayTS@tencent.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值