release: 使用IJPay集成微信支付app

release-2024-04-25
huangyw 2 months ago
parent 32fe7eacfc
commit a92790886a

@ -171,6 +171,12 @@
<version>3.9.1</version> <version>3.9.1</version>
</dependency> </dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-WxPay</artifactId>
<version>2.9.6</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
<finalName>app</finalName> <finalName>app</finalName>

@ -2,6 +2,8 @@ package com.luoo.user;
import com.luoo.user.listener.JPAEntityListener; import com.luoo.user.listener.JPAEntityListener;
import com.spring4all.mongodb.EnableMongoPlus; import com.spring4all.mongodb.EnableMongoPlus;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -72,4 +74,9 @@ public class UserApplication {
return new RedisLockUtil(); return new RedisLockUtil();
} }
@Bean
public CloseableHttpClient httpClient() {
return HttpClients.createDefault();
}
} }

@ -0,0 +1,55 @@
package com.luoo.user.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayConfig {
/**
* appId
*/
private String appId;
/**
*
*/
private String mchId;
/**
*
*/
private String mchSerialNo;
/**
* apiv3
*/
private String apiV3Key;
/**
*
*/
private String notifyUrl;
/**
* url
*/
// private String wxJsapiUrl;
/**
*
*/
private String privateKeyPath;
/**
*
*/
private String privateCertPath;
/**
*
*/
private String platformCertPath;
}

@ -0,0 +1,143 @@
package com.luoo.user.controller;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.core.utils.DateTimeZoneUtil;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
import com.ijpay.wxpay.model.UnifiedOrderModel;
import com.luoo.user.config.WxPayConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
/**
* @program: luoo_parent
* @description:
* @author: yawei.huang
* @create: 2024-11-12 11:59
**/
@RestController
@CrossOrigin
@RequestMapping("/wx/pay")
@Slf4j
public class WxPayController {
@Resource
private WxPayConfig wxPayConfig;
//支付
@RequestMapping("/app")
@ResponseBody
public String jsApiPay() {
try {
String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(wxPayConfig.getAppId())
.setMch_id(wxPayConfig.getMchId())
.setOut_trade_no(PayKit.generateStr())
.setTime_expire(timeExpire)
.setAttach("微信系开发脚手架 https://gitee.com/javen205/TNWX")
.setNotify_url(wxPayConfig.getNotifyUrl())
.setTotal_fee("1");
Map<String, Object> params = new HashMap<>();
params.put("appid", wxPayConfig.getAppId());
params.put("mchid", wxPayConfig.getMchId());
params.put("description", "雀乐会员充值");
params.put("out_trade_no", PayKit.generateStr());
params.put("time_expire", timeExpire);
params.put("notify_url", "https://www.baidu.com");
params.put("amount", new HashMap<String, Object>() {{
put("total", 1);
put("currency", "CNY");
}});
log.info("统一下单参数 {}", JSONUtil.toJsonStr(params));
IJPayHttpResponse response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
BasePayApiEnum.APP_PAY.toString(),
wxPayConfig.getMchId(),
wxPayConfig.getMchSerialNo(),
null,
wxPayConfig.getPrivateKeyPath(),
JSONUtil.toJsonStr(params)
);
log.info("统一下单响应 {}", response);
// 根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, wxPayConfig.getPlatformCertPath());
log.info("verifySignature: {}", verifySignature);
if (response.getStatus() == 200 && verifySignature) {
String body = response.getBody();
JSONObject jsonObject = JSONUtil.parseObj(body);
String prepayId = jsonObject.getStr("prepay_id");
Map<String, String> map = WxPayKit.jsApiCreateSign(wxPayConfig.getAppId(), prepayId, wxPayConfig.getPrivateKeyPath());
log.info("唤起支付参数:{}", map);
return JSONUtil.toJsonStr(map);
}
return JSONUtil.toJsonStr(response);
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
}
@RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
@ResponseBody
public void payNotify(HttpServletRequest request, HttpServletResponse response) {
Map<String, String> map = new HashMap<>(12);
try {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = HttpKit.readData(request);
log.info("支付通知密文 {}", result);
// 需要通过证书序列号查找对应的证书verifyNotify 中有验证证书的序列号
String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
wxPayConfig.getApiV3Key(), wxPayConfig.getPlatformCertPath());
log.info("支付通知明文 {}", plainText);
if (StrUtil.isNotEmpty(plainText)) {
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
} else {
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "签名错误");
}
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
} catch (Exception e) {
e.printStackTrace();
}
}
}

@ -0,0 +1,105 @@
package com.luoo.user.service;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.kit.AesUtil;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.OtherApiEnum;
import com.luoo.user.config.WxPayConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.FileWriter;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
/**
* @program: luoo_parent
* @description:
* @author: yawei.huang
* @create: 2024-11-12 12:00
**/
@Slf4j
@Service
public class WxPayService {
@Resource
private WxPayConfig wxPayConfig;
public void v3Get() {
// 获取平台证书列表
try {
IJPayHttpResponse response = WxPayApi.v3(
RequestMethodEnum.GET,
WxDomainEnum.CHINA.toString(),
OtherApiEnum.GET_CERTIFICATES.toString(),
wxPayConfig.getMchId(),
wxPayConfig.getMchSerialNo(),
null,
wxPayConfig.getPrivateKeyPath(),
""
);
String timestamp = response.getHeader("Wechatpay-Timestamp");
String nonceStr = response.getHeader("Wechatpay-Nonce");
String serialNumber = response.getHeader("Wechatpay-Serial");
String signature = response.getHeader("Wechatpay-Signature");
String body = response.getBody();
int status = response.getStatus();
log.info("serialNumber: {}", serialNumber);
log.info("status: {}", status);
log.info("body: {}", body);
if (status == 200) {
JSONObject jsonObject = JSONUtil.parseObj(body);
JSONArray dataArray = jsonObject.getJSONArray("data");
// 默认认为只有一个平台证书
JSONObject encryptObject = dataArray.getJSONObject(0);
JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
String associatedData = encryptCertificate.getStr("associated_data");
String cipherText = encryptCertificate.getStr("ciphertext");
String nonce = encryptCertificate.getStr("nonce");
String serialNo = encryptObject.getStr("serial_no");
final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, wxPayConfig.getPlatformCertPath());
log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
}
// 根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, wxPayConfig.getPlatformCertPath());
System.out.println("verifySignature:" + verifySignature);
} catch (Exception e) {
e.printStackTrace();
}
}
private String savePlatformCert(String associatedData, String nonce, String cipherText, String certPath) {
try {
AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
// 平台证书密文解密
// encrypt_certificate 中的 associated_data nonce ciphertext
String publicKey = aesUtil.decryptToString(
associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
cipherText
);
// 保存证书
FileWriter writer = new FileWriter(certPath);
writer.write(publicKey);
writer.close();
// 获取平台证书序列号
X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
return certificate.getSerialNumber().toString(16).toUpperCase();
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
}
}

@ -35,4 +35,16 @@ mybatis-plus:
pagehelper: pagehelper:
helperDialect: mysql helperDialect: mysql
supportMethodsArguments: true supportMethodsArguments: true
params: count=countSql params: count=countSql
wx:
pay:
appId: wxae6fb76efa147314
mchId: 1676389229
mchSerialNo: 541E42E46254E026DE1E1D13602865CE049866C2
apiV3Key: Indiecn2024181189288661529541569
notifyUri: https://beta.indie.cn/user/wx/pay/payNotify
privateKeyPath: D:\apiclient_key.pem
privateCertPath: D:\apiclient_cert.pem
platformCertPath: D:\apiclient_cert.pem

Loading…
Cancel
Save