首页 > 基础资料 博客日记
Java对接微信H5支付-V3版本接口
2024-10-30 06:00:06基础资料围观55次
Java资料网推荐Java对接微信H5支付-V3版本接口这篇文章给大家,欢迎收藏Java资料网享受知识的乐趣
Hi 👋, I'm shy有人见尘埃,有人见星辰 | <技术咨询> |
1、Java对接微信JS支付-V3版本接口
2、Java对接微信H5支付-V3版本接口
3、Java对接微信Native(扫码)支付-V3版本接口
前言: 近期因为公司业务需求,需要将所负责的项目对接微信H5支付,特来记录一下整个对接过程和遇到的一些坑
1. H5支付最好的使用场景是移动端浏览器
2. ios和Android APP内嵌H5页面都需要特殊的操作进行适配(下面会讲到)
3. 如果大家有在微信App内部使用的场景,一定要使用别的支付方式,因为微信App不支持使用H5支付
4. 使用H5支付的时候,需要在商户后台添加项目对应的域名,审核周期需要七天
,如果确认要使用的话,一定要第一时间在商户后台进行支付域名的申请,可以直接申请一级域名,二级域名也可以使用
对接流程图:
-----------------------------------------------------对接开始-----------------------------------------------------
1. 查阅微信对接文档
1、H5支付产品介绍
2、H5下单API
3、github代码参考
2. 数据准备
微信官网有详细的文档介绍,你需要准备那些东西
1、H5支付接入前准备
3. Java代码
1. 引入微信官网依赖
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>
2. 编写WeChatConfig,将微信公共配置进行配置化
/**
* @desc: 微信config
* @author: shy
* @date: 2024/4/9 10:06
*/
@Configuration
@Getter
public class WeChatConfig {
/**
* 商户号
*/
@Value("${wechat.pay.merchantId}")
public String merchantId;
/**
* 商户API私钥路径
*/
@Value("${wechat.pay.privateKeyPath}")
public String privateKeyPath;
/**
* 商户证书序列号
*/
@Value("${wechat.pay.merchantSerialNumber}")
public String merchantSerialNumber;
/**
* 商户APIV3密钥
*/
@Value("${wechat.pay.apiV3Key}")
public String apiV3Key;
/**
* AppId
*/
@Value("${wechat.pay.appId}")
public String appId;
private Config config;
@PostConstruct
public void initConfig() {
// 使用自动更新平台证书的RSA配置
// 一个商户号只能初始化一个配置,否则会因为重复的下载任务报错
config = new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
}
@Primary
@Bean()
public H5Service h5Service() {
return new H5Service.Builder()
.config(config)
.build();
}
@Primary
@Bean
public NotificationParser notificationParser() {
return new NotificationParser((NotificationConfig) config);
}
}
3. 编写预下单接口
controller
@Autowired
WechatService wechatService;
@RequestMapping("/v3/h5Pay")
public Result<String> h5Pay(Integer memberFeeType, HttpServletRequest request) {
MemberFeeTypeEnum memberFeeTypeEnum = MemberFeeTypeEnum.getMemberFeeTypeEnumByType(memberFeeType);
if (Objects.isNull(memberFeeTypeEnum)) {
return Result.fail(ResultEnum.PARAM_ERROR);
}
// 返回h5支付的url
String h5Url = wechatService.h5Pay(memberFeeType, request, memberFeeTypeEnum);
if (StringUtils.isBlank(h5Url)) {
return Result.fail("支付失败");
}
return Result.success(h5Url);
}
service
@Resource
private WechatPayOrderMapper wechatPayOrderMapper;
@Resource
private H5Service h5Service;
public String h5Pay(Integer memberFeeType, HttpServletRequest request, MemberFeeTypeEnum memberFeeTypeEnum) {
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest prepayRequest = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(memberFeeTypeEnum.getPrice());
prepayRequest.setAmount(amount);
prepayRequest.setAppid(appId);
prepayRequest.setMchid(merchantId);
prepayRequest.setDescription(memberFeeTypeEnum.getDesc());
prepayRequest.setNotifyUrl(notifyUrl);
String tradeNo = WeChatUtil.generateTradeNumber();
prepayRequest.setOutTradeNo(tradeNo);
SceneInfo sceneInfo = new SceneInfo();
sceneInfo.setPayerClientIp(WebUtil.getIP(request));
H5Info h5Info = new H5Info();
h5Info.setType(UserAgent.parseUserAgentString(request.getHeader("User-Agent")).getOperatingSystem().getName());
sceneInfo.setH5Info(h5Info);
prepayRequest.setSceneInfo(sceneInfo);
// 调用下单方法,得到应答
PrepayResponse response;
try {
response = h5Service.prepay(prepayRequest);
//预支付成功,创建预支付订单
WechatPayOrder wechatPayOrder = new WechatPayOrder();
//获取当前用户
String mobile = (String) request.getSession().getAttribute(Constants.MOBILE);
User user = redisTemplateUser.opsForValue().get(redis_user_prefix + mobile);
if (Objects.isNull(user)) {
log.error("用户信息不存在");
return null;
}
wechatPayOrder.setUserId(user.getId());
wechatPayOrder.setTradeNo(tradeNo);
wechatPayOrder.setMemberFeeType(memberFeeType);
wechatPayOrder.setAmount(memberFeeTypeEnum.getPrice());
wechatPayOrderMapper.addOrder(CoverUtil.coverMap(wechatPayOrder));
return response.getH5Url();
} catch (HttpException e) { // 发送HTTP请求失败
log.error("发送HTTP请求失败: {}", e.getHttpRequest());
} catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
log.error("服务返回状态异常: {}", e.getResponseBody());
} catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
log.error("返回体类型不合法: {}", e.getMessage());
} catch (Exception e) {
log.error("预下单异常: {}", e.getMessage());
}
return null;
}
4. 编写回调接口
controller
@RequestMapping("/v3/payNotify")
public WeChatPayResult payNotify(HttpServletRequest request) {
try {
return wechatService.payNotify(request);
} catch (Exception e) {
log.error("微信回调异常: ", e);
return new WeChatPayResult("FAIL", "FAIL");
}
}
service
@Resource
private NotificationParser notificationParser;
@Resource
private WechatPayOrderMapper wechatPayOrderMapper;
@Transactional
public WeChatPayResult payNotify(HttpServletRequest request) throws Exception{
log.info("微信回调开始-------------------------");
Transaction transaction;
try {
transaction = notificationParser.parse(WeChatUtil.handleNodifyRequestParam(request), Transaction.class);
if (transaction.getTradeState() == Transaction.TradeStateEnum.SUCCESS) {
WechatPayOrder wechatPayOrder = wechatPayOrderMapper.getOrderByTradeNo(transaction.getOutTradeNo());
if (Objects.isNull(wechatPayOrder)) {
log.error("订单不存在, 订单号: {}", transaction.getOutTradeNo());
return new WeChatPayResult("SUCCESS", "ORDER_DOES_NOT_EXIST");
}
//校验订单状态:若订单已支付则直接返回成功
if (Objects.equals(wechatPayOrder.getTradeStatus(), WeChatPayTradeStatusEnum.已支付.getType())) {
log.error("订单已支付, 订单号: {}", transaction.getOutTradeNo());
return new WeChatPayResult("SUCCESS", "ORDER_PAID");
}
//支付成功-修改订单状态
wechatPayOrderMapper.updateTradeStatus(transaction.getOutTradeNo(), transaction.getTransactionId(), wechatPayOrder.getVersion());
//支付成功-添加会员
MemberFeeTypeEnum memberFeeTypeEnum = MemberFeeTypeEnum.getMemberFeeTypeEnumByType(wechatPayOrder.getMemberFeeType());
userAwardService.addAward(wechatPayOrder.getUserId(), memberFeeTypeEnum.getMemberDays());
}
} catch (ValidationException e) {
// 签名验证失败,返回 401 UNAUTHORIZED 状态码
log.error("签名验证失败: ", e);
return new WeChatPayResult("FAIL", "UNAUTHORIZED");
}
log.info("微信回调结束, 微信回调报文: {}", transaction);
return new WeChatPayResult("SUCCESS", "SUCCESS");
}
5. 编写微信工具类
微信的依赖中有很多好用的工具类,但是在他们的文档中都没有体现,需要耐心地找一下
WeChatUtil
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.util.Random;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import com.wechat.pay.java.core.cipher.RSASigner;
import com.wechat.pay.java.core.cipher.SignatureResult;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.NonceUtil;
import com.wechat.pay.java.core.util.PemUtil;
/**
* @desc: 微信工具类
* @author: shy
* @date: 2024/4/8 16:10
*/
public class WeChatUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* 生成订单号
*
* @param
* @return String
* @author shy
* @date 2024/4/8 16:15
*/
public static String generateTradeNumber() {
// 定义订单号前缀
String prefix = "shy";
// 当前年月日
String currentTimeStr = DateUtil.getCurrent("yyyyMMddHHmmss");
// 获取当前时间戳
long timestamp = System.currentTimeMillis();
// 构造订单号
return prefix + currentTimeStr + timestamp;
}
/**
* 获取随机字符串 Nonce Str
*
* @param
* @return String
* @author shy
* @date 2024/4/16 17:07
*/
public static String generateNonceStr() {
return NonceUtil.createNonce(32);
}
/**
* 获取当前时间戳,单位秒
* @param
* @return long
* @author shy
* @date 2024/4/16 17:10
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis() / 1000;
}
public static String getSign(String signatureStr, String privateKeyPath, String merchantSerialNumber) {
PrivateKey privateKey = PemUtil.loadPrivateKeyFromPath(privateKeyPath);
RSASigner rsaSigner = new RSASigner(merchantSerialNumber, privateKey);
SignatureResult signatureResult = rsaSigner.sign(signatureStr);
return signatureResult.getSign();
}
/**
* 构造 RequestParam
*
* @param request
* @return RequestParam
* @author shy
* @date 2024/4/9 11:16
*/
public static RequestParam handleNodifyRequestParam(HttpServletRequest request) throws IOException {
// 请求头Wechatpay-Signature
String signature = request.getHeader("Wechatpay-Signature");
// 请求头Wechatpay-nonce
String nonce = request.getHeader("Wechatpay-Nonce");
// 请求头Wechatpay-Timestamp
String timestamp = request.getHeader("Wechatpay-Timestamp");
// 微信支付证书序列号
String serial = request.getHeader("Wechatpay-Serial");
// 签名方式
String signType = request.getHeader("Wechatpay-Signature-Type");
// 构造 RequestParam
return new RequestParam.Builder().serialNumber(serial).nonce(nonce).signature(signature).timestamp(timestamp).signType(signType).body(getRequestBody(request)).build();
}
public static String getRequestBody(HttpServletRequest request) throws IOException {
ServletInputStream stream;
BufferedReader reader = null;
StringBuilder sb = new StringBuilder();
try {
stream = request.getInputStream();
// 获取响应
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw new IOException("读取返回支付接口数据流出现异常!");
} finally {
if (reader != null) {
reader.close();
}
}
return sb.toString();
}
}
6. 测试接口
测试需要使用互联网可以连通的域名,所以我是在生产环境新创建了一个二级域名做的测试,并且回调地址也必须是你申请审核通过的支付域名才可以。
7.ios和Android App内嵌H5页面解决方案
不建议在APP中使用H5支付,最好可以使用APP支付(微信官方说的)
微信官网解决方案:
我们根据自身情况使用的解决方案(移动端开发的同事写的,我不是很懂)
1、ios(处理无法调起微信客户端)
2.Android(Referer原因)
8. 总结
可能是我阅读文档的方式不太对,微信支付的官方开发文档写的真是一言难尽,看了好长时间才大致看明白。所以写下这篇文章,愿后面开发的兄弟们对接起来都可以开开心心,顺顺利利!一次成功!
文章来源:https://blog.csdn.net/shy_1762538422/article/details/137640142
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签: