直接复制粘贴即可
java端使用的是springboot,前端使用的是uniapp。使用环境是公众号H5调用微信支付(JSAPI)。符合条件的可以直接复制粘贴。(按流程粘贴出来的代码)(最后有整体的代码)
按流程出来的代码
- (uniapp端)在前端访问后台接口
前端正常访问接口,传入参数有你商品的信息
transactionsH5Pay().then(res => {
在这接收参数,在第三步有体现
})
- java端 -第一步访问的接口
POM文件引入maven依赖
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.10</version>
</dependency>
uniapp端第一步访问的接口
@PostMapping("/gwc/weChat/pay/transactions/h5")
public AjaxResult weChatPayH5(HttpServletRequest res){
// 这是我的 根据token获取用户信息-可忽略
WxLoginUser wxUser = wxTokenService.getWxUser(res);
// 构建service
service = new JsapiService.Builder().config(config).build();
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
//下边的参数都是文档有的,金额,appid等,我写的静态的
amount.setTotal(100);
request.setAmount(amount);
request.setAppid(appid);
request.setMchid(merchantId);
request.setDescription("测试商品标题");
request.setNotifyUrl("回调地址");
request.setOutTradeNo("订单号");
Payer payer = new Payer();
payer.setOpenid("付钱人的openid");
request.setPayer(payer);
PrepayResponse response = service.prepay(request);
// 这就是JSAPI下单返回的会话标识,需要返回给前段,调支付用
System.out.println(response.getPrepayId());
return AjaxResult.success(response.getPrepayId());
}
// 里边会报错的依赖类我粘贴在这
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.core.Config;
private JsapiService service;
Config config = new RSAAutoCertificateConfig.Builder()
.merchantId("merchantId-商户号")
.privateKeyFromPath("私钥证书文件的地址")
.merchantSerialNumber("序列号-在微信支付里边获取,可以自己搜索一下")
.apiV3Key("私钥的key- 这个是自己在微信支付里边设置的")
.build();;
关于第二步里边几个参数的说明
appid 和 merchantId 这两个就不多说了。一个是公众号的appid在微信公众平台获取。一个是商户号在微信支付里边获取。
privateKeyPath 是你apiclient_key.pem的地址。
merchantSerialNumber 是序列号
apiV3Key 是证书的key
payNotifyUrl 是支付完后的回调地址-这是你自己的接口地址,用户支付完之后,微信调你的接口(外网需要可以访问到)
找不到的这个可以在网上搜索怎么找!
- uniapp端-接收参数,并访问后端获取支付所需的参数和签名。
// 第一步的方法
transactionsH5Pay().then(res => {
//第二步返回的参数
const prepayId = res.msg;
getPayConfigParams({
// 这是你当前页面的路径
webUrl: window.location.href,
// 返回的参数
extString: prepayId
}).then(res => {
//后续在这调起支付
})
})
- java端-接收路径参数和会话标识参数。(其中并没有存储,没有判断过期时间,大佬可自行添加,不添加没有影响)
@PostMapping("/pay/config/params")
private AjaxResult getPayConfigParams(@RequestBody PayUtil payUtil){
//接收参数的是实体类,就这两个字段,就不粘贴了
String webUrl = payUtil.getWebUrl();
String extString = payUtil.getExtString();
try{
//获取access_token
String grantParam = URLEncoder.encode("client_credential", "UTF-8");
String appidParam = URLEncoder.encode("你的appid", "UTF-8");
String secretParam = URLEncoder.encode("你的secret-都在微信公众平台获取", "UTF-8");
String fileUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type="+ grantParam +"&appid=" + appidParam + "&secret=" + secretParam;
URL url = new URL(fileUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
conn.disconnect();
JSONObject jsonObject = JSON.parseObject(response.toString());
// 获取到access_token
String accessToken = jsonObject.get("access_token").toString();
// 获取 ticket
String ticket= "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+ accessToken + "&type=jsapi";
URL urlTicket = new URL(ticket);
HttpURLConnection con = (HttpURLConnection) urlTicket.openConnection();
con.setRequestMethod("GET");
BufferedReader readerInfo = new BufferedReader(new InputStreamReader(con.getInputStream()));
String lineInfo;
StringBuilder responseIn = new StringBuilder();
while ((lineInfo = readerInfo.readLine()) != null) {
responseIn.append(lineInfo);
}
reader.close();
con.disconnect();
JSONObject jsonObjectinfo = JSON.parseObject(responseIn.toString());
// 获取到 ticket
String ticketStr = jsonObjectinfo.get("ticket").toString();
// 随机字符串
// 生成随机UUID并去掉其中的横杠
String uuid = UUID.randomUUID().toString().replace("-", "");
String randomStr = uuid.substring(0, 32);
// 时间戳
long currentTimestampMillis = System.currentTimeMillis();
long timestamp = currentTimestampMillis / 1000
//生成微信支付的签名
String generateSignature = generateSignature(appid, timestamp, randomStr,"prepay_id="+extString);
//返回前端的字段
Map<String,Object> map = new HashMap<>();
map.put("nonceStr", randomStr);
map.put("timestamp", timestamp);
map.put("signType", "RSA");
map.put("paySign", generateSignature);
return AjaxResult.success(map);
}catch (Exception e){
e.printStackTrace();
}
return AjaxResult.error();
}
// 封装的签名方法
private String generateSignature(String appId, long timestamp, String noncestr, String extString) {
try {
// 构建签名串
StringBuilder signatureStringBuilder = new StringBuilder();
signatureStringBuilder.append(appId).append("\n");
signatureStringBuilder.append(timestamp).append("\n");
signatureStringBuilder.append(noncestr).append("\n");
signatureStringBuilder.append(extString).append("\n");
String signatureData = signatureStringBuilder.toString();
Signature signature = Signature.getInstance("SHA256withRSA");
// 实际的私钥-方法在下边
PrivateKey privateKey = getPrivateKey();
signature.initSign(privateKey);
signature.update(signatureData.getBytes());
byte[] signatureBytes = signature.sign();
String base64Signature = Base64.getEncoder().encodeToString(signatureBytes);
return base64Signature;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private PrivateKey getPrivateKey() throws Exception {
// 上边对参数的说明的其中 apiclient_key.pem 里边的值
String privateKeyString = "-----BEGIN PRIVATE KEY-----\n" +
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDb4XGTiwe9W+t9\n" +
....
"dJNLl8NmXcRFHOgtk/w8eehrB1lK7LnXihHfSSxV0l43GArYOkJus7OvXjlneEXe\n" +
"T4NJ2NPqOIwKfo8gRKDfjv6zyRyR2TV8SE5f7wmeeXbJA4da4tAeOtjo562pkmON\n" +
"naIg+qhkrUuV8OwpjeR5mR4=\n" +
"-----END PRIVATE KEY-----\n";
// 去除首尾的多余内容,并生成PrivateKey对象
privateKeyString = privateKeyString.replaceAll("-----BEGIN PRIVATE KEY-----", "")
.replaceAll("-----END PRIVATE KEY-----", "").replaceAll("\n", "");
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyString);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
}
- uniapp端-调起支付
// 第一步的方法
transactionsH5Pay({你的商品信息}).then(res => {
//第二步返回的参数
const prepayId = res.msg;
getPayConfigParams({
// 这是你当前页面的路径
webUrl: window.location.href,
// 返回的参数
extString: prepayId
}).then(res => {
//后续在这调起支付
//注意:在我开发过程中发现一个问题,安卓可以调用成功,
//但是苹果手机报错:缺少参数timeStamp。
//这个问题是timeStamp的类型问题,要求是String类型,但是这里是long类型。
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId": "你的appid-也可以后端直接返回",
"timeStamp": res.data.timestamp,
"nonceStr": res.data.nonceStr,
"package": `prepay_id=${prepayId}`,
"signType": "RSA",
"paySign": res.data.paySign
},
function(resq) {
if (resq.err_msg == "get_brand_wcpay_request:ok") {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
console.log("支付结果回调:");
console.log(resq);
}
});
})
})
这五步就可以直接调起微信支付(V3-JSAPI支付)
整体代码
uniapp代码— 把第五步的内容 就是前端要写的代码
java代码 — 看着上边的可以直接复制粘贴好吧,勤快一点~~
以上就是调起支付的代码,支付完之后就牵扯到支付的回调
官方文档在这里:微信官方JSAPI支付通知
回调就对应着第二步 payNotifyUrl这个值。说白了就是一个接口地址(不要权限验证)。下边就是代码:
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
@PostMapping("/...../...../callback")
public Map<String, String> payNotifyInfo(@RequestBody JSONObject jsonObject){
System.out.println("------------------支付回调地址被调用-----------------------");
try{
// 你的秘钥,我是封装到实体中了
String key = wxPayConfig.getApiV3Key();
String json = jsonObject.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");
// 其中 AesUtil 这个类,在文章最下边。
String decryptData = new AesUtil(key.getBytes(StandardCharsets.UTF_8)).decryptToString(associated_data.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
//验签
JSONObject decryptDataObj = JSONObject.parseObject(decryptData, JSONObject.class);
//解码后的对象--下边的图片有体现
System.out.println(decryptDataObj);
//有你的订单号,下边就可以写自己的逻辑-----
***********
***********
***********
}catch(Exception e) {
e.printStackTrace();
}
Map<String, String> res = new HashMap<>();
res.put("code", "SUCCESS");
res.put("message", "成功");
return res;
}
// 其中所需要的maven依赖我写在这
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.0</version>
</dependency>
少了一个AesUtil,以下是该类的代码,感谢 YLJisKing666666 的提醒:
package com.ruoyi.system.util;
import java.io.IOException;
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)), "utf-8");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
}