一. 调用微信支付api-开发文档
1. Native支付-> JAVA SDK/DEMO-> 申请微信支付(公众号/提交资料/开户成功/在线签署/)-> 获取appid唯一标识 / mch_id商户号 / key商户秘钥
2. 调用思路: 组装API参数-> XML方式发送POST请求-> 到微信URL接口-> 微信以XML方式响应-> 畅购根据结果(支付URL)生成qrcode-> 判断订单状态
3. 统一下单: 微信SDK: com.github.wxpay.sdk.WXPay类
(1)微信SDK依赖: wxpay-sdk (2)com.github.wxpay.sdk包名不可变 public class MyConfig extends WXPayConfig { String getAppID() { return "wx8397f....."; } String getMchID() { return "1473....."; } String getKey() { return "T6m9iK73b0k.........."; } InputStream getCertStream() { return null; } IWXPayDomain getWXPayDomain() { return new IWXPayDomain() { public void report(String domain, long elapsedTimeMillis, Exception ex) { } public DomainInfo getDomain(WXPayConfig config) { return new DomainInfo("api.mch.weixin.qq.com",true); } }; } } (3)测试: public class PayTest { public static void main(String[] args) throws Exception { MyConfig myConfig = new MyConfig(); WXPay wxPay = new WXPay(myConfig); Map<String, String> map = new HashMap<>(); //需要传递的数据 map.put("body","xx"); map.put("out_trade_no", "4567542...."); map.put("total_fee","1"); map.put("spbill_create_ip","127.0.0.1"); map.put("notify_url","http://www.baidu.com"); map.put("trade_type","NATIVE"); Map<String, String> result = wxPay.unifiedOrder(map); System.out.println(result); }
4. 二维码显示qrcode:
(1) 插件QRcode.js: 生成二维码的JavaScript库
二. 统一下单生成微信支付二维码
1. 购物支付整体流程: 加入购物车-> 添加购物车addCart-> 点击结算-> 点击提交订单-> 选择支付方式-> 微信支付-> 微信支付二维码-> 支付成功页面
2. 订单结算页面-> 跳转到订单提交成功页面pay.html
(1)问题: 如何获取订单Id和订单金额? 在changgou_service_order中, user/controller/OrderController.java @Autowired private TokenDecode tokenDecode; /*** * 新增数据 * @param order * @return */ @PostMapping public Result<String> add(@RequestBody Order order){ //获取登录人名称 String username = tokenDecode.getUserInfo().get("username"); order.setUsername(username); String orderId = orderService.add(order); //把订单Id返回到支付方式pay.html页面 return new Result(true,StatusCode.OK,"添加成功", orderId); }
3. 订单结算页面pay.html -> 获取订单id和金额
changgou_web_order渲染微服务中, web/order/controler/OrderController.java @GetMapping("/toPayPage") public String toPayPage(String orderId, Model model) { //获取订单相关信息 Order order = orderFeign.findById(orderId).getData(); model.addAttribute("orderId", orderId); model.addAttribute("payMoney", order.getPayMoney()); return "pay"; }
4. 支付微服务 changgou_service_pay
(1) 依赖: wxpay-sdk / spring-boot-starter / spring-boot-starter-amqp (2) yml配置文件 server: port: 9010 spring: application: name: pay rabbitmq: host: 192.168.200.128 main: allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册 eureka: client: service-url: defaultZone: http://127.0.0.1:6868/eureka instance: prefer-ip-address: true wxpay: notify_url: http://www.laibiao.cross.echosite.cn/wxpay/notify #回调地址 (3) com.github.wxpay.sdk.WXPay类 public class MyConfig extends WXPayConfig { String getAppID() { return "wx8397f8696b538317"; } String getMchID() { return "1473426802"; } String getKey() { return "T6m9iK73b0kn9g5v426MKfHQH7X8rKwb"; } InputStream getCertStream() { return null; } IWXPayDomain getWXPayDomain() { return new IWXPayDomain() { public void report(String domain, long elapsedTimeMillis, Exception ex) { } public DomainInfo getDomain(WXPayConfig config) { return new DomainInfo("api.mch.weixin.qq.com",true); } }; } } (4) com.changgou.pay.service.impl.WxpayServiceImpl.java @Autowired private WXPay wxPay; @Value("${wxpay.notify_url}") private String notify_url; //统一下单接口调用 @Override public Map nativePay(String orderId, Integer money) { try { //1. 封装请求参数 Map<String, String> map = new HashMap<>(); map.put("body","畅购"); map.put("out_trade_no",orderId); BigDecimal payMoney = new BigDecimal("0.01"); BigDecimal fen = payMoney.multiply(new BigDecimal("100")); //值为1.00 fen.setScale(0, BigDecimal.ROUND_UP); //值为1 map.put("total_fee", String.valueOf(fen)); map.put("spbill_create_ip", "127.0.0.1"); map.put("notify_url",notify_url); map.put("trade_type", "NATIVE"); //2. 基于WXPay完成统一下单接口调用, 并获取返回结果 Map<String, String> result = wxPay.unifiedOrder(map); return result; } catch (Exception e) { e.printStackTrace(); return null; } } (5) com.changgou.pay.controller.WxpayController.java //下单 @GetMapping("/nativePay") public Result nativePay(@RequestParam("orderId")String orderId, @RequestParam("money")Integer money) { Map resultMap = wxPayService.nativePay(orderId, money); return new Result(true, StatusCode.OK, "", resultMap); }
5. 对接渲染微服务
(1)changgou_service_pay_api: 对外暴露feign远程调用接口 @FeignClient(name = "pay") public interface PayFeign { //下单 @GetMapping("/wxpay/nativePay") public Result nativePay(@RequestParam("orderId")String orderId, @RequestParam("money")Integer money); //基于微信查询订单 @GetMapping("/wxpay/query/{orderId}") public Result queryOrder(@PathVariable("orderId")String orderId); //基于微信关闭订单 @PutMapping("/wxpay/close/{orderId}") public Result closeOrder(@PathVariable("orderId")String orderId); } (2)changgou_web_order服务: web/order/controller/PayController.java @Controller @RequestMapping("/wxpay") public class PayController { @Autowired private OrderFeign orderFeign; @Autowired private PayFeign payFeign; //跳转到微信支付二维码 @GetMapping public String wxPay(String orderId, Model model) { //1. 根据orderId查询订单, 如果订单不存在, 跳转到错误页面 Result<Order> orderResult = orderFeign.findById(orderId); if (orderResult.getData()==null){ return "fail"; } //2. 判断订单支付转态, 未支付则跳转错误页面 Order order = orderResult.getData(); //不是未支付, 则跳转错误页 if (!"0".equals(order.getPayStatus())){ return "fail"; } //3. 基于payFeign调用统计下单接口, 并获取返回结果 Result payResult = payFeign.nativePay(orderId, order.getPayMoney()); if (payResult.getData()==null){ return "fail"; } //4. 封装结果数据 Map payMap = (Map) payResult.getData(); payMap.put("orderId", orderId); payMap.put("payMoney", order.getPayMoney()); model.addAllAttributes(payMap); return "wxpay"; } //支付成功页面的跳转 @RequestMapping("/toPaySuccess") public String toPaySuccess(Integer payMoney, Model model) { model.addAttribute("payMoney",payMoney); return "paysuccess"; } }
三. 支付回调 EcoSite(内网映射工具)
1. 用户支付完成后-> 微信发送响应-> 商家接收消息-> 修改订单支付状态
2. 配置Echosite-> config.yml文件-> 启动命令: echosite -config=config.yml start-all
3. 域名映射到本地ip的支付服务的端口9010
http://laibiao.cross.echosite.cn->127.0.0.1:9010
4. 查询订单验证通知: 微信查询订单-> 发送MQ-> 订单服务接收-> 修改订单状态
changgou_service_pay: pay.controller.WxpayController.java
//测试ecosite内网穿透 -> 支付回调 @RequestMapping("/notify") public void notifyLogic(HttpServletRequest request, HttpServletResponse response) { System.out.println("支付成功回调"); try { //输入流转换为字符串 String xml = ConvertUtils.convertToString(request.getInputStream()); System.out.println(xml); //基于微信发送的通知类容, 完成后续的业务逻辑处理 Map<String, String> map = WXPayUtil.xmlToMap(xml); if ("SUCCESS".equals(map.get("result_code"))){ //查询订单 Map result = wxPayService.queryOrder(map.get("out_trade_no")); System.out.println("查询订单结果" + result); if ("SUCCESS".equals(result.get("result_code"))){ //将订单的消息发送到mq Map message = new HashMap(); message.put("orderId", result.get("out_trad_no")); message.put("transactionId", result.get("transaction_id")); //消息发送 rabbitTemplate.convertAndSend("", RabbitMQConfig.ORDER_PAY, JSON.toJSONString(message)); //完成双向通信 rabbitTemplate.convertAndSend("paynotify", "", result.get("out_trade_no")); } }else { //输出错误原因 System.out.println("err_code_des"); } //给微信支付一个成功的响应 response.setContentType("text/xml"); String data = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; response.getWriter().write(data); } catch(Exception e) { e.printStackTrace(); } }
changgou_service_pay: pay.service.impl.WxpayServiceImpl.java
//基于微信查询订单 @Override public Map queryOrder(String orderId) { try { Map<String, String> map = new HashMap(); map.put("out_trade_no", orderId); Map<String, String> resultMap = wxPay.orderQuery(map); return resultMap; } catch(Exception e) { e.printStackTrace(); return null; } }
5. 对接订单服务: 修改订单状态, 记录订单日志
(1) order/service/impl/OrderServiceImpl.java //修改订单的支付状态, 并记录日志 @Override @Transactional public void updatePayStatus(String orderId, String transactionId) { //1.查询订单 Order order = orderMapper.selectByPrimaryKey(orderId); if (order !=null && "0".equals(order.getPayStatus())){ //2. 修改订单状态 order.setPayStatus("1"); order.setOrderStatus("1"); order.setUpdateTime(new Date()); order.setPayTime(new Date()); order.setTransactionId(transactionId); //微信返回的交易流水号 orderMapper.updateByPrimaryKey(order); //3. 记录订单日志 OrderLog orderLog = new OrderLog(); orderLog.setId(idWorker.nextId()+""); orderLog.setOperater("system"); orderLog.setOperateTime(new Date()); orderLog.setOrderStatus("1"); orderLog.setPayStatus("1"); orderLog.setRemarks("交易流水号"+transactionId); orderLog.setOrderId(orderId); orderLogMapper.insert(orderLog); } } (2) order/listener/OrderPayListener.java @Component public class OrderPayListener { @Autowired private OrderService orderService; @RabbitListener(queues = RabbitMQConfig.ORDER_PAY) public void receivePayMessage(String message) { System.out.println("接收到了订单支付的消息" + message); Map map = JSON.parseObject(message, Map.class); //调用业务层, 完成订单数据的修改 orderService.updatePayStatus((String)map.get("orderId"),(String)map.get("transactionId")); } }
四. 支付结果通知推送-> paySuccess.html
推送方案: (1) AJAX短轮询: 通过页面端JS定时异步刷新任务实现数据加载. 前端每隔2秒查询-> 后端提供方法(微信支付接口查询支付状态)-> 后端返回支付成功-> 跳转paySuccess.html 缺点: 服务端压力大, 实时效果差 (2) AJAX长轮询: 服务器端在没有数据时-> 请求阻塞-> 产生新数据/请求超时返回-> 客户端重新获取连接; 后端循环查询订单号的支付状态 缺点: 占用服务器资源 (3) WebSocket双向通信: 浏览器和服务器的全双工通信 1) RabbitMQ Web STOMP插件: STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议。前身是TTMP协议(一个简单的基于文本的协议),专为消息中间件设计 2) rabbitmq-plugins enable rabbitmq_web_stomp rabbitmq_web_stomp_examples
2. 完成双向通信:
(1) com.changgou.pay.controller //消息发送 rabbitTemplate.convertAndSend("", RabbitMQConfig.ORDER_PAY, JSON.toJSONString(message)); //完成双向通信 rabbitTemplate.convertAndSend("paynotify", "", result.get("out_trade_no")); (2) com.changgou.web.order.controller //支付成功页面的跳转 @RequestMapping("/toPaySuccess") public String toPaySuccess(Integer payMoney, Model model) { model.addAttribute("payMoney",payMoney); return "paysuccess"; }