本实例项目运行条件:
开发环境: 【Android Studio】
1. 到微信开放平台注册帐号并且创建移动应用
https://open.weixin.qq.com/cgi-bin/frame?t=home/app_tmpl&lang=zh_CN
2. 获得移动应用的权限【微信支付】
这个权限要求比较高,需要公司资质 并且 每年需要支付300元 才能开通 (这里不作讲解, 具体到官网上申请)
3. 配置应用签名, 这个签名通过 android打包文件jks生成或者keystore生成 【如何生成jks文件】
签名文件生成方法:
3.1 keytool -list -v -keystore jks文件(或者keystore文件)
3.2 获取指纹证书md5值, 将md5中的冒号去掉, 大写换成小写 (详情)
总结: 微信开放平台Android应用签名的本质便是我们签名文件jks(或者keystore)的MD5值
4. 配置支付密钥, 【如何配置密钥】
5. 应用程序开发完成后,debug模式是无法完成支付的,应用程序必须由相应的jks签名之后生成的apk包安装在手机上才能进行分支付(微信会校验应用签名)
2. 支付流程讲解
2.1 android程序启动后如下第一张图, 点击【确认支付】
2.1.1 android端向后台请求获得预支付信息
2.1.2 后台根据微信官网平台上的 配置信息 加上 订单信息 生成预支付信息
2.1.3 android端根据预支付信息 拉起微信支付页面进行支付(见下面第二张图)
3. 代码详解(Android端)
3.1 在android studio中引入 微信需要使用的jar包
3.2 在android工程对应的包名下面新建 包以及类, wxapi/WXPayEntryActivity
在AndroidManifest.xml中引用 WXPayEntryActivity
1 <application 2 android:allowBackup="true" 3 android:icon="@drawable/desk" 4 android:label="@string/app_name" 5 android:theme="@style/AppTheme"> 6 <activity 7 android:name=".activity.MainActivity" 8 android:screenOrientation="portrait" 9 android:label="@string/app_name"> 10 <intent-filter> 11 <action android:name="android.intent.action.MAIN" /> 12 <category android:name="android.intent.category.LAUNCHER" /> 13 </intent-filter> 14 </activity> 15 16 <activity 17 android:name=".wxapi.WXPayEntryActivity" 18 android:screenOrientation="portrait" 19 android:exported="true" 20 android:launchMode="singleTop"/> 21 </application>
WXPayEntryActivity 代码详情(支付完成后,onResp会被调用,BaseResp.ErrCode.ERR_OK 表明支付成功)
1 public class WXPayEntryActivity extends Activity implements IWXAPIEventHandler { 2 3 private static final String TAG = "WXPayEntryActivity"; 4 5 private IWXAPI api; 6 7 8 @Override 9 public void onCreate(Bundle savedInstanceState) { 10 super.onCreate(savedInstanceState); 11 12 Constant.wxApi.handleIntent(getIntent(), this); 13 } 14 15 @Override 16 protected void onNewIntent(Intent intent) { 17 super.onNewIntent(intent); 18 setIntent(intent); 19 api.handleIntent(intent, this); 20 } 21 22 @Override 23 public void onReq(BaseReq req) { 24 } 25 26 /** 27 * 得到支付结果回调 28 */ 29 @Override 30 public void onResp(BaseResp resp) 31 { 32 Log.i(TAG, "onPayFinish, errCode = " + resp.errCode); 33 34 String strPayResult = ""; 35 switch (resp.errCode) { 36 case BaseResp.ErrCode.ERR_OK: 37 Toast.makeText(this, "付款成功!", Toast.LENGTH_SHORT).show(); 38 break; 39 case BaseResp.ErrCode.ERR_USER_CANCEL: 40 //分享取消 41 //Toast.makeText(this, "付款取消!", Toast.LENGTH_SHORT).show(); 42 //Constant.WEIXIN_PAY_STATUS = "PAY_CANCEL"; 43 break; 44 case BaseResp.ErrCode.ERR_AUTH_DENIED: 45 //分享拒绝 46 //Toast.makeText(this, "付款拒绝!", Toast.LENGTH_SHORT).show(); 47 //Constant.WEIXIN_PAY_STATUS = "PAY_DENY"; 48 break; 49 } 50 51 //向之前页面返回支付结果信息 52 /*Intent intent = new Intent(); 53 intent.putExtra("payResult", strPayResult); 54 setResult(100, intent);*/ 55 finish(); 56 } 57 }
3.3 支付按钮的点击事件
通过http协议向后台请求相应的预支付信息, 根据这些信息组装相应的信息来调用微信接口, 拉起微信支付界面
1 @OnClick(R.id.pay) 2 public void pay(){ 3 String orderNum = OrderInfo.generateOutTradeNo(); 4 payService 5 .wpay(orderNum, totalPrice, address.getText().toString() + "-外卖订单") 6 .subscribe(new Action1<WeiXinPrePay>() { 7 @Override 8 public void call(WeiXinPrePay payInfo) { 9 if (Constant.wxApi != null) { 10 PayReq req = new PayReq(); 11 req.appId = payInfo.getAppId();// 微信开放平台审核通过的应用APPID 12 req.partnerId = payInfo.getMchId();// 微信支付分配的商户号 13 req.prepayId = payInfo.getPrepayId();// 预支付订单号,app服务器调用“统一下单”接口获取 14 req.nonceStr = payInfo.getNonceStr();// 随机字符串,不长于32位,服务器小哥会给咱生成 15 req.timeStamp = payInfo.getTimeStamp();// 时间戳,app服务器小哥给出 16 req.packageValue = "WXPay";// 固定值Sign=WXPay,可以直接写死,服务器返回的也是这个固定值 17 req.sign = payInfo.getPaySign();// 签名,服务器小哥给出,他会根据: om/wiki/doc/api/app/app.php?chapter=4_3指导得到这个 18 Constant.wxApi.sendReq(req); 19 } 20 } 21 }, new Action1<Throwable>() { 22 @Override 23 public void call(Throwable throwable) { 24 showToast(throwable.getMessage()); 25 } 26 }); 27 }
4 微信支付Java后台
4.1 后台代码结构图
4.2 微信配置信息 Config.properties
4.3 方法wxpay用于生成预支付订单信息
方法notifyWeiXinPay用于微信支付成功后的回调, 注意: 在手机端使用微信支付成功后,微信服务器会根据提供的回调地址进行回调, parameterMap.put("notify_url", wxnotify); (见下面代码)
在局域网是无法进行回调的,必须将你的服务端放在公网上进行测试, 回调函数会被多次调用,如果第一次成功后,你可以将业务数据状态标志为已处理, 对于相同订单的其它回调就不需要再次处理了
1 @Controller 2 @RequestMapping("/pay") 3 public class PayController { 4 5 String timeMillis = String.valueOf(System.currentTimeMillis() / 1000); 6 String randomString = PayCommonUtil.getRandomString(32); 7 //支付成功后的回调函数 8 public static String wxnotify = "http://gepanjiang.hk1.tunnelfrp.cc/WxPay/pay/notifyWeiXinPay.htm"; 9 10 public PayController() { 11 System.out.println("MainController构造函数"); 12 } 13 14 15 /** 16 * @param totalAmount 支付金额 17 * @param description 描述 18 * @param request 19 * @return 20 */ 21 @RequestMapping(value = "/wxpay.htm", produces = MediaType.APPLICATION_JSON_VALUE) 22 @ResponseBody 23 public Result wxpay(HttpServletRequest request) { 24 Result result = new Result(); 25 Long userId = new Long(1);//baseController.getUserId(); 26 27 BigDecimal totalAmount = new BigDecimal(request.getParameter("totalPrice")); 28 String trade_no = ""; 29 String description=""; 30 try { 31 trade_no = new String(request.getParameter("orderNum").getBytes("ISO-8859-1"),"UTF-8"); 32 description = request.getParameter("description"); 33 } catch (UnsupportedEncodingException e) { 34 // TODO Auto-generated catch block 35 e.printStackTrace(); 36 } 37 String openId = ""; 38 39 Map<String, String> map = weixinPrePay(trade_no,totalAmount,description,openId,request); 40 SortedMap<String, Object> finalpackage = new TreeMap<String, Object>(); 41 finalpackage.put("appId", ConfigManager.getInstance().getConfigItem("WXAppID")/*PayCommonUtil.APPID*/); 42 finalpackage.put("mchId", ConfigManager.getInstance().getConfigItem("MCH_ID")); 43 Long time = (System.currentTimeMillis() / 1000); 44 finalpackage.put("timeStamp", time.toString()); 45 finalpackage.put("nonceStr", map.get("nonce_str")); 46 finalpackage.put("prepayId", map.get("prepay_id")); 47 finalpackage.put("package", "Sign=WXPay"); 48 finalpackage.put("signType", "MD5"); 49 String sign = PayCommonUtil.createSign("UTF-8", finalpackage); 50 finalpackage.put("paySign", sign);//官方文档上是sign,当前示例代码是paySign 可能以前的 51 52 WeiXinPrePay prePay = new WeiXinPrePay(); 53 prePay.setAppId(ConfigManager.getInstance().getConfigItem("WXAppID")); 54 prePay.setMchId(ConfigManager.getInstance().getConfigItem("MCH_ID")); 55 prePay.setTimeStamp(time.toString()); 56 prePay.setNonceStr(map.get("nonce_str")); 57 prePay.setPrepayId(map.get("prepay_id")); 58 prePay.setSignType("MD5"); 59 prePay.setPaySign("paySign"); 60 result.setData(prePay); 61 result.setStateCode(GeneralConstant.SUCCESS); 62 result.setDesc("微信支付加载成功"); 63 64 return result; 65 } 66 67 68 /** 69 * 统一下单 70 * 应用场景:商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易回话标识后再在APP里面调起支付。 71 * @param trade_no 72 * @param totalAmount 73 * @param description 74 * @param openid 75 * @param sym 76 * @param request 77 * @return 78 */ 79 @SuppressWarnings("unchecked") 80 public Map<String, String> weixinPrePay(String trade_no,BigDecimal totalAmount, 81 String description, String openid, HttpServletRequest request) { 82 SortedMap<String, Object> parameterMap = new TreeMap<String, Object>(); 83 parameterMap.put("appid", ConfigManager.getInstance().getConfigItem("WXAppID")); //应用appid 84 parameterMap.put("mch_id", ConfigManager.getInstance().getConfigItem("MCH_ID")/*PayCommonUtil.MCH_ID*/); //商户号 85 //parameterMap.put("device_info", "WEB"); 86 parameterMap.put("nonce_str", randomString); 87 parameterMap.put("body", description); 88 parameterMap.put("out_trade_no", trade_no); 89 parameterMap.put("fee_type", "CNY"); 90 System.out.println("jiner"); 91 BigDecimal total = totalAmount.multiply(new BigDecimal(100)); //接口中参数支付金额单位为【分】,参数值不能带小数,所以乘以100 92 java.text.DecimalFormat df=new java.text.DecimalFormat("0"); 93 parameterMap.put("total_fee", df.format(total)); 94 System.out.println("jiner2"); 95 parameterMap.put("spbill_create_ip", PayCommonUtil.getRemoteHost(request)); 96 parameterMap.put("notify_url", wxnotify); 97 parameterMap.put("trade_type", "APP");//"JSAPI" 98 //trade_type为JSAPI是 openid为必填项 99 //parameterMap.put("openid", openid); 100 System.out.println(""); 101 String sign = PayCommonUtil.createSign("UTF-8", parameterMap); 102 System.out.println("jiner2"); 103 parameterMap.put("sign", sign); 104 String requestXML = PayCommonUtil.getRequestXml(parameterMap); 105 System.out.println(requestXML); 106 String result = PayCommonUtil.httpsRequest( 107 "https://api.mch.weixin.qq.com/pay/unifiedorder", "POST", 108 requestXML); 109 System.out.println(result); 110 Map<String, String> map = null; 111 try { 112 map = PayCommonUtil.doXMLParse(result); 113 } catch (JDOMException e) { 114 // TODO Auto-generated catch block 115 e.printStackTrace(); 116 } catch (IOException e) { 117 // TODO Auto-generated catch block 118 e.printStackTrace(); 119 } 120 return map; 121 } 122 123 124 125 /** 126 * 此函数会被执行多次,如果支付状态已经修改为已支付,则下次再调的时候判断是否已经支付,如果已经支付了,则什么也执行 127 * @param request 128 * @param response 129 * @return 130 * @throws IOException 131 * @throws JDOMException 132 */ 133 @RequestMapping(value = "notifyWeiXinPay.htm", produces = MediaType.APPLICATION_JSON_VALUE) 134 // @RequestDescription("支付回调地址") 135 @ResponseBody 136 public String notifyWeiXinPay(HttpServletRequest request, HttpServletResponse response) throws IOException, JDOMException { 137 System.out.println("微信支付回调"); 138 InputStream inStream = request.getInputStream(); 139 ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); 140 byte[] buffer = new byte[1024]; 141 int len = 0; 142 while ((len = inStream.read(buffer)) != -1) { 143 outSteam.write(buffer, 0, len); 144 } 145 String resultxml = new String(outSteam.toByteArray(), "utf-8"); 146 Map<String, String> params = PayCommonUtil.doXMLParse(resultxml); 147 outSteam.close(); 148 inStream.close(); 149 150 151 Map<String,String> return_data = new HashMap<String,String>(); 152 if (!PayCommonUtil.isTenpaySign(params)) { 153 // 支付失败 154 return_data.put("return_code", "FAIL"); 155 return_data.put("return_msg", "return_code不正确"); 156 return StringUtil.GetMapToXML(return_data); 157 } else { 158 System.out.println("===============付款成功=============="); 159 // ------------------------------ 160 // 处理业务开始 161 // ------------------------------ 162 // 此处处理订单状态,结合自己的订单数据完成订单状态的更新 163 // ------------------------------ 164 165 String total_fee = params.get("total_fee"); 166 double v = Double.valueOf(total_fee) / 100; 167 String out_trade_no = String.valueOf(Long.parseLong(params.get("out_trade_no").split("O")[0])); 168 Date accountTime = DateUtil.stringtoDate(params.get("time_end"), "yyyyMMddHHmmss"); 169 String ordertime = DateUtil.dateToString(new Date(), "yyyy-MM-dd HH:mm:ss"); 170 String totalAmount = String.valueOf(v); 171 String appId = params.get("appid"); 172 String tradeNo = params.get("transaction_id"); 173 174 return_data.put("return_code", "SUCCESS"); 175 return_data.put("return_msg", "OK"); 176 return StringUtil.GetMapToXML(return_data); 177 } 178 } 179 180 }