Android版-微信APP支付

欢迎留言、转发
微信极速开发系列文章(微信支付、授权获取用户信息等):点击这里

目录
1、注册账号、开发者认证
2、添加应用
3、申请微信支付
4、技术开发功能实现步骤介绍
5、代码实例

服务端源码地址:http://git.oschina.net/javen205/weixin_guide
客户端源码地址:https://github.com/Javen205/JPay

微信APP支付接入商户服务中心 官方介绍文档

1、注册账号、开发者认证

开放平台直接注册,注册邮箱不能与微信其他的产品同号。

比较坑的是微信公众号中的支付(微信买单、刷卡、公众号支付、wap支付)以及微信app支付都需要进行微信认证而不是公用一个微信商户平台(需要交两次认证的费用)。

开发者认证.png

微信认证这个时间比较短(毕竟交了300大洋)一般一个工作日就会有人联系你核查公司的资料。

微信认证(开发者资质认证)通过之后就可以在开放平台添加应用了(这个需要审核),应用通过之后就可以申请微信支付了(也需要审核)

2、添加应用

这个比较简单,按照提示操作就行 上图

添加应用-填写基本信息1
添加应用-填写基本信息2
添加应用-上传应用图片
添加应用-填写平台信息

应用包名只定义,应用签名可以使用资源下载中心的签名生成工具。务必记住包名以及签名keystore文件的密码,如果包名或者签名文件不对打包是唤不起微信支付的。

资源下载
下载的资源截图
应用签名工具

3、申请微信支付

如果添加的应用审核通过了(一个工作日),就可以直接申请微信支付了(7个工作日之内)。

应用审核通过-申请微信支付

审核通过之后将会收到审核通过的邮件,里面有登录商户平台的登录账户、密码、商户号以及一些操作指引的说明。服务端生成预付订单的签名需要密钥 设置方法可以参考这里

4、技术开发功能实现

微信APP支付介绍【文档
APP端开发步骤说明 【文档

这里主要聊聊Android微信支付,主要包括以下几个步骤
1、商户服务端生成订单并在微信平台生成预付订单
2、客户端调起微信支付进行支付
3、客户端回调支付结果
4、服务端接收支付通知

1、商户服务端生成订单并在微信平台生成预付订单

调起微信支付前需要服务器生成支付订单再调用【统一下单API】生成预付订单prepayId,再生成签名sign【调起支付API

以上两个步骤建议都在服务端完成,客户端(Android)通过接口获取对应的参数即可

2、客户端调起微信支付进行支付

通过微信提供的jar 唤起微信支付

调起微信支付
3、客户端回调支付结果

参照微信SDK Sample,在net.sourceforge.simcpux.wxapi包路径中实现WXPayEntryActivity类【包名或类名不一致会造成无法回调】
栗子说明:认真反复读了几遍,感觉这句话有歧义是一个坑,测试的时候一直不回调。这里他想说的意识如下:
比如你申请应用包名为:javen.com 那么回调的WXPayEntryActivity类必须放到javen.com.wxapi 的包下面

客户端回调支付结果
4、服务端接收支付通知

支付结果通知【官方文档

代码实现参考开源项目 【点击这里

5、代码实例

服务端代码:根据商户订单生成微信预付订单并返回唤起微信支付需要的参数。Demo中参数写成固定了仅供参考

此项目已开源 【点击这里】 如果对你有帮助请点击Start告诉我 hahaha 。以下代码对应的目录在 com.javen.weixin.controller.WeixinPayController.java

/**
     * 微信APP支付
     */
    public void appPay(){
        //不用设置授权目录域名
        //统一下单地址 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1#
        Map<String, String> params = new HashMap<String, String>();
        params.put("appid", appid);
        params.put("mch_id", partner);
        params.put("nonce_str", System.currentTimeMillis() / 1000 + "");
        params.put("body", "Javen微信公众号极速开发");
        String out_trade_no=System.currentTimeMillis()+"";
        params.put("attach", "custom json");
        params.put("out_trade_no", out_trade_no);
        int price=10000;
        params.put("total_fee", price+"");
        
        String ip = IpKit.getRealIp(getRequest());
        if (StrKit.isBlank(ip)) {
            ip = "127.0.0.1";
        }
        
        params.put("spbill_create_ip", ip);
        params.put("notify_url", notify_url);
        params.put("trade_type", "APP");

        String sign = PaymentKit.createSign(params, paternerKey);
        params.put("sign", sign);
        
        String xmlResult = PaymentApi.pushOrder(params);
        
System.out.println(xmlResult);
        Map<String, String> result = PaymentKit.xmlToMap(xmlResult);
        
        String return_code = result.get("return_code");
        String return_msg = result.get("return_msg");
        if (StrKit.isBlank(return_code) || !"SUCCESS".equals(return_code)) {
            ajax.addError(return_msg);
            renderJson(ajax);
            return;
        }
        String result_code = result.get("result_code");
        if (StrKit.isBlank(result_code) || !"SUCCESS".equals(result_code)) {
            ajax.addError(return_msg);
            renderJson(ajax);
            return;
        }
        // 以下字段在return_code 和result_code都为SUCCESS的时候有返回
        String prepay_id = result.get("prepay_id");
        //封装调起微信支付的参数 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12
        Map<String, String> packageParams = new HashMap<String, String>();
        packageParams.put("appid", appid);
        packageParams.put("partnerid", partner);
        packageParams.put("prepayid", prepay_id);
        packageParams.put("package", "Sign=WXPay");
        packageParams.put("noncestr", System.currentTimeMillis() + "");
        packageParams.put("timestamp", System.currentTimeMillis() / 1000 + "");
        String packageSign = PaymentKit.createSign(packageParams, paternerKey);
        packageParams.put("sign", packageSign);
        
        String jsonStr = JsonUtils.toJson(packageParams);
System.out.println("最新返回apk的参数:"+jsonStr);
        renderJson(jsonStr);
    }
客户端代码实现

使用单例模式统一入口,首先判断微信客户端是否安装,如果有安装再从商户服务器获取调起支付的参数

public class IPay {
    private static  IPay mIPay;
    private Context mContext;
    
    private IPay(Context context) {
        mContext = context;
    }
    
    public static IPay getIntance(Context context){
        if (mIPay == null) {
            synchronized(IPay.class){
                if (mIPay == null) {
                    mIPay = new IPay(context);
                }
            }
        }
        return mIPay;
    }
    
    //支付结果回调
    public interface IPayListener{
        void onPay(int code);
    }
    
    public void toTestPay(Order order,IPayListener listener){
        if (order != null) {
            if (IPayLogic.getIntance(mContext.getApplicationContext()).isWeixinAvilible()) {
                Constants.payListener = listener;
                new TestPayPrepay(mContext).execute();
            }else {
                Toast.makeText(mContext, "未安装微信", Toast.LENGTH_LONG).show();
            }
        }else {
            Toast.makeText(mContext, "参数异常 order is null", Toast.LENGTH_LONG).show();
        }
    }
    
}

调起微信支付、获取调取微信支付参数、判断微信是否安装逻辑实现


public class IPayLogic {
    private static  IPayLogic mIPayLogic;
    private Context mContext;
    
    private IPayLogic(Context context) {
        mContext = context;
    }
    
    public static IPayLogic getIntance(Context context){
        if (mIPayLogic == null) {
            synchronized(IPayLogic.class){
                if (mIPayLogic == null) {
                    mIPayLogic = new IPayLogic(context);
                }
            }
        }
        return mIPayLogic;
    }
    
    //测试
    public String testPay(){
        return HttpKit.get(Constants.TESTPAY_URL);
    }
    
    /**
     * 调起支付
     * @param appId
     * @param partnerId
     * @param prepayId
     * @param nonceStr
     * @param timeStamp
     * @param sign
     */
    public void startWXPay(String appId,String partnerId,String prepayId,
            String nonceStr,String timeStamp,String sign){

        IWXAPI api= WXAPIFactory.createWXAPI(mContext, null);
        api.registerApp(appId);
        
        boolean isPaySupported = api.getWXAppSupportAPI() >= Build.PAY_SUPPORTED_SDK_INT;
        if (!isPaySupported) {
            Toast.makeText(mContext, "请更新微信客户端", Toast.LENGTH_SHORT).show();
            return;
        }
        
        PayReq request = new PayReq();
        request.appId = appId;
        request.partnerId = partnerId;
        request.prepayId= prepayId;
        request.packageValue = "Sign=WXPay";
        request.nonceStr=nonceStr;
        request.timeStamp= timeStamp;
        request.sign= sign;
        api.sendReq(request);
    }

    
    /**
     * 判断微信是否安装
     * @param context
     * @return
     */
     public  boolean isWeixinAvilible() {
            final PackageManager packageManager = mContext.getPackageManager();// 获取packagemanager
            List<PackageInfo> pinfo = packageManager.getInstalledPackages(0);// 获取所有已安装程序的包信息
            if (pinfo != null) {
                for (int i = 0; i < pinfo.size(); i++) {
                    String pn = pinfo.get(i).packageName;
                    if (pn.equals("com.tencent.mm")) {
                        return true;
                    }
                }
            }
            return false;
        }

}

HttpKit MD5 工具类


/**
 * HttpKit
 */
public class HttpKit {
    
    private HttpKit() {}
    
    /**
     * https 域名校验
     */
    private class TrustAnyHostnameVerifier implements HostnameVerifier {
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }
    
    /**
     * https 证书管理
     */
    private class TrustAnyTrustManager implements X509TrustManager {
        public X509Certificate[] getAcceptedIssuers() {
            return null;  
        }
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }
    }
    
    private static final String GET  = "GET";
    private static final String POST = "POST";
    private static String CHARSET = "UTF-8";
    
    private static final SSLSocketFactory sslSocketFactory = initSSLSocketFactory();
    private static final TrustAnyHostnameVerifier trustAnyHostnameVerifier = new HttpKit().new TrustAnyHostnameVerifier();
    
    private static SSLSocketFactory initSSLSocketFactory() {
        try {
            TrustManager[] tm = {new HttpKit().new TrustAnyTrustManager() };
            SSLContext sslContext = SSLContext.getInstance("TLS");  // ("TLS", "SunJSSE");
            sslContext.init(null, tm, new java.security.SecureRandom());
            return sslContext.getSocketFactory();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public static void setCharSet(String charSet) {
        if (charSet.isEmpty()) {
            throw new IllegalArgumentException("charSet can not be blank.");
        }
        HttpKit.CHARSET = charSet;
    }
    
    private static HttpURLConnection getHttpConnection(String url, String method, Map<String, String> headers) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, KeyManagementException {
        URL _url = new URL(url);
        HttpURLConnection conn = (HttpURLConnection)_url.openConnection();
        if (conn instanceof HttpsURLConnection) {
            ((HttpsURLConnection)conn).setSSLSocketFactory(sslSocketFactory);
            ((HttpsURLConnection)conn).setHostnameVerifier(trustAnyHostnameVerifier);
        }
        
        conn.setRequestMethod(method);
        conn.setDoOutput(true);
        conn.setDoInput(true);
        
        conn.setConnectTimeout(19000);
        conn.setReadTimeout(19000);
        
        conn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
        conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36");
        
        if (headers != null && !headers.isEmpty())
            for (Entry<String, String> entry : headers.entrySet())
                conn.setRequestProperty(entry.getKey(), entry.getValue());
        
        return conn;
    }
    
    /**
     * Send GET request
     */
    public static String get(String url, Map<String, String> queryParas, Map<String, String> headers) {
        HttpURLConnection conn = null;
        try {
            conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), GET, headers);
            conn.connect();
            return readResponseString(conn);
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
        finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }
    
    public static String get(String url, Map<String, String> queryParas) {
        return get(url, queryParas, null);
    }
    
    public static String get(String url) {
        return get(url, null, null);
    }
    
    /**
     * Send POST request
     */
    public static String post(String url, Map<String, String> queryParas, String data, Map<String, String> headers) {
        HttpURLConnection conn = null;
        try {
            conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), POST, headers);
            conn.connect();
            
            OutputStream out = conn.getOutputStream();
            out.write(data.getBytes(CHARSET));
            out.flush();
            out.close();
            
            return readResponseString(conn);
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
        finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }
    
    public static String post(String url, Map<String, String> queryParas, String data) {
        return post(url, queryParas, data, null);
    }
    
    public static String post(String url, String data, Map<String, String> headers) {
        return post(url, null, data, headers);
    }
    
    public static String post(String url, String data) {
        return post(url, null, data, null);
    }
    
    private static String readResponseString(HttpURLConnection conn) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        try {
            inputStream = conn.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, CHARSET));
            String line = null;
            while ((line = reader.readLine()) != null){
                sb.append(line).append("\n");
            }
            return sb.toString();
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
        finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                }
            }
        }
    }
    
    /**
     * Build queryString of the url
     */
    private static String buildUrlWithQueryString(String url, Map<String, String> queryParas) {
        if (queryParas == null || queryParas.isEmpty())
            return url;
        
        StringBuilder sb = new StringBuilder(url);
        boolean isFirst;
        if (url.indexOf("?") == -1) {
            isFirst = true;
            sb.append("?");
        }
        else {
            isFirst = false;
        }
        
        for (Entry<String, String> entry : queryParas.entrySet()) {
            if (isFirst) isFirst = false;   
            else sb.append("&");
            
            String key = entry.getKey();
            String value = entry.getValue();
            if (!value.isEmpty())
                try {value = URLEncoder.encode(value, CHARSET);} catch (UnsupportedEncodingException e) {throw new RuntimeException(e);}
            sb.append(key).append("=").append(value);
        }
        return sb.toString();
    }
}

public class MD5 {
    public static String MD5sign(String s) {
        char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        try {
            byte[] btInput = s.getBytes("UTF-8");
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> 4 & 0xf];
                str[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(str).toLowerCase();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    public static void main(String[] args) {
        System.out.println(MD5sign("Hello world"));
    }
}

使用AsyncTask异步获取调起微信支付的相关参数。当然你也可以使用其他的异步网络请求开源框架


public class TestPayPrepay extends AsyncTask<Object, Integer, String> {
    private Context mContext;
    public TestPayPrepay(Context context) {
        this.mContext = context;
    }
    
    @Override
    protected String doInBackground(Object... params) {
        System.out.println("TestPayPrepay doInBackground");
        return  IPayLogic.getIntance(mContext).testPay();
    }
    
    @Override
    protected void onPostExecute(String result) {
        try {
            if (result!=null) {
                System.out.println("TestPayPrepay result>"+result);
                JSONObject data = new JSONObject(result);
                if(!data.has("code")){
                    String sign = data.getString("sign");
                    String timestamp = data.getString("timestamp");
                    String noncestr = data.getString("noncestr");
                    String partnerid = data.getString("partnerid");
                    String prepayid = data.getString("prepayid");
                    String appid = data.getString("appid");
                    Toast.makeText(mContext, "正在调起支付", Toast.LENGTH_SHORT).show();
                    
                    Constants.APP_ID = appid;
                    
                    IPayLogic.getIntance(mContext).startWXPay(appid, partnerid, prepayid, noncestr, timestamp, sign);
                }else{
                    String message = data.getString("message");
                    Log.d("PAY_GET", "返回错误"+message);
                    Toast.makeText(mContext, "返回错误:"+message, Toast.LENGTH_SHORT).show();
                }
            }else {
                System.out.println("get  prepayid exception, is null");
            }
        } catch (Exception e) {
            Log.e("PAY_GET", "异常:"+e.getMessage());
            Toast.makeText(mContext, "异常:"+e.getMessage(), Toast.LENGTH_SHORT).show();
        }
        super.onPostExecute(result);
    }

}

支付结果回调

 <activity
            android:name="[应用的包名].wxapi.WXPayEntryActivity"
            android:exported="true"
            android:theme="@android:style/Theme.Translucent"
            android:launchMode="singleTop" >
 </activity>

封装的是SDK 所以这里设置了一个透明的主题


public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler{
    private IWXAPI api;
    private IPayListener payListener;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏标题
        LinearLayout ll = new LinearLayout(this);
        setContentView(ll);
        api = WXAPIFactory.createWXAPI(this, Constants.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) {
    }

    @Override
    public void onResp(BaseResp resp) {
        payListener = Constants.payListener;
        int code = resp.errCode;  
        System.out.println("onResp errCode>"+code);
        if (payListener!=null) {
            payListener.onPay(code);
            System.out.println("payListener callback");
        }else {
            System.out.println("payListener not callback");
        }
    }
}

注意:如果回调的code一直返回-1
1、请检查应用包名以及apk 的签名是否与你提交到微信开放平台的一致
2、请检查返回调取微信支付的参数是否正确
大部分原因是第一种

微信APP支付.png

遗留问题:由于支付应用的包名不固定WXPayEntryActivity无法封装到jar中,需要单独在支付应用添加.wxapi 这个包名并复制 WXPayEntryActivity到此包中。如果有好的解决方案欢迎留言

微信开发系列文章 http://www.jianshu.com/p/a172a1b69fdd

推荐阅读
极速开发微信公众号之微信买单
极速开发微信公众号之公众号支付
极速开发微信公众号之扫码支付
极速开发微信公众号之刷卡支付
极速开发微信公众号之现金红包
极速开发微信公众号之模板消息

如果此文章对你有帮助请点击喜欢告诉我

服务端源码地址:http://git.oschina.net/javen205/weixin_guide
客户端源码地址:https://github.com/Javen205/JPay

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容