微信小微商户直连接口详解
项目背景:
小微商户根据接口申请入驻、查看状态、进行签约。商户信息配置、Native扫码及小程序发起支付、查询订单、进行退款、查询退款。小微商户资料修改及提现。
一、小微商户进件
遇到问题:
1、返回错误信息及解决办法
(1)、输入商户号后、平台提示证书不存在。API文档中也没有具体解释(如图1-1)?
图 1-1
已解决:关键在于参数中的商户号输入错误,经过沟通发现该商户号有两个,换个账号就可以解决问题
(2)解密敏感信息失败 已解决--------仔细查看需要进行加密的参数,当时手机号没有加密导致微信方解密敏感信息失败
(3)签名失败 已解决--------参数中的sign字段应方在最后再进行签名,不能跟api中的参数顺序一样
(4)身份证正面识别失败 已解决--------过期或失效的身份证无法识别成功
2、Java实现AES加密,抛出异常(如图1-2):
图 1-2
去该地址http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html下载${jdk_home}/jre/lib/security和${jre_home}/lib/security目录下的 local_policy.jar 和 US_export_policy.jar文件并且将其覆盖
3、小微商户进件流程
- 申 请 入 驻: (1)、先获取平台证书及序列号,并用返回的参数对证书进行解密得到平台证书的原文。ps:该原文用于对敏感信息加密
(2)、使用加密方法对敏感信息进行加密
(3)、将图片上传到接口获取图片id填写到请求入驻参数中
(4)、剩下的参数依次填入map中最后进行签名使用证书通过接口上传商户信息,ps:注意中文编码
- 查询申请状态:(1)、查询时使用自定义的编号或者微信返回的申请单号其一即可查询,ps:签名也是在最后的参数
(2)、将会返回审核状态,如果已审核成功变成待签约状态将会返回链接,商户扫码即可
二、小微商户配置
遇到问题:
1、 返回错误信息及解决办法
(1)绑定APPID时注意接口sub_appid参数示例 已解决:此处的sub_appid对小程序支付有影响,需配置为小程序的id否则在下单时会造成公众号id与sub_appid不匹配
2、配置流程
(1)将商户的商户号与固定小程序id绑定即可
三、小微商户交易
遇到问题:(appid-公众号id,mchid-商户号,subappid-小程序id及签名所需公钥请联系相关人员)
1、返回错误信息及解决办法
(1)参数的必填项为可选填,结果返回缺少参数 已解决:每个参数看好备注,不同的交易方式参数的可填项不一样
(2)native交易方式返回的支付URL打开无法支付 已解决:必须生成二维码后进行扫码支付,无法直接打开链接支付
(3)每天在没有余额时退款失败 已解决:每天都会自动提现到银行卡内导致账户无余额,无法直接退款,必须有余额
(4)查询退款时可能有多单返回 已解决:查询退款时,尽量使用退款单号查询,一个订单号会有退款总金额不超过订单金额的退款单数,当超过20单时必须使用退款单号
2、交易流程
(1)提交自己内部的唯一商品订单号、商品详情、金额(分)、交易方式等参数返回扫码地址或小程序调起支付的参数
(2)查询订单时提供商户号、微信订单号或商户订单号即可
(3)订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟,提供商户号、商户订单号即可关闭订单
(4)必须提供微信及内部订单号、内部唯一退款单号、订单总金额、退款金额申请退款,一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号再次申请退款
(5)查询退款提供退款单号即可
四、小微商户资料修改及提现
修改及提现流程:
(1)输入商户号及提现单的日期即可,提现单的日期是前一天的金额的提现,不是当天的交易金额
(2)提供商户号及银行名称、加密后的银行卡号即可修改成功
(3)重新发起提现时,如果提现一号的交易请提供2号的日期
(4)联系人信息提供后即可修改信息
五、代码实现
本项目使用SSM模型开发
1 public class WeiXinAccountController { 2 @Resource 3 WeiXinAccountService weiXinAccountService; 4 /** 5 * 上传图片的序列id 6 * @return 状态 7 */ 8 public ReturnModel<?> getPhotoid(@RequestBody WXPhotoModel wxPhotoModel){ 9 ReturnModel returnModel = weiXinAccountService.getPhotoid(wxPhotoModel); 10 return returnModel; 11 } 12 13 /** 14 * 查询审核状态 15 * @return 状态 16 */ 17 public ReturnModel<?> queryForApply(@RequestBody WXApplyStateModel wxApplyStateModel){ 18 ReturnModel returnModel = weiXinAccountService.queryForApply(wxApplyStateModel); 19 return returnModel; 20 } 21 22 /** 23 * 提交参数进行审核 24 * @param wxApplyForEntryModel 参数 25 * @return 是否提交成功//商户存在时不可编辑(不可再次提交) 26 */ 27 public ReturnModel<?> submitInfo(@RequestBody WXApplyForEntryModel wxApplyForEntryModel){ 28 return weiXinAccountService.submitInfo(wxApplyForEntryModel); 29 } 30 31 /*** 32 * 绑定AppID配置 33 * @param wxAddSubDevConfigModel 34 * @throws 35 */ 36 public ReturnModel<?> addSubDevConfig(@RequestBody WXAddSubDevConfigModel wxAddSubDevConfigModel){ 37 return weiXinAccountService.addSubDevConfig(wxAddSubDevConfigModel); 38 } 39 40 /** 41 * 统一下单native 42 * @param wxUnifiedOrderModel 43 * @return 44 */ 45 public ReturnModel<?> unifiedOrderByNATIVE(@RequestBody WXUnifiedOrderModel wxUnifiedOrderModel, HttpServletRequest httpServletRequest){ 46 String ip = WXHttpUtil.getSpbillCreateIp(httpServletRequest); 47 wxUnifiedOrderModel.setSpbillCreateIp(ip); 48 return weiXinAccountService.unifiedOrderByNATIVE(wxUnifiedOrderModel); 49 } 50 /** 51 * 统一下单jsapi 52 * @param wxUnifiedOrderModel 53 * @return 54 */ 55 public ReturnModel<?> unifiedOrderByJSAPI(@RequestBody WXUnifiedOrderModel wxUnifiedOrderModel, HttpServletRequest httpServletRequest){ 56 String ip = WXHttpUtil.getSpbillCreateIp(httpServletRequest); 57 wxUnifiedOrderModel.setSpbillCreateIp(ip); 58 return weiXinAccountService.unifiedOrderByJSAPI(wxUnifiedOrderModel); 59 } 60 /** 61 * 查询订单 62 * @param wxOrderQueryModel 63 * @return 64 */ 65 public ReturnModel<?> orderQuery(@RequestBody WXOrderQueryModel wxOrderQueryModel){ 66 return weiXinAccountService.orderQuery(wxOrderQueryModel); 67 } 68 /** 69 * 关闭订单,如果支付失败则需要关闭订单避免重复支付或超时系统退出不再受理也需要关闭 70 * @param wxCloseOrderModel 71 * @return 72 */ 73 public ReturnModel<?> closeOrder(@RequestBody WXCloseOrderModel wxCloseOrderModel){ 74 return weiXinAccountService.closeOrder(wxCloseOrderModel); 75 } 76 77 /** 78 * 申请退款 79 * @param wxRefundModel 80 * @return 81 */ 82 public ReturnModel<?> refund(@RequestBody WXRefundModel wxRefundModel){ 83 return weiXinAccountService.refund(wxRefundModel); 84 } 85 86 /** 87 * 查询退款 88 * @param wxRefundQueryModel 89 * @return 90 */ 91 public ReturnModel<?> refundQuery(@RequestBody WXRefundQueryModel wxRefundQueryModel){ 92 return weiXinAccountService.refundQuery(wxRefundQueryModel); 93 } 94 95 /** 96 * 提现状态查询 97 * @param wxQueryAutoWithdrawByDate 98 * @return 99 */ 100 public ReturnModel<?> queryAutoWithdrawByDate(@RequestBody WXQueryAutoWithdrawByDate wxQueryAutoWithdrawByDate){ 101 return weiXinAccountService.queryAutoWithdrawByDate(wxQueryAutoWithdrawByDate); 102 } 103 104 /** 105 * 修改银行卡号 106 * @param modifyarchives 107 * @return 108 * @throws Exception 109 */ 110 public ReturnModel<?> modifyArchives(@RequestBody WXModifyArchivesModel modifyarchives) throws Exception { 111 return weiXinAccountService.modifyArchives(modifyarchives); 112 } 113 114 /** 115 * 重新发起提现 116 * @param reautowithdrawbydate 117 * @return 118 */ 119 public ReturnModel<?> reAutoWithdrawByDate(@RequestBody WXQueryAutoWithdrawByDate reautowithdrawbydate) { 120 return weiXinAccountService.reAutoWithdrawByDate(reautowithdrawbydate); 121 } 122 123 /** 124 * 修改商户联系信息 125 * @param modifycontactinfo 126 * @return 127 * 128 * @throws Exception 129 */ 130 public ReturnModel<?> modifyContactInfo(@RequestBody WXModifyContactInfoModel modifycontactinfo) throws Exception { 131 return weiXinAccountService.modifyContactInfo(modifycontactinfo); 132 } 133 }
1 public class WeiXinAccountServiceImpl implements WeiXinAccountService { 2 private final Logger LOGGER = LoggerFactory.getLogger(WeiXinAccountServiceImpl.class); 3 /** 4 * 准备申请入驻的参数 5 * @return 入驻的参数 6 * @throws Exception 7 */ 8 public SortedMap<String, String> applyForEntry(WXApplyForEntryModel wxApplyForEntryModel) throws Exception { 9 //先获取平台证书及序列号,并用返回的参数对证书进行解密得到平台证书的原文。 10 // ps:该原文用于对敏感信息加密 11 String certificatesInfo = getCertficates(); 12 if(("fail").equals(certificatesInfo) || ("").equals(certificatesInfo)){ 13 return null; 14 } 15 JSONArray data = JSONObject.parseArray( 16 JSONObject.parseObject(certificatesInfo).getString("data")); 17 //得到平台系列号 18 String serialNo = JSONObject.parseObject( 19 JSONObject.toJSONString(data.get(0))).getString("serial_no"); 20 //得到用于加密敏感信息的证书原文 21 String cert = WXPayUtil.getWXCert(data); 22 String name = RSAEncryptUtil.rsaEncrypt(wxApplyForEntryModel.getIdCardName(),cert); 23 wxApplyForEntryModel.setVersion("3.0"); 24 wxApplyForEntryModel.setCertSn(serialNo); 25 wxApplyForEntryModel.setMchId(WXConstant.MCHID); 26 wxApplyForEntryModel.setNonceStr(String.valueOf(new Date().getTime())); 27 wxApplyForEntryModel.setSignType(WXConstant.SIGNTTYPE); 28 wxApplyForEntryModel.setIdCardNumber( 29 RSAEncryptUtil.rsaEncrypt(wxApplyForEntryModel.getIdCardNumber(),cert)); 30 wxApplyForEntryModel.setIdCardName(name); 31 wxApplyForEntryModel.setAccountName(name); 32 wxApplyForEntryModel.setAccountNumber( 33 RSAEncryptUtil.rsaEncrypt(wxApplyForEntryModel.getAccountNumber(),cert)); 34 wxApplyForEntryModel.setContact(name); 35 wxApplyForEntryModel.setContactPhone( 36 RSAEncryptUtil.rsaEncrypt(wxApplyForEntryModel.getContactPhone(),cert)); 37 SortedMap reqPara = AnnotationUtil.parseObjectToMap(wxApplyForEntryModel); 38 return reqPara; 39 } 40 /** 41 * 得到平台证书包含的参数json 42 * @return 43 */ 44 public String getCertficates(){ 45 SortedMap<String, String> reqDataToZS = new TreeMap<>(); 46 reqDataToZS.put("mch_id", WXConstant.MCHID);//服务商商户号 47 reqDataToZS.put("nonce_str", WXPayUtil.getRandomString(32));//随机字符串 48 reqDataToZS.put("sign_type", WXConstant.SIGNTTYPE);//签名类型 49 reqDataToZS.put("sign", WXPayUtil.generateSignature(reqDataToZS,WXConstant.KEY,WXConstant.HMACSHA256TYPE));//签名 50 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithoutCert(WXConstant.ZHENGSHUURL,reqDataToZS);//证书接口返回所需参数 51 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 52 if(("FAIL").equals(result.get("result_code"))){ 53 return "fail"; 54 } 55 return result.get("certificates"); 56 } 57 58 /** 59 * 得到上传照片的id 60 * @param wxPhotoModel 图片 61 * @return 图片id 62 */ 63 @Override 64 public ReturnModel<?> getPhotoid(WXPhotoModel wxPhotoModel){ 65 String cent = ""; 66 InputStream fileInputStream = null; 67 DataInputStream dis = null; 68 byte[] bufferOut; 69 try { 70 URL url = new URL(wxPhotoModel.getFileUrlPath()); 71 HttpURLConnection httpUrl = (HttpURLConnection) url.openConnection(); 72 httpUrl.setDoInput(true); 73 httpUrl.setRequestMethod("GET"); 74 fileInputStream = httpUrl.getInputStream(); 75 dis = new DataInputStream(fileInputStream); 76 bufferOut = new byte[httpUrl.getContentLength()]; 77 dis.read(bufferOut); 78 cent = MD5.md5HashCode(bufferOut); 79 } catch (Exception e) { 80 LOGGER.error(e.getMessage()); 81 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),e.getMessage()); 82 }finally { 83 if (fileInputStream!=null) { 84 try { 85 fileInputStream.close(); 86 } catch (IOException e) { 87 LOGGER.error(e.getMessage()); 88 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),e.getMessage()); 89 } 90 } 91 if(dis!=null){ 92 try { 93 dis.close(); 94 } catch (IOException e) { 95 LOGGER.error(e.getMessage()); 96 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),e.getMessage()); 97 } 98 } 99 } 100 if ("".equals(cent) || fileInputStream == null) 101 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"图片未获取到"); 102 SortedMap<String, String> reqData = new TreeMap<>(); 103 reqData.put("mch_id", WXConstant.MCHID); 104 reqData.put("sign_type", WXConstant.SIGNTTYPE); 105 reqData.put("media_hash", cent); 106 reqData.put("sign", WXPayUtil.generateSignature(reqData, 107 WXConstant.KEY, WXConstant.HMACSHA256TYPE)); 108 MultipartEntityBuilder builder = MultipartEntityBuilder.create(); 109 builder.addTextBody("mch_id",WXConstant.MCHID, ContentType.MULTIPART_FORM_DATA); 110 builder.addBinaryBody("media",bufferOut, ContentType.DEFAULT_BINARY,wxPhotoModel.getFileName()); 111 builder.addTextBody("media_hash",cent, ContentType.MULTIPART_FORM_DATA); 112 builder.addTextBody("sign_type",WXConstant.SIGNTTYPE, ContentType.MULTIPART_FORM_DATA); 113 builder.addTextBody("sign",reqData.get("sign"), ContentType.MULTIPART_FORM_DATA); 114 String returnStr = WXPayHttpUtil.WXHttpPostFormWithCert(WXConstant.MEDIAUURL, 115 WXConstant.MCHID,WXConstant.CERTPATH,builder); 116 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 117 if(result.isEmpty()){ 118 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"未获取到微信方图片id"); 119 } 120 return new ReturnModel<>(result); 121 } 122 123 @Override 124 public ReturnModel<?> unifiedOrderByNATIVE(WXUnifiedOrderModel wxUnifiedOrderModel) { 125 wxUnifiedOrderModel.setProductId(WXPayUtil.getRandomString(32)); 126 wxUnifiedOrderModel.setTradeType("NATIVE"); 127 return unifiedOrder(wxUnifiedOrderModel); 128 } 129 130 @Override 131 public ReturnModel<?> unifiedOrderByJSAPI(WXUnifiedOrderModel wxUnifiedOrderModel) { 132 wxUnifiedOrderModel.setSubAppid(WXConstant.SUBAPPID); 133 wxUnifiedOrderModel.setTradeType("JSAPI"); 134 return unifiedOrder(wxUnifiedOrderModel); 135 } 136 137 /** 138 * 查询审核状态 139 * @param applyState 参数-商户号、服务商自定义的用户编号 140 * @return 141 */ 142 @Override 143 public ReturnModel<?> queryForApply(WXApplyStateModel applyState){ 144 applyState.setVersion("1.0"); 145 applyState.setMchId(WXConstant.MCHID); 146 applyState.setNonceStr(WXPayUtil.getRandomString(32)); 147 applyState.setSignType(WXConstant.SIGNTTYPE); 148 SortedMap<String,String> reqData = AnnotationUtil.parseObjectToMap(applyState); 149 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithCert(WXConstant.QUERYURL, 150 reqData,WXConstant.CERTPATH); 151 if(("").equals(returnStr)){ 152 return new ReturnModel<>(ErrorCode.SERVER_ERROR.getCode(),"安全证书无法解析"); 153 } 154 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 155 if(("FAIL").equals(result.get("return_code"))){ 156 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),result.get("return_msg")); 157 } 158 if(("SUCCESS").equals(result.get("return_code"))){ 159 return new ReturnModel<>(result); 160 // String applymentStateDesc = "申请状态:"+result.get("applyment_state_desc"); 161 // if(("REJECTED").equals(result.get("applyment_state"))){ 162 // //审核结果json 163 // JSONArray audit_detail = JSONObject.parseArray( 164 // JSONObject.parseObject( 165 // result.get("audit_detail")).getString("audit_detail")); 166 // //得到审核详情 167 // String reject_reason = JSONObject.parseObject( 168 // JSONObject.toJSONString(audit_detail.get(0))).getString("reject_reason"); 169 // return applymentStateDesc+"\t审核详情:"+reject_reason; 170 // } 171 // if(("TO_BE_SIGNED").equals(result.get("applyment_state"))){ 172 // return applymentStateDesc+"\t小微商户号:" 173 // +result.get("sub_mch_id")+"\t签约链接:" 174 // +result.get("sign_url"); 175 // } 176 // if(("FINISH").equals(result.get("applyment_state"))){ 177 // return applymentStateDesc+"\t小微商户号:"+result.get("sub_mch_id"); 178 // } 179 // return applymentStateDesc; 180 } 181 return new ReturnModel<>(ErrorCode.SERVER_ERROR.getCode(),"结果解析错误"); 182 } 183 184 /** 185 * 提交商户申请 186 * @param wxApplyForEntryModel 请求商户信息 187 * @return 188 */ 189 @Override 190 public ReturnModel<?> submitInfo(WXApplyForEntryModel wxApplyForEntryModel) { 191 SortedMap<String,String> reqData = new TreeMap<>(); 192 try { 193 reqData = applyForEntry(wxApplyForEntryModel); 194 } catch (Exception e) { 195 LOGGER.error(e.getMessage()); 196 197 } 198 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithCert(WXConstant.SUBMITURL, 199 reqData, WXConstant.CERTPATH); 200 if(("").equals(returnStr)){ 201 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"安全证书无法解析"); 202 } 203 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 204 if(("FAIL").equals(result.get("return_code"))){ 205 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(), 206 "返回信息:"+result.get("return_msg")+"错误描述:"+result.get("err_code_des")); 207 } 208 if(("SUCCESS").equals(result.get("return_code"))){ 209 return new ReturnModel<>(result); 210 // if(("SUCCESS").equals(result.get("result_code"))){ 211 // return "微信分配的申请单号:"+result.get("applyment_id"); 212 // } 213 // if(("FAIL").equals(result.get("result_code"))){ 214 // if(("EXIST").equals(result.get("err_code"))){ 215 // return "商户已存在,对应的申请单当前状态不可编辑"; 216 // } 217 // return result.get("err_code_des"); 218 // } 219 } 220 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"结果解析错误"); 221 } 222 223 /** 224 * 提现状态 225 * @param wxQueryAutoWithdrawByDate 226 * @return 227 */ 228 @Override 229 public ReturnModel<?> queryAutoWithdrawByDate(WXQueryAutoWithdrawByDate wxQueryAutoWithdrawByDate) { 230 wxQueryAutoWithdrawByDate.setMchId(WXConstant.MCHID); 231 wxQueryAutoWithdrawByDate.setSignType(WXConstant.SIGNTTYPE); 232 wxQueryAutoWithdrawByDate.setNonceStr(WXPayUtil.getRandomString(32)); 233 SortedMap<String,String> reqData = AnnotationUtil.parseObjectToMap(wxQueryAutoWithdrawByDate); 234 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithCert(WXConstant.QUERYAUTOWITHDRAWURL, 235 reqData,WXConstant.CERTPATH); 236 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 237 if("SUCCESS".equals(result.get("return_code"))){ 238 return new ReturnModel<>(result); 239 // if("SUCCESS".equals(result.get("result_code"))){ 240 // if ("PROCESSING".equals(result.get("withdraw_status"))){ 241 // return result.get("withdraw_status"); 242 // }else if ("SUCCESS".equals(result.get("withdraw_status"))){ 243 // return result.get("date")+"\t金额(分):"+result.get("amount")+"\t提现成功时间:" 244 // +result.get("success_time"); 245 // } 246 // return result.get("withdraw_status"); 247 // }else if ("FAIL".equals(result.get("result_code"))){ 248 // if(result.get("err_code_des") != null && !"".equals(result.get("err_code_des"))){ 249 // return result.get("err_code_des"); 250 // } 251 // return result.get("err_code"); 252 // } 253 }else if("FAIL".equals(result.get("return_code"))){ 254 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),result.get("return_msg")); 255 } 256 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"结果解析错误"); 257 } 258 259 /** 260 * 统一下单 261 * @param wxUnifiedOrderModel 262 * @return 263 */ 264 @Override 265 public ReturnModel<?> unifiedOrder(WXUnifiedOrderModel wxUnifiedOrderModel) { 266 wxUnifiedOrderModel.setAppid(WXConstant.APPID); 267 wxUnifiedOrderModel.setMchId(WXConstant.MCHID); 268 wxUnifiedOrderModel.setNonceStr(WXPayUtil.getRandomString(32)); 269 wxUnifiedOrderModel.setSignType(WXConstant.SIGNTTYPE); 270 wxUnifiedOrderModel.setNotifyUrl(WXConstant.NOTIFYURL); 271 SortedMap<String,String> reqData = AnnotationUtil.parseObjectToMap(wxUnifiedOrderModel); 272 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithoutCert(WXConstant.UNIFIEDORDERURL, reqData); 273 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 274 if(("SUCCESS").equals(result.get("return_code"))){ 275 if(("SUCCESS").equals(result.get("result_code"))){ 276 // if ("NATIVE".equals(wxUnifiedOrderModel.getTradeType())){ 277 // return new ReturnModel<>(result); 278 // }else 279 if("JSAPI".equals(wxUnifiedOrderModel.getTradeType())){ 280 Map<String,String> payinfo = new HashMap<>(); 281 payinfo.put("appId",wxUnifiedOrderModel.getAppid()); 282 payinfo.put("nonceStr",WXPayUtil.getRandomString(32)); 283 payinfo.put("package","prepay_id="+result.get("prepay_id")); 284 payinfo.put("signType",WXConstant.MD5TYPE); 285 payinfo.put("timeStamp",String.valueOf(new Date().getTime())); 286 payinfo.put("paySign",WXPayUtil.generateSignature(payinfo,WXConstant.KEY,WXConstant.MD5TYPE)); 287 // String info = JSONObject.toJSONString(payinfo); 288 return new ReturnModel<>(payinfo); 289 } 290 } 291 // if(("FAIL").equals(result.get("result_code"))){ 292 // return result.get("err_code_des"); 293 // } 294 return new ReturnModel<>(result); 295 }else if(("FAIL").equals(result.get("return_code"))){ 296 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),result.get("return_msg")); 297 } 298 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"结果解析错误"); 299 } 300 301 /** 302 * 绑定AppID配置 303 * @param wxAddSubDevConfigModel 304 * @return 305 */ 306 @Override 307 public ReturnModel<?> addSubDevConfig(WXAddSubDevConfigModel wxAddSubDevConfigModel) { 308 wxAddSubDevConfigModel.setAppid(WXConstant.APPID); 309 wxAddSubDevConfigModel.setMchId(WXConstant.MCHID); 310 wxAddSubDevConfigModel.setSubAppid(WXConstant.SUBAPPID); 311 SortedMap<String,String> reqData = AnnotationUtil.parseObjectToMap(wxAddSubDevConfigModel); 312 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithCert(WXConstant.ADDSUBDEVCONFIGURL, reqData, WXConstant.CERTPATH); 313 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 314 if(("SUCCESS").equals(result.get("return_code"))) { 315 return new ReturnModel<>(result); 316 // if(("SUCCESS").equals(result.get("result_code"))){ 317 // return "配置成功"; 318 // }else if(("FAIL").equals(result.get("result_code"))){ 319 // return result.get("err_code_des").replaceAll("[a-zA-Z]",""); 320 // } 321 }else if(("FAIL").equals(result.get("return_code"))){ 322 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),result.get("return_msg")); 323 } 324 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"结果解析错误"); 325 } 326 327 /** 328 * 订单查询 329 * @param wxOrderQueryModel 330 * @return 331 */ 332 @Override 333 public ReturnModel<?> orderQuery(WXOrderQueryModel wxOrderQueryModel) { 334 wxOrderQueryModel.setSubAppid(WXConstant.SUBAPPID); 335 wxOrderQueryModel.setAppid(WXConstant.APPID); 336 wxOrderQueryModel.setMchId(WXConstant.MCHID); 337 wxOrderQueryModel.setNonceStr(WXPayUtil.getRandomString(32)); 338 wxOrderQueryModel.setSignType(WXConstant.SIGNTTYPE); 339 SortedMap<String,String> reqData = AnnotationUtil.parseObjectToMap(wxOrderQueryModel); 340 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithoutCert(WXConstant.ORDERQUERYURL, reqData); 341 Map<String,String> result= WXPayXmlUtil.parseXMLToMap(returnStr); 342 if(("SUCCESS").equals(result.get("return_code"))){ 343 return new ReturnModel<>(result); 344 // if(("SUCCESS").equals(result.get("result_code")) && ("SUCCESS").equals(result.get("trade_state"))){ 345 // return result.get("trade_state_desc"); 346 // }else if(("SUCCESS").equals(result.get("result_code"))){ 347 // return result.get("out_trade_no")+"交易状态:"+result.get("trade_state"); 348 // }else if(("FAIL").equals(result.get("result_code"))){ 349 // return result.get("err_code_des"); 350 // } 351 }else if (("FAIL").equals(result.get("return_code"))){ 352 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),result.get("return_msg")); 353 } 354 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"结果解析错误"); 355 } 356 357 /** 358 * 关闭订单 359 * @param wxCloseOrderModel 360 * @return 361 */ 362 @Override 363 public ReturnModel<?> closeOrder(WXCloseOrderModel wxCloseOrderModel) { 364 wxCloseOrderModel.setAppid(WXConstant.APPID); 365 wxCloseOrderModel.setMchId(WXConstant.MCHID); 366 wxCloseOrderModel.setNonceStr(WXPayUtil.getRandomString(32)); 367 SortedMap<String,String> reqData = AnnotationUtil.parseObjectToMap(wxCloseOrderModel); 368 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithoutCert(WXConstant.CLOSEORDERURL,reqData); 369 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 370 if(("SUCCESS").equals(result.get("return_code"))){ 371 return new ReturnModel<>(result); 372 // if(("SUCCESS").equals(result.get("result_code"))){ 373 // return "关闭成功"; 374 // }else if(("FAIL").equals(result.get("result_code"))){ 375 // if(("ORDERPAID").equals(result.get("err_code"))){ 376 // return "订单已支付,不能发起关单,请当作已支付的正常交易"; 377 // }else if(("SYSTEMERROR").equals(result.get("err_code"))){ 378 // return "系统异常,请重新调用"; 379 // }else if(("ORDERCLOSED").equals(result.get("err_code"))){ 380 // return "订单已关闭,无需继续调用"; 381 // }else if(("SIGNERROR").equals(result.get("err_code"))){ 382 // return "请检查签名参数和方法是否都符合签名算法要求"; 383 // }else if(("REQUIRE_POST_METHOD").equals(result.get("err_code"))){ 384 // return "请检查请求参数是否通过post方法提交"; 385 // }else if(("REQUIRE_POST_METHOD").equals(result.get("err_code"))){ 386 // return "请检查XML参数格式是否正确"; 387 // } 388 // } 389 }else if(("FAIL").equals(result.get("return_code"))){ 390 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),result.get("return_msg")); 391 } 392 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"结果解析错误"); 393 } 394 395 /** 396 * 申请退款 397 * @param wxRefundModel 398 * @return 399 */ 400 @Override 401 public ReturnModel<?> refund(WXRefundModel wxRefundModel) { 402 wxRefundModel.setSubAppid(WXConstant.SUBAPPID); 403 wxRefundModel.setAppid(WXConstant.APPID); 404 wxRefundModel.setMchId(WXConstant.MCHID); 405 wxRefundModel.setNonceStr(WXPayUtil.getRandomString(32)); 406 wxRefundModel.setNotifyUrl(WXConstant.NOTIFYURL); 407 wxRefundModel.setSignType(WXConstant.SIGNTTYPE); 408 SortedMap<String,String> reqData = AnnotationUtil.parseObjectToMap(wxRefundModel); 409 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithCert(WXConstant.REFUNDURL, 410 reqData,WXConstant.CERTPATH); 411 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 412 if(("SUCCESS").equals(result.get("return_code"))){ 413 return new ReturnModel<>(result); 414 // if(("SUCCESS").equals(result.get("result_code"))){ 415 // return "申请退款成功"; 416 // }else if(("FAIL").equals(result.get("result_code"))){ 417 // return result.get("err_code_des"); 418 // } 419 }else if(("FAIL").equals(result.get("return_code"))){ 420 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),result.get("return_msg")); 421 } 422 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"结果解析错误"); 423 } 424 425 /** 426 * 查询退款(微信退款id查询,只查一笔) 427 * @param wxRefundQueryModel 428 * @return 429 */ 430 @Override 431 public ReturnModel<?> refundQuery(WXRefundQueryModel wxRefundQueryModel) { 432 wxRefundQueryModel.setSubAppid(WXConstant.SUBAPPID); 433 wxRefundQueryModel.setAppid(WXConstant.APPID); 434 wxRefundQueryModel.setMchId(WXConstant.MCHID); 435 wxRefundQueryModel.setSignType(WXConstant.SIGNTTYPE); 436 wxRefundQueryModel.setNonceStr(WXPayUtil.getRandomString(32)); 437 SortedMap<String,String> reqData = AnnotationUtil.parseObjectToMap(wxRefundQueryModel); 438 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithoutCert(WXConstant.REFUNDQUERYURL,reqData); 439 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 440 if(("SUCCESS").equals(result.get("return_code"))){ 441 return new ReturnModel<>(result); 442 // if(("SUCCESS").equals(result.get("result_code"))){ 443 // return result.get("refund_status_0"); 444 // }else if(("FAIL").equals(result.get("result_code"))){ 445 // return result.get("err_code_des"); 446 // } 447 }else if(("FAIL").equals(result.get("return_code"))){ 448 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),result.get("return_msg")); 449 } 450 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"结果解析错误"); 451 } 452 453 /** 454 * 修改银行卡号 455 * @param modifyarchives 456 * @return 457 */ 458 @Override 459 public ReturnModel<?> modifyArchives(WXModifyArchivesModel modifyarchives) { 460 modifyarchives.setVersion("1.0"); 461 modifyarchives.setMchId(WXConstant.MCHID); 462 modifyarchives.setNonceStr(WXPayUtil.getRandomString(32)); 463 modifyarchives.setSignType(WXConstant.SIGNTTYPE); 464 String certificatesInfo = getCertficates(); 465 if(("fail").equals(certificatesInfo) || ("").equals(certificatesInfo)){ 466 return new ReturnModel<>(ErrorCode.SERVER_ERROR.getCode(),"安全证书无法解析"); 467 } 468 JSONArray data = JSONObject.parseArray( 469 JSONObject.parseObject(certificatesInfo).getString("data")); 470 //得到平台系列号 471 String serialNo = JSONObject.parseObject( 472 JSONObject.toJSONString(data.get(0))).getString("serial_no"); 473 //得到用于加密敏感信息的证书原文 474 String cert = WXPayUtil.getWXCert(data); 475 modifyarchives.setCertSn(serialNo); 476 modifyarchives.setAccountNumber(RSAEncryptUtil.rsaEncrypt("6214830195599542",cert)); 477 478 SortedMap<String,String> reqData = AnnotationUtil.parseObjectToMap(modifyarchives); 479 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithCert(WXConstant.MODIFYARCHIVESURL,reqData,WXConstant.CERTPATH); 480 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 481 if("SUCCESS".equals(result.get("return_code"))){ 482 return new ReturnModel<>(result); 483 // if("FAIL".equals(result.get("result_code"))){ 484 // return result.get("err_code"); 485 // }else if ("SUCCESS".equals(result.get("result_code"))){ 486 // return result.get("sub_mch_id")+"商户下的银行卡号修改成功"; 487 // } 488 }else if ("FAIL".equals(result.get("return_code"))){ 489 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),result.get("return_msg")); 490 } 491 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"结果解析错误"); 492 } 493 494 /** 495 * 重新发起提现 496 * @param reautowithdrawbydate 497 * @return 498 */ 499 @Override 500 public ReturnModel<?> reAutoWithdrawByDate(WXQueryAutoWithdrawByDate reautowithdrawbydate) { 501 reautowithdrawbydate.setSignType(WXConstant.SIGNTTYPE); 502 reautowithdrawbydate.setMchId(WXConstant.MCHID); 503 reautowithdrawbydate.setNonceStr(WXPayUtil.getRandomString(32)); 504 SortedMap<String,String> reqData = AnnotationUtil.parseObjectToMap(reautowithdrawbydate); 505 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithCert(WXConstant.REAUTOWITHDRAWBYDATEURL, 506 reqData,WXConstant.CERTPATH); 507 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 508 if("SUCCESS".equals(result.get("return_code"))){ 509 return new ReturnModel<>(result); 510 // if ("SUCCESS".equals(result.get("result_code"))){ 511 // return result.get("date")+"\t金额:"+result.get("amount"); 512 // }else if ("FAIL".equals(result.get("result_code"))){ 513 // return result.get("err_code")+result.get("err_code_des"); 514 // } 515 }else if ("FAIL".equals(result.get("return_code"))){ 516 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),result.get("return_msg")); 517 } 518 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"结果解析错误"); 519 } 520 521 /** 522 * 修改商户联系信息 523 * @param modifycontactinfo 524 * @return 525 */ 526 @Override 527 public ReturnModel<?> modifyContactInfo(WXModifyContactInfoModel modifycontactinfo) { 528 modifycontactinfo.setVersion("1.0"); 529 modifycontactinfo.setMchId(WXConstant.MCHID); 530 modifycontactinfo.setNonceStr(WXPayUtil.getRandomString(32)); 531 modifycontactinfo.setSignType(WXConstant.SIGNTTYPE); 532 String certificatesInfo = getCertficates(); 533 if(("fail").equals(certificatesInfo) || ("").equals(certificatesInfo)){ 534 return new ReturnModel<>(ErrorCode.SERVER_ERROR.getCode(),"安全证书无法解析"); 535 } 536 JSONArray data = JSONObject.parseArray( 537 JSONObject.parseObject(certificatesInfo).getString("data")); 538 //得到平台系列号 539 String serialNo = JSONObject.parseObject( 540 JSONObject.toJSONString(data.get(0))).getString("serial_no"); 541 //得到用于加密敏感信息的证书原文 542 String cert = WXPayUtil.getWXCert(data); 543 modifycontactinfo.setCertSn(serialNo); 544 modifycontactinfo.setMobilePhone(RSAEncryptUtil.rsaEncrypt(modifycontactinfo.getMobilePhone(),cert)); 545 modifycontactinfo.setEmail(RSAEncryptUtil.rsaEncrypt(modifycontactinfo.getEmail(),cert)); 546 SortedMap<String,String> reqData = AnnotationUtil.parseObjectToMap(modifycontactinfo); 547 String returnStr = WXPayHttpUtil.WXHttpPostXMLWithCert(WXConstant.MODIFYCONTACTINFOURL, 548 reqData,WXConstant.CERTPATH); 549 Map<String,String> result = WXPayXmlUtil.parseXMLToMap(returnStr); 550 if ("SUCCESS".equals(result.get("return_code"))){ 551 return new ReturnModel<>(result); 552 // if ("SUCCESS".equals(result.get("result_code"))){ 553 // return result.get("sub_mch_id")+"商户修改信息成功"; 554 // }else if ("FAIL".equals(result.get("result_code"))){ 555 // return result.get("err_code")+result.get("err_code_des"); 556 // } 557 }else if ("FAIL".equals(result.get("return_code"))){ 558 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),result.get("return_msg")); 559 } 560 return new ReturnModel<>(ErrorCode.BUSINESS_ERROR.getCode(),"结果解析错误"); 561 } 562 }
1 public class WXPayHttpUtil { 2 private static final Logger LOGGER = LoggerFactory.getLogger(WXPayHttpUtil.class); 3 4 private final static String UTFCHARSET = "UTF-8"; 5 private final static String XMLCONTENT = "text/xml;charset=utf-8"; 6 private final static String FORMCONTENT = "multipart/form-data;charset=utf-8"; 7 /** 8 * 需证书上传form表单形式接口 9 * @param url 接口地址 10 * @param mcdId 商户号即密码 11 * @param certPath 证书文件位置 12 * @param builder 表单参数 13 * @return 14 */ 15 public static String WXHttpPostFormWithCert(String url, String mcdId, String certPath, MultipartEntityBuilder builder){ 16 HttpPost uploadFile = new HttpPost(url); 17 SSLConnectionSocketFactory sslsf = null; 18 StringBuilder stringBuilder = new StringBuilder(); 19 //读取本机存放的PKCS12证书文件 20 ClassLoader classLoader = WXPayHttpUtil.class.getClassLoader(); 21 URL resource = classLoader.getResource(certPath); 22 try(FileInputStream pkcfile = new FileInputStream(new File(resource.getPath())); 23 ){ 24 //指定读取证书格式为PKCS12 25 KeyStore keyStore = KeyStore.getInstance("PKCS12"); 26 //"100000"为密码即商户号 27 keyStore.load(pkcfile, mcdId.toCharArray()); 28 SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mcdId.toCharArray()).build(); 29 //指定TLS版本 30 sslsf = new SSLConnectionSocketFactory(sslcontext); 31 } catch (Exception e) { 32 LOGGER.debug(e.getMessage()); 33 return ""; 34 } 35 HttpEntity multipart = builder.build(); 36 uploadFile.setEntity(multipart); 37 uploadFile.setHeader("Content-Type", FORMCONTENT); 38 try(CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslsf).build(); 39 CloseableHttpResponse response = client.execute(uploadFile);) 40 { 41 if(response != null && response.getEntity() != null){ 42 HttpEntity entity = response.getEntity(); 43 try ( 44 InputStream in = entity.getContent(); 45 BufferedReader reader = new BufferedReader(new InputStreamReader(in,"UTF-8"))) 46 { 47 reader.lines().forEach(line->{ 48 stringBuilder.append(line); 49 }); 50 } 51 } 52 } catch (IOException e) { 53 LOGGER.debug(e.getMessage()); 54 return ""; 55 } 56 57 58 return stringBuilder.toString(); 59 } 60 61 /** 62 * 不需证书上传xml形式接口 63 * @param url 接口地址 64 * @param reqDataTo 请求参数 65 * @return 66 */ 67 public static String WXHttpPostXMLWithoutCert(String url,SortedMap<String, String> reqDataTo){ 68 HttpPost httpPost = new HttpPost(url); 69 StringEntity postEntity = new StringEntity(WXPayXmlUtil.parseMapToXML(reqDataTo), UTFCHARSET); 70 httpPost.setHeader("Content-Type", XMLCONTENT); 71 httpPost.setEntity(postEntity); 72 StringBuilder stringBuilder = new StringBuilder(); 73 try(CloseableHttpClient client = HttpClients.createDefault(); 74 CloseableHttpResponse response = client.execute(httpPost);) 75 {if(response != null && response.getEntity() != null){ 76 HttpEntity entity = response.getEntity(); 77 try ( 78 InputStream in = entity.getContent(); 79 BufferedReader reader = new BufferedReader(new InputStreamReader(in,"UTF-8"))) 80 { 81 reader.lines().forEach(line->{ 82 stringBuilder.append(line); 83 });; 84 } 85 } 86 } catch (IOException e) { 87 LOGGER.debug(e.getMessage()); 88 return ""; 89 } 90 return stringBuilder.toString(); 91 } 92 93 /** 94 * 需证书上传xml形式接口 95 * @param url 接口地址 96 * @param reqData 参数 97 * @param certPath 证书文件位置 98 * @return 99 */ 100 public static String WXHttpPostXMLWithCert(String url,SortedMap<String, String> reqData,String certPath){ 101 HttpPost queryState = new HttpPost(url); 102 SSLConnectionSocketFactory sslsf = null; 103 //读取本机存放的PKCS12证书文件 104 ClassLoader classLoader = ConfigTool.class.getClassLoader(); 105 URL resource = classLoader.getResource(certPath); 106 try(FileInputStream pkcfile = new FileInputStream(new File(resource.getPath())); 107 ){ 108 //指定读取证书格式为PKCS12 109 KeyStore keyStore = KeyStore.getInstance("PKCS12"); 110 //"100000"为密码即商户号 111 keyStore.load(pkcfile, reqData.get("mch_id").toCharArray()); 112 SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial( 113 keyStore, reqData.get("mch_id").toCharArray()).build(); 114 sslsf = new SSLConnectionSocketFactory(sslcontext, 115 new String[]{"TLSv1"}, 116 null, 117 new DefaultHostnameVerifier()); 118 } catch (Exception e) { 119 LOGGER.debug(e.getMessage()); 120 return ""; 121 } 122 StringBuilder stringBuilder = new StringBuilder(); 123 StringEntity postEntity = new StringEntity(WXPayXmlUtil.parseMapToXML(reqData), "UTF-8"); 124 queryState.setEntity(postEntity); 125 queryState.setHeader("Content-Type", XMLCONTENT); 126 try(CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslsf).build(); 127 CloseableHttpResponse response = client.execute(queryState);) 128 { 129 if(response != null && response.getEntity() != null){ 130 HttpEntity entity = response.getEntity(); 131 try ( 132 InputStream in = entity.getContent(); 133 BufferedReader reader = new BufferedReader( 134 new InputStreamReader(in,"UTF-8"))) 135 { 136 reader.lines().forEach(line->{ 137 stringBuilder.append(line); 138 }); 139 } 140 } 141 } catch (IOException e) { 142 LOGGER.debug(e.getMessage()); 143 return ""; 144 } 145 return stringBuilder.toString(); 146 } 147 148 }
1 public class WXPayUtil { 2 private static final String ALGORITHM = "AES/GCM/NoPadding"; 3 private static final int TAG_LENGTH_BIT = 128; 4 private static final String AES_KEY = "9bbbd2223e5240bb2252d05222210c27"; // APIv3密钥 5 6 /** 7 * 8 * @param aad 9 * @param iv 10 * @param cipherText 11 * @return 12 * @throws Exception 13 */ 14 private static String aesgcmDecrypt(String aad, String iv, String cipherText){ 15 try { 16 final Cipher cipher = Cipher.getInstance(ALGORITHM, "SunJCE"); 17 SecretKeySpec key = new SecretKeySpec(AES_KEY.getBytes(), "AES"); 18 GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, iv.getBytes()); 19 cipher.init(Cipher.DECRYPT_MODE, key, spec); 20 cipher.updateAAD(aad.getBytes()); 21 return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText))); 22 } catch (Exception e) { 23 return "fail"; 24 } 25 } 26 public static String getWXCert(JSONArray data) { 27 //敏感信息证书生成所需参数 28 String encryptCertificate = JSONObject.parseObject( 29 JSONObject.toJSONString(data.get(0))).getString("encrypt_certificate"); 30 String nonce = JSONObject.parseObject( 31 encryptCertificate).getString("nonce"); 32 String associatedData = JSONObject.parseObject( 33 encryptCertificate).getString("associated_data"); 34 //要被解密的证书字符串 35 String cipherText = JSONObject.parseObject( 36 encryptCertificate).getString("ciphertext"); 37 String wechatpayCert = ""; 38 try { 39 wechatpayCert = aesgcmDecrypt(associatedData, nonce, cipherText); 40 } catch (Exception e) { 41 return wechatpayCert; 42 } 43 return wechatpayCert; 44 } 45 /** 46 * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。 47 * 48 * @param data 待签名数据 49 * @param key API密钥 50 * @param signType 签名方式 51 * @return 签名 52 */ 53 public static String generateSignature(final Map<String, String> data, String key, String signType) { 54 Set<String> keySet = data.keySet(); 55 String[] keyArray = keySet.toArray(new String[keySet.size()]); 56 Arrays.sort(keyArray); 57 StringBuilder sb = new StringBuilder(); 58 for (String k : keyArray) { 59 if (k.equals("sign")) { 60 continue; 61 } 62 if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名 63 sb.append(k).append("=").append(data.get(k).trim()).append("&"); 64 } 65 sb.append("key=").append(key); 66 if ("MD5".equals(signType)) { 67 return MD5.getMD5(sb.toString()).toUpperCase(); 68 } 69 else if ("HMACSHA256".equals(signType)) { 70 return HMACSHAUtil.getHMACSHA256(sb.toString(), key); 71 } 72 return signType; 73 } 74 //获取指定位数的随机字符串(包含小写字母、大写字母、数字,0<length) 75 public static String getRandomString(int length) { 76 //随机字符串的随机字符库 77 String KeyString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 78 StringBuffer sb = new StringBuffer(); int len = KeyString.length(); 79 for (int i = 0; i < length; i++) { 80 sb.append(KeyString.charAt((int) Math.round(Math.random() * (len - 1)))); 81 } 82 return sb.toString(); 83 }
1 public final class WXPayXmlUtil { 2 public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { 3 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); 4 documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 5 documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); 6 documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); 7 documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); 8 documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 9 documentBuilderFactory.setXIncludeAware(false); 10 documentBuilderFactory.setExpandEntityReferences(false); 11 12 return documentBuilderFactory.newDocumentBuilder(); 13 } 14 15 public static Document newDocument() throws ParserConfigurationException { 16 return newDocumentBuilder().newDocument(); 17 } 18 19 /** 20 * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 21 * 22 * @param strXML 23 * @return 24 */ 25 public static Map parseXMLToMap(String strXML) { 26 Map<String, String> data = new HashMap<String, String>(); 27 try { 28 DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder(); 29 InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8")); 30 Document doc = documentBuilder.parse(stream); 31 doc.getDocumentElement().normalize(); 32 NodeList nodeList = doc.getDocumentElement().getChildNodes(); 33 for (int idx = 0; idx < nodeList.getLength(); ++idx) { 34 Node node = nodeList.item(idx); 35 if (node.getNodeType() == Node.ELEMENT_NODE) { 36 org.w3c.dom.Element element = (org.w3c.dom.Element) node; 37 data.put(element.getNodeName(), element.getTextContent()); 38 } 39 } 40 try { 41 stream.close(); 42 } catch (Exception ex) { 43 return data; 44 } 45 } catch (Exception ex) { 46 return data; 47 } 48 return data; 49 } 50 51 /** 52 * map转xml 53 * @param data 54 * @return 55 */ 56 public static String parseMapToXML(SortedMap<String, String> data) { 57 // StringBuffer sb = new StringBuffer(); 58 // sb.append("<xml>"); 59 // Set es = parameters.entrySet(); 60 // Iterator it = es.iterator(); 61 // while (it.hasNext()) { 62 // Map.Entry entry = (Map.Entry)it.next(); 63 // String k = (String)entry.getKey(); 64 // String v = (String)entry.getValue(); 65 // if (null != v && !"".equals(v)) { 66 // sb.append("<" + k + ">" + parameters.get(k) + "</" + k + ">\n"); 67 // } 68 // } 69 // sb.append("</xml>"); 70 // return sb.toString(); 71 // } 72 Document document = null; 73 try { 74 document = WXPayXmlUtil.newDocument(); 75 } catch (ParserConfigurationException e) { 76 e.printStackTrace(); 77 } 78 org.w3c.dom.Element root = document.createElement("xml"); 79 document.appendChild(root); 80 for (String key: data.keySet()) { 81 String value = data.get(key); 82 if (value == null) { 83 value = ""; 84 } 85 value = value.trim(); 86 if(!("").equals(value)){ 87 org.w3c.dom.Element filed = document.createElement(key); 88 filed.appendChild(document.createTextNode(value)); 89 root.appendChild(filed); 90 } 91 92 } 93 TransformerFactory tf = TransformerFactory.newInstance(); 94 Transformer transformer = null; 95 StringWriter writer = new StringWriter(); 96 String output = ""; 97 try { 98 transformer = tf.newTransformer(); 99 DOMSource source = new DOMSource(document); 100 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); 101 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); 102 StreamResult result = new StreamResult(writer); 103 transformer.transform(source, result); 104 output = writer.getBuffer().toString(); //.replaceAll("\n|\r", ""); 105 } catch (Exception e) { 106 return output; 107 }finally { 108 try { 109 writer.close(); 110 } catch (IOException e) { 111 return output; 112 } 113 } 114 return output; 115 } 116 }
1 public class RSAEncryptUtil { 2 private static final String CIPHER_PROVIDER = "SunJCE"; 3 private static final String TRANSFORMATION_PKCS1Paddiing = "RSA/ECB/PKCS1Padding"; 4 private static final String CHAR_ENCODING = "UTF-8";//固定值,无须修改 5 //数据加密方法 6 private static byte[] encryptPkcs1padding(PublicKey publicKey, byte[] data) throws Exception { 7 Cipher ci = Cipher.getInstance(TRANSFORMATION_PKCS1Paddiing, CIPHER_PROVIDER); 8 ci.init(Cipher.ENCRYPT_MODE, publicKey); 9 return ci.doFinal(data); 10 } 11 //加密后的秘文,使用base64编码方法 12 private static String encodeBase64(byte[] bytes) throws Exception { 13 return Base64.getEncoder().encodeToString(bytes); 14 } 15 //对敏感内容(入参Content)加密,其中PUBLIC_KEY_FILENAME为存放平台证书的路径,平台证书文件存放明文平台证书内容,且为pem格式的平台证书(平台证书的获取方式参照平台证书及序列号获取接口,通过此接口得到的参数certificates包含了加密的平台证书内容ciphertext,然后根据接口文档中平台证书解密指引,最终得到明文平台证书内容) 16 public static String rsaEncrypt(String Content,String cert){ 17 final byte[] PublicKeyBytes = cert.getBytes(); 18 X509Certificate certificate = null; 19 try { 20 certificate = X509Certificate.getInstance(PublicKeyBytes); 21 } catch (CertificateException e) { 22 return ""; 23 } 24 PublicKey publicKey = certificate.getPublicKey(); 25 try { 26 return encodeBase64(encryptPkcs1padding(publicKey, Content.getBytes(CHAR_ENCODING))); 27 } catch (Exception e) { 28 return ""; 29 } 30 } 31 }
1 public class MD5 { 2 3 /** 4 * 签名字符串 5 * 6 * @param text 7 * 需要签名的字符串 8 * @param key 9 * 密钥 10 * @param input_charset 11 * 编码格式 12 * @return 签名结果 13 */ 14 public static String sign(String text, String key, String charset) throws Exception { 15 text = text + key; 16 return DigestUtils.md5Hex(getContentBytes(text, charset)); 17 } 18 19 /** 20 * 签名字符串 21 * 22 * @param text 23 * 需要签名的字符串 24 * @param sign 25 * 签名结果 26 * @param key 27 * 密钥 28 * @param input_charset 29 * 编码格式 30 * @return 签名结果 31 */ 32 public static boolean verify(String text, String sign, String key, String charset) throws Exception { 33 text = text + key; 34 String mysign = DigestUtils.md5Hex(getContentBytes(text, charset)); 35 if (mysign.equals(sign)) { 36 return true; 37 } else { 38 return false; 39 } 40 } 41 42 /** 43 * @param content 44 * @param charset 45 * @return 46 * @throws SignatureException 47 * @throws UnsupportedEncodingException 48 */ 49 private static byte[] getContentBytes(String content, String charset) { 50 if (charset == null || "".equals(charset)) { 51 return content.getBytes(); 52 } 53 try { 54 return content.getBytes(charset); 55 } catch (UnsupportedEncodingException e) { 56 throw new RuntimeException("签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); 57 } 58 } 59 /** 60 * 对文件进行MD5获取其Hash值 61 * 62 * @param fis 63 * @return 64 */ 65 public static String md5HashCode(InputStream fis) { 66 try { 67 MessageDigest MD5 = MessageDigest.getInstance("MD5"); 68 byte[] buffer = new byte[8192]; 69 int length; 70 while ((length = fis.read(buffer)) != -1) { 71 MD5.update(buffer, 0, length); 72 } 73 return new String(Hex.encodeHex(MD5.digest())); 74 } catch (Exception e) { 75 return ""; 76 } 77 } 78 79 public static String md5HashCode(byte[] fis) { 80 try { 81 MessageDigest MD5 = MessageDigest.getInstance("MD5"); 82 MD5.update(fis, 0, fis.length); 83 return new String(Hex.encodeHex(MD5.digest())); 84 } catch (Exception e) { 85 return ""; 86 } 87 } 88 89 /** 90 * 生成 MD5 91 * 92 * @param data 待处理数据 93 * @return MD5结果 94 */ 95 public static String getMD5(String data) { 96 MessageDigest md; 97 byte[] array = new byte[0]; 98 try { 99 md = MessageDigest.getInstance("MD5"); 100 array = md.digest(data.getBytes("UTF-8")); 101 } catch (NoSuchAlgorithmException e) { 102 return ""; 103 } catch (UnsupportedEncodingException e) { 104 return ""; 105 } 106 StringBuilder sb = new StringBuilder(); 107 for (byte item : array) { 108 sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); 109 } 110 return sb.toString().toUpperCase(); 111 } 112 }
1 public class AnnotationUtil { 2 /** 3 * object转map 4 * @param 5 * @return 6 */ 7 public static SortedMap<String, String> parseObjectToMap(Object obj){ 8 ArrayList<Field> fileds = new ArrayList<>(); 9 Class<?> objClass = obj.getClass(); 10 fileds.addAll(Arrays.asList(objClass.getDeclaredFields())); 11 if(objClass.getSuperclass()!=null){ 12 fileds.addAll(Arrays.asList(objClass.getSuperclass().getDeclaredFields())); 13 } 14 SortedMap<String,String> map = new TreeMap<>(); 15 fileds.forEach(filed->{ 16 filed.setAccessible(true); 17 Annotation annotations = filed.getAnnotation(WXPayParam.class); 18 String k = ((WXPayParam) annotations).value(); 19 try { 20 String v = String.valueOf(filed.get(obj)); 21 if (null != v && !"".equals(v) && !"null".equals(v)) { 22 map.put(k,v); 23 } 24 } catch (IllegalAccessException e) { 25 return; 26 } 27 }); 28 if(WXConstant.SIGNTTYPE.equals(map.get("sign_type"))){ 29 map.put("sign",WXPayUtil.generateSignature(map,WXConstant.KEY,WXConstant.HMACSHA256TYPE)); 30 }else{ 31 map.put("sign",WXPayUtil.generateSignature(map,WXConstant.KEY,WXConstant.MD5TYPE)); 32 } 33 return map; 34 } 35 }
1 public class HMACSHAUtil { 2 /** 3 * 生成 HMACSHA256 4 * @param data 待处理数据 5 * @param key 密钥 6 * @return 加密结果 7 * @throws Exception 8 */ 9 public static String getHMACSHA256(String data, String key){ 10 Mac sha256_HMAC; 11 byte[] array = null ; 12 try { 13 sha256_HMAC = Mac.getInstance("HmacSHA256"); 14 SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"); 15 sha256_HMAC.init(secret_key); 16 array = sha256_HMAC.doFinal(data.getBytes("UTF-8")); 17 } catch (Exception e) { 18 return ""; 19 } 20 StringBuilder sb = new StringBuilder(); 21 for (byte item : array) { 22 sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); 23 } 24 return sb.toString().toUpperCase(); 25 } 26 }
六、接口学习总结
微信接口可以说是相当标准的API文档,说明也比较详细但是对参数的说明不是很准确,导致自己在连接接口的时候看不懂什么意思,一顿询问相关人员参数的含义,希望自己连接微信接口后对于其他的接口也会轻车熟路,一定要细心每一个参数的配置、顺序就没多大问题
ps:关注一下本人公众号,每周都有新更新哦!
有问题的话,博客回复太慢,可以加一下本人微信:请备注问题!谢谢