一、需要准备的资料
1.小程序AppID
如:wx2e56f5******
2.商户号
如:1641******
3.商户API私钥路径:什么是商户API证书?如何获取商户API证书? 获取文件如下图:
如:
本地路径:E:\Env\eladmin\cert\c2\apiclient_key.pem(本地部署写入绝对路径)
服务器路径:/data/cert/eladmin/c2/apiclient_key.pem(服务器写入相对路径)
4.商户证书序列号:登录商户平台【API安全】->【API证书】->【查看证书】,可查看商户API证书序列号。
如:4F24D009CDBC89A**********************
5.商户APIV3密钥:API v3密钥 - 通用规则 | 微信支付商户文档中心
如:tYsmXJrIr*****************
【商户平台】->【账户中心】->【API安全】的页面设置该密钥,请求才能通过微信支付的签名校验。密钥的长度为32个字节。
6.商户平台证书路径
如:
本地路径:E:\Env\eladmin\cert\c2\apiclient_cert.pem(本地部署写入绝对路径)
服务器路径:/data/cert/eladmin/c2/apiclient_cert.pem(服务器写入相对路径)
7.商户平台证书路径p12
如:
本地路径:E:\Env\eladmin\cert\c2\apiclient_cert.p12(本地部署写入绝对路径)
服务器路径:/data/cert/eladmin/c2/apiclient_cert.p12(服务器写入相对路径)
二、代码部分
1.pom.xml内加入微信支付扩展
com.github.wechatpay-apiv3
wechatpay-java
0.2.7
2.在resources的dev和prod内加入微信支付必填字段 #微信支付字段
wx:
merchantId: 164*******
privateKeyPath: E:\Env\eladmin\cert\c2\apiclient_key.pem
certPath: E:\Env\eladmin\cert\c2\apiclient_cert.pem
certP12Path: E:\Env\eladmin\cert\c2\apiclient_cert.p12
merchantSerialNumber: 4F24D009CDBC**********************
apiV3key: tYsmXJr******************
appId: wx2e56f5******
3.微信支付测试接口 3-1:测试下单接口 /api/weChatPay/ceshiJSAPI 3-2:测试微信支付结果回调地址,当微信支付后,微信方返回支付信息,在回调接口内触发业务逻辑 注:测试回调接口必须为线上接口,线下无法实现 可先在线上将微信返回数据打印到txt文件内,然后在本地测试回调接口,触发业务逻辑 /api/weChatPay/ceshiPayNotify(放入线上的测试回调接口) /api/weChatPay/payNotify(本地测试回调接口)--可在postman内测试 测试时,在Headers内Wechatpay-Serial、Wechatpay-Nonce、Wechatpay-Timestamp、Wechatpay-Signature必填上 微信返回的json,直接放入raw-json内进行测试 4.退款测试接口 /api/weChatPay/ceshiRefund(回调接口不是必填项,可用可不用)
WeChatPayService.java:可放在Service内
----------------------------------------------------
package me.zhengjie.modules.api.service;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Random;
@Service
@RequiredArgsConstructor
public class WeChatPayService {
/** 商户API私钥路径 */
@Value("${wx.privateKeyPath}") //可自己写死路径
public String privateKeyPath;
//=============生成签名=====开始===================================================
/**
* 作用:使用字段appId、timeStamp、nonceStr、package计算得出的签名值
* 场景:根据微信统一下单接口返回的 prepay_id 生成调启支付所需的签名值
* @param appId
* @param timestamp
* @param nonceStr
* @param pack package
* @return
* @throws Exception
*/
public String getSign(String appId, long timestamp, String nonceStr, String pack) throws Exception{
String message = buildMessage(appId, timestamp, nonceStr, pack);
String paySign= sign(message.getBytes("utf-8"));
return paySign;
}
private String buildMessage(String appId, long timestamp, String nonceStr, String pack) {
return appId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ pack + "\n";
}
private String sign(byte[] message) throws Exception{
Signature sign = Signature.getInstance("SHA256withRSA");
//这里需要一个PrivateKey类型的参数,就是商户的私钥。
sign.initSign(getPrivateKey(privateKeyPath));
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
/**
* 获取私钥。
*
* @param filename 私钥文件路径 (required)
* @return 私钥对象
*/
public static PrivateKey getPrivateKey(String filename) throws IOException {
String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
try {
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
/**
* 获取随机位数的字符串
* @param length
* @return
*/
public static String getRandomString(int length) {
String base = "abcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
//=============生成签名=====结束===================================================
/**
* 获取请求文体
* @param request
* @return
* @throws IOException
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
ServletInputStream stream = null;
BufferedReader reader = null;
StringBuffer sb = new StringBuffer();
try {
stream = request.getInputStream();
// 获取响应
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw new IOException("读取返回支付接口数据流出现异常!");
} finally {
reader.close();
}
return sb.toString();
}
}
Controller代码如下:
package me.zhengjie.modules.api.rest;
import cn.hutool.core.io.FileUtil;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.annotation.AnonymousAccess;
import me.zhengjie.annotation.Log;
import me.zhengjie.config.FileProperties;
import me.zhengjie.exception.BadRequestException;
import me.zhengjie.modules.api.service.WeChatPayService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@RestController
@RequiredArgsConstructor
@Api(tags = "1.微信小程序")
@RequestMapping("/api/weChatPay")
public class WeChatPayContoller {
private final WeChatPayService weChatPayService;
private final FileProperties fileProperties;
/**
* appID
*/
@Value("${wx.appId}") // 可自己写死--我引用的是自己写的配置内(写死的方式如:appId=wx12345678910)
public String appId;
/**
* 商户号
*/
@Value("${wx.merchantId}") // 可自己写死
public String merchantId;
/**
* 商户API私钥路径
*/
@Value("${wx.privateKeyPath}") //可自己写死
public String privateKeyPath;
/**
* 商户证书序列号
*/
@Value("${wx.merchantSerialNumber}") // 可自己写死
public String merchantSerialNumber;
/**
* 商户APIV3密钥
*/
@Value("${wx.apiV3key}") //可自己写死
public String apiV3key;
/**
* 商户平台证书路径
*/
@Value("${wx.certPath}") // 可自己写死
public String certPath;
/**
* 商户平台证书路径p12
*/
@Value("${wx.certP12Path}") //可自己写死
public String certP12Path;
private static Map configMap = new HashMap<>();
private static Config config = null;
private static JsapiService service = null;
private static RefundService refundService = null;
/**
* JSAPI 测试下单接口
*/
@PostMapping(value = "/ceshiJSAPI")
@Log("JSAPI 测试下单接口")
@ApiOperation("JSAPI 测试下单接口")
@AnonymousAccess
@Transactional(rollbackFor = Exception.class)
public JSONObject ceshiJSAPI() throws Exception {
if (config == null) {
config =
new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3key)
.build();
}
if (service == null) {
service = new JsapiService.Builder().config(config).build();
}
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(1);//金额,1为0.01元
request.setAmount(amount);
request.setAppid(appId);
request.setMchid(merchantId);
request.setDescription("测试商品标题");//下单标题
request.setNotifyUrl("https://www.ceshi123.com/api/weChatPay/ceshiPayNotify");//要求必须为线上接口
String ddh = weChatPayService.getRandomString(20);
request.setOutTradeNo(ddh);//订单号
Payer payer = new Payer();
payer.setOpenid("oMr**********");//openid
request.setPayer(payer);
PrepayResponse response = service.prepay(request);
JSONObject result = new JSONObject();
Long date = new Date().getTime();//获取当前时间戳
String nonceStr = weChatPayService.getRandomString(28);//随机字符串,长度为32个字符以下
result.put("timeStamp", date.toString());//时间戳
result.put("nonceStr", nonceStr);//随机字符串,长度为32个字符以下
result.put("package", "prepay_id=" + response.getPrepayId());//PrepayId
result.put("signType", "RSA");//签名类型
result.put("paySign", weChatPayService.getSign(appId, date, nonceStr, "prepay_id=" + response.getPrepayId()));//签名
return result;
}
/**
* 测试微信支付结果回调地址(放入线上测试)
*
* @param
*/
@PostMapping(value = "/ceshiPayNotify")
@Log("ceshiPayNotify方法")
@ApiOperation("ceshiPayNotify方法")
@AnonymousAccess
public void ceshi_payNotify(HttpServletRequest request) throws IOException {
//微信返回的证书序列号
String serialNo = request.getHeader("Wechatpay-Serial");
//微信返回的随机字符串
String nonceStr = request.getHeader("Wechatpay-Nonce");
//微信返回的时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
//微信返回的签名
String wechatSign = request.getHeader("Wechatpay-Signature");
String singType = request.getHeader("Wechatpay-Signature-Type");
String collect = request.getReader().lines().collect(Collectors.joining());
try {
FileUtil.writeUtf8String(collect, fileProperties.getPath().getPath() + "/huidiao-collect.txt");//打印看效果
FileUtil.writeUtf8String(serialNo, fileProperties.getPath().getPath() + "/huidiao-serialNo.txt");//打印看效果
FileUtil.writeUtf8String(singType, fileProperties.getPath().getPath() + "/huidiao-Signature-Type.txt");//打印看效果
FileUtil.writeUtf8String(nonceStr, fileProperties.getPath().getPath() + "/huidiao-nonceStr.txt");//打印看效果
FileUtil.writeUtf8String(timestamp, fileProperties.getPath().getPath() + "/huidiao-timestamp.txt");//打印看效果
FileUtil.writeUtf8String(wechatSign, fileProperties.getPath().getPath() + "/huidiao-wechatSign.txt");//打印看效果
//TODO 业务校验---可以写入自己的业务逻辑
} catch (Exception e) {
System.out.println("e = " + e);
FileUtil.writeUtf8String("22222222222222222222", fileProperties.getPath().getPath() + "/huidiao22.txt");//打印看效果
}
}
/**
* 微信支付结果回调地址(本地)
*
* @param request
* @return
*/
@PostMapping(value = "/payNotify")
@AnonymousAccess
@Transactional(rollbackFor = Exception.class)
public ResponseEntity