low话不多说,直接上代码!
红色框框是核心jar包!
黑色框框是获取客户端的IP地址工具类!
紫色框框是微信支付的流程代码!
蓝色框框是订单实体类!
由于小黑我技术不咋地,所以以下代码仅供参考,copy过去后是跑不起来的,不过可以经过改动代码使其跑起来,反正思路是这样的!
(代码处如有发现错误的自行矫正修改,例如没有对事务控制,逻辑有误等!! 还有,大神看也,不喜勿喷)
此处代码就是用来调用微信支付SDK的,然后SDK会返回几个参数,这几个参数就是给APP端(ios/安卓)调起支付的参数!! (至于移动端哥们怎么调起就不归我们管了,只要我们的签名是有效的就没问题)
1 package pay.weixin; 2 3 import com.alibaba.fastjson.JSON; 4 5 import entity.Order; 6 7 /** 8 * 使用微信支付SDK 9 * 使用流程:调用微信SDK App支付统一下单方法 获取签名与参数 将其返回给app端 10 * @author 小黑 11 * */ 12 public class WechatPayDemo { 13 14 // 因为没有搭配Spring,所以就无法注入 15 // @Resouce 16 // private WechatPayService wechatPayService; 17 18 /** 当做是接收订单id的参数 */ 19 private Long orderId; 20 21 public static void main(String[] args) { 22 23 /** 无法注入那就使用原始的new的方式 */ 24 WechatPayService wechatPayService = new WechatPayService(); 25 26 /** 根据订单id查找订单对象(模拟效果,先new着先) */ 27 //Order order = findById(orderId); 28 29 Order order = new Order(); 30 31 /** 调用微信App统一下单 */ 32 Object payResponse = wechatPayService.orderAppPay(order); 33 if(payResponse != null) { 34 35 /** 将微信参数与签名转json数据 */ 36 String wechatParams = JSON.toJSONString(payResponse); 37 38 System.out.println("到这一步基本算success了" + wechatParams); 39 40 /** 最后返回签名与参数给app端即可 */ 41 //return wechatParams; 42 43 }else { 44 System.out.println("支付失败"); 45 } 46 } 47 48 public Long getOrderId() { 49 return orderId; 50 } 51 public void setOrderId(Long orderId) { 52 this.orderId = orderId; 53 } 54 55 }
此处代码就是微信支付的SDK了,通过配置APPID&商户号&密钥等参数信息!!通过参数信息和密钥来获取‘统一支付‘返回的结果对象,
然后获取结果对象的签名状态对其验签看是否有效!如有效就可以通过结果对象以及APPID&商户号&密钥来生成支付APP请求数据了!
1 package pay.weixin; 2 import java.math.BigDecimal; 3 import com.alibaba.fastjson.JSON; 4 import entity.Order; 5 import util.IpUtils; 6 import weixin.popular.api.PayMchAPI; 7 import weixin.popular.bean.paymch.MchPayApp; 8 import weixin.popular.bean.paymch.Unifiedorder; 9 import weixin.popular.bean.paymch.UnifiedorderResult; 10 import weixin.popular.util.PayUtil; 11 import weixin.popular.util.XMLConverUtil; 12 13 /** 14 * 应用场景:商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易回话标识后再在APP里面调起支付。 15 * 像商户号和appid这些需要自己去申请获取,不过一般找产品会拿给你的,密钥需要自己配置,具体看官方文档如何生成密钥! 16 * 17 * @author 小黑 18 * */ 19 20 //@Service 这里没有配置Sping 所以就注释了 若项目使用了Spring的话 记得要依赖注入进去 21 public class WechatPayService { 22 23 /************ 微信开放平台配置 ***************/ 24 25 /** APPID */ 26 private static final String OPEN_DELIVERAPP_APPID = ""; 27 28 /** 商户号 */ 29 private static final String OPEN_DELIVERAPP_MCH_ID = ""; 30 31 /** 密钥 */ 32 private static final String OPEN_USERAPP_APPSECRET = ""; 33 34 /** 用户订单支付结果异步通知url */ 35 private static final String OPEN_USER_ORDER_NOTIFY_ASYNC = "http://xxx/pay/weixin/WechatOrderNotify.java"; 36 37 /** 38 * @description:使用微信支付-APP支付方式-统一下单 39 * @param: 订单对象 40 * @return: 支付参数,如果支付失败则返回null 41 * @author: Fanhaojian 42 */ 43 public MchPayApp orderAppPay(Order order) { 44 45 /** 用于封装统一支付请求参数的类 */ 46 Unifiedorder unifiedorder = new Unifiedorder(); 47 48 /** APPID */ 49 unifiedorder.setAppid("appId"); 50 51 /** 商户号 */ 52 unifiedorder.setMch_id("商户号"); 53 54 /** 随机字符串(可用UUID工具类生成) */ 55 unifiedorder.setNonce_str("随机字符串"); 56 57 /** 商品描述(可以用订单号代替) */ 58 unifiedorder.setBody(order.getOrderCode()); 59 60 /** 商户订单号(可以用订单号代替) */ 61 unifiedorder.setOut_trade_no(order.getOrderCode()); 62 63 /** 订单总金额(切记:单位为分 例如:0.09元的金额要转成900分) */ 64 unifiedorder.setTotal_fee(order.getRealityPrice().multiply(new BigDecimal(100)).intValue() + ""); 65 66 /** 用户端请求IP地址 */ 67 unifiedorder.setSpbill_create_ip(IpUtils.getIpFromRequest()); 68 69 /** 异步通知回调地址 */ 70 unifiedorder.setNotify_url(OPEN_USER_ORDER_NOTIFY_ASYNC); 71 72 /** 交易类型 */ 73 unifiedorder.setTrade_type("APP"); 74 75 System.out.println("微信APP支付--(签名前):" + XMLConverUtil.convertToXML(unifiedorder)); 76 77 /** 调用统一支付请求返回的结果对象 */ 78 UnifiedorderResult unifiedorderResult = PayMchAPI.payUnifiedorder(unifiedorder, OPEN_USERAPP_APPSECRET); 79 80 System.out.println("微信APP支付--支付统一下单接口请求状态(return_code):" + unifiedorderResult.getReturn_code()); 81 System.out.println("微信APP支付--支付统一下单接口请求状态(return_msg):" + unifiedorderResult.getReturn_msg()); 82 System.out.println("微信APP支付--支付统一下单接口请求状态(result_code):" + unifiedorderResult.getResult_code()); 83 System.out.println("微信APP支付--支付请求参数封装(签名后):" + XMLConverUtil.convertToXML(unifiedorder)); 84 System.out.println("微信APP支付--支付统一下单接口返回数据:" + JSON.toJSONString(unifiedorderResult)); 85 86 // 下单结果验签; 87 if(unifiedorderResult.getSign_status() != null && unifiedorderResult.getSign_status()) { 88 System.out.println("微信APP支付验签成功"); 89 /** 生成支付APP请求数据(签名以及相关的数据,提供给移动端的) */ 90 return PayUtil.generateMchAppData(unifiedorderResult.getPrepay_id(), 91 OPEN_DELIVERAPP_APPID, 92 OPEN_DELIVERAPP_MCH_ID, 93 OPEN_USERAPP_APPSECRET); 94 } 95 return null; 96 } 97 }
此处代码就是APP端调起支付后然后输入密码支付完成后的一个异步回调通知,就是支付完成后微信那边会请求我们填的异步回调URL的接口,并携带相关参数过来,这样做是为了让我们去处理订单信息以及额外的业务!
(注意:异步回调URL实在上面的微信SDK那里配置的!)
1 package pay.weixin; 2 import java.nio.charset.Charset; 3 import java.util.Map; 4 import javax.servlet.http.HttpServletRequest; 5 import org.apache.struts2.ServletActionContext; 6 import entity.Order; 7 import weixin.popular.bean.paymch.MchBaseResult; 8 import weixin.popular.bean.paymch.MchPayNotify; 9 import weixin.popular.util.SignatureUtil; 10 import weixin.popular.util.StreamUtils; 11 import weixin.popular.util.XMLConverUtil; 12 13 /** 14 * @description:微信支付异步通知请求URL 15 * @author:小黑 16 */ 17 public class WechatOrderNotify { 18 19 /** 20 * 该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。 21 * 通知url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action” 22 * */ 23 24 /** 25 * 小黑提醒:这段文字得仔细看一遍,能帮助你更加理解! 26 * 27 * 应用场景: 28 * 支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。 29 * 对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知, 30 * 尽可能提高通知的成功率,但微信不保证通知最终能成功。 (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒) 31 * 32 * 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 33 * 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。 34 * 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。 35 * 36 * 特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。 37 * */ 38 39 /** 密钥 */ 40 private static final String OPEN_USERAPP_APPSECRET = ""; 41 42 /** 声明应答变量 */ 43 private String yingDaXml; 44 45 /** 46 * 微信支付异步通知请求URL方法 47 * @throws Exception 48 * */ 49 public String wechatOrderNotify() throws Exception { 50 // 解析微信支付异步通知请求参数; 51 HttpServletRequest request = ServletActionContext.getRequest(); 52 String xml = StreamUtils.copyToString(request.getInputStream(), Charset.forName("utf-8")); 53 Map<String, String> params = XMLConverUtil.convertToMap(xml); 54 MchPayNotify payNotify = XMLConverUtil.convertToObject(MchPayNotify.class, xml); 55 56 /** 打印日志信息 */ 57 System.out.println("微信支付用户订单支付结果异步通知请求参数(xml):" + params); 58 System.out.println("return_code:" + payNotify.getReturn_code()); 59 System.out.println("return_msg:" + params.get("return_msg")); 60 61 /** 校验支付成功还是失败 */ 62 if("SUCCESS".equals(payNotify.getReturn_code())) { 63 64 /** 获取微信后台返回来的异步通知参数 */ 65 String outTradeNo = payNotify.getOut_trade_no(); // 用户订单号; 66 String tradeNo = payNotify.getTransaction_id(); // 微信交易号; 67 String tradeStatus = payNotify.getResult_code(); // 微信支付状态; 68 Integer totalFee = payNotify.getTotal_fee(); // 支付金额 69 70 /** 验证签名 */ 71 boolean flag = SignatureUtil.validateSign(params, OPEN_USERAPP_APPSECRET); 72 73 /** 打印日志信息 */ 74 System.out.println("微信支付--APP支付方式支付用户订单异步通知订单号(out_trade_no):" + outTradeNo); 75 System.out.println("微信支付--APP支付方式支付用户订单异步通知交易号(trade_no):" + tradeNo); 76 System.out.println("微信支付--APP支付方式支付用户订单异步通知交易状态(result_code):" + tradeStatus); 77 System.out.println("微信支付--APP支付方式支付用户订单异步通知支付金额(total_fee):" + totalFee); 78 System.out.println("微信支付--APP支付方式支付用户订单异步通知验签状态:" + flag); 79 80 /** 通过订单号去数据库查找对应的订单 */ 81 /** 例如: */ 82 //String order = "select * from order_tbl o where o.order_code =" + outTradeNo; 83 84 /** 这里为了不让报错,就先new出来假装从数据库查询到的 */ 85 Order order = new Order(); 86 87 /** 获取订单的支付状态 false表示未支付 true表示已支付 */ 88 Boolean payStatus = order.getPayStatus(); 89 90 /** 这个if里面具体要做什么呢? 91 * 92 * 1.你要处理你的订单业务,比如: 93 * 订单的一些状态,像待付款变成待发货,未支付变成已支付, 94 * 除了修改订单字段外,有可能还需要统计支付订单数量展示在后台 95 * 当然还可能需要记录这笔订单所属的店主他的资产,就是将订单的实付金额加到店主的冻结金额钱包内 96 * ......就举这两三个例子吧!毕竟每个项目的业务不同,具体就看你需要怎么处理你在去编写业务代码了 97 * 2.处理完业务后你还需要给微信那边做应答,说白了就是微信那边来调用我们这边接口,我们总得return个success给他们吧!就是告诉微信您的回调我这边收到了!*/ 98 if(flag && "SUCCESS".equals(tradeStatus) && payStatus) { 99 100 /** 处理业务 */ 101 //哒哒哒... 102 //哒哒哒... 103 //哒哒哒... 104 /** 处理业务 */ 105 106 /** 封装通知信息,给微信做应答的 */ 107 MchBaseResult baseResult = new MchBaseResult(); 108 baseResult.setReturn_code("SUCCESS"); 109 baseResult.setReturn_msg("OK"); 110 yingDaXml = XMLConverUtil.convertToXML(baseResult); 111 112 }else if(flag && "SUCCESS".equals(tradeStatus)){ 113 /** 这里的应答是说明是支付是成功的,但是这里的应答不做任何业务处理,只是单纯的做应答而已 */ 114 MchBaseResult baseResult = new MchBaseResult(); 115 baseResult.setReturn_code("SUCCESS"); 116 baseResult.setReturn_msg("OK"); 117 yingDaXml = XMLConverUtil.convertToXML(baseResult); 118 }else { 119 /** 这里的应答是说明是支付失败了 */ 120 MchBaseResult baseResult = new MchBaseResult(); 121 baseResult.setReturn_code("FAIL"); 122 baseResult.setReturn_msg("FAIL"); 123 yingDaXml = XMLConverUtil.convertToXML(baseResult); 124 } 125 }else { 126 /** 这里的应答是说明是支付失败了 */ 127 MchBaseResult baseResult = new MchBaseResult(); 128 baseResult.setReturn_code("FAIL"); 129 baseResult.setReturn_msg("FAIL"); 130 yingDaXml = XMLConverUtil.convertToXML(baseResult); 131 } 132 133 return yingDaXml; 134 135 /** 看到最后你可能会对那里的if else if else 比较迷糊 136 * 是这样的,你可以在看一眼上面的‘小黑提醒‘那段话里的注意 137 * 就是‘同样的通知可能会多次发送给商户系统‘,这里也是我之前踩到的坑了! 138 * 微信在回调请求我们这边接口后,然后我们给微信那边返回OK的应答,但是微信他那边还是会再次请求过来的 139 * 对于这样多次的请求回调,我们在处理业务的时候难免会出错,所以我们就这样做: 140 * 在他第一次回调请求的时候,我们就通过请求的参数有个订单号然后通过订单号获取订单的支付状态,当然订单支付状态肯定是‘未支付‘ 141 * 然后介时候就来个if(除了判断‘验签状态‘ 与 ‘交易状态‘外,我们还需要判断订单的支付状态是否为‘未支付‘) 142 * 如果是‘未支付‘状态,那我们就可以去处理业务啦! 然后在处理业务时将订单的支付状态修改为‘已支付‘. 143 * 好了,接着给微信做应答后,过15秒后,微信那边发起第二次回调,然后也是通过订单号获取订单对象然后拿到订单支付状态, 144 * 介时候我们回到那个if判断,判断条件是‘未支付‘,但我们刚才在第一次回调的时候就已经将订单的支付状态改为‘已支付‘了! 145 * so,介时候就进入了else if 判断内了 , 所以else if 那个判断只是单纯的做了应答而已,不操作业务处理! 146 * */ 147 } 148 }
此处代码就是获取客户端IP地址的工具类!你们也可以自己在百度找一个自己喜欢的!
1 package util; 2 3 import java.util.regex.Pattern; 4 5 import javax.servlet.http.HttpServletRequest; 6 7 import org.apache.commons.lang.text.StrTokenizer; 8 import org.apache.struts2.ServletActionContext; 9 10 11 /** 12 * @author 小黑 13 * @tags 获取客户端IP地址工具类 14 */ 15 public class IpUtils { 16 public static final String HEADER = "x-forwarded-for"; 17 public static final String PARAM = "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"; 18 public static final Pattern PATTERN = Pattern.compile("^(?:" + PARAM + "\\\\.){3}" + PARAM + "$"); 19 20 public static String longToIpV4(long longIp) { 21 int octet3 = (int) ((longIp >> 24) % 256); 22 int octet2 = (int) ((longIp >> 16) % 256); 23 int octet1 = (int) ((longIp >> 8) % 256); 24 int octet0 = (int) ((longIp) % 256); 25 return octet3 + "." + octet2 + "." + octet1 + "." + octet0; 26 } 27 28 public static long ipV4ToLong(String ip) { 29 String[] octets = ip.split("\\\\."); 30 return (Long.parseLong(octets[0]) << 24) + (Integer.parseInt(octets[1]) << 16) 31 + (Integer.parseInt(octets[2]) << 8) + Integer.parseInt(octets[3]); 32 } 33 34 public static boolean isIPv4Private(String ip) { 35 long longIp = ipV4ToLong(ip); 36 return (longIp >= ipV4ToLong("10.0.0.0") && longIp <= ipV4ToLong("10.255.255.255")) 37 || (longIp >= ipV4ToLong("172.16.0.0") && longIp <= ipV4ToLong("172.31.255.255")) 38 || longIp >= ipV4ToLong("192.168.0.0") && longIp <= ipV4ToLong("192.168.255.255"); 39 } 40 41 public static boolean isIPv4Valid(String ip) { 42 return PATTERN.matcher(ip).matches(); 43 } 44 45 46 /** 47 * @description:获取客户端请求时的真实IP地址;方法二 48 * @return 客户端请求IP地址 49 */ 50 public static String getClientIp(HttpServletRequest request) { 51 String ip = request.getHeader("x-forwarded-for"); 52 if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 53 ip = request.getHeader("Proxy-Client-IP"); 54 } 55 if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 56 ip = request.getHeader("WL-Proxy-Client-IP"); 57 } 58 if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { 59 ip = request.getRemoteAddr(); 60 } 61 if( ip.indexOf(",")!= -1){ 62 ip = ip.split(",")[0]; 63 } 64 return ip; 65 } 66 67 /** 68 * @description:获取客户端请求时的真实IP地址;方法一 69 * @return 客户端请求IP地址 70 */ 71 public static String getIpFromRequest() { 72 HttpServletRequest request = ServletActionContext.getRequest(); 73 String ip; 74 boolean found = false; 75 if ((ip = request.getHeader(HEADER)) != null) { 76 StrTokenizer tokenizer = new StrTokenizer(ip, ","); 77 while (tokenizer.hasNext()) { 78 ip = tokenizer.nextToken().trim(); 79 if (isIPv4Valid(ip) && !isIPv4Private(ip)) { 80 found = true; 81 break; 82 } 83 } 84 } 85 if (!found) { 86 ip = request.getRemoteAddr(); 87 } 88 return ip; 89 } 90 }
关于jar包可以在githua下载,如果用maven那里也有对应的配置信息
如果我注释不够清晰,可以参考官方文档来看代码 地址:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
然后我们JAVA端用到的API就只是‘统一下单‘和‘支付结果通知‘这两个!
思路是这样的: 我们通过调起统一下单API接口来获取到一些参数数据,然后这些参数数据就是要给IOS/安卓他们使用的, 他们会拿到这些参数去调用‘调起支付接口‘的API,然后调起后支付完成之后微信后台就会给我们所填的异步通知回调URL的接口发送请求好让我们处理业务信息
由于微信开发者平台的微信APP支付没有对应的Java端Demo下载,所以我就趁工作之余来编写此随笔,帮助大家也是帮助自己!!!
OK!那到此结束吧! 如果有什么问题可以在评论处留言,如果我工作忙没时间回复可以加下此QQ群:575501906 进群说找小黑就行了!