首先来看一下公众号支付的业务流程图。
支付开发中,一般在第10步异步通知成功了,就基本上可以百分百说明支付成功了,可以修改支付状态为已支付。
开发步骤:
1.pom.xml中添加Maven依赖
2.微信服务号信息配置
package com.wechat.order.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/*微信公众号账号设置*/
@Data//使用了lombok依赖,相当于在代码中getter和setter的作用
@Component
@ConfigurationProperties(prefix = "wechat")//账号信息写在了application.yml中。
public class WeChatAccountConfig {
private String mpAppId;
private String mpAppSecret;
/**
* 商户号
*/
private String mchId;
/**
* 商户密钥
*/
private String mchKey;
/**
* 商户证书路径
*/
private String keyPath;
/**
* 异步通知路径
*/
private String notifyUrl;
}
package com.wechat.order.config;
import com.lly835.bestpay.config.WxPayH5Config;
import com.lly835.bestpay.service.impl.BestPayServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/*微信公众账号支付配置*/
@Component
public class WeChatPayConfig {
@Autowired
private WeChatAccountConfig weChatAccountConfig;
//把你要实例化的对象转化成一个Bean,放在IoC容器中
@Bean
public BestPayServiceImpl bestPayService() {
//支付类, 所有方法都在这个类里
BestPayServiceImpl bestPayService = new BestPayServiceImpl();
bestPayService.setWxPayH5Config(wxPayH5Config());
return bestPayService;
}
@Bean
private WxPayH5Config wxPayH5Config() {
WxPayH5Config wxPayH5Config = new WxPayH5Config();
wxPayH5Config.setAppId(weChatAccountConfig.getMpAppId());//设置微信公众号的appid
wxPayH5Config.setAppSecret(weChatAccountConfig.getMpAppSecret());// 设置微信公众号的app corpSecret
wxPayH5Config.setMchId(weChatAccountConfig.getMchId());// 设置商户号
wxPayH5Config.setMchKey(weChatAccountConfig.getMchKey());// 设置商户密钥
wxPayH5Config.setKeyPath(weChatAccountConfig.getKeyPath());// 设置商户证书路径
wxPayH5Config.setNotifyUrl(weChatAccountConfig.getNotifyUrl());// 设置支付后异步通知url
return wxPayH5Config;
}
}
3.调用方法发起支付
controller
/**
* 微信支付发起
* @param orderId
* @param returnUrl
* @return
*/
@RequestMapping("/create")
public ModelAndView create(@RequestParam("orderId")String orderId,
@RequestParam("returnUrl")String returnUrl,
HashMap map){
//根据订单id查询订单,数据库查询
OrderDTO orderDTO = orderService.findOne(orderId);
if(orderDTO == null){//订单不存在抛出错误
throw new SellException(ResultEnum.ORDER_NOT_EXIST);
}
//微信支付
PayResponse payResponse = wechatPayService.create(orderDTO);
//携带参数传入网页发起支付的前端模板
map.put("orderId", orderId);
map.put("payResponse", payResponse);
return new ModelAndView("pay/create",map);
}
service:
//微信支付发起
public PayResponse create(OrderDTO orderDTO) {
PayRequest payRequest = new PayRequest();
payRequest.setPayTypeEnum(BestPayTypeEnum.WXPAY_H5);//支付方式,微信公众账号支付
payRequest.setOrderId(orderDTO.getOrderId());//订单号.
payRequest.setOrderName(payName);//订单名字.
payRequest.setOrderAmount(orderDTO.getOrderAmount().doubleValue());//订单金额.
payRequest.setOpenid(orderDTO.getBuyerOpenid());//微信openid, 仅微信支付时需要
log.info("【微信支付】request={}", JsonUtil.toJson(payRequest));//将payRequest格式化一下,再显示在日志上,便于观看数据
PayResponse payResponse = bestPayService.pay(payRequest);
log.info("【微信支付】response={}", JsonUtil.toJson(payResponse));//将payResponse格式化一下,再显示在日志上,便于观看响应后返回的数据);
return payResponse;
}
JsonUtil格式转换工具类:
/**
* 把对象转换成json格式,便于观看
*/
public class JsonUtil {
public static String toJson(Object object) {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setPrettyPrinting();
Gson gson = gsonBuilder.create();
return gson.toJson(object);
}
}
4.在static文件夹下编写微信内H5调起支付的前端代码pay.html(可以从微信支付官方文档中拷贝代码),检测是否可以支付,
此步骤可省略。
这个前端代码就要写好几次。如果放在后端的话,只要给个请求,就可以直接调用这段代码。
5.对支付的前端代码动态注入参数以发起支付。
这里我们用到模板技术。
(1)在pom.xml中引入freemarker依赖
(2)把controller中发起支付的方法返回类型改为ModelAndView,return new ModelAndView("path",model);//path为模板存放的路径,不包括模板后缀名。这里model我们采用Map类型,把payResponse和returnUrl携带过去。
(3)在相应的地方,创建模板文件xx.ftl。如我上述步骤的path为pay/create,则我在resources/templates目录下创建文件夹pay,在pay目录下再创建create.ftl文件。
(4)修改pay.html成模板,参数动态注入。${xxxx}
(5)模板中,支付后进行跳转到returnUrl。
create.ftl模板:
6.填写微信支付的支付授权目录。
支付授权目录就写http://xxxxx(网站根路径)/sell/
7.微信异步通知,并在相应的controller中进行支付状态修改。
依靠前端返回的支付成功来判断成功并不安全,容易被篡改。必须依靠后端异步通知来判断是否已支付成功
(1)验证签名,支付状态
(2)校验支付金额与订单金额是否一致,2个金额相减小于0.01即认为相等。
(3)修改订单支付状态
service:
/*微信支付异步通知*/
@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;
}
controller:
/**
* 微信异步通知
* @param notifyData
*/
@PostMapping("/notify")
public ModelAndView notify(@RequestBody String notifyData) {
wechatPayService.notify(notifyData);
//返回给微信处理结果
return new ModelAndView("pay/success");
}
}
success.ftl异步通知成功模板:
service:
/**
* 退款
* @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;
}
}