乐优商城项目总结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);
}
最后,一个简单的支付基本流程就跑完了。。
来源:CSDN
作者:风&浩
链接:https://blog.csdn.net/NEBEEEEEEK2/article/details/90482487