springboot整合微信支付

 springboot整合微信支付_第1张图片

低价云服务器  链接 —>>> 开发云 - 一站式云服务平台


 

 

讲解微信支付V3接口真实开发代码,非demo

使用微信支付需要开通微信支付商户号:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式

springboot整合微信支付_第2张图片

微信支付提供多种支付功能,包括【JSAPI支付、APP支付、H5支付、Native支付、小程序支付、合单支付】

本贴将讲述JSAPI支付、Native支付、小程序支付、支付成功回调、退款成功回调、订单超时未支付优雅的处理方案

JSAPI支付、小程序支付的作用:

在小程序、公众号、微信浏览器中调用微信指定的函数,发起调用页面

例如在小程序中的付款按钮事件中调用wx.requestPayment函数,就可以唤起微信支付页面进行支付

springboot整合微信支付_第3张图片

springboot整合微信支付_第4张图片

springboot整合微信支付_第5张图片

Native支付的作用:

PC网站,生成二维码,用户打开微信扫一扫进行支付

springboot整合微信支付_第6张图片

 准备工作:

  • mchId

        微信支付平台开通的商户号

        登录商户号:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式,产品中心->开发配置中查看商户号

  • appId

        JSAPI支付在公众号中使用,需要公众号的appid,需要在微信开放平台开通服务号或者公众号类型的账号,如果是在小程序中使用,需要在微信开放平台开通小程序类型的账号:微信开放平台

  • v3 appKey

        微信支付v3版本的appkey

        登录商户号:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式,账户中心->API安全->APIv3密钥进行设置 

  • API证书

        微信支付接口每一步都需要的证书,用于验证

        登录商户号:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式,账户中心->API安全->申请API证书,进行获取

springboot整合微信支付_第7张图片

  • 微信平台证书

        微信支付成功时的回调,验签回调返回的密文时使用

        获取步骤:GitHub - wechatpay-apiv3/CertificateDownloader: Java 微信支付 APIv3 平台证书的命令行下载工具,下载CertificateDownloader源码,导入到开发工具中,打开com.elias.test.CertificateDownloaderTest类,填写相关信息,run运行springboot整合微信支付_第8张图片

         然后就会在指定的目录下生成一个.pem文件

springboot整合微信支付_第9张图片

  • 配置回调域名

        假如你有一个域名:http://027.com

        登录商户号:微信支付 - 中国领先的第三方支付平台 | 微信支付提供安全快捷的支付方式,产品中心->开发配置->支付配置

        springboot整合微信支付_第10张图片

开始:---------------------------------------------------------------------------------

上代码:

首先你有一个springboot项目

springboot整合微信支付_第11张图片

 在pom.xml中添加 http和生成二维码需要的jar包



	cn.hutool
	hutool-http
	latest-version




	com.google.zxing
	javase
	latest-version


	com.google.zxing
	core
	latest-version

把我们准备工作中获取到的证书,添加到项目中,这里我们放在 resources/cert 目录中

springboot整合微信支付_第12张图片

 添加微信配置,这里我们放在 resources/pay 目录中

springboot整合微信支付_第13张图片

 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接口为例

springboot整合微信支付_第14张图片

准备微信支付常量类

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 测试一下

springboot整合微信支付_第15张图片

代码已经写好了,大家可以下载到自己的电脑上,copy自己需要的功能到自己的项目中使用

代码地址:微信扫一扫登录、微信支付、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国际化、图片滑块验证码、数据库敏感数据加密、字段脱敏、微信支付、微信扫一扫登录

springboot整合微信支付_第16张图片

 springboot整合微信支付_第17张图片

 springboot整合微信支付_第18张图片

你可能感兴趣的:(java,微信支付)