低价云服务器 链接 —>>> 开发云 - 一站式云服务平台
讲解微信支付V3接口真实开发代码,非demo
使用微信支付需要开通微信支付商户号:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式
微信支付提供多种支付功能,包括【JSAPI支付、APP支付、H5支付、Native支付、小程序支付、合单支付】
本贴将讲述JSAPI支付、Native支付、小程序支付、支付成功回调、退款成功回调、订单超时未支付优雅的处理方案
JSAPI支付、小程序支付的作用:
在小程序、公众号、微信浏览器中调用微信指定的函数,发起调用页面
例如在小程序中的付款按钮事件中调用wx.requestPayment函数,就可以唤起微信支付页面进行支付
Native支付的作用:
PC网站,生成二维码,用户打开微信扫一扫进行支付
准备工作:
微信支付平台开通的商户号
登录商户号:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式,产品中心->开发配置中查看商户号
JSAPI支付在公众号中使用,需要公众号的appid,需要在微信开放平台开通服务号或者公众号类型的账号,如果是在小程序中使用,需要在微信开放平台开通小程序类型的账号:微信开放平台
微信支付v3版本的appkey
登录商户号:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式,账户中心->API安全->APIv3密钥进行设置
微信支付接口每一步都需要的证书,用于验证
登录商户号:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式,账户中心->API安全->申请API证书,进行获取
微信支付成功时的回调,验签回调返回的密文时使用
获取步骤:GitHub - wechatpay-apiv3/CertificateDownloader: Java 微信支付 APIv3 平台证书的命令行下载工具,下载CertificateDownloader源码,导入到开发工具中,打开com.elias.test.CertificateDownloaderTest类,填写相关信息,run运行
然后就会在指定的目录下生成一个.pem文件
假如你有一个域名:http://027.com
登录商户号:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式,产品中心->开发配置->支付配置
开始:---------------------------------------------------------------------------------
上代码:
首先你有一个springboot项目
在pom.xml中添加 http和生成二维码需要的jar包
cn.hutool
hutool-http
latest-version
com.google.zxing
javase
latest-version
com.google.zxing
core
latest-version
把我们准备工作中获取到的证书,添加到项目中,这里我们放在 resources/cert 目录中
添加微信配置,这里我们放在 resources/pay 目录中
wxpay_v3.properties 文件中的配置
### 商户号/服务号/公众号appid,
v3.appId=**************
### 小程序appId
v3.appletId=*************
### 商户号
v3.mchId=*************
###v3 的 apiKey密钥
v3.apiKey3=**********************
###apiclient_key.pem文件的路径,使用这种路径写法,后续不会出现idea和jar包两种启动方式jar找不到文件的错误
v3.keyPath=cert/apiclient_key.pem
###apiclient_cert.pem文件的路径
v3.certPath=cert/apiclient_cert.pem
###apiclient_cert.p12文件的路径
v3.certP12Path=cert/apiclient_cert.p12
### 微信 平台证书路径
v3.platformCertPath=cert/wechatpay_72E89C7A03483426DA8028.pem
### 微信 回调域名
v3.domain=http://027.com
创建读取 wxpay_v3.properties 配置文件的类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* 微信支付V3 配置信息
*
* @author lixx
* @version 1.0
* @since 2021-08-13 14:49
*/
@Data
@Component
@PropertySource("classpath:pay/wxpay_v3.properties")
@ConfigurationProperties(prefix = "v3")
public class WxPayV3Property {
/**
* 商户号appId
*/
private String appId;
/**
* 小程序appId
*/
private String appletId;
/**
* 商户号
*/
private String mchId;
/**
* v3 的 apiKey密钥
*/
private String apiKey3;
/**
* key.pem文件的路径
*/
private String keyPath;
/**
* cert.pem文件的路径
*/
private String certPath;
/**
* cert.p12文件的路径
*/
private String certP12Path;
/**
* 平台证书文件的路径
*/
private String platformCertPath;
/**
* 回调域名
*/
private String domain;
}
签名生成:
以微信文档里的示例/v3/certificates接口为例
准备微信支付常量类
public class WxPayConstant {
/**
* 域名
*/
public interface Domain {
/**
* 中国国内
*/
String CHINA = "https://api.mch.weixin.qq.com";
/**
* 中国国内(备用域名)
*/
String CHINA2 = "https://api2.mch.weixin.qq.com";
/**
* 东南亚
*/
String HK = "https://apihk.mch.weixin.qq.com";
/**
* 其它
*/
String US = "https://apius.mch.weixin.qq.com";
/**
* 获取公钥
*/
String FRAUD = "https://fraud.mch.weixin.qq.com";
/**
* 活动
*/
String ACTION = "https://action.weixin.qq.com";
/**
* 刷脸支付
* PAY_APP
*/
String PAY_APP = "https://payapp.weixin.qq.com";
}
/**
* 属性
*/
public interface Attribute {
/**
* 认证类型,目前为固定值 WECHATPAY2-SHA256-RSA2048
*/
String AUTH_TYPE = "WECHATPAY2-SHA256-RSA2048";
/**
* 微信支付,二维码图片存放的目录名称
*/
String DIRECTORY = "WxPayQrCodeTempFiles";
/**
* 符合ISO 4217标准的三位字母代码,目前只支持人民币:CNY。
*/
String CURRENCY = "CNY";
}
/**
* url 接口地址
*/
public interface Api {
/**
* 支付回调地址
*/
String CALL_BACK_NOTIFY = "/wx/pay/callBackNotify";
/**
* 退款回调地址
*/
String CALL_BACK_REFUND_NOTIFY = "/wx/pay/callBackRefundNotify";
/**
* 获取商户平台证书
*/
String V3_CERTIFICATES = "/v3/certificates";
/**
* 微信支付订单号查询
*
* 第一个%s:订单号
* 第二个%s: 商户号
*/
String V3_PAY_ID = "/v3/pay/transactions/id/%s?mchid=%s";
/**
* JSAPI下单
*/
String V3_PAY_JSAPI = "/v3/pay/transactions/jsapi";
/**
* native下单
*/
String V3_PAY_NATIVE = "/v3/pay/transactions/native";
/**
* h5 下单
*/
String V3_PAY_H5 = "/v3/pay/transactions/h5";
/**
* 退款接口
*/
String V3_REFUND = "/v3/refund/domestic/refunds";
/**
* 退款订单查询
*/
String V3_REFUND_ID = "/v3/refund/domestic/refunds/%s";
}
}
创建一个注解工具类
@Component
public class WxPayBean {
@Autowired
private WxPayV3Property v3Property;
/**
* 构建微信支付签名
*
* @param method {@link RequestMethod} 请求方法
* @param urlSuffix 可通过 {@link WxPayConstant.Api} 来获取,URL挂载参数需要自行拼接
* @param body 接口请求参数,***注意*** 如果无参数,要传入""字符串,不能传null
* @return {@link String} 返回 v3 所需的 Authorization
* @throws Exception 异常信息
*/
public String buildAuthorization(RequestMethod method, String urlSuffix, String body) throws Exception {
return WxPayUtils.buildAuthorization(method, urlSuffix, v3Property.getMchId(), body, v3Property.getKeyPath(), v3Property.getCertPath());
}
}
创建WxPayUtils工具类
@Slf4j
public class WxPayUtils {
/**
* 构建 v3 接口所需的 Authorization
*
* @param method {@link RequestMethod} 请求方法
* @param urlSuffix 可通过 {@link WxPayConstant.Api} 来获取,URL挂载参数需要自行拼接
* @param mchId 商户Id
* @param body 接口请求参数
* @param keyPath 商户 key.pem 私钥证书路径
* @param certPath 商户 cert.pem 证书路径
* @return {@link String} 返回 v3 所需的 Authorization
* @throws Exception 异常信息
*/
public static String buildAuthorization(RequestMethod method, String urlSuffix, String mchId,
String body, String keyPath, String certPath) throws Exception {
String timestamp = timestamp();
String nonceStr = nonceStr();
String serialNo = RsaUtils.getSerialNo(certPath);
// 构建签名参数
String buildSignMessage = buildSignMessage(method, urlSuffix, timestamp, nonceStr, body);
String signature = createSign(buildSignMessage, keyPath);
log.info("微信支付接口:{},请求参数:{}", urlSuffix, body);
log.info("微信API证书序列号:{}", serialNo);
// 根据平台规则生成请求头 authorization
return getAuthorization(mchId, serialNo, nonceStr, timestamp, signature);
}
/**
* 生成时间戳
*/
private static String timestamp() {
return String.valueOf(System.currentTimeMillis() / 1000);
}
/**
* 生成随机字符串
*/
private static String nonceStr() {
return IdUtil.fastSimpleUUID();
}
/**
* 构造签名串
*
* @param method {@link RequestMethod} GET,POST,PUT等
* @param url 可通过 {@link WxPayConstant.Api} 来获取,URL挂载参数需要自行拼接
* @param timestamp 获取发起请求时的系统当前时间戳
* @param nonceStr 随机字符串
* @param body 请求报文主体
* @return 待签名字符串
*/
public static String buildSignMessage(RequestMethod method, String url, String timestamp, String nonceStr, String body) {
ArrayList arrayList = new ArrayList<>();
arrayList.add(method.toString());
arrayList.add(url);
arrayList.add(timestamp);
arrayList.add(nonceStr);
arrayList.add(body);
return buildSignMessage(arrayList);
}
/**
* 构造签名串
*
* @param signMessage 待签名的参数
* @return 构造后带待签名串
*/
public static String buildSignMessage(ArrayList signMessage) {
if (signMessage == null || signMessage.size() <= 0) {
return null;
}
StringBuilder sbf = new StringBuilder();
for (String str : signMessage) {
sbf.append(str).append("\n");
}
String signStr = sbf.toString();
log.info("微信构建的签名串:\n{}", signStr);
return signStr;
}
/**
* v3 接口创建签名
*
* @param signMessage 待签名的参数
* @param keyPath 商户私钥证书路径
* @return 生成 v3 签名
* @throws Exception 异常信息
*/
public static String createSign(String signMessage, String keyPath) throws Exception {
if (StrUtil.isEmpty(signMessage)) {
return null;
}
// 生成签名
return RsaUtils.encryptByPrivateKey(signMessage, keyPath);
}
/**
* 获取授权认证信息
*
* @param mchId 商户号
* @param serialNo 商户API证书序列号
* @param nonceStr 请求随机串
* @param timestamp 时间戳
* @param signature 签名值
* @return 请求头 Authorization
*/
public static String getAuthorization(String mchId, String serialNo, String nonceStr, String timestamp, String signature) {
Map params = new HashMap<>(5);
params.put("mchid", mchId);
params.put("serial_no", serialNo);
params.put("nonce_str", nonceStr);
params.put("timestamp", timestamp);
params.put("signature", signature);
String authorization = WxPayConstant.Attribute.AUTH_TYPE.concat(" ").concat(createLinkString(params, ",", false, true));
log.info("微信生成的完整签名:{}", authorization);
return authorization;
}
public static String createLinkString(Map params, String connStr, boolean encode, boolean quotes) {
List keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
StringBuilder content = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
// 拼接时,不包括最后一个&字符
if (i == keys.size() - 1) {
if (quotes) {
content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"');
} else {
content.append(key).append("=").append(encode ? urlEncode(value) : value);
}
} else {
if (quotes) {
content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"').append(connStr);
} else {
content.append(key).append("=").append(encode ? urlEncode(value) : value).append(connStr);
}
}
}
return content.toString();
}
/**
* 获取商户私钥
*
* @param keyPath 商户私钥证书路径
* @return {@link PrivateKey} 商户私钥
* @throws Exception 异常信息
*/
public static PrivateKey getPrivateKey(String keyPath) throws Exception {
Resource resource = new ClassPathResource(keyPath);
String originalKey = IoUtil.read(resource.getInputStream(), StandardCharsets.UTF_8);
return getPrivateKeyByKeyContent(originalKey);
}
/**
* 获取商户私钥
*
* @param originalKey 私钥文本内容
* @return {@link PrivateKey} 商户私钥
* @throws Exception 异常信息
*/
public static PrivateKey getPrivateKeyByKeyContent(String originalKey) throws Exception {
String privateKey = originalKey
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
return RsaUtils.loadPrivateKey(privateKey);
}
}
创建RsaUtils工具类
public class RsaUtils {
/**
* cert.pem 获取商户序列号
*
* @param certPath cert.pem 证书路径
*/
public static String getSerialNo(String certPath) throws IOException {
Resource resource = new ClassPathResource(certPath);
X509Certificate certificate = getCertificate(resource.getInputStream());
return certificate.getSerialNumber().toString(16).toUpperCase();
}
/**
* key.pem 私钥签名
*
* @param data 需要加密的数据
* @param keyPath 商户私钥证书路径
* @return 加密后的数据
* @throws Exception 异常信息
*/
public static String encryptByPrivateKey(String data, String keyPath) throws Exception {
PrivateKey privateKey = WxPayUtils.getPrivateKey(keyPath);
Signature signature = Signature.getInstance("SHA256WithRSA");
signature.initSign(privateKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
byte[] signed = signature.sign();
return StrUtil.str(Base64.encode(signed));
}
/**
* key.pem 从字符串中加载私钥
*
* 加载时使用的是PKCS8EncodedKeySpec(PKCS#8编码的Key指令)。
*
* @param privateKeyStr 私钥
* @return {@link PrivateKey}
* @throws Exception 异常信息
*/
public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
try {
byte[] buffer = Base64.decode(privateKeyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
return keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException e) {
throw new Exception("无此算法");
} catch (InvalidKeySpecException e) {
throw new Exception("私钥非法");
} catch (NullPointerException e) {
throw new Exception("私钥数据为空");
}
}
}
以上工具类创建完成,我们创建一个接口调用测试一下
@Autowired
private WxPayBean wxPayBean;
/**
* 生成微信支付签名 test
*/
@GetMapping("test")
public void getSign() throws Exception {
String authorization = wxPayBean.buildAuthorization("get", WxPayConstant.Api.V3_CERTIFICATES, "");
System.err.println(authorization);
}
打印签名结果如下
使用 postman 测试一下
代码地址:微信扫一扫登录、微信支付、springsecurity&oauth2-Java文档类资源-CSDN下载项目中使用到的技术包含SpringBoot、SpringSecurity&oauth2(安全更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/a1053765496/24871993
代码中使用的版本:springboot2.3.12.RELEASE、maven3.5+、mysql5.7、mybatis-plus3.4.3.3、
JDK1.8、flywaydb6.5.7
代码中使用的技术:SpringBoot、SpringSecurity&oauth2(安全资源和授权中心模式、包括登录接口自定义返回字段、自定义手机号+密码登录、自定义免密登录)、Queue队列、线程池、xss攻击配置、SpringCache、Mybatis-Plus、Validation、各种自定义aop、发送邮件、Redis、Lombok、flywaydb(自动生成数据库表和初始化数据)、生成二维码、execl、跨域、全局异常处理、i18n国际化、图片滑块验证码、数据库敏感数据加密、字段脱敏、微信支付、微信扫一扫登录