微信支付
用第三方工具SDK微信支付
官方文档:
https://pay.weixin.qq.com/wiki
第三方SDK:
https://github.com/Pay-Group/best-pay-sdk
注意:与官方文档步骤做对比
1.引入依赖
<dependency> <groupId>cn.springboot</groupId> <artifactId>best-pay-sdk</artifactId> <version>1.1.0</version> </dependency>
2.项目配置文件配置
wechat: mpAppId: wxd898fcb01713c658 mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx openAppId: wx6ad144e54af67d87 openAppSecret: 91a2ff6d38a2bbccfb7e9f9079108e2e mchId: 1483469312 mchKey: 06C56A89949D617xxxxxxxxxxx //上面三个在微信商家平台得到 keyPath: /var/weixin_cert/h5.p12 notifyUrl: http://sell.natapp4.cc/sell/pay/notify------没这个就不能发起支付
3.创建参数配置文件,在授权的基础上补全
@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {
/**
* 公众平台id
*/
private String mpAppId;
/**
* 公众平台密钥
*/
private String mpAppSecret;
/**
* 开放平台id
*/
private String openAppId;
/**
* 开放平台密钥
*/
private String openAppSecret;
/**
* 商户号
*/
private String mchId;
/**
* 商户密钥
*/
private String mchKey;
/**
* 商户证书路径
*/
private String keyPath;
/**
* 微信支付异步通知地址
*/
private String notifyUrl;
/**
* 微信模版id
*/
private Map<String, String> templateId;
}
/
4.配置支付配置文件
@Component
public class WechatPayConfig {
@Autowired
private WechatAccountConfig accountConfig;
@Bean
public BestPayServiceImpl bestPayService() {
BestPayServiceImpl bestPayService = new BestPayServiceImpl();
bestPayService.setWxPayH5Config(wxPayH5Config());
return bestPayService;
}
@Bean
public WxPayH5Config wxPayH5Config() {
WxPayH5Config wxPayH5Config = new WxPayH5Config();
wxPayH5Config.setAppId(accountConfig.getMpAppId());
wxPayH5Config.setAppSecret(accountConfig.getMpAppSecret());
wxPayH5Config.setMchId(accountConfig.getMchId());
wxPayH5Config.setMchKey(accountConfig.getMchKey());
wxPayH5Config.setKeyPath(accountConfig.getKeyPath());
wxPayH5Config.setNotifyUrl(accountConfig.getNotifyUrl());
return wxPayH5Config;
}
}
5.创建payservice与serviceImpl,先测试配置文件中参数是否能得到
public interface PayService {
PayResponse create(OrderDTO orderDTO);
}
public class PayServiceImpl implements PayService {
@Autowired
private BestPayServiceImpl bestPayService;
@Autowired
private OrderService orderService;
@Override
public PayResponse create(OrderDTO orderDTO) {
PayRequest payRequest = new PayRequest();
bestPayService.pay(payRequest);
}
}
6-1.参数json格式化
public class JsonUtil {
public static String toJson(Object object) {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setPrettyPrinting();
Gson gson = gsonBuilder.create();
return gson.toJson(object);
}
}
6-2. serviceimpl传入参数//这一步已经完成调用统一下单api,并返回预付单信息
@Service
@Slf4j
public class PayServiceImpl implements PayService {
private static final String ORDER_NAME = "微信点餐订单";//自定义
@Autowired
private BestPayServiceImpl bestPayService;
@Autowired
private OrderService orderService;
@Override
public PayResponse create(OrderDTO orderDTO) {
PayRequest payRequest = new PayRequest();
payRequest.setOpenid(orderDTO.getBuyerOpenid());
payRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue());
payRequest.setOrderId(orderDTO.getOrderId());
payRequest.setOrderName(ORDER_NAME);
payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5);
log.info("【微信支付】发起支付, request={}", JsonUtil.toJson(payRequest));
PayResponse payResponse = bestPayService.pay(payRequest);
log.info("【微信支付】发起支付, response={}", JsonUtil.toJson(payResponse));
return payResponse;
}
}
7-1.静态注入参数,每次测试需手动修改数据由网页发起支付,代码放在后端,static文件夹下 pay.html-----------参数就是6-2返回的参数
<script>
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"wxd898fcb01713c658", //公众号名称,由商户传入
"timeStamp":"1499569906", //时间戳,自1970年以来的秒数
"nonceStr":"bVsQpcfsKUAzO8r0", //随机串
"package":"prepay_id=wx2017070911112036b51eaddc0529394957",
"signType":"MD5", //微信签名方式:
"paySign":"78CA85306AB823156E1032EFB5BB1C76" //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
}
);
}
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
</script>
7-2.动态注入参数 使用freemaker模板引擎
7-2-1 引入freemaker依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
7-2-2在controller里引入模板引擎名称
@Controller
@RequestMapping("/pay")
public class PayController {
@Autowired
private OrderService orderService;
@Autowired
private PayService payService;
@GetMapping("/create")
public ModelAndView create(@RequestParam("orderId") String orderId,
@RequestParam("returnUrl") String returnUrl,
Map<String, Object> map) {
//1. 查询订单
OrderDTO orderDTO = orderService.findOne(orderId);
if (orderDTO == null) {
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
//2. 发起支付
PayResponse payResponse = payService.create(orderDTO);
map.put("payResponse", payResponse);
map.put("returnUrl", returnUrl);
return new ModelAndView("pay/create", map);
}
}}
7-2-3 创建create模板,放在templates文件夹下 create.ftl
<script>
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"${payResponse.appId}", //公众号名称,由商户传入
"timeStamp":"${payResponse.timeStamp}", //时间戳,自1970年以来的秒数
"nonceStr":"${payResponse.nonceStr}", //随机串
"package":"${payResponse.packAge}",
"signType":"MD5", //微信签名方式:
"paySign":"${payResponse.paySign}" //微信签名
},
function(res){
// if(res.err_msg == "get_brand_wcpay_request:ok" ) {
// } // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
location.href = "${returnUrl}";
}
);
}
if (typeof WeixinJSBridge == "undefined"){
if( document.addEventListener ){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
</script>
7-2-4微信异步通知,改变支付状态
返回给微信处理结果的模板引擎
<xml>
<return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
</xml>
controller层
@Controller
@RequestMapping("/pay")
public class PayController {
@Autowired
private OrderService orderService;
@Autowired
private PayService payService;
@GetMapping("/create")
public ModelAndView create(@RequestParam("orderId") String orderId,
@RequestParam("returnUrl") String returnUrl,
Map<String, Object> map) {
//1. 查询订单
OrderDTO orderDTO = orderService.findOne(orderId);
if (orderDTO == null) {
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
//2. 发起支付
PayResponse payResponse = payService.create(orderDTO);
map.put("payResponse", payResponse);
map.put("returnUrl", returnUrl);
return new ModelAndView("pay/create", map);
}
/**
* 微信异步通知
* @param notifyData
*/
@PostMapping("/notify")
public ModelAndView notify(@RequestBody String notifyData) {
payService.notify(notifyData);
//返回给微信处理结果
return new ModelAndView("pay/success");
}
}
serviceimpl层 特别注意:金额校验时有精度差别,所以必须处理
金额校验精度处理
public class MathUtil {
private static final Double MONEY_RANGE = 0.01;
/**
* 比较2个金额是否相等
* @param d1
* @param d2
* @return
*/
public static Boolean equals(Double d1, Double d2) {
Double result = Math.abs(d1 - d2);
if (result < MONEY_RANGE) {
return true;
}else {
return false;
}
}
}
serviceimpl层
@Service
@Slf4j
public class PayServiceImpl implements PayService {
private static final String ORDER_NAME = "微信点餐订单";
@Autowired
private BestPayServiceImpl bestPayService;
@Autowired
private OrderService orderService;
@Override
public PayResponse create(OrderDTO orderDTO) {
PayRequest payRequest = new PayRequest();
payRequest.setOpenid(orderDTO.getBuyerOpenid());
payRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue());
payRequest.setOrderId(orderDTO.getOrderId());
payRequest.setOrderName(ORDER_NAME);
payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5);
log.info("【微信支付】发起支付, request={}", JsonUtil.toJson(payRequest));
PayResponse payResponse = bestPayService.pay(payRequest);
log.info("【微信支付】发起支付, response={}", JsonUtil.toJson(payResponse));
return payResponse;
}
@Override
public PayResponse notify(String notifyData) {
//1. 验证签名
//2. 支付的状态
//3. 支付金额
//4. 支付人(下单人 == 支付人)
PayResponse payResponse = bestPayService.asyncNotify(notifyData);
log.info("【微信支付】异步通知, payResponse={}", JsonUtil.toJson(payResponse));
//查询订单
OrderDTO orderDTO = orderService.findOne(payResponse.getOrderId());
//判断订单是否存在
if (orderDTO == null) {
log.error("【微信支付】异步通知, 订单不存在, orderId={}", payResponse.getOrderId());
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
//判断金额是否一致(0.10 0.1)
if (!MathUtil.equals(payResponse.getOrderAmount(), orderDTO.getOrderAmount().doubleValue())) {
log.error("【微信支付】异步通知, 订单金额不一致, orderId={}, 微信通知金额={}, 系统金额={}",
payResponse.getOrderId(),
payResponse.getOrderAmount(),
orderDTO.getOrderAmount());
throw new SellException(ResultEnum.WXPAY_NOTIFY_MONEY_VERIFY_ERROR);
}
//修改订单的支付状态
orderService.paid(orderDTO);
return payResponse;
}
}
8.退款
8-1 在官方文档中下载API安全证书,并配置文件路径
wechat: mpAppId: wxd898fcb01713c658 mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx openAppId: wx6ad144e54af67d87 openAppSecret: 91a2ff6d38a2bbccfb7e9f9079108e2e mchId: 1483469312 mchKey: 06C56A89949D617xxxxxxxxxxx keyPath: /var/weixin_cert/h5.p12//这个是API安全证书文件配置路径
orderserviceimpl
@Autowired
private PayService payService;
@Override
@Transactional
public OrderDTO cancel(OrderDTO orderDTO) {
OrderMaster orderMaster = new OrderMaster();
//判断订单状态
if (!orderDTO.getOrderStatus().equals(OrderStatusEnum.NEW.getCode())) {
log.error("【取消订单】订单状态不正确, orderId={}, orderStatus={}", orderDTO.getOrderId(), orderDTO.getOrderStatus());
throw new SellException(ResultEnum.ORDER_STATUS_ERROR);
}
//修改订单状态
orderDTO.setOrderStatus(OrderStatusEnum.CANCEL.getCode());
BeanUtils.copyProperties(orderDTO, orderMaster);
OrderMaster updateResult = orderMasterRepository.save(orderMaster);
if (updateResult == null) {
log.error("【取消订单】更新失败, orderMaster={}", orderMaster);
throw new SellException(ResultEnum.ORDER_UPDATE_FAIL);
}
//返回库存
if (CollectionUtils.isEmpty(orderDTO.getOrderDetailList())) {
log.error("【取消订单】订单中无商品详情, orderDTO={}", orderDTO);
throw new SellException(ResultEnum.ORDER_DETAIL_EMPTY);
}
List<CartDTO> cartDTOList = orderDTO.getOrderDetailList().stream()
.map(e -> new CartDTO(e.getProductId(), e.getProductQuantity()))
.collect(Collectors.toList());
productService.increaseStock(cartDTOList);
//如果已支付, 需要退款
if (orderDTO.getPayStatus().equals(PayStatusEnum.SUCCESS.getCode())) {
payService.refund(orderDTO);
}
return orderDTO;
}
payserviceimpl
@Service
@Slf4j
public class PayServiceImpl implements PayService {
private static final String ORDER_NAME = "微信点餐订单";
@Autowired
private BestPayServiceImpl bestPayService;
@Autowired
private OrderService orderService;
@Override
public PayResponse create(OrderDTO orderDTO) {
PayRequest payRequest = new PayRequest();
payRequest.setOpenid(orderDTO.getBuyerOpenid());
payRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue());
payRequest.setOrderId(orderDTO.getOrderId());
payRequest.setOrderName(ORDER_NAME);
payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5);
log.info("【微信支付】发起支付, request={}", JsonUtil.toJson(payRequest));
PayResponse payResponse = bestPayService.pay(payRequest);
log.info("【微信支付】发起支付, response={}", JsonUtil.toJson(payResponse));
return payResponse;
}
@Override
public PayResponse notify(String notifyData) {
//1. 验证签名
//2. 支付的状态
//3. 支付金额
//4. 支付人(下单人 == 支付人)
PayResponse payResponse = bestPayService.asyncNotify(notifyData);
log.info("【微信支付】异步通知, payResponse={}", JsonUtil.toJson(payResponse));
//查询订单
OrderDTO orderDTO = orderService.findOne(payResponse.getOrderId());
//判断订单是否存在
if (orderDTO == null) {
log.error("【微信支付】异步通知, 订单不存在, orderId={}", payResponse.getOrderId());
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
//判断金额是否一致(0.10 0.1)
if (!MathUtil.equals(payResponse.getOrderAmount(), orderDTO.getOrderAmount().doubleValue())) {
log.error("【微信支付】异步通知, 订单金额不一致, orderId={}, 微信通知金额={}, 系统金额={}",
payResponse.getOrderId(),
payResponse.getOrderAmount(),
orderDTO.getOrderAmount());
throw new SellException(ResultEnum.WXPAY_NOTIFY_MONEY_VERIFY_ERROR);
}
//修改订单的支付状态
orderService.paid(orderDTO);
return payResponse;
}
/**
* 退款
* @param orderDTO
*/
@Override
public RefundResponse refund(OrderDTO orderDTO) {
RefundRequest refundRequest = new RefundRequest();
refundRequest.setOrderId(orderDTO.getOrderId());
refundRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue());
refundRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5);
log.info("【微信退款】request={}", JsonUtil.toJson(refundRequest));
RefundResponse refundResponse = bestPayService.refund(refundRequest);
log.info("【微信退款】response={}", JsonUtil.toJson(refundResponse));
return refundResponse;
}
}
总结:
支付流程
授权:遇到redirect_url错误时,立马确定授权地址填错了
支付
授权+支付
浙公网安备 33010602011771号