引入依赖
<dependency> <groupId>com.github.javen205</groupId> <artifactId>IJPay-WxPay</artifactId> <version>2.9.6</version> </dependency>
去微信下载证书文件 开发指引 - 小程序支付 | 微信支付商户文档中心 (qq.com)
配置证书并读取
创建: wxpay_v3.properties
#服务商/直连商户平台 公众号appid
v3.appId=wx5316............
#秘钥
v3.keyPath=cert/apiclient_key.pem
#平台证书路径
v3.certPath=cert/apiclient_cert.pem
#CA证书 格式.pemxq
v3.certP12Path=cert/apiclient_cert.p12
#CA证书 格式.p12
v3.platformCertPath=cert/platformCert.pem
#服务商id/商户id
v3.mchId=160.........
#自定义 apiv3 秘钥
v3.apiKey3=52UJDFCAP.....................
#自定义 api 秘钥
v3.apiKey=2542a43.........................
#项目域名(测试,用不着)
#项目域名(正式)
v3.domain=http://223.00.00.00:6002/iot-sim
读取配置文件的配置类
创建配置类 WxPayV3Bean
package com.baymax.vpp.cop.pay.constant;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
/**
* <p>微信配置 Bean</p>
*/
@Component
@PropertySource("classpath:/production/wxpay_v3.properties")
@ConfigurationProperties(prefix = "v3")
public class WxPayV3Bean {
// 服务商的appId
private String appId;
// 服务商私钥
private String keyPath;
// 服务商证书序列号 平台证书路径
private String certPath;
// CA证书 格式.pemxq
private String certP12Path;
//CA证书 格式.p12
private String platformCertPath;
//服务商商号
private String mchId;
// 自定义 api 秘钥
private String apiKey;
//服务商APIv3 #自定义 apiv3 秘钥
private String apiKey3;
private String domain;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getKeyPath() {
return keyPath;
}
public void setKeyPath(String keyPath) {
this.keyPath = keyPath;
}
public String getCertPath() {
return certPath;
}
public void setCertPath(String certPath) {
this.certPath = certPath;
}
public String getCertP12Path() {
return certP12Path;
}
public void setCertP12Path(String certP12Path) {
this.certP12Path = certP12Path;
}
public String getPlatformCertPath() {
return platformCertPath;
}
public void setPlatformCertPath(String platformCertPath) {
this.platformCertPath = platformCertPath;
}
public String getMchId() {
return mchId;
}
public void setMchId(String mchId) {
this.mchId = mchId;
}
public String getApiKey() {
return apiKey;
}
public void setApiKey(String apiKey) {
this.apiKey = apiKey;
}
public String getApiKey3() {
return apiKey3;
}
public void setApiKey3(String apiKey3) {
this.apiKey3 = apiKey3;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
@Override
public String toString() {
return "WxPayV3Bean{" +
"keyPath='" + keyPath + '\'' +
", certPath='" + certPath + '\'' +
", certP12Path='" + certP12Path + '\'' +
", platformCertPath='" + platformCertPath + '\'' +
", mchId='" + mchId + '\'' +
", apiKey='" + apiKey + '\'' +
", apiKey3='" + apiKey3 + '\'' +
", domain='" + domain + '\'' +
'}';
}
}
获取 微信调用接口的 CloseableHttpClient
private CloseableHttpClient httpClientCreat() {
String merchantId = wxPayV3Bean.getMchId();
String merchantSerialNumber = WechatPayUtils.getSerialNumber();
String apiV3Key = wxPayV3Bean.getApiKey3();
try {
PrivateKey merchantPrivateKey = WechatPayUtils.getPrivateKey();
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(merchantId, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), apiV3Key.getBytes("utf-8"));
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
return httpClient;
} catch (Exception e) {
System.out.println(e);
}
return null;
}
获取私钥 PrivateKey 、获取证书序列号、 从证书管理器中获取 verifier
package com.baymax.vpp.cop.pay.utils;
import cn.hutool.core.util.StrUtil;
import com.ijpay.core.kit.PayKit;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.springframework.core.io.ClassPathResource;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
public class WechatPayUtils {
// 获取私钥 返回字符串
public static String getPrivateKeyStr() {
try {
ClassPathResource classPathResource = new ClassPathResource("/cert/apiclient_key.pem");
InputStream inputStream = classPathResource.getInputStream();
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(inputStream);
return merchantPrivateKey.toString();
} catch (Exception e) {
System.out.println(e);
}
return "";
}
// 获取私钥 PrivateKey
public static PrivateKey getPrivateKey() {
try {
ClassPathResource classPathResource = new ClassPathResource("/cert/apiclient_key.pem");
InputStream inputStream = classPathResource.getInputStream();
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(inputStream);
return merchantPrivateKey;
} catch (Exception e) {
System.out.println(e);
}
return null;
}
// 获取证书序列号
public static String getSerialNumber() {
String serialNo = "";
if (StrUtil.isEmpty(serialNo)) {
try {
//docker打包上传到Linux后,获取不到路径,改为以下方法
ClassPathResource resource = new ClassPathResource("cert/apiclient_cert.pem");
InputStream in = resource.getInputStream();
// 获取证书序列号
X509Certificate certificate = PayKit.getCertificate(in);
serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
} catch (Exception e) {
}
}
return serialNo;
}
//从证书管理器中获取 verifier
public static Verifier getVerifier(String mchId, String apiKey3) throws Exception {
String serialnumber = getSerialNumber();
//获取商户私钥
PrivateKey merchantPrivateKey = getPrivateKey();
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId, new PrivateKeySigner(serialnumber, merchantPrivateKey)), apiKey3.getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
Verifier verifier = certificatesManager.getVerifier(mchId);
return verifier;
}
}
调用微信接口
基础下单接口
微信支付订单号查询订单
商户订单号查询订单
微信小程序-退款申请
查询单笔退款(通过商户退款单号)
package com.baymax.vpp.cop.pay.wechat.pay;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.io.IOException;
import java.security.PrivateKey;
import java.text.MessageFormat;
/**
* 微信支付接口封装
*
* @author hawods
* @version 2023-09-04
*/
@Slf4j
@Service
public class WechatPayManager {
@Resource
WxPayV3Bean wxPayV3Bean;
/**
* 基础下单接口
*
* @param request
* @return
*/
public PrepayResponse prepay(PrepayRequest request) {
CloseableHttpClient httpClient = httpClientCreat();
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
String requestBody = JsonUtil.toJson(request);
log.debug("微信支付请求体: {}", requestBody);
httpPost.setEntity(new StringEntity(requestBody, "UTF-8"));
int responseStatus;
String responseBody;
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
responseStatus = response.getStatusLine().getStatusCode();
responseBody = EntityUtils.toString(response.getEntity());
} catch (IOException e) {
throw new ServiceException("微信支付请求失败", e);
}
if (responseStatus != HttpStatus.OK.value() || StrUtil.isBlank(responseBody)) {
log.error("微信支付请求异常,响应体: {}", responseBody);
Object message = JsonUtil.toMap(responseBody).get("message");
throw new ServiceException("微信支付请求异常: " + message);
}
log.debug("微信支付请求响应: {}", responseBody);
return JsonUtil.parse(responseBody, PrepayResponse.class);
}
/**
* 微信支付订单号查询订单
*
* @param transactionId 微信支付订单号查询订单
* @return
*/
public Transaction queryOrder(String transactionId) {
try {
CloseableHttpClient httpClient = httpClientCreat();
//请求URL
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/id/" + transactionId);
uriBuilder.setParameter("mchid", wxPayV3Bean.getMchId());
//完成签名并执行请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());
if (statusCode == HttpStatus.OK.value() || statusCode == HttpStatus.NO_CONTENT.value()) {
if (StrUtil.isBlank(responseBody)) {
log.error("微信查询支付异常,响应体: {}", responseBody);
Object message = JsonUtil.toMap(responseBody).get("message");
throw new ServiceException("微信查询支付异常: " + message);
}
log.debug("微信支付请求响应: {}", responseBody);
return JsonUtil.parse(responseBody, Transaction.class);
}
} catch (Exception e) {
throw new ServiceException("微信支付请求失败", e);
}
return null;
}
/**
* 商户订单号查询订单
*
* @param outTradeNo 商户订单号查询订单
* @return
*/
public Transaction queryOrderByOutTradeNo(String outTradeNo) {
try {
CloseableHttpClient httpClient = httpClientCreat();
//请求URL
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo);
uriBuilder.setParameter("mchid", wxPayV3Bean.getMchId());
//完成签名并执行请求
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
String responseBody = EntityUtils.toString(response.getEntity());
if (statusCode == HttpStatus.OK.value() || statusCode == HttpStatus.NO_CONTENT.value()) {
if (StrUtil.isBlank(responseBody)) {
log.error("微信查询支付异常,响应体: {}", responseBody);
Object message = JsonUtil.toMap(responseBody).get("message");
throw new ServiceException("微信查询支付异常: " + message);
}
log.debug("微信支付请求响应: {}", responseBody);
return JsonUtil.parse(responseBody, Transaction.class);
}
} catch (Exception e) {
throw new ServiceException("微信支付请求失败", e);
}
return null;
}
/**
* 微信小程序-退款申请
*
* @param request
* @return
*/
public ChinaUmsRefundVo refundWechat(RefundRequest request) {
CloseableHttpClient httpClient = httpClientCreat();
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
String requestBody = JsonUtil.toJson(request);
log.debug("微信退款请求体: {}", requestBody);
httpPost.setEntity(new StringEntity(requestBody, "UTF-8"));
int responseStatus;
String responseBody;
CloseableHttpResponse response = null;
ChinaUmsRefundVo chinaUmsRefundVo = new ChinaUmsRefundVo();
try {
response = httpClient.execute(httpPost);
responseStatus = response.getStatusLine().getStatusCode();
responseBody = EntityUtils.toString(response.getEntity());
} catch (IOException e) {
chinaUmsRefundVo.setIsSuccess(false);
throw new ServiceException("微信退款请求失败", e);
}
if (responseStatus != HttpStatus.OK.value() || StrUtil.isBlank(responseBody)) {
chinaUmsRefundVo.setIsSuccess(false);
log.error("微信退款请求异常,响应体: {}", responseBody);
Object message = JsonUtil.toMap(responseBody).get("message");
throw new ServiceException("微信退款请求异常: " + message);
}
log.debug("微信退款请求响应: {}", responseBody);
chinaUmsRefundVo.setRefundAmount(Long.valueOf(String.valueOf(request.getAmount().getRefund())));
chinaUmsRefundVo.setIsSuccess(true);
chinaUmsRefundVo.setErrCode(null);
chinaUmsRefundVo.setErrMsg(null);
return chinaUmsRefundVo;
}
/**
* 查询单笔退款(通过商户退款单号)
*
* @param outRefundNo
* @return
*/
public WechatRefundVO selectRefundByOutRefundNo(String outRefundNo) {
CloseableHttpClient httpClient = httpClientCreat();
String uriPattern = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/" + outRefundNo;
String uri = MessageFormat.format(
uriPattern,
URLUtil.encodePathSegment(outRefundNo)
);
HttpGet httpGet = new HttpGet(uri);
httpGet.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
httpGet.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
int responseStatus;
String responseBody;
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
responseStatus = response.getStatusLine().getStatusCode();
responseBody = EntityUtils.toString(response.getEntity());
} catch (IOException e) {
throw new ServiceException("查询单笔退款失败", e);
}
if (responseStatus != HttpStatus.OK.value() || StrUtil.isBlank(responseBody)) {
log.error("查询单笔退款异常,响应体: {}", responseBody);
Object message = JsonUtil.toMap(responseBody).get("message");
throw new ServiceException("查询单笔退款异常: " + message);
}
log.debug("查询单笔退款响应: {}", responseBody);
return JSONObject.parseObject(responseBody, WechatRefundVO.class);
}
private CloseableHttpClient httpClientCreat() {
String merchantId = wxPayV3Bean.getMchId();
String merchantSerialNumber = WechatPayUtils.getSerialNumber();
String apiV3Key = wxPayV3Bean.getApiKey3();
try {
PrivateKey merchantPrivateKey = WechatPayUtils.getPrivateKey();
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(merchantId, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), apiV3Key.getBytes("utf-8"));
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
return httpClient;
} catch (Exception e) {
System.out.println(e);
}
return null;
}
}
对发起支付参数进行签名
private PrepayResponseDto sign(PrepayResponseDto response) {
StringBuilder sb = new StringBuilder();
sb.append(response.getAppId()).append("\n")
.append(response.getTimeStamp()).append("\n")
.append(response.getNonceStr()).append("\n")
.append(response.getPackageStr()).append("\n");
// String paySign = wechatPayManager.sign(sb.toString());
PrivateKey privateKey = WechatPayUtils.getPrivateKey();
try {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(sb.toString().getBytes(StandardCharsets.UTF_8));
String paySign = Base64.encode(signature.sign());
response.setPaySign(paySign);
} catch (Exception e) {
}
return response;
}
签名返回实体类 PrepayResponseDto
package com.baymax.vpp.cop.pay.dto.wechatpay;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 签名返回实体类 PrepayResponseDto
*
*/
@Data
@Accessors(chain = true)
public class PrepayResponseDto {
/**
* 应用ID
*/
@ApiModelProperty(value = "应用ID")
private String appId;
/**
* 时间戳
*/
@ApiModelProperty(value = "时间戳")
private String timeStamp;
/**
* 随机字符串
*/
@ApiModelProperty(value = "随机字符串")
private String nonceStr;
/**
* 订单详情扩展字符串
*/
@ApiModelProperty(value = "订单详情扩展字符串")
@JsonProperty("package")
private String packageStr;
/**
* 签名方式
*/
@ApiModelProperty(value = "签名方式")
private String signType;
/**
* 签名
*/
@ApiModelProperty(value = "签名")
private String paySign;
@ApiModelProperty(value = "是否发起成功")
boolean isSuc;
}
WechatPayServiceImpl 接口实现类
package com.baymax.vpp.cop.pay.payment.impl;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baymax.core.redis.cache.HulkRedis;
import com.baymax.core.secure.utils.AuthUtil;
import com.baymax.core.tool.api.R;
import com.baymax.core.tool.utils.Func;
import com.baymax.core.tool.utils.ObjectUtil;
import com.baymax.vpp.cop.common.enums.pay.ENewPayChannel;
import com.baymax.vpp.cop.common.enums.pay.ENoticeStatus;
import com.baymax.vpp.cop.common.enums.pay.EPayClient;
import com.baymax.vpp.cop.common.enums.pay.EPayStatus;
import com.baymax.vpp.cop.common.exception.ServiceException;
import com.baymax.vpp.cop.common.utils.DateUtils;
import com.baymax.vpp.cop.core.entity.OrderPayment;
import com.baymax.vpp.cop.core.feign.ICoreFeign;
import com.baymax.vpp.cop.core.feign.IPayCallbackClient;
import com.baymax.vpp.cop.pay.constant.WxPayV3Bean;
import com.baymax.vpp.cop.pay.dto.PayResult;
import com.baymax.vpp.cop.pay.dto.PayResultCallback;
import com.baymax.vpp.cop.pay.dto.WechatPayResult;
import com.baymax.vpp.cop.pay.dto.mpWechat.RefundRequest;
import com.baymax.vpp.cop.pay.dto.mpWechat.WechatRefundVO;
import com.baymax.vpp.cop.pay.dto.wechatpay.PrepayRequestDto;
import com.baymax.vpp.cop.pay.dto.wechatpay.PrepayResponseDto;
import com.baymax.vpp.cop.pay.entity.Merchant;
import com.baymax.vpp.cop.pay.entity.PayOrder;
import com.baymax.vpp.cop.pay.entity.TenantWechatConfig;
import com.baymax.vpp.cop.pay.mapper.CoreOrderPaymentMapper;
import com.baymax.vpp.cop.pay.payment.WechatPayService;
import com.baymax.vpp.cop.pay.service.IMerchantService;
import com.baymax.vpp.cop.pay.service.IOrderService;
import com.baymax.vpp.cop.pay.service.ITenantWechatConfigService;
import com.baymax.vpp.cop.pay.utils.WechatPayUtils;
import com.baymax.vpp.cop.pay.vo.ChinaUmsRefundVo;
import com.baymax.vpp.cop.pay.vo.wechatFen.WcMerchantConfigVo;
import com.baymax.vpp.cop.pay.wechat.pay.*;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.cert.SafeSingleScheduleExecutor;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 微信小程序支付
*
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class WechatPayServiceImpl implements WechatPayService {
private final WechatPayManager wechatPayManager;
private final IMerchantService iMerchantService;
private final ITenantWechatConfigService iTenantWechatConfigService;
private final IOrderService iOrderService;
private final ICoreFeign iCoreFeign;
private ScheduledExecutorService executor;
private final List<OrderRequest> orderRequests = new ArrayList<>();
private final HulkRedis hulkRedis;
private final IPayCallbackClient payCallbackClient;
@Autowired
CoreOrderPaymentMapper orderPaymentMapper;
@Autowired
WxPayV3Bean wxPayV3Bean;
@Value("${wechatPay.notifyUrl.callback}")
private String notifyUrlCallback;
@Value("${wechatPay.notifyUrl.refundCallback}")
private String notifyUrlRefundCallback;
/**
* 基础下单
*
* @param request
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public WechatPayResult prepay(PrepayRequestDto request) {
String notifyUrl = notifyUrlCallback;
Long id = IdUtil.getSnowflakeNextId();
// 获取用户openid
String openid = request.getOpenId();
if (StrUtil.isEmpty(request.getOpenId())) {
openid = AuthUtil.getUserAccount();
}
// 调用微信支付下单
PrepayRequest req = new PrepayRequest()
.setAppid(wxPayV3Bean.getAppId())
.setMchid(wxPayV3Bean.getMchId())
.setOutTradeNo(request.getWorkId()) // 订单支付表 id 传给微信 存到 cop_pay_order 里面的 workId 字段
.setDescription(request.getDescription())
// 外网回调地址
.setNotifyUrl(notifyUrl)
.setAmount(new PrepayRequest.Amount().setTotal(request.getAmount()))
.setPayer(new PrepayRequest.Payer(openid));
PrepayResponse res = wechatPayManager.prepay(req);
// 创建支付记录
PayOrder payOrder = new PayOrder();
payOrder.setId(id);
payOrder.setMerchantId(wxPayV3Bean.getMchId());
payOrder.setWorkId(request.getWorkId());
payOrder.setResMerchantId(Func.toStr(request.getResMerchantId()));
payOrder.setPayStatus(EPayStatus.paying);
payOrder.setCreateTime(new Date());
payOrder.setOrderType("pay");
payOrder.setPayClient(EPayClient.wechatMp);
payOrder.setPayChannel(ENewPayChannel.wechatMiniProgram);
payOrder.setNoticeStatus(ENoticeStatus.tempFail);
payOrder.setNoticeUrl(notifyUrl);
payOrder.setAmount(Long.valueOf(String.valueOf(request.getAmount())));
iOrderService.save(payOrder);
// 组织前端发起支付参数
PrepayResponseDto response = new PrepayResponseDto();
String nonceStr = IdUtil.objectId();
response.setAppId(wxPayV3Bean.getAppId())
.setTimeStamp(String.valueOf(System.currentTimeMillis() / 1000))
.setNonceStr(nonceStr)
.setPackageStr("prepay_id=" + res.getPrepayId())
.setSignType("RSA");
// 支付参数签名
PrepayResponseDto sign = sign(response);
WechatPayResult result = new WechatPayResult();
com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
jsonObject.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
jsonObject.put("nonceStr", response.getNonceStr());
jsonObject.put("package", response.getPackageStr());
jsonObject.put("signType", response.getSignType());
jsonObject.put("paySign", sign.getPaySign());
result.setMiniPayRequest(jsonObject);
result.setResultCode(0);
result.setErrorMsg("操作成功");
// result.setOrderId( orderId );
return result;
}
/**
* 微信支付订单号查询订单
*
* @param transactionId 微信支付订单号查询订单
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Transaction queryOrder(String transactionId) {
Transaction transaction = wechatPayManager.queryOrder(transactionId);
return transaction;
}
/**
* 商户订单号查询订单
*
* @param outTradeNo 商户订单号查询订单
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Transaction queryOrderByOutTradeNo(String outTradeNo) {
Transaction transaction = wechatPayManager.queryOrderByOutTradeNo(outTradeNo);
return transaction;
}
//微信支付回调通知
@Override
public void callback(HttpServletRequest servletRequest, HttpServletResponse response) throws Exception {
log.info("----------->微信支付回调开始");
Map<String, String> map = new HashMap<>();
String timeStamp = servletRequest.getHeader("Wechatpay-Timestamp");
String nonce = servletRequest.getHeader("Wechatpay-Nonce");
String signature = servletRequest.getHeader("Wechatpay-Signature");
String certSn = servletRequest.getHeader("Wechatpay-Serial");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(servletRequest.getInputStream()))) {
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
String obj = stringBuilder.toString();
log.info("微信支付回调请求参数:{},{},{},{},{}", obj, timeStamp, nonce, signature, certSn);
WechatPayUtils.getPrivateKey();
Verifier verifier = WechatPayUtils.getVerifier(wxPayV3Bean.getMchId(), wxPayV3Bean.getApiKey3());
String sn = verifier.getValidCertificate().getSerialNumber().toString(16).toUpperCase(Locale.ROOT);
NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(sn)
.withNonce(nonce)
.withTimestamp(timeStamp)
.withSignature(signature)
.withBody(obj)
.build();
NotificationHandler handler = new NotificationHandler(verifier, wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
// 验签和解析请求体
com.wechat.pay.contrib.apache.httpclient.notification.Notification notification = handler.parse(request);
JSONObject res = JSONObject.parseObject(notification.getDecryptData());
log.info("微信支付回调响应参数:{}", res.toJSONString());
if (res != null) {
//如果支付成功
String tradeState = res.getString("trade_state");
if ("SUCCESS".equals(tradeState)) {
//拿到商户-订单支付表 id
String outTradeNo = res.getString("out_trade_no"); // 订单支付表 id 传给微信 存到 cop_pay_order 里面的 workId 字段
String transactionId = res.getString("transaction_id"); // 微信支付系统生成的订单号
JSONObject amountJson = res.getJSONObject("amount");
int payerTotal = amountJson.getInteger("payer_total");
hulkRedis.set("支付订单号-paymentId: " + outTradeNo, outTradeNo + " - " + transactionId + " - " + amountJson + " - " + DateUtils.dateToStr(new Date()));
LambdaQueryWrapper<PayOrder> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(PayOrder::getWorkId, outTradeNo);
List<PayOrder> list = iOrderService.list(queryWrapper);
if (list == null || list.size() == 0) {
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();
}
PayOrder payOrder = list.get(0);
if (payOrder != null) {
if (payOrder.getPayStatus() == EPayStatus.suc) {
//如果支付状态为suc 说明订单已经支付成功了,直接响应微信服务器返回成功
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
response.setStatus(200);
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
//验证用户支付的金额和订单金额是否一致 paying
if (payOrder.getPayStatus() != EPayStatus.suc) {
if (payerTotal == payOrder.getAmount().intValue()) {
//修改订单状态
PayOrder payOrderUpdate = new PayOrder();
payOrderUpdate.setId(payOrder.getId());
payOrderUpdate.setPayStatus(EPayStatus.suc);
payOrderUpdate.setFlowNum(transactionId); // 微信支付系统生成的订单号
payOrderUpdate.setUpdateTime(new Date());
// 修改订单状态
iOrderService.updateById(payOrderUpdate);
// 支付成功 修改订单的充电状态
PayResultCallback callback = new PayResultCallback();
callback.setWorkId(payOrder.getWorkId());
callback.setPayStatus(payOrderUpdate.getPayStatus());
callback.setFailMsg(payOrder.getFailMsg());
payCallbackClient.payResultCallback(callback);
//响应微信服务器
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
response.setStatus(200);
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
}
}
} else {
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
response.setStatus(200);
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
} else {
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
response.setStatus(200);
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
} catch (Exception e) {
log.error("微信支付回调失败 error!", e);
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();
}
}
/**
* 微信小程序-退款申请
*
* @param request
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public R<ChinaUmsRefundVo> refundWechat(RefundRequest request) {
String notifyUrl = notifyUrlRefundCallback;
RefundRequest.Amount amount = request.getAmount();
int refund = amount.getRefund();
int total = amount.getTotal();
if (refund > total) {
return R.fail("退款金额不能大于订单支付总金额");
}
// 查询订单对应的支付记录 EFeeType.chargeFee
PayOrder payOrder = orderPaymentMapper.selectPayOrderByOrderId(request.getOutTradeNo(), "suc", "chargeFee");
if (payOrder == null) {
return R.fail("未查询到付款记录!");
}
request.setNotifyUrl(notifyUrl);
request.setTransactionId(payOrder.getFlowNum());
request.setOutRefundNo(System.currentTimeMillis() + "");
request.setOutTradeNo(payOrder.getWorkId());
ChinaUmsRefundVo res = wechatPayManager.refundWechat(request);
return R.data(res);
}
// 微信小程序-退款申请-回调
@Override
@Transactional(rollbackFor = Exception.class)
public void refundCallback(HttpServletRequest servletRequest, HttpServletResponse response) {
log.info("----------->微信小程序-退款申请-回调");
Map<String, String> map = new HashMap<>(12);
String timeStamp = servletRequest.getHeader("Wechatpay-Timestamp");
String nonce = servletRequest.getHeader("Wechatpay-Nonce");
String signature = servletRequest.getHeader("Wechatpay-Signature");
String certSn = servletRequest.getHeader("Wechatpay-Serial");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(servletRequest.getInputStream()))) {
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
String obj = stringBuilder.toString();
log.info("微信退款回调请求参数:{},{},{},{},{}", obj, timeStamp, nonce, signature, certSn);
WechatPayUtils.getPrivateKey();
Verifier verifier = WechatPayUtils.getVerifier(wxPayV3Bean.getMchId(), wxPayV3Bean.getApiKey3());
String sn = verifier.getValidCertificate().getSerialNumber().toString(16).toUpperCase(Locale.ROOT);
NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(sn)
.withNonce(nonce)
.withTimestamp(timeStamp)
.withSignature(signature)
.withBody(obj)
.build();
NotificationHandler handler = new NotificationHandler(verifier, wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
// 验签和解析请求体
com.wechat.pay.contrib.apache.httpclient.notification.Notification notification = handler.parse(request);
JSONObject res = JSONObject.parseObject(notification.getDecryptData());
log.info("微信退款回调响应参数:{}", res.toJSONString());
if (res != null) {
//如果退款成功
String tradeState = res.getString("refund_status");
if ("SUCCESS".equals(tradeState)) {
//拿到商户订单号
String outTradeNo = res.getString("out_trade_no"); // 返回的商户订单号 订单支付表 id 传给微信 存到 cop_pay_order 里面的 workId 字段
/*// String transactionId = res.getString("transaction_id"); //微信支付订单号
String refundPaymentId = res.getString("out_refund_no"); // 商户退款单号
String refundId = res.getString("refund_id"); // 微信退款单号
JSONObject amountJson = res.getJSONObject("amount");
// 退款金额,币种的最小单位,只能为整数,不能超过原订单支付金额,如果有使用券,后台会按比例退。
int refund = amountJson.getInteger("refund");*/
OrderPayment orderPayment = orderPaymentMapper.selectById(outTradeNo);
if (orderPayment != null) {
//如果退款状态为 refundSuc 说明订单已经退款成功了,直接响应微信服务器返回成功
if (orderPayment.getPayStatus() == EPayStatus.refundSuc) {
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
if (orderPayment.getPayStatus() != EPayStatus.refundSuc) {
//修改订单状态
OrderPayment payment = new OrderPayment();
payment.setId(orderPayment.getId());
payment.setPayStatus(EPayStatus.refundSuc);
payment.setUpdateTime(new Date());
orderPaymentMapper.updateById(payment);
//响应微信服务器
response.setStatus(200);
map.put("code", "SUCCESS");
map.put("message", "SUCCESS");
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
}
}
}
} 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) {
log.error("微信支付回调失败", e);
}
}
/**
* 查询单笔退款(通过商户退款单号)
*
* @param outRefundNo
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public WechatRefundVO selectRefundByOutRefundNo(String outRefundNo) {
WechatRefundVO res = wechatPayManager.selectRefundByOutRefundNo(outRefundNo);
return res;
}
}
WechatPayService接口
package com.baymax.vpp.cop.pay.payment;
import com.baymax.core.tool.api.R;
import com.baymax.vpp.cop.pay.dto.WechatPayResult;
import com.baymax.vpp.cop.pay.dto.mpWechat.RefundRequest;
import com.baymax.vpp.cop.pay.dto.mpWechat.WechatRefundVO;
import com.baymax.vpp.cop.pay.dto.wechatpay.PrepayRequestDto;
import com.baymax.vpp.cop.pay.vo.ChinaUmsRefundVo;
import com.baymax.vpp.cop.pay.wechat.pay.Transaction;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 微信小程序支付
*/
public interface WechatPayService {
/**
* 基础下单
*
* @param request
* @return
*/
public WechatPayResult prepay(PrepayRequestDto request);
/**
* 微信支付回调通知
* @param servletRequest
* @param response
* @throws Exception
*/
public void callback(HttpServletRequest servletRequest, HttpServletResponse response) throws Exception;
/**
* 查询订单支付结果
*
* @param transactionId 微信支付订单号
* @return
*/
public Transaction queryOrder(String transactionId);
/**
* 商户订单号查询订单
*
* @param outTradeNo 商户订单号查询订单
* @return
*/
public Transaction queryOrderByOutTradeNo(String outTradeNo);
/**
* 退款申请
*
* @param request
* @return
*/
public R<ChinaUmsRefundVo> refundWechat(RefundRequest request);
//微信小程序-退款申请-回调
public void refundCallback(HttpServletRequest servletRequest, HttpServletResponse response);
/**
* 查询单笔退款(通过商户退款单号)
*
* @param outRefundNo
* @return
*/
public WechatRefundVO selectRefundByOutRefundNo(String outRefundNo);
}
WechatPayController
package com.baymax.vpp.cop.pay.controller;
import com.baymax.core.log.annotation.ApiLog;
import com.baymax.core.tool.api.R;
import com.baymax.vpp.cop.pay.dto.WechatPayResult;
import com.baymax.vpp.cop.pay.dto.mpWechat.RefundRequest;
import com.baymax.vpp.cop.pay.dto.mpWechat.WechatRefundVO;
import com.baymax.vpp.cop.pay.dto.wechatpay.PrepayRequestDto;
import com.baymax.vpp.cop.pay.payment.WechatPayService;
import com.baymax.vpp.cop.pay.vo.ChinaUmsRefundVo;
import com.baymax.vpp.cop.pay.wechat.pay.Transaction;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 微信原生-微信小程序支付
*/
@RestController
@AllArgsConstructor
@RequestMapping("/wechatPay")
@Api(value = "微信原生-微信小程序支付", tags = "微信原生-微信小程序支付")
public class WechatPayController {
@Autowired
private WechatPayService wechatPayService;
/**
* 基础下单
*
* @param request
* @return
* @throws Exception
*/
@ApiOperation(value = "基础下单")
@ApiLog("基础下单")
@PostMapping("/prepay")
public WechatPayResult prepay(@RequestBody PrepayRequestDto request) throws Exception {
WechatPayResult prepay = wechatPayService.prepay(request);
return prepay;
}
/**
* 微信支付订单号查询订单
*
* @param transactionId 微信支付订单号
* @return
*/
@ApiOperation(value = "微信支付订单号查询订单")
@ApiLog("微信支付订单号查询订单")
@ResponseBody
@GetMapping("/queryOrder")
public Transaction queryOrder(String transactionId) throws Exception {
return wechatPayService.queryOrder(transactionId);
}
/**
* 商户订单号查询订单
*
* @param outTradeNo 商户订单号查询订单
* @return
*/
@ApiOperation(value = "商户订单号查询订单")
@ApiLog("商户订单号查询订单")
@ResponseBody
@GetMapping("/queryOrderByOutTradeNo")
public Transaction queryOrderByOutTradeNo(@RequestParam("outTradeNo") String outTradeNo) throws Exception {
return wechatPayService.queryOrderByOutTradeNo(outTradeNo);
}
/**
* 处理微信支付回调
*
* @param servletRequest
* @param response
*/
@ApiOperation(value = "处理微信支付回调")
@ApiLog("处理微信支付回调")
@ResponseBody
@RequestMapping("/callback")
public void callback(HttpServletRequest servletRequest, HttpServletResponse response) throws Exception {
wechatPayService.callback(servletRequest, response);
}
/**
* 微信小程序-退款申请
*
* @param request
* @return
*/
@ApiOperation(value = "退款申请")
@ApiLog("微信小程序-退款申请")
@PostMapping("/refundWechat")
public R<ChinaUmsRefundVo> refundWechat(@RequestBody RefundRequest request) {
return wechatPayService.refundWechat(request);
}
/**
* 微信小程序-退款申请 回调
*
* @param servletRequest
* @param response
*/
@ApiOperation(value = "微信小程序-退款申请-回调")
@ApiLog("微信小程序-退款申请-回调")
@ResponseBody
@RequestMapping("/refundCallback")
public void refundCallback(HttpServletRequest servletRequest, HttpServletResponse response) throws Exception {
wechatPayService.refundCallback(servletRequest, response);
}
/**
* 查询单笔退款(通过商户退款单号)
*
* @param outRefundNo
* @return
*/
@ApiOperation(value = "查询单笔退款(通过商户退款单号) ")
@ApiLog("查询单笔退款(通过商户退款单号) ")
@PostMapping("/selectRefundByOutRefundNo")
public WechatRefundVO selectRefundByOutRefundNo(@RequestParam("tenantId") String outRefundNo) {
return wechatPayService.selectRefundByOutRefundNo(outRefundNo);
}
}
支付、退款回调配置
wechatPay:
notifyUrl:
callback: http://IP:端口/wechatPay/callback
refundCallback: http://IP:端口/wechatPay/refundCallback
微信小程序 预支付响应结果 WechatPayResult
package com.baymax.vpp.cop.pay.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 微信小程序 预支付响应结果
*/
@Data
public class WechatPayResult implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("结果码,0成功 1失败")
Integer resultCode = 1;
@ApiModelProperty("错误码")
Integer errorCode = 99;
@ApiModelProperty("描述消息")
String errorMsg = "请重新操作";
@ApiModelProperty("订单id")
Long orderId;
@ApiModelProperty(value = "小程序支付用的请求报文,带有签名信息")
private Object miniPayRequest;
}
微信支付订单 Transaction
package com.baymax.vpp.cop.pay.wechat.pay;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* 微信支付订单 Transaction
*/
@Data
@ToString
@Accessors(chain = true)
public class Transaction {
/**
* amount
*/
private TransactionAmount amount;
/**
* appid
*/
private String appid;
/**
* mchid
*/
private String mchid;
/**
* outTradeNo
*/
@JsonProperty("out_trade_no")
private String outTradeNo;
/**
* payer
*/
private TransactionPayer payer;
/**
* successTime
*/
@JsonProperty("success_time")
private String successTime;
/**
* tradeState
*/
@JsonProperty("trade_state")
private TradeStateEnum tradeState;
/**
* tradeStateDesc
*/
@JsonProperty("trade_state_desc")
private String tradeStateDesc;
/**
* transactionId
*/
@JsonProperty("transaction_id")
private String transactionId;
public enum TradeStateEnum {
SUCCESS,
REFUND,
NOTPAY,
CLOSED,
REVOKED,
USERPAYING,
PAYERROR,
ACCEPT
}
@Data
@Accessors(chain = true)
public static class TransactionAmount {
/**
* currency
*/
private String currency;
/**
* payerCurrency
*/
@JsonProperty("payer_currency")
private String payerCurrency;
/**
* payerTotal
*/
@JsonProperty("payer_total")
private Integer payerTotal;
/**
* total
*/
private Integer total;
}
@Data
@Accessors(chain = true)
public static class TransactionPayer {
/**
* openid
*/
private String openid;
}
}
基础下单请求DTO PrepayRequestDto
package com.baymax.vpp.cop.pay.dto.wechatpay;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.NotNull;
/**
* 基础下单请求DTO PrepayRequestDto
*/
@Data
@Accessors(chain = true)
public class PrepayRequestDto {
/**
* 业务流水号 订单支付表 id
*/
@ApiModelProperty(value = "业务流水号", required = true)
private String workId;
/**
* 来源(外部)商户ID
*/
@ApiModelProperty(value = "来源(外部)商户ID", required = true)
private Long resMerchantId;
/**
* 商品描述
*/
@ApiModelProperty(value = "商品描述", required = true)
private String description;
/**
* 总金额 说明:订单总金额,单位为分
*/
@ApiModelProperty(value = "总金额", required = true)
private Integer amount;
@NotNull
@ApiModelProperty("微信用户标识")
private String openId;
}
微信小程序支付 退款请求 RefundRequest
package com.baymax.vpp.cop.pay.dto.mpWechat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 微信小程序支付 退款请求 RefundRequest
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Accessors(chain = true)
public class RefundRequest {
/**
* 【商户订单号】 原支付交易对应的商户订单号,与transaction_id二选一
*/
@JsonProperty("out_trade_no")
private String outTradeNo;
/**
* 【微信支付订单号】 原支付交易对应的微信订单号,与out_trade_no二选一
*/
@JsonProperty("transaction_id")
private String transactionId;
/**
* 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
*/
@JsonProperty("out_refund_no")
private String outRefundNo;
// 【金额信息】 订单金额信息
private Amount amount;
// 【退款结果回调url】 异步接收微信支付退款结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的这个地址。
@JsonProperty("notify_url")
private String notifyUrl;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public static class Amount {
// 【退款金额】 退款金额,单位为分,只能为整数,不能超过原订单支付金额。
private Integer refund;
// 原支付交易的订单总金额,单位为分,只能为整数。
private Integer total;
// 【退款币种】 符合ISO 4217标准的三位字母代码,目前只支持人民币:CNY。
private String currency;
}
}
基础下单请求 PrepayRequest
package com.baymax.vpp.cop.pay.wechat.pay;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 基础下单请求 PrepayRequest
*
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Accessors(chain = true)
public class PrepayRequest {
/**
* 公众号ID 说明:公众号ID
*/
private String appid;
/**
* 直连商户号 说明:直连商户号
*/
private String mchid;
/**
* 商品描述 说明:商品描述
*/
private String description;
/**
* 商户订单号 说明:商户-订单号
*/
@JsonProperty("out_trade_no")
private String outTradeNo;
/**
* 交易结束时间 说明:订单失效时间,格式为rfc3339格式
*/
@JsonProperty("time_expire")
private String timeExpire;
/**
* 附加数据 说明:附加数据
*/
private String attach;
/**
* 通知地址 说明:有效性:1. HTTPS;2. 不允许携带查询串。
*/
@JsonProperty("notify_url")
private String notifyUrl;
/**
* amount 【订单金额】 订单金额信息
*/
private Amount amount;
/**
* payer appid
*/
private Payer payer;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public static class Amount {
/**
* 总金额 说明:订单总金额,单位为分
*/
private Integer total;
/**
* 货币类型 说明:CNY:人民币,境内商户号仅支持人民币。
*/
private String currency;
}
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public static class Payer {
/**
* 用户标识 说明:用户在商户appid下的唯一标识。
*/
private String openid;
}
}
微信小程序支付 退款请求 RefundRequest
package com.baymax.vpp.cop.pay.dto.mpWechat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 微信小程序支付 退款请求 RefundRequest
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Accessors(chain = true)
public class RefundRequest {
/**
* 【商户订单号】 原支付交易对应的商户订单号,与transaction_id二选一
*/
@JsonProperty("out_trade_no")
private String outTradeNo;
/**
* 【微信支付订单号】 原支付交易对应的微信订单号,与out_trade_no二选一
*/
@JsonProperty("transaction_id")
private String transactionId;
/**
* 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
*/
@JsonProperty("out_refund_no")
private String outRefundNo;
// 【金额信息】 订单金额信息
private Amount amount;
// 【退款结果回调url】 异步接收微信支付退款结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的这个地址。
@JsonProperty("notify_url")
private String notifyUrl;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public static class Amount {
// 【退款金额】 退款金额,单位为分,只能为整数,不能超过原订单支付金额。
private Integer refund;
// 原支付交易的订单总金额,单位为分,只能为整数。
private Integer total;
// 【退款币种】 符合ISO 4217标准的三位字母代码,目前只支持人民币:CNY。
private String currency;
}
}
退款视图对象 ChinaUmsRefundVo
package com.baymax.vpp.cop.pay.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 退款视图对象 ChinaUmsRefundVo
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChinaUmsRefundVo {
@ApiModelProperty("退款结果")
private Boolean isSuccess;
@ApiModelProperty("错误编号")
private String errCode;
@ApiModelProperty("错误信息")
private String errMsg;
@ApiModelProperty("退款金额")
private Long refundAmount;
}
微信小程序支付退款响应 WechatRefundVO
package com.baymax.vpp.cop.pay.dto.mpWechat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 微信小程序支付退款响应 WechatRefundVO
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Accessors(chain = true)
public class WechatRefundVO {
/**
* 【微信支付退款号】 微信支付退款号
*/
@JsonProperty("refund_id")
private String refundId;
// 【商户退款单号】 商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@ ,同一退款单号多次请求只退一笔。
@JsonProperty("out_refund_no")
private String outRefundNo;
// 【微信支付订单号】 微信支付交易订单号
@JsonProperty("transaction_id")
private String transactionId;
//【商户订单号】 原支付交易对应的商户订单号
@JsonProperty("out_trade_no")
private String outTradeNo;
//【退款渠道】 退款渠道 可选取值: ORIGINAL: 原路退款; BALANCE: 退回到余额; OTHER_BALANCE: 原账户异常退到其他余额账户; OTHER_BANKCARD: 原银行卡异常退到其他银行卡;
@JsonProperty("channel")
private String channel;
//退款入账账户】 取当前退款单的退款入账方,有以下几种情况: 1)退回银行卡:{银行名称}{卡类型}{卡尾号} 2)退回支付用户零钱:支付用户零钱 3)退还商户:商户基本账户商户结算银行账户 4)退回支付用户零钱通:支付用户零钱通
@JsonProperty("user_received_account")
private String userReceivedAccount;
// 【退款创建时间】 退款受理时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
@JsonProperty("create_time")
private String createTime;
// 【退款状态】 退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台(pay.weixin.qq.com)-交易中心,手动处理此笔退款。;可选取值:;SUCCESS: 退款成功;CLOSED: 退款关闭;PROCESSING: 退款处理中;ABNORMAL: 退款异常
@JsonProperty("status")
private String status;
// 【金额信息】 金额详细信息
private Amount amount;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public static class Amount {
//总金额 说明:订单总金额,单位为分
private Integer total;
//【订单金额】 订单总金额,单位为分
private Integer refund;
// 【用户支付金额】 现金支付金额,单位为分,只能为整数
@JsonProperty("payer_total")
private Integer payerTotal;
// 【用户退款金额】 退款给用户的金额,单位为分,不包含所有优惠券金额
@JsonProperty("payer_refund")
private Integer payerRefund;
// 【应结退款金额】 去掉非充值代金券退款金额后的退款金额,单位为分,退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额
@JsonProperty("settlement_refund")
private Integer settlementRefund;
// 【应结订单金额】 应结订单金额=订单金额-免充值代金券金额,应结订单金额<=订单金额,单位为分
@JsonProperty("settlement_total")
private Integer settlementTotal;
// 【优惠退款金额】 优惠退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见代金券或立减优惠,单位为分
@JsonProperty("discount_refund")
private Integer discountRefund;
// 【退款币种】 符合ISO 4217标准的三位字母代码,目前只支持人民币:CNY。
private String currency;
}
}
支付订单表实体类 PayOrder
package com.baymax.vpp.cop.pay.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baymax.vpp.cop.common.enums.pay.ENewPayChannel;
import com.baymax.vpp.cop.common.enums.pay.ENoticeStatus;
import com.baymax.vpp.cop.common.enums.pay.EPayClient;
import com.baymax.vpp.cop.common.enums.pay.EPayStatus;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import java.util.Date;
/**
* 支付订单表实体类 PayOrder
*/
@Data
@TableName("cop_pay_order")
@ApiModel(value = "支付订单表对象", description = "支付订单表")
public class PayOrder {
/**
* id
*/
private Long id;
/**
* 业务流水号(由发起支付的服务生成)( 微信支付系统生成的订单号-微信支付id)
*/
private String workId;
/**
* 来源(外部)商户ID* 用于区分不同商户的支付订单(微信的商户ID/小桔的运营商ID...)
*/
private String resMerchantId;
/**
* 商户ID
*/
private String merchantId;
/**
* 支付金额(分)
*/
private Long amount;
/**
* 支付渠道(微信分/微信扫码/支付宝扫码/银座钱包...)
*/
private ENewPayChannel payChannel;
/**
* 支付客户端(支付宝/微信小程序/微信网页支付)
*/
private EPayClient payClient;
/**
* 订单类型pay,refund
*/
private String orderType;
/**
* 支付状态(init,paying,suc,fail,cancel,close)
*/
private EPayStatus payStatus;
/**
* 交易流水号(平台返回) 微信支付系统生成的订单号
*/
private String flowNum;
/**
* 通知URL(通知支付发起方支付状态)
*/
private String noticeUrl;
/**
* 通知状态(init,tempFail,suc,fail)
*/
private ENoticeStatus noticeStatus;
/**
* 支付失败说明
*/
private String failMsg;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
微信小程序 预支付响应结果 WechatPayResult
package com.baymax.vpp.cop.pay.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 微信小程序 预支付响应结果 WechatPayResult
*/
@Data
public class WechatPayResult implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("结果码,0成功 1失败")
Integer resultCode = 1;
@ApiModelProperty("错误码")
Integer errorCode = 99;
@ApiModelProperty("描述消息")
String errorMsg = "请重新操作";
@ApiModelProperty("订单id")
Long orderId;
@ApiModelProperty(value = "小程序支付用的请求报文,带有签名信息")
private Object miniPayRequest;
}