乐优商城项目总结day(20)

点点圈 提交于 2019-11-30 16:12:20

乐优商城项目总结day(20)

微信支付

整个支付流程采用扫码支付的模式二:详细步骤

需要引入的依赖:

<dependency>
          <groupId>com.github.wxpay</groupId>
          <artifactId>wxpay-sdk</artifactId>
          <version>0.0.3</version>
 </dependency>
 <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.9.6</version>
  </dependency>

相关的配置:

public class PayConfig implements WXPayConfig {

    private String appID;  //公众账号ID

    private String mchID;  //商户号

    private String key;  //生成签名的密钥

    private int httpConnectTimeoutMs;   //连接超时时间

    private int httpReadTimeoutMs;  //读取超时时间

    private String notifyUrl;// 下单通知回调地址


    @Override
    public InputStream getCertStream() {
        return null;
    }
}
@Configuration
public class PayConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "leyou.pay")
    public PayConfig payConfig() {
        return new PayConfig();
    }

    @Bean
    public WXPay wxPay(PayConfig payConfig) {
        return new WXPay(payConfig, WXPayConstants.SignType.HMACSHA256);
    }
}

wxpay-sdk提供了WXPay用来生成预订单以及查询订单状态,需要传入WXPayConfig接口的实现以及签名的加密算法。

封装支付的工具类:

@Slf4j
@Component
public class PayHelper {

    @Autowired
    private PayConfig payConfig;

    @Autowired
    private WXPay wxPay;

    @Autowired
    private OrderDao orderDao;

    @Autowired
    private OrderStatusDao orderStatusDao;

    public String createPayUrl(Long orderId, Long totalPay, String desc) {
        try {
            Map<String, String> data = new HashMap<>();
            // 商品描述
            data.put("body", desc);
            // 订单号
            data.put("out_trade_no", orderId.toString());
            // 总金额,单位是分
            data.put("total_fee", totalPay.toString());
            // 调用微信支付的终端ip
            data.put("spbill_create_ip", "127.0.0.1");
            // 回调地址
            data.put("notify_url", payConfig.getNotifyUrl());
            // 交易类型为扫码支付
            data.put("trade_type", "NATIVE");

            // 利用wxPay工具完成下单
            Map<String, String> result = wxPay.unifiedOrder(data);
            // 判断通信标识和业务标识
            isSuccess(result);
            // 校验签名
            isValidSign(result);
            // 下单成功,获取支付链接
            String url = result.get("code_url");
            return url;
        } catch (Exception e) {
            log.error("【微信下单】 创建预交易订单异常", e);
            return null;
        }
    }

    public void isSuccess(Map<String, String> result) {
        // 判断通信标识
        String returnCode = result.get("return_code");
        if(FAIL.equals(returnCode)) {
            // 通信失败
            log.error("【微信下单】 微信下单通信失败,失败原因:{}", result.get("return_msg"));
            throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL);
        }
        // 判断交易是否成功
        String resultCode = result.get("result_code");
        if(FAIL.equals(resultCode)) {
            // 交易失败
            log.error("【微信下单】 微信下单交易失败,错误代码:{},错误代码描述:{}",
                    result.get("err_code"), result.get("err_code_des"));
            throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL);
        }
    }

    public void isValidSign(Map<String, String> result) {
        try {
            // 重新生成签名
            String sign1 = WXPayUtil.generateSignature(result, payConfig.getKey(), SignType.HMACSHA256);
            String sign2 = WXPayUtil.generateSignature(result, payConfig.getKey(), SignType.MD5);
            // 和传过来的签名进行比较
            String sign = result.get("sign");
            if(!StringUtils.equals(sign1, sign) && !StringUtils.equals(sign2, sign)) {
                throw new LyException(ExceptionEnum.INVALID_SIGN_ERROR);
            }
        } catch (Exception e) {
            throw new LyException(ExceptionEnum.INVALID_SIGN_ERROR);
        }
    }

    public PayStateEnum queryPayState(Long orderId) {
        try {
            Map<String, String> data = new HashMap<>();
            // 订单号
            data.put("out_trade_no", orderId.toString());
            // 利用wxPay工具完成订单查询
            Map<String, String> result = wxPay.orderQuery(data);
            // 判断通信标识和业务标识
            isSuccess(result);
            // 校验签名
            isValidSign(result);
            // 校验金额
            String totalFeeStr = result.get("total_fee");
            String tradeNo = result.get("out_trade_no");
            if(StringUtils.isBlank(totalFeeStr) || StringUtils.isBlank(tradeNo)) {
                throw new LyException(ExceptionEnum.INVALID_ORDER_PARAM);
            }
            Long totalFee = Long.valueOf(totalFeeStr);
            Order order = orderDao.selectByPrimaryKey(orderId);
            if(totalFee != /*order.getActualPay()*/ 1L) {
                throw new LyException(ExceptionEnum.INVALID_ORDER_PARAM);
            }
            // 判断交易状态
            String tradeState = result.get("trade_state");
            // 如果为成功,更新订单状态
            if(StringUtils.equals(tradeState, "SUCCESS")) {
                OrderStatus orderStatus = new OrderStatus();
                orderStatus.setOrderId(orderId);
                orderStatus.setStatus(OrderStatusEnum.PAY_UP.getCode());
                orderStatus.setPaymentTime(new Date());
                int count = orderStatusDao.updateByPrimaryKeySelective(orderStatus);
                if(count != 1) {
                    throw new LyException(ExceptionEnum.ORDER_STATUS_UPDATE_ERROR);
                }
                // 返回支付成功
                return PayStateEnum.SUCCESS;
            }
            if(StringUtils.equals(tradeState, "NOTPAY") || StringUtils.equals(tradeState, "USERPAYING")) {
                return PayStateEnum.NOT_PAY;
            }
            // 如果为其他状态,则返回支付失败
            return PayStateEnum.FAIL;
        } catch (Exception e) {
            return PayStateEnum.NOT_PAY;
        }
    }
}

工具类中包括了生成预订单、校验通信与业务是否成功、签名是否一致以及订单支付状态的查询。

生成预订单

用户支付之前需要生成支付需要的二维码链接。

/**
     * 生成微信支付链接
     * @param orderId
     * @return
     */
    @GetMapping("/url/{id}")
    public ResponseEntity<String> createPayUrl(@PathVariable("id") Long orderId) {
        return ResponseEntity.ok(orderService.createPayUrl(orderId));
    }
public String createPayUrl(Long orderId) {
        Order order = queryOrderById(orderId);
        // 判断订单状态
        Integer status = order.getOrderStatus().getStatus();
        if(status != OrderStatusEnum.UN_PAY.getCode()) {
            throw new LyException(ExceptionEnum.ORDER_STATUS_ERROR);
        }
        // 生成微信支付链接
        Long actualPay = /*order.getActualPay()*/ 1L;
        String desc = order.getOrderDetails().get(0).getTitle();
        String payUrl = payHelper.createPayUrl(orderId, actualPay, desc);
        if(payUrl == null) {
            throw new LyException(ExceptionEnum.WX_PAY_ORDER_FAIL);
        }
        return payUrl;
    }

支付回调

当用户扫码支付成功后,微信会将消息传给我们下订单时提供的回调地址,在回调完成后需要给微信发送成功收到回调信息的响应,否则微信将会继续发送请求。如果在本地测试,需要进行外网穿透,可以使用natapp。

/**
     * 微信支付成功回调
     * @param result
     * @return
     */
    @PostMapping(value = "/pay", produces = "application/xml")
    public Map<String, String> handleNotify(@RequestBody Map<String, String> result) {
        log.info("【支付回调】 接受微信支付回调, 结果:{}", result);
        orderService.handleNotify(result);
        Map<String, String> msg = new HashMap<>();
        msg.put("return_code", "SUCCESS");
        msg.put("return_msg", "OK");
        return msg;
    }
public void handleNotify(Map<String, String> result) {
        // 1 判断通信标识和业务标识
        payHelper.isSuccess(result);
        // 2 校验签名
        payHelper.isValidSign(result);
        // 3 校验金额
        String totalFeeStr = result.get("total_fee");
        String tradeNo = result.get("out_trade_no");
        if(StringUtils.isBlank(totalFeeStr) || StringUtils.isBlank(tradeNo)) {
            throw new LyException(ExceptionEnum.INVALID_ORDER_PARAM);
        }
        Long totalFee = Long.valueOf(totalFeeStr);
        Long orderId = Long.valueOf(tradeNo);
        Order order = orderDao.selectByPrimaryKey(orderId);
        if(totalFee != /*order.getActualPay()*/ 1L) {
            throw new LyException(ExceptionEnum.INVALID_ORDER_PARAM);
        }
        // 4 更新订单状态
        OrderStatus orderStatus = new OrderStatus();
        orderStatus.setOrderId(orderId);
        orderStatus.setStatus(OrderStatusEnum.PAY_UP.getCode());
        orderStatus.setPaymentTime(new Date());
        int count = orderStatusDao.updateByPrimaryKeySelective(orderStatus);
        if(count != 1) {
            throw new LyException(ExceptionEnum.ORDER_STATUS_UPDATE_ERROR);
        }
        log.info("【支付回调】 订单支付成功! 订单id:{}", orderId);
    }

由于微信使用的是xml进行发送和接收,因此需要引入解析xml和序列化的依赖,@RequestBody会将接收到的xml解析为java对象,produces = "application/xml"指定了java对象最终会被序列化为xml响应回去。

支付状态查询

生成预订单后,前端会生成支付二维码,同时会不断检测订单支付的状态,如果用户的支付状态为成功,则会跳转到支付成功的页面,因此后端需要提供订单支付状态的查询接口。

/**
     * 查询订单的支付状态
     * @param orderId
     * @return
     */
    @GetMapping("/state/{id}")
    public ResponseEntity<Integer> queryPayState(@PathVariable("id") Long orderId) {
        return ResponseEntity.ok(orderService.queryPayState(orderId).getValue());
    }
public PayStateEnum queryPayState(Long orderId) {
        OrderStatus orderStatus = orderStatusDao.selectByPrimaryKey(orderId);
        // 如果订单状态不是未付款状态,则微信付款已经成功
        if(orderStatus.getStatus() != OrderStatusEnum.UN_PAY.getCode()) {
            return PayStateEnum.SUCCESS;
        }
        // 如果订单状态是未付款状态,微信付款可能已经成功,必须去微信查询支付状态
        return payHelper.queryPayState(orderId);
    }

最后,一个简单的支付基本流程就跑完了。。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!