开发前的准备:(必须)
1.小程序标识(appid) 2.商户号(mch_id)3.商户密钥(key)
4.API证书(apiclient_cert.p12)这个证书根据自己需求添加
API证书(我这里用到所以要加,自己不需要的就不要加吧):可以去微信商户平台下载,自己百度
我们使用官网的SDK进行开发: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
我们将官网的SDK用IDEA打开看看
可以看到目前微信官网最新的SDK是v3.0.9版本
点击README.md可以看到官方给的Demo
直接将SDK的java类复制到项目中,注意也要讲pom文件中的依赖也要添加到自己的项目中
我的API证书放在了这里
第一步:配置MyWXConfig类 继承WXPayConfig 抽象类
/** * @author: zhuhualian * @date: 2019-11-15 10:46 * @description: */ public class MyWXConfig extends WXPayConfig { private byte[] certData; public MyWXConfig() throws Exception { String path = "cert/apiclient_cert.p12"; File file = new File(this.getClass().getClassLoader().getResource(path).getFile()); //File file = new File("/data/project/cert/apiclient_cert.p12");//这个是你API证书放在服务器上的位置(上传服务器时记得换这里) InputStream certStream = new FileInputStream(file); this.certData = new byte[(int) file.length()]; certStream.read(this.certData); certStream.close(); } @Override public String getAppID() { return "xxxxx";//你自己的小程序AppID } @Override public String getMchID() { return "xxxxx";//你自己的微信商户ID } @Override public String getKey() { return "xxxxx";//你自己的微信商户密钥 } @Override public InputStream getCertStream() { ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData); return certBis; } @Override public int getHttpConnectTimeoutMs() { return 8000; } @Override public int getHttpReadTimeoutMs() { return 10000; } @Override IWXPayDomain getWXPayDomain() { IWXPayDomain iwxPayDomain=new IWXPayDomain() { @Override public void report(String domain, long elapsedTimeMillis, Exception ex) { } @Override public DomainInfo getDomain(WXPayConfig config) { return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API,true);//微信工具常量类有 "api.mch.weixin.qq.com"; wxpay.unifiedorder() /pay/unifiedorder } }; return iwxPayDomain; } }
第二步:找到 SDK 中的 WxPay 类 修改里面的代码
public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
this.config = config;
this.notifyUrl = notifyUrl;
this.autoReport = autoReport;
this.useSandbox = useSandbox;
if (useSandbox) {
this.signType = SignType.MD5; // 沙箱环境
}
else {
//this.signType = SignType.HMACSHA256;
this.signType = SignType.MD5; //注意:这点是个坑! 默认是HMACSHAS56加密 一定要修改成MD5 不然无论如何都会报 “微信签名失败” 的错误!
} this.wxPayRequest = new WXPayRequest(config); }
第三步:编写统一下单和返回给小程序需要的参数即可
openid:就是微信用户登录你的小程序授权你获得的openid
outtradeno:订单号
IpUtils:获取IP的工具类,后面会将代码贴出来
交易类型trade_type:
NATIVE -- 原生支付
JSAPI -- 公众号支付 - 小程序支付
MWEB -- H5支付
APP -- app支付
public Object pay2(String openid,String outtradeno, HttpServletRequest request) throws Exception { //统一下单支付 HashMap<String, String> map = new HashMap<>(); Map<String, String> data = new HashMap<>(); data.put("body", "微信支付"); //商品描述 data.put("total_fee", "1"); // 标价金额 单位:分 data.put("openid", openid); //用户标识 trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识 data.put("out_trade_no", outtradeno + ""); //商户系统内部订单号 data.put("nonce_str", WXPayUtil.generateNonceStr()); //随机字符串,长度要求在32位以内。推荐随机数生成算法 data.put("spbill_create_ip",IpUtils.getIpAddr(request)); //支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP 自定获取ip data.put("notify_url", "120.25.214.5:8082/wx/notify"); // 没用到.通知地址:通知url必须为外网可访问的url,不能携带参数。 data.put("trade_type", "JSAPI");//交易类型 data.put("sign_type", WXPayConstants.MD5); //签名类型//MyWxPayConfig 配置了一些默认信息 appid,商户号,商户秘钥,请求域名 .. MyWXConfig myWxPayConfig = new MyWXConfig(); WXPay wxpay = new WXPay(myWxPayConfig); Map<String, String> rMap = wxpay.unifiedOrder(data);//生成一次签名 sign System.out.println(rMap); // 下面只是为了生成第二次签名 仅此而已 String return_code = rMap.get("return_code");//返回状态码 String result_code = rMap.get("result_code");//结果状态码 String nonce_str = rMap.get("nonce_str"); //随即字符串 Long s = System.currentTimeMillis() / 1000;//获取时间戳除以千变字符串 String timeStamp = String.valueOf(s); if ("SUCCESS".equals(return_code) && return_code.equals(result_code)) { map.put("appId", myWxPayConfig.getAppID());//你的appid map.put("timeStamp", timeStamp);//这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误 map.put("nonceStr", nonce_str); map.put("package", "prepay_id=" + rMap.get("prepay_id")); map.put("signType", "MD5"); System.out.println("二次签名参数 : " + map);//需要生成二次签名 所用的参数 //再次签名sign,这个签名用于小程序端调用wx.requesetPayment方法 String sign = WXPayUtil.generateSignature(map, myWxPayConfig.getKey());//你的商户号key map.put("paySign", sign); // 生成签名 重要 System.out.println("生成的签名paySign : " + sign); return map; //将map响应给前端 微信支付接口需要的参数 } return new Result<>("0",Result.ERROR); }
IpUtils类
public class IpUtils { /* IpUtils工具类方法 * 获取真实的ip地址 * @param request * @return */ public static String getIpAddr(HttpServletRequest request) { String ip = request.getHeader("X-Forwarded-For"); if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){ //多次反向代理后会有多个ip值,第一个ip才是真实ip int index = ip.indexOf(","); if(index != -1){ return ip.substring(0,index); }else{ return ip; } } ip = request.getHeader("X-Real-IP"); if(StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)){ return ip; } return request.getRemoteAddr(); } }
容易遇到的错误 ! 容易遇到的错误 ! 容易遇到的错误 !
1 商户号key 不要与 appid 的secret 弄混淆了
2 SDK 工具类中 Wxpay 类中 this.signType = SignType.HMACSHA256; HMACSHA256 改成 MD5
3 第二次签名需要的五个参数一个不能少 appId,nonceStr,package,signType,timeStamp。 注意 都是以 驼峰命名 不然也会报错
OK,搞定了!!!!!!!!!!!!!!!!!!