准备工作
微信支付开发前,需要先获取商家信息,包括商户号
、AppId
、证书和密钥
:
- 获取商户号:微信商户平台
申请成为商户
=>提交资料
=>签署协议
=>获取商户号
; - 获取AppID:微信公众平台
注册服务号
=>服务号认证
=>获取APPID
=>绑定商户号
; - 申请商户证书:登录商户平台 =>
选择 账户中心
=>安全中心
=>API安全
=>申请API证书 包括商户证书和商户私钥
; - 获取微信的证书:获取APIv3秘钥 登录商户平台 =>
选择 账户中心
=>安全中心
=>API安全
=>设置APIv3密钥
。
工具类
添加依赖
引入微信支付开放平台的 API 依赖,以便能够使用 Java 调用相关 API 接口。
微信支付 Java SDK 地址:
https://github.com/wechatpay-apiv3/wechatpay-java
<dependency><groupId>com.github.wechatpay-apiv3</groupId><artifactId>wechatpay-java</artifactId><version>0.2.12</version>
</dependency>
支付工具类
- JSAPI支付下单:生成预支付订单并返回支付参数
- 关闭订单
- 微信支付订单号查询订单
- 商户订单号查询订单
- 申请退款:微信支付订单号和商家订单号二选一;
- 退款查询
package com.tansci.utils;import com.alibaba.fastjson2.JSON;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.*;
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.QueryByOutRefundNoRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.system.ApplicationHome;import java.util.Objects;/*** @path:com.tansci.utils.WxPayUtil.java* @className:WxPayUtil.java* @description: 微信小程序支付工具类* @author:tanyp* @editNote:*/
@Slf4j
public class WxPayUtil {// appIDprivate static String appid = "wx1fdfgfh149c6353";// 商户号private static String merchantId = "124589286";// 商户证书序列号private static String merchantSerialNumber = "3D3D4ADM154FDG44DFG45GF1SDF4JFA8DF95";// 商户APIV3密钥private static String apiV3Key = "yyh1466255d14dggh524dg666983286";// 商户API私钥private static String privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPH5wD/SO6+3jMUdUio0awAwR+Ni7s22Csqk8EQLoYbeOzCN+4+bKJ4/jAuJ8CuqaCCCXtCDRlriLW35C7uzeuOiL3tN";// 支付回调地址private static String payNotifyUrl = "https://tansci.top/api/pay/payCallback";// 支付回调地址private static String refunNotifyUrl = "https://tansci.top/api/pay/refunCallback";private static JsapiServiceExtension jsapiService;private static RefundService refundService;public static void initConfig() {// 初始化商户配置Config config = new RSAAutoCertificateConfig.Builder().merchantId(merchantId).privateKey(privateKey).merchantSerialNumber(merchantSerialNumber).apiV3Key(apiV3Key).build();// 初始化服务jsapiService = new JsapiServiceExtension.Builder().config(config).build();refundService = new RefundService.Builder().config(config).build();}/*** @methodName:jsapi* @description:JSAPI支付下单* @author:tanyp* @Params: [orderId, goodsName, openId, amount]* @Return: java.lang.String* @editNote:*/public static Object jsapi(String orderId, String goodsName, String openId, Integer amount) {try {if (Objects.isNull(jsapiService)) {initConfig();}PrepayRequest request = new PrepayRequest();request.setAppid(appid);request.setMchid(merchantId);request.setOutTradeNo(orderId);request.setDescription(goodsName);request.setNotifyUrl(payNotifyUrl);// 支付者Payer payer = new Payer();payer.setOpenid(openId);request.setPayer(payer);// 订单金额,单位为分(正数)Amount _amount = new Amount();_amount.setCurrency("CNY");_amount.setTotal(amount);request.setAmount(_amount);log.info("JSAPI 支付下单请求参数:{}", JSON.toJSON(request));PrepayWithRequestPaymentResponse response = jsapiService.prepayWithRequestPayment(request);log.info("JSAPI 支付下单返回参数:{}", JSON.toJSON(response));return response;} catch (HttpException e) {// 发送HTTP请求失败log.error("支付异常,发送HTTP请求失败:{}", e);return e.getMessage();} catch (ServiceException e) {log.error("支付异常,服务返回状态异常:{}", e);return e.getErrorMessage();} catch (MalformedMessageException e) {log.error("支付异常,服务返回成功,返回体类型不合法,或者解析返回体失败:{}", e);return e.getMessage();}}/*** @methodName:closeOrder* @description:关闭订单* @author:tanyp* @Params: [orderId]* @Return: java.lang.String* @editNote:*/public static String closeOrder(String orderId) {try {if (Objects.isNull(jsapiService)) {initConfig();}CloseOrderRequest request = new CloseOrderRequest();request.setMchid(merchantId);request.setOutTradeNo(orderId);jsapiService.closeOrder(request);return "ok";} catch (HttpException e) {// 发送HTTP请求失败log.error("支付异常,发送HTTP请求失败:{}", e);return e.getMessage();} catch (ServiceException e) {log.error("支付异常,服务返回状态异常:{}", e);return e.getErrorMessage();} catch (MalformedMessageException e) {log.error("支付异常,服务返回成功,返回体类型不合法,或者解析返回体失败:{}", e);return e.getMessage();}}/*** @methodName:queryOrderById* @description:微信支付订单号查询订单* @author:tanyp* @Params: [transactionId]* @Return: com.wechat.pay.java.service.payments.model.Transaction* @editNote:*/public static Transaction queryOrderById(String transactionId) {try {if (Objects.isNull(jsapiService)) {initConfig();}QueryOrderByIdRequest request = new QueryOrderByIdRequest();request.setMchid(merchantId);request.setTransactionId(transactionId);return jsapiService.queryOrderById(request);} catch (HttpException e) {// 发送HTTP请求失败log.error("支付异常,发送HTTP请求失败:{}", e);} catch (ServiceException e) {log.error("支付异常,服务返回状态异常:{}", e);} catch (MalformedMessageException e) {log.error("支付异常,服务返回成功,返回体类型不合法,或者解析返回体失败:{}", e);}return null;}/*** @methodName:queryOrderByOutTradeNo* @description:商户订单号查询订单* @author:tanyp* @Params: [orderId]* @Return: com.wechat.pay.java.service.payments.model.Transaction* @editNote:*/public static Transaction queryOrderByOutTradeNo(String orderId) {try {if (Objects.isNull(jsapiService)) {initConfig();}QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();request.setMchid(merchantId);request.setOutTradeNo(orderId);return jsapiService.queryOrderByOutTradeNo(request);} catch (HttpException e) {// 发送HTTP请求失败log.error("支付异常,发送HTTP请求失败:{}", e);} catch (ServiceException e) {log.error("支付异常,服务返回状态异常:{}", e);} catch (MalformedMessageException e) {log.error("支付异常,服务返回成功,返回体类型不合法,或者解析返回体失败:{}", e);}return null;}/*** @methodName:refund* @description:申请退款* @author:tanyp* @Params: [transactionId, orderId, refunId, reason, refunAmount, payAmount]* @Return: com.wechat.pay.java.service.refund.model.Refund* @editNote:*/public static Refund refund(String transactionId, String orderId, String refunId, String reason, Integer refunAmount, Integer payAmount) {try {if (Objects.isNull(refundService)) {initConfig();}CreateRequest request = new CreateRequest();if (Objects.isNull(transactionId)) {request.setTransactionId(transactionId);}request.setOutTradeNo(orderId);request.setOutRefundNo(refunId);request.setNotifyUrl(refunNotifyUrl);request.setReason(reason);// 订单金额,单位为分(正数)AmountReq _amount = new AmountReq();_amount.setCurrency("CNY");_amount.setRefund(refunAmount.longValue());_amount.setTotal(payAmount.longValue());request.setAmount(_amount);return refundService.create(request);} catch (HttpException e) {// 发送HTTP请求失败log.error("退款异常,发送HTTP请求失败:{}", e);} catch (ServiceException e) {log.error("退款异常,服务返回状态异常:{}", e);} catch (MalformedMessageException e) {log.error("退款异常,服务返回成功,返回体类型不合法,或者解析返回体失败:{}", e);}return null;}/*** @methodName:queryByOutRefundNo* @description:退款查询* @author:tanyp* @Params: [refunId]* @Return: com.wechat.pay.java.service.refund.model.Refund* @editNote:*/public static Refund queryByOutRefundNo(String refunId) {try {if (Objects.isNull(refundService)) {initConfig();}QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();request.setOutRefundNo(refunId);return refundService.queryByOutRefundNo(request);} catch (HttpException e) {// 发送HTTP请求失败log.error("退款异常,发送HTTP请求失败:{}", e);} catch (ServiceException e) {log.error("退款异常,服务返回状态异常:{}", e);} catch (MalformedMessageException e) {log.error("退款异常,服务返回成功,返回体类型不合法,或者解析返回体失败:{}", e);}return null;}}
回调通知
创建一个公开的 HTTP 端点,接受来自微信支付的支付、退款回调通知。
PayController.java
package com.tansci.controller;import com.tansci.common.WrapMapper;
import com.tansci.common.Wrapper;
import com.tansci.service.PayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;/*** @ClassName: PayController.java* @Description: 支付、退款回调通知* @Author: tanyp**/
@RestController
@RequestMapping("/api/pay")
public class PayController {@Autowiredprivate PayService payService;@PostMapping("/payCallback")public Wrapper payCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {return WrapMapper.ok(payService.payCallback(request, response));}@PostMapping("/refunCallback")public Wrapper refunCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {return WrapMapper.ok(payService.refunCallback(request, response));}}
PayService.java
package com.tansci.service;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;public interface PayService {Object payCallback(HttpServletRequest request, HttpServletResponse response) throws Exception;Object refunCallback(HttpServletRequest request, HttpServletResponse response) throws Exception;}
PayServiceImpl.java
package com.tansci.service.impl;import com.alibaba.fastjson2.JSON;
import com.tansci.service.PayService;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
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.model.Transaction;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.Objects;import static com.wechat.pay.java.core.http.Constant.*;@Slf4j
@Service
public class PayServiceImpl implements PayService {// 商户号private static String merchantId = "124589286";// 商户证书序列号private static String merchantSerialNumber = "3D3D4ADM154FDG44DFG45GF1SDF4JFA8DF95";// 商户APIV3密钥private static String apiV3Key = "yyh1466255d14dggh524dg666983286";// 商户API私钥private static String privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPH5wD/SO6+3jMUdUio0awAwR+Ni7s22Csqk8EQLoYbeOzCN+4+bKJ4/jAuJ8CuqaCCCXtCDRlriLW35C7uzeuOiL3tN";@Overridepublic Object payCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {//读取请求体的信息ServletInputStream inputStream = request.getInputStream();StringBuffer stringBuffer = new StringBuffer();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String s;//读取回调请求体while ((s = bufferedReader.readLine()) != null) {stringBuffer.append(s);}String s1 = stringBuffer.toString();String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);String nonce = request.getHeader(WECHAT_PAY_NONCE);String signType = request.getHeader("Wechatpay-Signature-Type");String serialNo = request.getHeader(WECHAT_PAY_SERIAL);String signature = request.getHeader(WECHAT_PAY_SIGNATURE);// 如果已经初始化了 RSAAutoCertificateConfig,可直接使用// 没有的话,则构造一个NotificationConfig config = new RSAAutoCertificateConfig.Builder().merchantId(merchantId).privateKey(privateKey).merchantSerialNumber(merchantSerialNumber).apiV3Key(apiV3Key).build();// 初始化 NotificationParserNotificationParser parser = new NotificationParser(config);RequestParam requestParam = new RequestParam.Builder().serialNumber(serialNo).nonce(nonce).signature(signature).timestamp(timestamp)// 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048.signType(signType).body(s1).build();Transaction parse = parser.parse(requestParam, Transaction.class);log.info("支付回调参数:{}", JSON.toJSON(parse));try {/*** trade_state:* SUCCESS:支付成功* REFUND:转入退款* NOTPAY:未支付* CLOSED:已关闭* REVOKED:已撤销(付款码支付)* USERPAYING:用户支付中(付款码支付)* PAYERROR:支付失败(其他原因,如银行返回失败)*/if ("SUCCESS".equals(parse.getTradeState().toString())) {// 成功log.info("==========支付回调【支付成功】=============");} else if ("USERPAYING".equals(parse.getTradeState().toString())|| "NOTPAY".equals(parse.getTradeState().toString())) {// 未支付log.info("==========支付回调【未支付】=============");} else if ("PAYERROR".equals(parse.getTradeState().toString())|| "REVOKED".equals(parse.getTradeState().toString())|| "REFUND".equals(parse.getTradeState().toString())|| "CLOSED".equals(parse.getTradeState().toString())) {// 支付失败log.info("==========支付回调【支付失败】=============");}} catch (Exception e) {log.error("支付回调处理异常:{}", e);return HttpStatus.INTERNAL_SERVER_ERROR;}log.info("-----------------------支付回调完成-----------------------");return HttpStatus.OK;}@Overridepublic Object refunCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {//读取请求体的信息ServletInputStream inputStream = request.getInputStream();StringBuffer stringBuffer = new StringBuffer();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String s;//读取回调请求体while ((s = bufferedReader.readLine()) != null) {stringBuffer.append(s);}String s1 = stringBuffer.toString();String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);String nonce = request.getHeader(WECHAT_PAY_NONCE);String signType = request.getHeader("Wechatpay-Signature-Type");String serialNo = request.getHeader(WECHAT_PAY_SERIAL);String signature = request.getHeader(WECHAT_PAY_SIGNATURE);// 如果已经初始化了 RSAAutoCertificateConfig,可直接使用// 没有的话,则构造一个NotificationConfig config = new RSAAutoCertificateConfig.Builder().merchantId(merchantId).privateKey(privateKey).merchantSerialNumber(merchantSerialNumber).apiV3Key(apiV3Key).build();// 初始化 NotificationParserNotificationParser parser = new NotificationParser(config);RequestParam requestParam = new RequestParam.Builder().serialNumber(serialNo).nonce(nonce).signature(signature).timestamp(timestamp)// 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048.signType(signType).body(s1).build();RefundNotification parse = parser.parse(requestParam, RefundNotification.class);log.info("退款回调参数:{}", JSON.toJSON(parse));try {/*** refund_status:* SUCCESS:退款成功* CLOSED:退款关闭* ABNORMAL:退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往【商户平台—>交易中心】,手动处理此笔退款*/if ("SUCCESS".equals(parse.getRefundStatus().toString())) {// 成功log.info("==========退款回调【退款失败】=============");} else if ("CLOSED".equals(parse.getRefundStatus().toString()) || "ABNORMAL".equals(parse.getRefundStatus().toString())) {// 失败log.info("==========退款回调【退款失败】=============");}} catch (Exception e) {log.error("退款回调处理异常:{}", e);return HttpStatus.INTERNAL_SERVER_ERROR;}log.info("-----------------------退款回调完成-----------------------");return HttpStatus.OK;}}