准备事项
appId(小程序分配)
小程序密钥(小程序配置界面获取)
商户号
api密钥(商家后台自己设置)
支付流程图
开发工具和环境
idea+jdk1.8+mysql 5.7 + spring cloud
JAVA代码
Controller
package cn.nhdc.cloud.modules.mx.controller;
import cn.nhdc.cloud.common.utils.JwtUtil;
import cn.nhdc.cloud.modules.customer.model.entity.Customer;
import cn.nhdc.cloud.modules.customer.service.ICustomerService;
import cn.nhdc.cloud.modules.mx.model.vo.MxOrderVo;
import cn.nhdc.cloud.modules.mx.service.OrderHandleService;
import cn.nhdc.common.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* @author Asa
* @createDate 2020-11-28
*/
@Slf4j
@RestController
@RequestMapping("/order/handle")
public class OrderHandleController {
@Resource
private OrderHandleService handleService;
@Resource
private ICustomerService customerService;
/**
* 提交订单
* @param mxOrderVo 下单信息
*/
@PostMapping("/submitOrder")
public Map<String, String> save(@RequestBody @Validated MxOrderVo mxOrderVo,
HttpServletRequest request) {
checkReq(request);
mxOrderVo.setUserId(JwtUtil.getUserId());
// //TODO
// mxOrderVo.setUserId(1291198124810846209L);
Customer customer = customerService.getById(mxOrderVo.getUserId());
if (customer == null) {
throw new BusinessException("用户不存在,请重新登录后重试!");
}
return handleService.submitOrder(mxOrderVo, customer, request);
}
private void checkReq(HttpServletRequest request) {
String userAgent = request.getHeader("user-agent").toLowerCase();
//非微信客户端不能参与
if(userAgent.indexOf("micromessenger") == -1){
throw new BusinessException("参数错误!");
}
}
/**
* 微信回调函数
*/
@PostMapping("/notify")
public void notify(HttpServletRequest request,HttpServletResponse response) {
handleService.notifyOrder(request,response);
}
/**
* 查询订单状态
*/
@GetMapping("/query")
public Boolean query(String sn) {
return handleService.query(sn);
}
/**
* 待付款下提交订单
* @param id
* @return
*/
@GetMapping("/reSubmitOrder")
public Map<String, Object> reSubmitOrder(@RequestParam("id") Long id) {
return handleService.reSubmitOrder(id);
}
}
Service:
订单业务逻辑
package cn.nhdc.cloud.modules.mx.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.nhdc.cloud.common.enums.*;
import cn.nhdc.cloud.common.utils.JwtUtil;
import cn.nhdc.cloud.modules.customer.model.entity.Customer;
import cn.nhdc.cloud.modules.mx.model.vo.MxOrderVo;
import cn.nhdc.cloud.modules.mx.model.vo.MxProductDto;
import cn.nhdc.cloud.modules.mx.service.AsyncMxOrderService;
import cn.nhdc.cloud.modules.mx.service.IMxProductService;
import cn.nhdc.cloud.modules.mx.service.IMxUserDataService;
import cn.nhdc.cloud.modules.mx.service.OrderHandleService;
import cn.nhdc.cloud.modules.order.mapper.ProductOrderMapper;
import cn.nhdc.cloud.modules.order.model.entity.ProductOrder;
import cn.nhdc.cloud.modules.pay.service.IWxPayService;
import cn.nhdc.cloud.modules.pay.wxsdk.WXPayConstants;
import cn.nhdc.cloud.modules.pay.wxsdk.WXPayUtil;
import cn.nhdc.cloud.modules.product.service.ISnService;
import cn.nhdc.common.exception.BusinessException;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.autoconfigure.klock.annotation.Klock;
import org.springframework.boot.autoconfigure.klock.model.LockTimeoutStrategy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author Asa
* @createDate 2020-11-28
*/
@Slf4j
@Service("orderHandleService")
public class OrderHandleServiceImpl implements OrderHandleService {
@Resource
private IMxProductService mxProductService;
@Resource
private AsyncMxOrderService asyncMxOrderService;
@Resource
private ISnService snService;
@Resource
private ProductOrderMapper productOrderMapper;
@Resource
private IWxPayService wxPayService;
@Resource
private IMxUserDataService userDataService;
@Override
@Transactional(rollbackFor = Exception.class)
@Klock(name="xwh:mall:mxOrder", keys = {
"#mxOrderVo.mxProductId"}, lockTimeoutStrategy = LockTimeoutStrategy.FAIL_FAST)
public Map<String, String> submitOrder(MxOrderVo mxOrderVo, Customer customer, HttpServletRequest request) {
mxOrderVo.setUserId(customer.getId());
MxProductDto dbProductInfo = mxProductService.getInfoById(mxOrderVo.getMxProductId(), customer.getId(),
MxPublishStatusEnum.PUBLISHED, OrderStatusEnum.CANCEL);
//检查是否能秒杀
checkCanMx(mxOrderVo, dbProductInfo);
//设置订单基本信息
ProductOrder order = getProductOrder(mxOrderVo, dbProductInfo);
mxProductService.updateMxProductRemainNum(mxOrderVo);
//生成微信支付所需参数
Map<String, String> wxResult = wxPayService.generateOrder(order, customer.getOpenId(),request);
//更新商品信息
asyncMxOrderService.saveInfo(mxOrderVo, order, wxResult);
return wxResult;
}
private ProductOrder getProductOrder(MxOrderVo mxOrderVo, MxProductDto dbProductInfo) {
ProductOrder order = new ProductOrder();
BeanUtils.copyProperties(mxOrderVo, order);
//当前时间
Date dateTime = new Date();
//设置商品单号
String sn = dbProductInfo.getSn();
order.setProductSn(sn);
order.setRemark(mxOrderVo.getRemark());
//设置单号
BigDecimal multiply = dbProductInfo.getPrice().multiply(BigDecimal.valueOf(mxOrderVo.getQuantity()));
order.setSn(snService.generateOrder(dateTime));
order.setScore(dbProductInfo.getPrice());
//存入类型
order.setBelongTo(BelongToEnum.SEC_KILL);
order.setTotalScore(multiply);
order.setStatus(OrderStatusEnum.UN_PAYMENT.getStatus());
//设置商品信息
order.setProductId(dbProductInfo.getProductId());
order.setProductName(dbProductInfo.getName());
order.setProductImg(StringUtils.isNotBlank(dbProductInfo.getImages()) ? dbProductInfo.getImages().split(",")[0] : null);
//设置用户
order.setCustomerId(mxOrderVo.getUserId());
return order;
}
private void checkCanMx(MxOrderVo mxOrderVo, MxProductDto dbProductInfo) {
if (dbProductInfo == null){
throw new BusinessException("商品秒杀已结束!");
}
if(dbProductInfo.getMxStartTime().compareTo(new Date()) == 1){
throw new BusinessException("该商品秒杀还未开始!");
}
if (dbProductInfo.getRemainQuantity() <= 0){
throw new BusinessException("没有库存了,请联系客服人员!");
}
if (dbProductInfo.getPrice().compareTo(BigDecimal.ZERO)<=0){
throw new BusinessException("商品价格出问题了,请联系客服人员!");
}
int num = dbProductInfo.getTotal() == null ? 0 : dbProductInfo.getTotal();
num = num + mxOrderVo.getQuantity();
if (dbProductInfo.getLimitNum().compareTo(num) == -1) {
throw new BusinessException("下单数量超过限购数量!");
}
}
@Override
public void notifyOrder(HttpServletRequest request, HttpServletResponse response) {
Map<String, String> map = getWXCallbackParamStr(request, response);
if(CollectionUtil.isEmpty(map)){
wxPayService.respToWx(response, "参数出错");
return;
}
if (WXPayConstants.FAIL.equals(map.get("return_code"))){
log.error("微信支付回调结果失败! {}", JSONObject.toJSONString(map));
return;
}
//处理支付结果
if (WXPayConstants.SUCCESS.equals(map.get("result_code"))) {
doNotifyPayedSuccess(response, map);
} else {
String wxRespStr = JSONObject.toJSONString(map);
log.error("微信支付回调结果失败! {}", wxRespStr);
//商户订单号
String ordersSn = map.get("out_trade_no");
//处理业务逻辑
ProductOrder order = productOrderMapper.getOrderBySn(ordersSn);
//微信端订单号
String transactionId = map.get("transaction_id");
//更新状态信息
userDataService.updateUserOrderStatusByOrderId(order.getId(), OrderStatusEnum.UN_PAYMENT, WxPayStatusEnum.FAIL,
transactionId, wxRespStr);
//通知微信服务器已经支付成功
wxPayService.respToWx(response, null);
}
}
private void doNotifyPayedSuccess(HttpServletResponse response, Map<String, String> map) {
//验证签名是否正确
boolean isOK = wxPayService.checkCallbackSign(response, map);
if(!isOK){
return;
}
//商户订单号
String ordersSn = map.get("out_trade_no");
//处理业务逻辑
ProductOrder order = productOrderMapper.getOrderBySn(ordersSn);
if (order == null) {
log.error("微信支付回调失败!商品不存在, {}", JSONObject.toJSONString(map));
wxPayService.respToWx(response, "商品不存在");
}
// 因为微信回调可能多次,所以当第一次回调成功了,那么我们就不再执行逻辑了
if(!OrderStatusEnum.UN_PAYMENT.getStatus().equals(order.getStatus())){
wxPayService.respToWx(response, null);
return;
}
//实际支付的订单金额:单位 分
String totalFee = map.get("total_fee");
//将分转换成元-实际支付金额:元
BigDecimal amountPay = (new BigDecimal(totalFee).divide(new BigDecimal("100"))).setScale(2);
//根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等
if (amountPay.compareTo(order.getTotalScore()) != 0) {
log.error("微信支付回调失败!金额不一致, {}", JSONObject.toJSONString(map));
wxPayService.respToWx(response, "金额不一致");
return;
}
//微信端订单号
String transactionId = map.get("transaction_id");
//更新状态信息
userDataService.updateUserOrderStatusByOrderId(order.getId(), OrderStatusEnum.UN_DELIVER, WxPayStatusEnum.SUCCESS,
transactionId, null);
productOrderMapper.updateOrderStatusById(order.getId(), OrderStatusEnum.UN_DELIVER);
//通知微信服务器已经支付成功
wxPayService.respToWx(response, null);
}
private Map<String, String> getWXCallbackParamStr(HttpServletRequest request, HttpServletResponse response) {
StringBuilder sb = new StringBuilder();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
String line = null;
while ((line = br.readLine()) != null) {
sb.append(line);
}
IoUtil.close(br);
} catch (IOException e) {
log.error("解析微信回调入参出错", e);
}
String notifyXml = sb.toString();
log.info("接收到的报文:{}", notifyXml);
if(StringUtils.isEmpty(notifyXml)){
wxPayService.respToWx(response, "参数出错");
return null;
}
//sb为微信返回的xml
Map<String, String> map = null;
try {
map = WXPayUtil.xmlToMap(notifyXml);
} catch (Exception e) {
log.error("解析微信回调入参出错", e);
}
return map;
}
@SneakyThrows
@Override
public Boolean query(String orderSn) {
ProductOrder order = productOrderMapper.getOrderBySn(orderSn);
if (ObjectUtils.isEmpty(order)){
throw new BusinessException("订单不存在,订单编号"+orderSn);
}
//异步回调结束不需要再去查询微信订单
if (!order.getStatus().equals(OrderStatusEnum.UN_PAYMENT.getStatus())){
return true;
}
Map<String, String> reqData = new HashMap<>();
reqData.put("out_trade_no", order.getSn());
Map<String, String> wxResult = wxPayService.orderQuery(reqData);
String trade_state = wxResult.get("trade_state");
if(TradeStateEnum.FAIL.getStatus().equals(trade_state)
|| TradeStateEnum.USERPAYING.getStatus().equals(trade_state)){
return false;
} else if(TradeStateEnum.SUCCESS.getStatus().equals(trade_state)){
//更新状态信息
userDataService.updateUserOrderStatusByOrderId(order.getId(), OrderStatusEnum.UN_DELIVER,
WxPayStatusEnum.SUCCESS, wxResult.get("transaction_id"), null);
productOrderMapper.updateOrderStatusById(order.getId(), OrderStatusEnum.UN_DELIVER);
return true;
}
//TODO 其他状态
return true;
}
@Override
public Map<String, Object> reSubmitOrder(Long id) {
Long userId = JwtUtil.getUserId();
String wxTempData = userDataService.getWxTempData(id, userId);
if(StringUtils.isEmpty(wxTempData)){
return Collections.emptyMap();
}
return JSONObject.parseObject(wxTempData);
}
}
微信调用逻辑: --支付 回调 退款 查询
package cn.nhdc.cloud.modules.pay.service.Impl;
import cn.hutool.core.collection.CollectionUtil;
import cn.nhdc.cloud.config.WXPayConfig;
import cn.nhdc.cloud.modules.mx.model.entity.MxUserData;
import cn.nhdc.cloud.modules.order.model.entity.ProductOrder;
import cn.nhdc.cloud.modules.pay.service.IWxPayService;
import cn.nhdc.cloud.modules.pay.wxsdk.MyWxPayRequest;
import cn.nhdc.cloud.modules.pay.wxsdk.WXPayConstants;
import cn.nhdc.cloud.modules.pay.wxsdk.WXPayUtil;
import cn.nhdc.common.exception.BusinessException;
import com.alibaba.fastjson.JSONObject;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* @author Asa
* @since 2020-11-28 16:33:08
*/
@Slf4j
@Service("wxPayService")
public class WxPayServiceImpl implements IWxPayService {