第一步配置API key:微信支付文档
第二步下载并配置商户证书:微信支付文档
第三步对接微信支付接口:微信支付文档
- 添加pom依赖
<!-- 微信支付API -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.12</version>
</dependency>
<!-- SBSS 用到的HTTP工具包:okhttp 3.13.1 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.13.1</version>
</dependency>
<!--工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.1</version>
</dependency>
- yml配置文件
weixin:
# AppID(小程序ID)
appId: xxxxxxx
# AppSecret(小程序密钥)
appSecret: xxxxxxx
# 接口链接
url: https://api.weixin.qq.com
# 认证类型
schema: xxxxxxx
# 商户号ID
mchid: xxxxxxx
# 商户证书序号
serialNo: xxxxxxx
# 商户私钥字符串
privateKey: xxxxxxx
#支付接口链接
payUrl: https://api.mch.weixin.qq.com
#回调接口链接
notifyUrl: https://xxx.xxx.com
#支付金额:1元
amount: 100
#apiv3密钥
apiV3Key: xxxxxxx
- 微信支付配置
package com.example.demo.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@Data
@Component
public class WechatConfig {
/**
* AppID(小程序ID)
*/
@Value("${weixin.appId}")
private String appId;
/**
* AppSecret(小程序密钥)
*/
@Value("${weixin.appSecret}")
private String appSecret;
/**
* 微信接口链接
*/
@Value("${weixin.url}")
private String url;
/**
* 商户私钥字符串(下载并配置商户证书中的私钥)
*/
@Value("${weixin.privateKey}")
private String privateKey;
/**
* 商户号
*/
@Value("${weixin.mchid}")
private String mchid;
/**
* 商户证书序号
*/
@Value("${weixin.serialNo}")
private String serialNo;
/**
* 认证类型
*/
@Value("${weixin.schema}")
private String schema;
/**
* 支付接口链接
*/
@Value("${weixin.payUrl}")
private String payUrl;
/**
* 回调路径
*/
@Value("${weixin.notifyUrl}")
private String notifyUrl;
/**
* 支付金额(单位是分)
*/
@Value("${weixin.amount}")
private BigDecimal amount;
/**
* 认证类型
*/
@Value("${weixin.apiV3Key}")
private String apiV3Key;
}
- 生成请求签名方法
package com.example.demo.util;
import com.example.demo.config.WechatConfig;
import com.wechat.pay.java.core.util.PemUtil;
import okhttp3.HttpUrl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
@Component
public class WechatCreateToken {
@Autowired
private WechatConfig wechatConfig;
/**
* 获取签名认证信息
* @param method 请求类型
* @param url 请求路径
* @param timestamp 时间戳
* @param nonceStr 随机数
* @param body 请求参数
* @return 结果
*/
public String getAuthorization(String method, HttpUrl url, long timestamp, String nonceStr, String body){
//构造签名串
String message = buildMessage(method, url, timestamp, nonceStr, body);
//Base64编码得到签名值
String signature = sign(message);
return "mchid=\"" + wechatConfig.getMchid() + "\","
+ "serial_no=\"" + wechatConfig.getSerialNo() + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "signature=\"" + signature + "\"";
}
/**
* 构造签名串
* @param method 请求类型
* @param url 请求路径
* @param timestamp 时间戳
* @param nonceStr 随机数
* @param body 请求参数
* @return 结果
*/
String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
String canonicalUrl = url.encodedPath();
if (url.encodedQuery() != null) {
canonicalUrl += "?" + url.encodedQuery();
}
return method + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
}
/**
*对签名结果进行Base64编码得到签名值
* @param message 参数
* @return 结果
*/
public String sign(String message){
try {
//加载商户私钥(privateKey:私钥字符串)
Signature sign = Signature.getInstance("SHA256withRSA");
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromString(wechatConfig.getPrivateKey());
sign.initSign(merchantPrivateKey);
sign.update(message.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sign.sign());
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
/**
*获取支付签名值
* @param timestamp 时间戳
* @param nonceStr 随机数
* @param prepayId 预支付交易会话标识
* @return 结果
*/
public String getPaySignStr(long timestamp, String nonceStr,String prepayId){
//签名
String message = wechatConfig.getAppId() + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ "prepay_id=" + prepayId + "\n";
return sign(message);
}
}
- 微信支付第三方接口方法
package com.example.demo.util;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.demo.config.WechatConfig;
import okhttp3.HttpUrl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
@Component
public class WechatUtil {
@Autowired
private WechatConfig wechatConfig;
@Autowired
private WechatCreateToken wechatCreateToken;
/**
* 获取小程序全局唯一后台接口调用凭据,token有效期为7200s
* @return 结果
*/
public String getAccessToken(){
//请求参数
JSONObject paramMap = new JSONObject();
paramMap.put("grant_type","client_credential");
paramMap.put("appid", wechatConfig.getAppId());
paramMap.put("secret", wechatConfig.getAppSecret());
String body = HttpUtil.createGet(wechatConfig.getUrl() + "/cgi-bin/token")
.form(paramMap)
.execute()
.body();
//返回值
JSONObject result = JSONUtil.parseObj(body);
if (result.get("errcode") != null){
throw new IllegalArgumentException("获取token失败");
}
return result.get("access_token", String.class);
}
/**
* 手机号验证
* @param accessToken 接口调用凭证
* @param code 微信code
* @return 结果
*/
public String getPhoneNumber(String accessToken,String code){
//请求参数
JSONObject paramMap = new JSONObject();
paramMap.put("code", code);
String body = HttpUtil.createPost(wechatConfig.getUrl() + "/wxa/business/getuserphonenumber?access_token=" + accessToken)
.header("Content-Type", "application/json")
.body(paramMap.toString())
.execute()
.body();
//返回值
JSONObject result = JSONUtil.parseObj(body);
if (result.get("errcode").equals(0)){
//用户手机号信息
Map phoneInfo = result.get("phone_info", Map.class);
return String.valueOf(phoneInfo.get("phoneNumber"));
}else {
throw new IllegalArgumentException("code无效");
}
}
/**
* 获取用户openid
* @param code 微信code
* @return 结果
*/
public String getOpenid(String code){
//请求参数
JSONObject paramMap = new JSONObject();
paramMap.put("js_code", code);
paramMap.put("appid", wechatConfig.getAppId());
paramMap.put("secret", wechatConfig.getAppSecret());
paramMap.put("grant_type", "authorization_code");
String body = HttpUtil.createGet(wechatConfig.getUrl() + "/sns/jscode2session")
.form(paramMap)
.execute()
.body();
//返回值
JSONObject result = JSONUtil.parseObj(body);
if (result.get("openid") != null){
return result.get("openid", String.class);
}else {
throw new IllegalArgumentException("微信认证失败");
}
}
/**
*获取微信支付的必要参数
*/
public Map<String,Object> getPaySignParam(String openid) {
//商户订单号(自定义)
String orderNo = "WX" + DateUtil.format(new Date(), "yyyyMMddHHmmss") + RandomUtil.randomNumbers(12);
//预支付交易会话标识
String prepayId = getPrepayId(openid, orderNo);
//时间戳
long timestamp = System.currentTimeMillis() / 1000;
//随机数
String nonceStr = UUID.randomUUID().toString().replace("-", "");
//签名
String paySign = wechatCreateToken.getPaySignStr(timestamp, nonceStr, prepayId);
//返回参数
HashMap<String, Object> resultParam = new HashMap<>();
resultParam.put("orderId", orderNo);
resultParam.put("timeStamp", timestamp + "");
resultParam.put("nonceStr", nonceStr);
resultParam.put("package", "prepay_id=" + prepayId);
resultParam.put("signType", "RSA");
resultParam.put("paySign", paySign);
return resultParam;
}
/**
* 获取预支付交易会话标识(生成订单)
* @param openid 微信id
* @param orderNo 商户订单号
* @return 结果
*/
public String getPrepayId(String openid,String orderNo){
//请求参数
JSONObject param = new JSONObject();
//小程序id
param.put("appid", wechatConfig.getAppId());
//商户号id
param.put("mchid", wechatConfig.getMchid());
//商品描述
param.put("description", "xxx费用");
//商户订单号
param.put("out_trade_no", orderNo);
//支付通知回调接口(需要https路径)
param.put("notify_url", wechatConfig.getNotifyUrl() + "/notifyUrl");
//金额
JSONObject amountParam = new JSONObject();
amountParam.put("total", wechatConfig.getAmount());
amountParam.put("currency", "CNY");
param.put("amount", amountParam);
//支付者信息
JSONObject payerParam = new JSONObject();
payerParam.put("openid", openid);
param.put("payer",payerParam);
//随机数
String nonceStr = UUID.randomUUID().toString().replace("-", "");
//时间戳
long timestamp = System.currentTimeMillis() / 1000;
//下单生成预订单
String body = param.toString();
//生成签名
String authorization = wechatConfig.getSchema() + " " + wechatCreateToken.getAuthorization("POST", Objects.requireNonNull(HttpUrl.parse(wechatConfig.getPayUrl() + "/v3/pay/transactions/jsapi")), timestamp, nonceStr, body);
String result = HttpUtil.createPost(wechatConfig.getPayUrl() + "/v3/pay/transactions/jsapi")
.header("Authorization", authorization)
.header("Content-Type", "application/json")
.header("Accept","application/json")
.body(body)
.execute()
.body();
//返回值
JSONObject data = JSONUtil.parseObj(result);
if (data.get("prepay_id") == null){
throw new IllegalArgumentException("下单失败");
}
return data.get("prepay_id",String.class);
}
/**
*获取订单信息
*/
public Map<String,Object> getOrderInfo(String orderNo){
//请求参数
JSONObject paramMap = new JSONObject();
paramMap.put("mchid", wechatConfig.getMchid());
//随机数
String nonceStr = UUID.randomUUID().toString().replace("-", "");
//时间戳
long timestamp = System.currentTimeMillis() / 1000;
String authorization = wechatConfig.getSchema() + " " + wechatCreateToken.getAuthorization("GET", Objects.requireNonNull(HttpUrl.parse(wechatConfig.getPayUrl() + "/v3/pay/transactions/out-trade-no/" + orderNo + "?mchid=" + wechatConfig.getMchid())), timestamp, nonceStr, "");
String result = HttpUtil.createGet(wechatConfig.getPayUrl() + "/v3/pay/transactions/out-trade-no/" + orderNo)
.header("Authorization",authorization)
.header("Accept","application/json")
.form(paramMap)
.execute()
.body();
JSONObject data = JSONUtil.parseObj(result);
String tradeState = data.get("trade_state",String.class);
if ("SUCCESS".equals(tradeState)) {//支付成功
Map<String,Object> map = new HashMap<>();
map.put("out_trade_no",data.get("out_trade_no",String.class));
map.put("success_time",data.get("success_time",Date.class));
BigDecimal money = new BigDecimal(data.get("amount", Map.class).get("payer_total").toString());
map.put("amount",money.divide(new BigDecimal(100),2, RoundingMode.HALF_UP));
return map;
} else {
throw new IllegalArgumentException("支付失败");
}
}
}
实体类
package com.example.demo.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
@Data
public class LoginDTO implements Serializable{
@NotBlank(message = "手机code不能为空")
@ApiModelProperty("手机code")
private String weixinCode;
@NotBlank(message = "登录code不能为空")
@ApiModelProperty("登录code")
private String loginCode;
}
package com.example.demo.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
public class WechatDTO implements Serializable {
@ApiModelProperty("随机串")
private String nonceStr;
@ApiModelProperty("签名")
private String signature;
@ApiModelProperty("证书序列号")
private String serialNo;
@ApiModelProperty("时间戳")
private String timestamp;
@ApiModelProperty("加密类型")
private String signatureType;
@ApiModelProperty("报文")
private String body;
}
控制层controller
package com.example.demo.controller;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.dto.LoginDTO;
import com.example.demo.dto.WechatDTO;
import com.example.demo.service.WechatService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
@RestController
public class WechatController {
@Autowired
private WechatService wechatService;
@ApiOperation(value = "授权登录获取手机号")
@RequestMapping(value = "/auth", method = RequestMethod.POST)
public String login(@Valid @RequestBody LoginDTO loginDTO){
return wechatService.login(loginDTO);
}
@ApiOperation(value = "获取支付签名")
@GetMapping("/getPaySign")
public Map<String,Object> getPaySign(){
return wechatService.getPaySign();
}
@ApiOperation(value = "获取订单信息")
@GetMapping("/getOrderInfo")
public Map<String,Object> getOrderInfo(String orderNo){
return wechatService.getOrderInfo(orderNo);
}
@ApiOperation("支付通知(回调)")
@RequestMapping(value = "/notifyUrl",method = RequestMethod.POST)
@ResponseBody
public JSONObject payNotifyUrl(HttpServletRequest request) {
//获取报文
String body = getRequestBody(request);
//随机串
String nonceStr = request.getHeader("Wechatpay-Nonce");
//微信传递过来的签名
String signature = request.getHeader("Wechatpay-Signature");
//证书序列号(微信平台)
String serialNo = request.getHeader("Wechatpay-Serial");
//时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
//加密类型
String signatureType = request.getHeader("Wechatpay-Signature-Type");
WechatDTO wechatDTO = new WechatDTO();
wechatDTO.setBody(body);
wechatDTO.setNonceStr(nonceStr);
wechatDTO.setSerialNo(serialNo);
wechatDTO.setTimestamp(timestamp);
wechatDTO.setSignatureType(signatureType);
wechatDTO.setSignature(signature);
wechatService.payNotifyUrl(wechatDTO);
JSONObject jsonObject = new JSONObject();
jsonObject.put("code","SUCCESS");
jsonObject.put("message","成功");
return jsonObject;
}
/**
* 读取请求数据流
*/
private String getRequestBody(HttpServletRequest request) {
StringBuffer sb = new StringBuffer();
try (ServletInputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
return sb.toString();
}
}
service接口
package com.example.demo.service;
import com.example.demo.dto.LoginDTO;
import com.example.demo.dto.WechatDTO;
import java.util.Map;
public interface WechatService {
String login(LoginDTO loginDTO);
Map<String,Object> getPaySign();
void payNotifyUrl(WechatDTO wechatDTO);
Map<String, Object> getOrderInfo(String orderNo);
}
业务逻辑处理
package com.example.demo.service.impl;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.config.WechatConfig;
import com.example.demo.dto.LoginDTO;
import com.example.demo.dto.WechatDTO;
import com.example.demo.service.WechatService;
import com.example.demo.util.AesUtil;
import com.example.demo.util.WechatUtil;
import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction;
import com.wechat.pay.java.core.notification.NotificationParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Map;
@Slf4j
@Service
public class WechatServiceImpl implements WechatService {
@Autowired
private WechatUtil wechatUtil;
@Autowired
private WechatConfig wechatConfig;
@Override
public String login(LoginDTO loginDTO) {
//获取微信用户ID,存储在数据库中
String openid = wechatUtil.getOpenid(loginDTO.getLoginCode());
//获取小程序全局唯一后台接口调用凭据
String accessToken = wechatUtil.getAccessToken();
//微信获取手机号
String phoneNumber = wechatUtil.getPhoneNumber(accessToken, loginDTO.getWeixinCode());
return phoneNumber;
}
@Override
public Map<String,Object> getPaySign() {
//获取数据库存储的微信id
String openid = "xxxx";
return wechatUtil.getPaySignParam(openid);
}
@Override
public Map<String, Object> getOrderInfo(String orderNo) {
return wechatUtil.getOrderInfo(orderNo);
}
@Override
public void payNotifyUrl(WechatDTO wechatDTO) {
//第一种
manualDecryption(wechatDTO);
//第二种
notificationParser(wechatDTO);
}
}
- 支付回调接口-解密参数
第一种使用AesUtil解密工具类
package com.example.demo.util;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AesUtil {
static final int KEY_LENGTH_BYTE = 32;
static final int TAG_LENGTH_BIT = 128;
private final byte[] aesKey;
public AesUtil(byte[] key) {
if (key.length != KEY_LENGTH_BYTE) {
throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
}
this.aesKey = key;
}
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
}
业务处理
public void manualDecryption(WechatDTO wechatDTO){
//证书序列号(微信平台)
if (wechatDTO.getSerialNo().equals(wechatConfig.getSerialNo())){
throw new IllegalArgumentException("验签失败");
}
//附加数据
String associatedData = (String) JSONUtil.getByPath(JSONUtil.parse(wechatDTO.getBody()), "resource.associated_data");
//数据密文
String ciphertext = (String) JSONUtil.getByPath(JSONUtil.parse(wechatDTO.getBody()), "resource.ciphertext");
//随机串
String nonce = (String) JSONUtil.getByPath(JSONUtil.parse(wechatDTO.getBody()), "resource.nonce");
//解密
try {
//验签成功
String decryptData = new AesUtil(wechatConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8)).decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
//支付参数
Transaction transaction = JSONObject.parseObject(decryptData, Transaction.class);
log.info("参数-------" + JSONObject.toJSONString(transaction));
//订单编号(本地生成)
String outTradeNo = transaction.getOutTradeNo();
//通过本地订单编号处理业务逻辑(并将付款编号保存到本地)
log.info("订单编码" + outTradeNo);
//业务逻辑处理
if (transaction.getTradeState().equals(Transaction.TradeStateEnum.SUCCESS)) {
}
} catch (GeneralSecurityException | IOException e) {
log.error("解密失败:"+JSONObject.toJSONString(wechatDTO.getBody()));
throw new IllegalArgumentException(e);
}
}
第二种初始化获取证书
package com.example.demo.config;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
public class WechatInitConfig {
private Config config;
@Autowired
private WechatConfig wechatConfig;
@PostConstruct
public void init(){
config = new RSAAutoCertificateConfig.Builder()
.merchantId(wechatConfig.getMchid())
.privateKey(wechatConfig.getPrivateKey())
.merchantSerialNumber(wechatConfig.getSerialNo())
.apiV3Key(wechatConfig.getApiV3Key())
.build();
}
@Bean("NotificationParser")
public NotificationParser getNotificationParser(){
return new NotificationParser((NotificationConfig) config);
}
}
业务数据处理
@Autowired
private NotificationParser notificationParser;
public void notificationParser(WechatDTO wechatDTO){
// 获取HTTP请求头中的 Wechatpay-Signature 、 Wechatpay-Nonce 、 Wechatpay-Timestamp 、 Wechatpay-Serial 、 Request-ID 、Wechatpay-Signature-Type 对应的值,构建 RequestParam 。
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(wechatDTO.getSerialNo())
.nonce(wechatDTO.getNonceStr())
.signature(wechatDTO.getSignature())
.timestamp(wechatDTO.getTimestamp())
// 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
.signType(wechatDTO.getSignatureType())
.body(wechatDTO.getBody())
.build();
Transaction transaction = notificationParser.parse(requestParam, Transaction.class);
System.out.println("参数-------"+ JSONObject.toJSONString(transaction));
//处理业务逻辑
Transaction.TradeStateEnum tradeState = transaction.getTradeState();
//订单编号(本地生成)
String outTradeNo = transaction.getOutTradeNo();
System.out.println("订单编号"+outTradeNo);
//付款编号 (微信返回)
String transactionId = transaction.getTransactionId();
//通过本地订单编号处理业务逻辑(并将付款编号保存到本地)
System.out.println("付款编号"+transactionId);
if (tradeState.equals(Transaction.TradeStateEnum.SUCCESS)) {
//业务处理
}
}