maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.9</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.22</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
建立一个存放微信开放平台和商户信息的类 WechatPayProperties
wx:
pay:
appId: ***** # 应用ID
appSecret: ***** #开发者的AppSecret
merchantId: ***** #商户id
merchantSerialNumber: ***** #商户证书序列
apiV3Key: ***** #V3密钥
privateKeyPath: G:\\*****\\apiclient_key.pem #apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径
privateCertPath: G:\\*****\\apiclient_cert.pem #apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径
privateCertificatePath: G:\\*****\\wechatpay_******.pem
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@Getter
@Setter
@ConfigurationProperties(prefix = "wx.pay")
@ToString
public class WechatPayProperties {
/**
* APPID
*/
private String appId;
/*
* appSecret
* */
private String appSecret;
/**
* 商户号
*/
private String merchantId;
/**
* 商户API证书序列号
*/
private String merchantSerialNumber;
/**
* APIv3密钥
*/
private String apiV3Key;
/**
* apiclient_key.pem证书文件
*/
private String privateKeyPath;
/**
* apiclient_cert.pem证书文件
*/
private String privateCertPath;
/*
* privateCertificatePath
* */
private String privateCertificatePath;
}
微信支付通用工具类
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.UUID;
public class WxPayUtils {
private static Logger logger = LoggerFactory.getLogger(WxPayUtils.class);
/**
* 生成支付所需的参数
*
* @param appId 公众号的APP_ID
* @param prepay_id 下单接口返回的参数 预支付交易会话标识
* @param privateKeyPath 私钥地址
* @return JSONObject
* @throws Exception e
*/
public static JSONObject getTokenWeiXin(String appId, String prepay_id, String privateKeyPath) {
// 获取随机字符串
String nonceStr = getNonceStr();
// 获取微信小程序支付package
String packagestr = "prepay_id=" + prepay_id;
long timestamp = System.currentTimeMillis() / 1000;
//签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值
String message = buildMessageTwo(appId, timestamp, nonceStr, packagestr);
//获取对应的签名
String signature = sign(message.getBytes(StandardCharsets.UTF_8), privateKeyPath);
// 组装返回
JSONObject json = new JSONObject(new JSONObject());
json.put("appId", appId);
json.put("timeStamp", String.valueOf(timestamp));
json.put("nonceStr", nonceStr);
json.put("package", packagestr);
json.put("signType", "RSA");
json.put("paySign", signature);
return json;
}
/**
* @return
* @Author 神传子
* @MethodName decryptToString
* @Description //TODO 解密支付回调参数
* @Date 11:27 2023/5/23
* @Param aesKey ApiV3Key
* @Param associatedData 返回参数的【resource.associated_data】
* @Param nonce 返回参数的【resource.ciphertext】
* @Param ciphertext 返回参数的【resource.nonce】
*/
public static String decryptToString(byte[] aesKey, byte[] associatedData, byte[] nonce, String ciphertext)
throws GeneralSecurityException, IOException {
final int KEY_LENGTH_BYTE = 32;
final int TAG_LENGTH_BIT = 128;
if (aesKey.length != KEY_LENGTH_BYTE){
logger.info("无效的ApiV3Key,长度必须为32个字节");
return "";
}
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)), "utf-8");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
public static JSONObject doGet(String url) {
//获取DefaultHttpClient请求
HttpClient client = HttpClientBuilder.create().build();
HttpGet get = new HttpGet(url);
JSONObject response = null;
try {
HttpResponse res = client.execute(get);
if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
HttpEntity entity = res.getEntity();
// 返回json格式
String result = EntityUtils.toString(res.getEntity());
response = JSON.parseObject(result);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return response;
}
// 获取随机字符串
public static String getNonceStr() {
return UUID.randomUUID().toString()
.replaceAll("-", "")
.substring(0, 32);
}
/**
* 随机字符串 订单号
*
* @return
*/
public static String generateNonceStr() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
// 签名连接成一串
private static String buildMessageTwo(String appId, long timestamp, String nonceStr, String packag) {
return appId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ packag + "\n";
}
/**
* 生成签名
*
* @param message message
* @return String
*/
private static String sign(byte[] message, String privateKeyPath) {
PrivateKey merchantPrivateKey = null;
X509Certificate wechatPayCertificate = null;
try {
merchantPrivateKey = PemUtil.loadPrivateKey(
new FileInputStream(privateKeyPath));
/*wechatPayCertificate = PemUtil.loadCertificate(
new FileInputStream(wechatPayProperties.getPrivateCertificatePath()));*/
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Signature sign = null;
try {
sign = Signature.getInstance("SHA256withRSA");
//这里需要一个PrivateKey类型的参数,就是商户的私钥。
sign.initSign(merchantPrivateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
}
SslUtils 忽略安全证书请求
import javax.net.ssl.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class SslUtils {
private static void trustAllHttpsCertificates() throws Exception {
TrustManager[] trustAllCerts = new TrustManager[1];
TrustManager tm = new miTM();
trustAllCerts[0] = tm;
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
}
static class miTM implements TrustManager, X509TrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public boolean isServerTrusted(X509Certificate[] certs) {
return true;
}
public boolean isClientTrusted(X509Certificate[] certs) {
return true;
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType)
throws CertificateException {
return;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType)
throws CertificateException {
return;
}
}
/**
* 忽略HTTPS请求的SSL证书,必须在openConnection之前调用
*
* @throws Exception
*/
public static void ignoreSsl() throws Exception {
HostnameVerifier hv = new HostnameVerifier() {
@Override
public boolean verify(String urlHostName, SSLSession session) {
System.out.println("Warning: URL Host: " + urlHostName + " vs. " + session.getPeerHost());
return true;
}
};
trustAllHttpsCertificates();
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}
}
前置引入
@Resource
CloseableHttpClient httpClient;
@Resource
WechatPayProperties wechatPayProperties;
获取 openId
public JSONObject getOpenId(JSONObject jsonObject) {
log.info("========== getAppId 传入参数 ==========");
log.info(jsonObject);
log.info("======================================");
String url = "https://api.weixin.qq.com/sns/oauth2/access_token";
//前端回调拿到的 code
String code = jsonObject.getString("code");
final String APP_ID = wechatPayProperties.getAppId();
final String SECRET = wechatPayProperties.getAppSecret();
//获取openId
url += "?appid=" + APP_ID;
url += "&secret=" + SECRET;
url += "&grant_type=" + "authorization_code";
url += "&code=" + code;
log.info("【获取openid的路径】" + url);
JSONObject openIdInfo = WxPayUtils.doGet(url);
log.info("【调用获取openid的主体信息】" + openIdInfo);
if (openIdInfo.toJSONString().indexOf("openid") != -1) {
//openIdInfo会包含openId
return appletApiExecSuccess(openIdInfo, "获取openId成功");
} else {
return appletApiExecErr("获取openid失败");
}
}
发起支付,返回的是前端发起支付的参数
/**
* @return com.alibaba.fastjson.JSONObject
* @Author 神传子
* @MethodName payment
* @Description //TODO
* @Date 9:39 2023/3/31
* @Param [jsonObject]
*/
public JSONObject payment(JSONObject inObj) {
log.info("========== getAppId 传入参数 ==========");
log.info(inObj);
log.info("======================================");
// 构建POST请求
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
// 使用JSON库,构建请求参数对象
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
JSONObject attachObj = new JSONObject();
rootNode
// 直连商户号
.put("mchid", wechatPayProperties.getMerchantId())
// 应用ID
.put("appid", wechatPayProperties.getAppId())
// 商品描述
.put("description", "测试支付")
// 商户订单号
.put("out_trade_no", WxPayUtils.generateNonceStr())
// 通知地址
.put("notify_url", "http://******/zzapi/his/wx/notify/success")
.put("attach", attachObj.toJSONString());
// 订单金额对象
rootNode.putObject("amount")
// 总金额,单位为分
.put("total", 1);
//支付者
rootNode.putObject("payer")
//用户标识
.put("openid", inObj.getString("openId"));
try {
SslUtils.ignoreSsl();
} catch (Exception e) {
e.printStackTrace();
}
try {
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
// 执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
// 获取响应
JSONObject prepay_id_obj = JSON.parseObject(EntityUtils.toString(response.getEntity()));
System.out.println("【下单返回参数】" + prepay_id_obj);
if (prepay_id_obj.get("prepay_id") == null) {
return appletApiExecErr(prepay_id_obj.getString("message"));
}
//返回签名
String prepay_id = prepay_id_obj.getString("prepay_id");
JSONObject tokenWeiXin = WxPayUtils.getTokenWeiXin(wechatPayProperties.getAppId(), prepay_id, wechatPayProperties.getPrivateKeyPath());
return appletApiExecSuccess(tokenWeiXin, "获取支付信息成功");
} catch (IOException e) {
e.printStackTrace();
return appletApiExecErr("获取支付信息失败");
}
}
微信支付完成回调
public JSONObject notifySuccess(JSONObject inObj) {
log.info("========== 微信支付回调参数 ==========");
log.info(inObj);
log.info("======================================");
String method = Thread.currentThread().getStackTrace()[1].getMethodName();
try {
String key = wechatPayProperties.getApiV3Key();
String json = inObj.toString();
String associated_data = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.associated_data");
String ciphertext = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.ciphertext");
String nonce = (String) JSONUtil.getByPath(JSONUtil.parse(json), "resource.nonce");
String decryptData = WxPayUtils.decryptToString(
key.getBytes(StandardCharsets.UTF_8),
associated_data.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
//验签成功
JSONObject decryptDataObj = JSONObject.parseObject(decryptData, JSONObject.class);
System.out.println("【decryptDataObj】" + decryptDataObj);
//返回自定义参数
String attach = decryptDataObj.getString("attach");
JSONObject attachObj = JSONObject.parseObject(attach);
System.out.println(attachObj);
//decryptDataObj 为解码后的obj,其内容如下。之后便是验签成功后的业务处理
} catch (Exception e) {
log.info("{} ,parms{}, 异常:", method, inObj.toJSONString(), e);
}
JSONObject outObj = new JSONObject();
outObj.put("code", "SUCCESS");
outObj.put("message", "成功");
return outObj;
}
退费
public JSONObject refund(JSONObject inObj) {
String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
// 构建POST请求
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
// 使用JSON库,构建请求参数对象
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode
// 商户订单号
.put("transaction_id", "****************")
// 商户退款单号
.put("out_refund_no", "123457");
// 订单金额对象
rootNode.putObject("amount")
// 退款金额 单位:分
.put("refund", 1)
// 原订单金额 单位:分
.put("total", 1)
// 退款币种
.put("currency", "CNY");
try {
SslUtils.ignoreSsl();
} catch (Exception e) {
e.printStackTrace();
}
try {
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
// 执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
// 获取响应
JSONObject refundObj = JSONObject.parseObject(EntityUtils.toString(response.getEntity()));
System.out.println(refundObj);
if (refundObj.get("transaction_id") != null) {
return appletApiExecSuccess(refundObj, "发起退款成功");
}
} catch (Exception e) {
e.printStackTrace();
}
return appletApiExecErr("发起退款失败");
}
uniapp前端实现:uniapp + JSAPIv3