SDK实现小程序加Java后端完成V3接口支付
1.前期准备
1.1业务流程图
1.2.概要流程
1、小程序,传递微信支付需要参数,比如订单号,使用者的OpenId,金额等等,调起统一支付接口进行预付下单
2、后端调用微信支付系统后生成6个必要参数返回给前端。
后台调用微信支付系统需要组装必要的参数:
{
"amount": {
"total": 1 //支付金额 单位为分
},
"mchid": "填写自己的商户号", //商户号
"description": "支付描述",//支付描述
"notify_url": "支付回调接收地址",//支付后回调接收地址
"payer": {
"openid": "当前使用者的openId" //当前用户的openID
},
"out_trade_no": "订单号,也就是业务订单号自己进行生成",//支付的订单号,支付后回调信息会将这个订单号带回来
"appid": "小程序的AppId" //小程序的APPID
}
3、6个必要参数用于小程序能否成功唤起微信支付
如下:
订单号、金额、openid等去请求微信下单接口,微信返回预支付交易会话标识prepay_id
后端给appid、timestamp、nonceStr、prepayId签名,并将签名、timestamp、nonceStr、prepay_id返回给小程序
{
"appId": "小程序的APPID",//小程序的APPID
"timeStamp": "时间戳",//时间戳
"nonceStr": "随机串,保证唯一",//随机串,保证唯一
"package": "",
"signType": "",//签名类型
"paySign": "" //签名信息
}
4、小程序调用wx.requestPayment拉起微信支付
5、用户支付后,微信支付系统会回调信息,后端接收,做对应的业务逻辑处理
1.3.微信支付前必要准备
1、微信官方文档–小程序支付接口文档描述(最终是组成概要流程中的参数形式):
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
2、微信官方文档–商户号及微信V3证书下载
https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_8_1.shtml
V3证书下载成功后文件(共3个)如下图:
apiclient_key.pem,apiclient_cert.pem,apiclient_cert.p12
1.4.所需要的所有参数文字说明
获取商户号
微信商户平台:https://pay.weixin.qq.com/ 步骤:申请成为商户 => 提交资料 => 签署协议 => 获取商户号
获取AppID
微信公众平台:https://mp.weixin.qq.com/ 步骤:注册服务号 => 服务号认证 => 获取APPID => 绑定商户号
申请商户证书
步骤:登录商户平台 => 选择 账户中心 => 安全中心 => API安全 => 申请API证书 包括商户证书和商户私钥
获取微信的证书
可以预先下载,也可以通过编程的方式获取。
获取APIv3秘钥(在微信支付回调通知和商户获取平台证书使用APIv3密钥)
步骤:登录商户平台 => 选择 账户中心 => 安全中心 => API安全 => 设置APIv3密钥
1.5.需要安装的Maven依赖包
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.9</version>
</dependency>
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-WxPay</artifactId>
<version>2.9.8</version>
</dependency>
因为IJPay-WxPay聚合支付每个版本他的SDK可能调用参数不一样,这里我调通的是2.9.8版本。
附上gitee地址:https://gitee.com/javen205/IJPay
也请大家给IJPay点点Star收藏一下
2.Java示例代码
2.1.Controller层
@RequiredArgsConstructor
@RestController
@RequestMapping("/payment")
public class WeChatPayController {
private final WechatPayService wxPayNewService;
/**
* 微信统一下单接口
*
* @param iWxPayParamVO 业务需要的参数
* @return
*/
@PostMapping("/doUnifiedOrder")
public Map<String, String> doUnifiedOrder() throws Exception {
return wxPayNewService.doUnifiedOrder();
}
/**
* 微信支付的回调接收接口
*
* @param request
* @param response
*/
@RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
public void callBack(HttpServletRequest request, HttpServletResponse response) {
wxPayNewService.callBack(request, response);
}
/**
* 生成v3证书
*/
@RequestMapping("/createPlatformCert")
@ResponseBody
public String createPlatformCert() throws IOException {
return wxPayNewService.createPlatformCert();
}
/**
* 通过订单号查询支付情况
*
* @param outTradeNo 订单号
* @return String
*/
@RequestMapping("/query")
@ResponseBody
public String query(@RequestParam String outTradeNo) {
return wxPayNewService.query(outTradeNo);
}
}
2.2.Service层
public interface WechatPayService {
/**
* 微信统一下单接口
*
* @return Map
* @throws Exception 异常
*/
Map<String, String> doUnifiedOrder() throws Exception;
/**
* 获取v3的证书
*
* @return String类型
* @throws IOException IO异常
*/
String createPlatformCert() throws IOException;
/**
* 微信支付回调接口
*
* @param request 请求
* @param response 详情
*/
void callBack(HttpServletRequest request, HttpServletResponse response);
/**
* 查询账单
*
* @param outTradeNo
* @return
*/
String query(String outTradeNo);
}
2.3.ServiceImpl层
@RequiredArgsConstructor
@Service
@Slf4j
public class WechatPayServiceImpl implements WechatPayService {
/**
* 微信用户openId
*/
private final String openId = "";
/**
* 这个是微信小程序的appid
*/
private final String appId = "";
/**
* 商户号Id
*/
private final String mchId = "";
/**
* 平台证书地址,平台证书需要手动下载或者使用v3接口进行下载
*/
private final String platformCertPath = "";
/**
* 客户证书地址,也就是apiclient_cert.pem的路径
*/
private final String apiClientCertPath = "";
/**
* 证书密钥地址,也就是apiclient_key.pem的路径
*/
private final String apiClientKeyPath = "";
/**
* 回调地址,需要进行公网上,开发环境建议使用内网穿透到,/payment/payNotify的路径中去
*/
private final String notifyUrl = "";
/**
* 微信统一下单接口
*
* @return Map
* @throws Exception 异常
*/
@Override
public Map<String, String> doUnifiedOrder() throws Exception {
//先写死1, 1就是1分钱,100=1元
int price = 1;
String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
.setAppid(appId)
.setMchid(mchId)
.setDescription("支付说明")
//这是随机数
.setOut_trade_no(PayKit.generateStr())
.setTime_expire(timeExpire)
.setAttach("附加说明")
//回调地址
.setNotify_url(notifyUrl)
.setAmount(new Amount().setTotal(price))
.setPayer(new Payer().setOpenid(openId));
log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
IJPayHttpResponse response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
BasePayApiEnum.JS_API_PAY.toString(),
mchId,
getSerialNumber(),
null,
apiClientKeyPath,
JSONUtil.toJsonStr(unifiedOrderModel)
);
// log.info("统一下单响应 {}", response);
Map<String, String> map = new HashMap<>(16);
//这个是证书文件,先写死,后续调整成读取证书文件的服务器存放地址,根据证书序列号查询对应的证书来验证签名结果
boolean verifySignatures = WxPayKit.verifySignature(response, platformCertPath);
log.info("verifySignature: {}", verifySignatures);
if (response.getStatus() == HttpServletResponse.SC_OK && verifySignatures) {
// 根据证书序列号查询对应的证书来验证签名结果
String body = response.getBody();
JSONObject jsonObject = JSONUtil.parseObj(body);
String prepayId = jsonObject.getStr("prepay_id");
// 私钥
map = WxPayKit.jsApiCreateSign(appId, prepayId, apiClientKeyPath);
// log.info("唤起支付参数:{}", map);
}
// todo 微信预支付的订单新入库,为了业务查询记录
return map;
}
/**
* 微信支付回调接口
*
* @param request 请求
* @param response 详情
*/
@Override
public void callBack(HttpServletRequest request, HttpServletResponse response) {
log.info("收到微信支付回调");
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);
// 根据证书序列号查询对应的证书来验证签名结果
String platformCertPath = this.platformCertPath;
//这个商户号对应的那个V3秘钥
String mckKey = "";
//需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, mckKey, platformCertPath);
log.info("支付通知明文 {}", plainText);
//这个就是具体的业务情况
savePayPlainText(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();
}
}
/**
* 查询账单
*
* @param outTradeNo
* @return
*/
@Override
public String query(String outTradeNo) {
try {
Map<String, String> params = new HashMap<>(16);
params.put("mchid", mchId);
log.info("统一下单参数 {}", JSONUtil.toJsonStr(params));
IJPayHttpResponse response = WxPayApi.v3(
RequestMethodEnum.GET,
WxDomainEnum.CHINA.toString(),
String.format(BasePayApiEnum.ORDER_QUERY_BY_OUT_TRADE_NO.toString(), outTradeNo),
wxPayConfig.getMchId(),
getSerialNumber(),
null,
wxPayConfig.getApiClientKeyPath(),
params
);
log.info("查询响应 {}", response);
if (response.getStatus() == HttpServletResponse.SC_OK) {
// 根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, wxPayConfig.getPlatformCertPath());
log.info("verifySignature: {}", verifySignature);
return response.getBody();
}
return JSONUtil.toJsonStr(response);
} catch (Exception e) {
log.error("系统异常", e);
return e.getMessage();
}
}
/**
* 获取v3的证书
*
* @return String类型
*/
@Override
public String createPlatformCert() {
//这个商户号对应的那个V3秘钥
String mckKey = "";
// 获取平台证书列表
try {
IJPayHttpResponse response = WxPayApi.v3(
RequestMethodEnum.GET,
WxDomainEnum.CHINA.toString(),
CertAlgorithmTypeEnum.getCertSuffixUrl(CertAlgorithmTypeEnum.RSA.getCode()),
mchId,
getSerialNumber(),
null,
apiClientKeyPath,
"",
AuthTypeEnum.RSA.getCode()
);
String serialNumber = response.getHeader("Wechatpay-Serial");
String body = response.getBody();
int status = response.getStatus();
log.info("serialNumber: {}", serialNumber);
log.info("status: {}", status);
// log.info("body: {}", body);
if (status == HttpServletResponse.SC_OK) {
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, mckKey, nonce, cipherText, platformCertPath);
log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
}
// 根据证书序列号查询对应的证书来验证签名结果
boolean verifySignature = WxPayKit.verifySignature(response, platformCertPath);
if (verifySignature) {
return body;
} else {
return "平台证书不正确";
}
} catch (Exception e) {
e.printStackTrace();
return "不能获取平台证书";
}
}
private String savePlatformCert(String associatedData, String apiKey3, String nonce, String cipherText, String certPath) {
try {
AesUtil aesUtil = new AesUtil(apiKey3.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
);
// log.info("获取证书key:{},保存路径platformCert:{}", publicKey, certPath);
log.info("保存路径platformCert:{}", certPath);
//将生成的证书写入指定路径,文件名为:cert.pem
FileOutputStream fos = new FileOutputStream(certPath);
fos.write(publicKey.getBytes());
fos.close();
// 获取平台证书序列号
X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
return certificate.getSerialNumber().toString(16).toUpperCase();
} catch (Exception e) {
log.error("写入证书错误:{}", e);
return e.getMessage();
}
}
/**
* 保存订单的支付通知明文
*
* @param plainText 纯文本
*/
private void savePayPlainText(String plainText) {
JSONObject jsonObject = JSONUtil.parseObj(plainText);
//这个就是发起订单时的那个订单号
String outTradeNo = jsonObject.getStr("out_trade_no");
//todo 把微信支付回调的明文消息存进数据库,方便后续校验查看
log.info("业务订单号,outTradeNo:{}", outTradeNo);
//todo 把微信支付后需要处理的具体业务处理了
}
/**
* 获取证书序列号
*
* @return String
*/
private String getSerialNumber() {
log.info("验证证书路径:{}", apiClientCertPath);
// 获取证书序列号
X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(apiClientCertPath));
String serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
log.info("获取证书序列号:{},", serialNo);
return serialNo;
}
}
2.4.调用成功后接口返回示例
doUnifiedOrder接口参数返回示例
{
"timeStamp": "",
"signType": "RSA",
"package": "prepay_id=",
"paySign": "",
"nonceStr": "",
"appId": ""
}
createPlatformCert接口参数返回示例
{
"data": [
{
"effective_time": "2023-10-17T17:25:40+08:00",
"encrypt_certificate": {
"algorithm": "",
"associated_data": "",
"ciphertext": "",
"nonce": ""
},
"expire_time": "2028-10-15T17:25:40+08:00",
"serial_no": ""
}
]
}
query接口参数返回示例
{
"amount": {
"currency": "CNY",
"payer_currency": "CNY",
"payer_total": 1,
"total": 1
},
"appid": "",
"attach": "",
"bank_type": "",
"mchid": "",
"out_trade_no": "",
"payer": {
"openid": ""
},
"promotion_detail": [],
"success_time": "2023-10-18T17:21:04+08:00",
"trade_state": "",
"trade_state_desc": "订单发生过退款,退款详情请查询退款单",
"trade_type": "JSAPI",
"transaction_id": ""
}
3.最后
到此为止微信支付后端Java编写完毕,完成统一下单接口、回调接口、查询账单接口、下载平台证书接口
在这感谢:https://blog.csdn.net/weixin_41451078/article/details/125134636提供思路
以及IJPay全体开发人员:https://gitee.com/javen205/IJPay