一、需要准备的资料
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,直接放入postman软件的body内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}") // 可自己写死--我引用的是自己写的配置内(写死的方式如:public String 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
- //获取报文
- String body = weChatPayService.getRequestBody(request);
- //随机串
- String nonce = request.getHeader("Wechatpay-Nonce");
- //微信传递过来的签名
- String signature = request.getHeader("Wechatpay-Signature");
- //证书序列号(微信平台)
- String wechatPaySerial = request.getHeader("Wechatpay-Serial");
- String singType = request.getHeader("Wechatpay-Signature-Type");
- //时间戳
- String timestamp = request.getHeader("Wechatpay-Timestamp");
- if (configMap.get("config") == null) {
- config = new RSAAutoCertificateConfig.Builder()
- .merchantId(merchantId)
- .privateKeyFromPath(privateKeyPath)
- .merchantSerialNumber(merchantSerialNumber)
- .apiV3Key(apiV3key)
- .build();
- configMap.put("config", config);
- }
- else {
- config = configMap.get("config");
- }
- NotificationParser parser = new NotificationParser((NotificationConfig) config);
- RequestParam.Builder builder = new RequestParam.Builder();
- builder.body(body);
- builder.signature(signature);
- builder.nonce(nonce);
- builder.timestamp(timestamp);
- builder.serialNumber(wechatPaySerial);
- builder.signType(singType);
- RequestParam requestParam = builder.build();
-
- Transaction parse = parser.parse(requestParam, Transaction.class);
- if ("SUCCESS".equals(parse.getTradeState().toString())) {
- //支付成功,你的业务逻辑
-
- return new ResponseEntity<>(HttpStatus.OK);
-
- } else {
- throw new BadRequestException( "支付失败");
- }
- }
-
-
- /** 测试退款申请
- * @return*/
- @PostMapping(value = "/ceshiRefund")
- @Log("JSAPI 测试退款接口")
- @ApiOperation("JSAPI 测试退款接口")
- @AnonymousAccess
- @Transactional(rollbackFor = Exception.class)
- public JSONObject ceshiRefund(String No) throws Exception {
- if (config == null) {
- config = new RSAAutoCertificateConfig.Builder()
- .merchantId(merchantId)
- .privateKeyFromPath(privateKeyPath)
- .merchantSerialNumber(merchantSerialNumber)
- .apiV3Key(apiV3key)
- .build();
- }
- if (refundService == null) {
- // 初始化服务
- refundService = new RefundService.Builder().config(config).build();
- }
- CreateRequest request = new CreateRequest();
- request.setOutTradeNo(No);//商户订单号
- request.setOutRefundNo(weChatPayService.getRandomString(16));//商户退款单号,随机生成
- request.setReason("取消订单的退款");//退款原因
- AmountReq amount = new AmountReq();//金额信息
- amount.setRefund(Long.parseLong("1"));//退款金额(0.01元)
- amount.setTotal(Long.parseLong("1"));//原订单退款金额(0.01元)
- amount.setCurrency("CNY");//人民币
- request.setAmount(amount);
-
- // request.setNotifyUrl("https://https://www.ceshi123.com/api/weChatPay/ceshiRefundNotify");//回调接口,退款时非必填--这里我就没写接口
-
- JSONObject result = new JSONObject();
- try {
- Refund response = refundService.create(request);
- result.put("code", "SUCCESS");
- result.put("message", "退款成功");
- return result;
- } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
- // 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义
- return JSONObject.parseObject(e.getResponseBody());
- }
- }
-
- @PostMapping(value = "/cancelWorder")
- @Log("取消订单)")
- @ApiOperation("取消订单")
- @Transactional(rollbackFor = Exception.class)
- public ResponseEntity
- Long worderId=resources.getWorderId();//订单id
- OwWorderDto owWorderDto = owWorderService.findById(worderId);//根据订单id,获取订单信息
-
- //微信退款==开始
- WeChatRefundVo weChatRefundVo=new WeChatRefundVo();
- weChatRefundVo.setOutTradeNo(owWorderDto.getWorderNo().toString());//商户订单号
- weChatRefundVo.setRefund(owWorderDto.getMoney());//退款金额
- weChatRefundVo.setTotal(owWorderDto.getMoney());//原订单退款金额
- JSONObject json=weChatRefund(weChatRefundVo);
- System.out.println(json);
- System.out.println("=======================code========"+json.getString("code"));
- System.out.println("=======================message========"+json.getString("message"));
- if(json.getString("message").equals("订单已全额退款") || json.getString("code").equals("SUCCESS"))
- {
- //业务逻辑部分,改变订单状态等操作
-
- return new ResponseEntity<>(HttpStatus.OK);
- }
- else
- {
- throw new BadRequestException(json.getString("message"));
- }
- return new ResponseEntity<>(HttpStatus.NO_CONTENT);
- }
-
- }
- WeChatRefundVo取消订单时,用到的对象:
-
-
- package me.zhengjie.modules.api.service.dto;
-
- import lombok.Data;
-
- import java.math.BigDecimal;
-
- @Data
- public class WeChatRefundVo {
-
- // 商户订单号
- private String outTradeNo;
-
- // 退款金额
- private BigDecimal refund;
-
- //原订单金额
- private BigDecimal total;
-
-
- }