一,准备工作
申请应用号:appid
申请商户号:mchid
设置APIV3密钥:apiV3Key
下载并配置商户证书,商户证书序列号:mchSerialNo ;商户API私钥路径:privateKey
二,小程序支付流程
需要后端完成的一共有三个接口
1,拿到订单参数后,向微信发送请求支付消息,把拿到的参数返回给前端,用于用户付款
2,前端输入密码后,微信会异步回调一个接口,成功扣款后做对应业务处理
3,查询支付结果返回给前端
三,设置常量和基本配置信息
1,导入依赖
<!--微信小程序支付-->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.9</version>
</dependency>
2,加载配置信息到yml文件中
# 微信
wx:
# 小程序支付
pay:
# 应用号
appId: ********
# 商户号
mchId: ********
# 商户APIV3密钥(32位)
apiV3Key: ********************************
# 商户证书序列号
mchSerialNo: ****************
# 支付成功回调 url 需要公网可访问
notifyUrl: http://改成自己的回调地址/system/order/wechatPayCallback
# 退款成功回调 url 需要公网可访问
refundNotifyUrl: http://改成自己的回调地址/system/order/wechatRefundBack
# 商户API私钥路径 【证书路径】
privateKey: "**********"
3,用工具类来保存配置信息WeChatToolConfig
@Data
@Component
public class WeChatToolConfig {
/**
* 应用id
*/
@Value("${wx.pay.appId}")
private String appId;
/**
* 商户id
*/
@Value("${wx.pay.mchId}")
private String mchId;
/**
* 商户APIV3密钥
*/
@Value("${wx.pay.apiV3Key}")
private String apiV3Key;
/**
* 商户证书序列号
*/
@Value("${wx.pay.mchSerialNo}")
private String mchSerialNo;
/**
* 商户私钥
*/
@Value("${wx.pay.privateKey}")
private String privateKey;
/**
* 支付成功回调url
*/
@Value("${wx.pay.notifyUrl}")
private String notifyUrl;
/**
* 退款成功回调url
*/
@Value("${wx.pay.refundNotifyUrl}")
private String refundNotifyUrl;
}
4,定义一个枚举来保存微信支付API WxApiTypeEnum
public enum WxApiTypeEnum {
/**
* 小程序 下单API
*/
CREATE_ORDER("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"),
/**
* 申请退款API 通过 outTradeNo 商户订单号 退单
*/
REFUND_ORDER("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds"),
/**
* 查询支付订单API ONE{}=>outTradeNo – 商户订单号 系统生 TOW{}=>mchId商户的商户号,由微信支付生成并下发。
*/
QUERY_CREATE_ORDER("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{}?mchid={}"),
/**
* 查询退款订单API {}=>outRefundNo 商户退款单号
*/
QUERY_REFUND_ORDER("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/{}"),
/**
* 关闭订单API {}=>outTradeNo 商户订单号
*/
CLOSE_ORDER("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{}/close"),
/**
* 货物录入
*/
GOOD_ENTERING("https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token={}"),
/**
* 接口调用凭证
*/
API_VOUCHER("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}");
private final String value;
WxApiTypeEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
5,定义一个类保存HTTP请求头常量 WechatPayHttpHeaders
public class WechatPayHttpHeaders {
// 请求头配置
public static final String ACCEPT = "Accept";
public static final String CONTENT_TYPE = "Content-type";
public static final String APPLICATION_JSON = "application/json";
public static final String APPLICATION_JSON_UTF = "application/json; charset=utf-8";
/**
* 微信回调参数==>微信序列号
*/
public static final String WECHATPAY_SERIAL = "Wechatpay-Serial";
/**
* 微信回调参数==>应答随机串
*/
public static final String WECHATPAY_NONCE = "Wechatpay-Nonce";
/**
* 微信回调参数==>应答时间戳
*/
public static final String WECHATPAY_TIMESTAMP = "Wechatpay-Timestamp";
/**
* 微信回调参数==>应答签名
*/
public static final String WECHATPAY_SIGNATURE = "Wechatpay-Signature";
private WechatPayHttpHeaders() {
// Don't allow instantiation
}
}
6,微信小程序支付工具类
public class WeChatUtil {
private static final Logger log = LoggerFactory.getLogger(WeChatUtil.class);
/**
* 签名生成 计算签名
*
* @param message 签名体
* @param privateKey 商户私钥
* @return String
*/
public static String sign(byte[] message, PrivateKey privateKey) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
/**
* 构造HttpClient 实现 微信申请接口 调用功能
*
* @param verifier 微信验签器
* @return CloseableHttpClient
*/
public static CloseableHttpClient getHttpClient(WeChatToolConfig config, Verifier verifier) {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(config.getPrivateKey().getBytes(StandardCharsets.UTF_8)));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(config.getMchId(), config.getMchSerialNo(), merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier));
return builder.build();
}
/**
* 构造HttpClient 实现 微信申请接口 调用功能 不用微信签名认证 账单申请中可用到
*
* @param verifier 微信验签器
* @return CloseableHttpClient
*/
public static CloseableHttpClient getHttpClientNoSign(WeChatToolConfig config, Verifier verifier) {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(config.getPrivateKey().getBytes(StandardCharsets.UTF_8)));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(config.getMchId(), config.getMchSerialNo(), merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier))
.withValidator(response -> true);
return builder.build();
}
/**
* 获取验证功能
*
* @return Verifier 微信验签器
*/
public static Verifier getVerifier(WeChatToolConfig config) {
try {
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(config.getPrivateKey().getBytes(StandardCharsets.UTF_8)));
// 定时更新平台证书功能
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(config.getMchId(), new WechatPay2Credentials(config.getMchId(),
new PrivateKeySigner(config.getMchSerialNo(), merchantPrivateKey)), config.getApiV3Key().getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier=>验签器
return certificatesManager.getVerifier(config.getMchId());
} catch (Exception e) {
log.warn("验证出错");
throw new CustomException("微信证书出错 联系客服");
}
}
/**
* 回调验证
*
* @param request 微信回调请求
* @return String
*/
public static Notification verifyBack(HttpServletRequest request, WeChatToolConfig config) throws IOException, ValidationException, ParseException {
// 应答报文主体
BufferedReader br = request.getReader();
String str;
StringBuilder builder = new StringBuilder();
while ((str = br.readLine()) != null) {
builder.append(str);
}
// 构建request,传入必要参数
// 参数 1.微信序列号 2.应答随机串 3.应答时间戳 4.应答签名 5.应答报文主体
NotificationRequest notificationRequest = new NotificationRequest.Builder()
.withSerialNumber(request.getHeader(WechatPayHttpHeaders.WECHATPAY_SERIAL))
.withNonce(request.getHeader(WechatPayHttpHeaders.WECHATPAY_NONCE))
.withTimestamp(request.getHeader(WechatPayHttpHeaders.WECHATPAY_TIMESTAMP))
.withSignature(request.getHeader(WechatPayHttpHeaders.WECHATPAY_SIGNATURE))
.withBody(builder.toString())
.build();
NotificationHandler handler = new NotificationHandler(WeChatUtil.getVerifier(config), config.getApiV3Key().getBytes(StandardCharsets.UTF_8));
// 验签和解析请求体
log.info("验签和解析请求体==============================开始验证==============================");
return handler.parse(notificationRequest);
}
/**
* 验证用户支付金额 场景 微信回调验证
*
* @param jsonObject 微信回调json 返回参数
* @param total 数据库记录金额
* @return Boolean
*/
public static Boolean verifyMoney(JSONObject jsonObject, Integer total) {
// 总金额计数值 用户支付计算
int userPayTotal = WeChatPayParam.NUM_ZERO;
// 1.验证订单金额
// 用户支付金额
int payerTotal = (int) jsonObject.getJSONObject(WeChatPayParam.AMOUNT).get(WeChatPayParam.AMOUNT_PAYER_TOTAL);
userPayTotal += payerTotal;
// CASH充值型代金券 要加上优惠金额 银行优惠 获取
// 排空 如果没有优惠则跳过
if (!ObjectUtil.isEmpty(jsonObject.get(WeChatPayParam.PROMOTION_DETAIL))) {
JSONArray jsonArray = jsonObject.getJSONArray(WeChatPayParam.PROMOTION_DETAIL);
for (Object object : jsonArray) {
JSONObject json = (JSONObject) object;
// 如果优惠类型为CASH 则要和 用户支付金额 累加
if (WeChatPayParam.WX_DISCOUNT_TYPE.equals(json.get(WeChatPayParam.PROMOTION_DETAIL_TYPE).toString())) {
userPayTotal += (int) json.get(WeChatPayParam.AMOUNT);
}
}
}
// 2.总金额 预支付时的 金额 与 total 用户支付金额
// 微信端返回的支付总金额
int wxTotal = (int) jsonObject.getJSONObject(WeChatPayParam.AMOUNT).get(WeChatPayParam.AMOUNT_TOTAL);
// 校验通知的信息是否与商户侧的信息一致,防止数据泄露导致出现“假通知”,造成资金损失。
log.info("微信回调金额===比较=== " + "微信端返回的支付总金额" + jsonObject.getJSONObject(WeChatPayParam.AMOUNT).get(WeChatPayParam.AMOUNT_TOTAL) + "================用户支付总金额计算" + userPayTotal);
return wxTotal == userPayTotal && total == wxTotal;
}
/**
* 获取接口调用凭证
*/
public static JSONObject getAccessToken(WeChatToolConfig config) throws Exception {
log.info("initAccessToken:开始运行...");
// 获取secret
String secret = WxMaConfiguration.getMaService(config.getAppId()).getWxMaConfig().getSecret();
// 获取url
String url = StrFormatter.format(WxApiTypeEnum.API_VOUCHER.getValue(), config.getAppId(), secret);
return getBodyAsJsonByGet(url, config);
}
/**
* 调用微信支付 get请求 统一配置
* 要微信签名认证
*
* @param url 请求url
* @return JSONObject
*/
public static JSONObject getBodyAsJsonByGet(String url, WeChatToolConfig config) throws IOException {
// 1.构造httpGet请求
HttpGet httpGet = new HttpGet(url);
httpGet.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
// 2.调起微信查询订单接口
CloseableHttpResponse response = WeChatUtil.getHttpClient(config, WeChatUtil.getVerifier(config)).execute(httpGet);
// 3.返回结果信息
String bodyAsString = EntityUtils.toString(response.getEntity());
// 转为JSON格式返回
return JSON.parseObject(bodyAsString);
}
/**
* 调用微信支付 post请求 统一配置
* 要微信签名认证
*
* @param url 请求url
* @param requestParam 请求参数
* @param config 基本配置
* @return JSONObject
*/
public static JSONObject getBodyAsJsonByPost(String url, JSONObject requestParam, WeChatToolConfig config) throws Exception {
// 1.设置请求url和请求头
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader(WechatPayHttpHeaders.ACCEPT, WechatPayHttpHeaders.APPLICATION_JSON);
httpPost.addHeader(WechatPayHttpHeaders.CONTENT_TYPE, WechatPayHttpHeaders.APPLICATION_JSON_UTF);
// 2.传入参数
String jsonString = JSON.toJSONString(requestParam);
httpPost.setEntity(new StringEntity(jsonString, StandardCharsets.UTF_8));
// 3.调用接口 获取预支付交易会话标识 prepay_id
CloseableHttpResponse response = WeChatUtil.getHttpClient(config, WeChatUtil.getVerifier(config)).execute(httpPost);
// 4.获取返回值 转为JSON
String bodyAsString = EntityUtils.toString(response.getEntity());
return JSON.parseObject(bodyAsString);
}
/**
* 获取签名
*
* @param appId appId
* @param timestamp 时间戳
* @param nonce 随机字符串
* @param wxPackage 订单详情扩展字符串
* @param privateKey 商户私钥
*/
public static String getSign(String appId, String timestamp, String nonce, String wxPackage, String privateKey) throws Exception {
// APP调起支付API 构造签名串
// 应用id
String builder = appId + "\n" +
// 时间戳
timestamp + "\n" +
// 随机字符串
nonce + "\n" +
// 预支付交易会话标识 prepay_id=wx201410272009395522657a690389285100 必须以此格式
wxPackage + "\n";
// 读取privateKey商户私钥
PrivateKey merchantPrivateKey = PemUtils.getPrivateKey(privateKey);
// 计算签名
return WeChatUtil.sign(builder.getBytes(StandardCharsets.UTF_8), merchantPrivateKey);
}
}
7,微信支付参数 WeChatPayParam
public class WeChatPayParam {
/**
* 应用ID
*/
public final static String APPID = "appid";
/**
* 直连商户号
*/
public final static String MCHID = "mchid";
/**
* 用户支付状态
*/
public final static String TRADE_STATE = "trade_state";
/**
* 用户退款状态
*/
public final static String STATUS = "status";
/**
* 商品描述
*/
public final static String DESCRIPTION = "description";
/**
* 通知地址
*/
public final static String NOTIFY_URL = "notify_url";
/**
* 预支付交易会话标识
*/
public final static String PREPAY_ID = "prepay_id";
/**
* 商户订单号
*/
public final static String OUT_TRADE_NO = "out_trade_no";
/**
* 商户退款单号
*/
public final static String OUT_REFUND_NO = "out_refund_no";
/**
* 微信支付系统生成的订单号
*/
public final static String TRANSACTION_ID = "transaction_id";
/**
* 微信支付系统生成的订单号
*/
public final static String REFUND_ID = "refund_id";
/**
* 订单金额
*/
public final static String AMOUNT = "amount";
/**
* 总金额 下单时系统计算金额 单位为分
*/
public final static String AMOUNT_TOTAL = "total";
/**
* 支付者
*/
public final static String PAYER = "payer";
/**
* 用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid
*/
public final static String OPENID = "openid";
/**
* 总金额 退款时 退款金额
*/
public final static String AMOUNT_REFUND = "refund";
/**
* 总金额 退款时 币种
*/
public final static String AMOUNT_CURRENCY = "currency";
/**
* 用户支付金额
*/
public final static String AMOUNT_PAYER_TOTAL = "payer_total";
/**
* 优惠功能
*/
public final static String PROMOTION_DETAIL = "promotion_detail";
/**
* 优惠类型
*/
public final static String PROMOTION_DETAIL_TYPE = "type";
/**
* 微信优惠 CASH:充值型代金券
*/
public final static String WX_DISCOUNT_TYPE = "CASH";
/**
* 下单回调给微信支付成功信息
*/
public final static String WX_BACK_OK = "SUCCESS";
/**
* 退款回调 退款状态
*/
public final static String REFUND_STATUS = "refund_status";
/**
* 微信回调 通知数据
*/
public final static String RESOURCE = "resource";
/**
* 微信账单类型 交易账单
*/
public final static String TRADE_BILL = "tradebill";
/**
* 微信账单类型 资金账单
*/
public final static String FUND_FLOW_BILL = "fundflowbill";
/**
* 微信账单下载地址
*/
public final static String DOWNLOAD_URL = "download_url";
/**
* 微信回调 通知数据=>数据密文
*/
public final static String RESOURCE_CIPHERTEXT = "ciphertext";
/**
* 微信回调 通知数据=>附加数据
*/
public final static String RESOURCE_ASSOCIATED_DATA = "associated_data";
/**
* 微信回调 通知数据=>随机串
*/
public final static String RESOURCE_NONCE = "nonce";
/**
* 发货信息 状态码
*/
public final static String ERR_CODE = "errcode";
/**
* 发货信息 状态信息
*/
public final static String ERR_MSG = "errmsg";
/**
* 请求支付 错误状态码
*/
public final static String CODE = "code";
/**
* 请求支付 状态信息
*/
public final static String MESSAGE = "message";
/**
* 固定支付参数 package
*/
public final static String WX_PACKAGE = "prepay_id=";
/**
* 币种 RMB
*/
public final static String CURRENCY_RMB = "CNY";
/**
* 支付成功
*/
public final static String PAY_SUCCESS = "支付成功";
/**
* 退款成功
*/
public final static String REFUND_SUCCESS = "退款成功";
/**
* 订单单号类型
*/
public final static String ORDER_NUMBER_TYPE = "order_number_type";
/**
* 商品信息
*/
public final static String ITEM_DESC = "item_desc";
/**
* 订单
*/
public final static String ORDER_KEY = "order_key";
/**
* 物流模式
*/
public final static String LOGISTICS_TYPE = "logistics_type";
/**
* 发货模式
*/
public final static String DELIVERY_MODE = "delivery_mode";
/**
* 物流信息列表
*/
public final static String SHIPPING_LIST = "shipping_list";
/**
* 上传时间
*/
public final static String UPLOAD_TIME = "upload_time";
/**
* 获取到的凭证
*/
public final static String ACCESS_TOKEN = "access_token";
/**
* 数字 0~11
*/
public final static int NUM_NEGATIVE_ONE = -1;
public final static int NUM_ZERO = 0;
public final static int NUM_ONE = 1;
public final static int NUM_TWO = 2;
public final static int NUM_THREE = 3;
public final static int NUM_FOUR = 4;
public final static int NUM_FIVE = 5;
public final static int NUM_SIX = 6;
public final static int NUM_SEVEN = 7;
public final static int NUM_EIGHT = 8;
public final static int NUM_NINE = 9;
public final static int NUM_TEN = 10;
public final static int NUM_ELEVEN = 11;
public final static int NUM_15 = 15;
public final static int NUM_16 = 16;
public final static int NUM_19 = 19;
public final static int NUM_TWENTY = 20;
public final static int NUM_30 = 30;
public final static int NUM_32 = 32;
public final static int NUM_50 = 50;
public final static int NUM_60 = 60;
public final static int NUM_64 = 64;
public final static int NUM_90 = 90;
public final static int NUM_100 = 100;
public final static int NUM_200 = 200;
public final static int NUM_1024 = 1024;
public final static int NUM_5000 = 5000;
public final static int NUM_24_time = 86400; // 60*60*60*24 一天
}
四,微信支付请求下单接口
1,控制层 拿到需要支付订单id进行一系列操作
@Autowired
private WaybillService waybillService;
/**
* 微信请求下单支付
*
* @param waybillId 运单id
* @return 付款所需参数
*/
@RepeatSubmit
@GetMapping("/weChatCreateOrder/{waybillId}")
@Operation(summary = "微信请求下单支付")
public AjaxResult<CreateOrderVo> weChatCreateOrder(@PathVariable("waybillId") String waybillId) {
return waybillService.weChatCreateOrder(waybillId);
}
2,返回对象 CreateOrderVo 用户付款需要的参数
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class CreateOrderVo implements Serializable {
// 微信小程序需要参数
// "appId": "wx499********7c70e", // 微信开放平台 - 应用 - AppId,注意和微信小程序、公众号 AppId 可能不一致
// "timeStamp": 1597935292, // 时间戳(单位:秒)
// "nonceStr": "c5sEwbaNPiXAF3iv", // 随机字符串
// "package": "wx202254********************fbe90000", // 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=***;示例值:prepay_id=wx201410272009395522657a690389285100
// "signType": "RSA" 签名类型,默认为RSA,仅支持RSA。
// "paySign": "A842B45937F6EFF60DEC7A2EAA52D5A0" // 签名,这里用的 MD5/RSA 签名
@Serial
private static final long serialVersionUID = 1L;
/**
* 时间戳
*/
@Schema(description = "时间戳")
private String timeStamp;
/**
* 随机字符串
*/
@Schema(description = "随机字符串")
private String nonceStr;
/**
* 订单详情扩展字符串 package
*/
@Schema(description = "订单详情扩展字符串 package")
private String wxPackage;
/**
* 签名方式
*/
@Schema(description = "签名方式")
private String signType;
/**
* 签名
*/
@Schema(description = "签名")
private String paySign;
/**
* 运单号
*/
@Schema(description = "运单号")
private String outTradeNo;
}
3,业务层 请求下单获取prepay_id构造签名
// 第三节中保存的微信配置信息 注入进来
@Autowired
private WeChatToolConfig config;
@Autowired
private PayService payService;
/**
* 微信请求下单支付
*
* @param waybillId 运单id
* @return 付款所需参数
*/
@Override
public AjaxResult<CreateOrderVo> weChatCreateOrder(String waybillId) {
// 查询运单
Waybill waybill = getById(waybillId, "运单id不存在");
// 调用微信请求下单接口
int total = waybill.getGoodTotalPrice().multiply(new BigDecimal("100.00")).intValue();
// 获取付款所需参数
CreateOrderVo createOrder = payService.createPayOrder(waybill.getGoodName(), waybillId, waybill.getOpenId(), total, config);
log.info("支付返回" + createOrder);
return AjaxResult.success(createOrder);
}
/**
* 小程序请求下单
*
* @param description 商品描述
* @param outTradeNo 商户订单号
* @param openId appid下的唯一标识
* @param total 总金额
* @param config 基本配置
* @return 小程序下单前端Vo
*/
@Override
public CreateOrderVo createPayOrder(String description, String outTradeNo, String openId, int total, WeChatToolConfig config) {
// 配置下单参数
JSONObject requestParam = new JSONObject();
// 商户id
requestParam.put(WeChatPayParam.MCHID, config.getMchId());
// 应用id
requestParam.put(WeChatPayParam.APPID, config.getAppId());
// 商品描述
requestParam.put(WeChatPayParam.DESCRIPTION, description);
// 回调地址
requestParam.put(WeChatPayParam.NOTIFY_URL, config.getNotifyUrl());
// 订单号
requestParam.put(WeChatPayParam.OUT_TRADE_NO, outTradeNo);
// 金额
JSONObject amount = new JSONObject();
// 金额 单位 分
amount.put(WeChatPayParam.AMOUNT_TOTAL, "dev".equals(SpringUtils.getActiveProfile()) ? 1 : total);
// 货币类型 人民币
amount.put(WeChatPayParam.AMOUNT_CURRENCY, WeChatPayParam.CURRENCY_RMB);
// 金额
requestParam.put(WeChatPayParam.AMOUNT, amount);
// 支付者
JSONObject payer = new JSONObject();
// 微信登录时生成的openid,创建订单时存入订单中
payer.put(WeChatPayParam.OPENID, openId);
// 支付者
requestParam.put(WeChatPayParam.PAYER, payer);
try {
// 调用post请求
JSONObject jsonObject = WeChatUtil.getBodyAsJsonByPost(WxApiTypeEnum.CREATE_ORDER.getValue(), requestParam, config);
// 异常情况
if (Objects.nonNull(jsonObject.get(WeChatPayParam.CODE))) {
throw new CustomException(jsonObject.get(WeChatPayParam.MESSAGE).toString());
}
// 时间戳
String timestamp = String.valueOf(DateUtil.currentSeconds());
// 随机字符串
String nonce = RandomUtil.randomString(WeChatPayParam.NUM_32);
// 订单详情扩展字符串 prepay_id=wx201410272009395522657a690389285100 必须以此格式
String wxPackage = WeChatPayParam.WX_PACKAGE + jsonObject.get(WeChatPayParam.PREPAY_ID);
// 获取签名
String sign = WeChatUtil.getSign(config.getAppId(), timestamp, nonce, wxPackage, config.getPrivateKey());
return new CreateOrderVo(timestamp, nonce, wxPackage, "RSA", sign, outTradeNo);
} catch (Exception e) {
log.error("请求下单失败:" + outTradeNo);
log.error(e.getMessage());
throw new CustomException("请求下单失败:" + e.getMessage());
}
}
五,微信回调接口 注:需使用工具解决内网穿透问题 例:路由侠
1,控制层
/**
* 支付回调给微信确认
*/
@Operation(summary = "支付回调给微信确认")
@IgnoreAuth
@PostMapping("/wechatPayCallback")
public Map<String, String> wechatCallback(HttpServletRequest request) {
Map<String, String> result = new HashMap<>(WeChatPayParam.NUM_TWO);
// 判断支付是否成功
if (waybillService.verifyCreateOrder(request)) {
result.put(WeChatPayParam.CODE, WeChatPayParam.WX_BACK_OK);
} else {
result.put(WeChatPayParam.CODE, "FAIL");
result.put(WeChatPayParam.MESSAGE, "失败");
}
return result;
}
2,业务层 判断验证是否成功
/**
* 微信支付回调验证判定 核对成功 数据异步入库
*/
@Override
@Transactional
public Boolean verifyCreateOrder(HttpServletRequest request) {
log.info("===================================进入微信支付回调核对订单中========================================");
// 获取微信通知详情
JSONObject decryptData = payService.getDecryptData(request, config);
// 获取运单号
String waybillId = decryptData.get(WeChatPayParam.OUT_TRADE_NO).toString();
// 获取微信支付生成的订单号
String transactionId = decryptData.get(WeChatPayParam.TRANSACTION_ID).toString();
log.info(decryptData.get(WeChatPayParam.OUT_TRADE_NO).toString() + "订单回调信息记录:订单状态:" + waybillId);
// 判断支付是否成功
if (StrUtil.equals(WeChatPayParam.WX_BACK_OK, decryptData.get(WeChatPayParam.TRADE_STATE).toString())) {
// 查询运单
Waybill waybill = getById(waybillId, "运单id不存在");
// 判断金额是否与数据库一致
if (WeChatUtil.verifyMoney(decryptData, getAmountByOrderNo(waybillId))) {
// 修改订单状态,保存微信支付订单号
updateStatus(waybill, transactionId);
// 判断发货是否成功
if (payService.verifyDeliverGood(waybill.getGoodName(), waybill.getOpenId(), transactionId, config)) {
// 修改发货状态 确认
updateDeliverGood(waybill, ShipmentEnum.OK);
} else {
// 修改发货状态 未确认
updateDeliverGood(waybill, ShipmentEnum.NOT);
}
return true;
}
}
return false;
}
/**
* 获取通知
*
* @param request 请求
* @param config 微信配置
* @return 通知详情
*/
@Override
public JSONObject getDecryptData(HttpServletRequest request, WeChatToolConfig config) {
try {
// 解析微信通知
Notification notification = WeChatUtil.verifyBack(request, config);
// 获取通知简要说明
String summary = notification.getSummary();
// 判断支付状态
if (WeChatPayParam.PAY_SUCCESS.equals(summary) || WeChatPayParam.REFUND_SUCCESS.equals(summary)) {
// 返回详情信息
return JSON.parseObject(notification.getDecryptData());
} else {
// 支付失败
throw new CustomException(summary);
}
} catch (Exception e) {
log.error("回调失败验证" + e);
e.printStackTrace();
throw new CustomException("支付回调失败:" + e.getMessage());
}
}
/**
* 判断发货是否成功
*
* @param description 商品描述
* @param openId appid下的唯一标识
* @param transactionId 微信支付生成订单号
* @param config 基本配置
* @return 是否成功
*/
@Override
public Boolean verifyDeliverGood(String description, String openId, String transactionId, WeChatToolConfig config) {
try {
// 获取接口调用凭证
JSONObject jsonObject = WeChatUtil.getAccessToken(config);
// 异常情况
if (Objects.nonNull(jsonObject.get(WeChatPayParam.CODE))) {
throw new CustomException(jsonObject.get(WeChatPayParam.MESSAGE).toString());
}
String accessToken = jsonObject.get(WeChatPayParam.ACCESS_TOKEN).toString();
// 构建请求参数
JSONObject requestParam = new JSONObject();
// 订单
JSONObject orderKey = new JSONObject();
orderKey.put(WeChatPayParam.ORDER_NUMBER_TYPE, OrderNumberTypeEnum.WECHAT.getCode());
orderKey.put(WeChatPayParam.TRANSACTION_ID, transactionId);
requestParam.put(WeChatPayParam.ORDER_KEY, orderKey);
// 物流模式 用户自提
requestParam.put(WeChatPayParam.LOGISTICS_TYPE, LogisticsTypeEnum.VIRTUAL.getCode());
// 发货模式 统一发货
requestParam.put(WeChatPayParam.DELIVERY_MODE, DeliveryModeEnum.UNIFIED_DELIVERY.getCode());
// 物流信息列表
List<JSONObject> shippingLists = new ArrayList<>();
Map<String, Object> map = new HashMap<>();
map.put(WeChatPayParam.ITEM_DESC, description);
shippingLists.add(new JSONObject(map));
requestParam.put(WeChatPayParam.SHIPPING_LIST, shippingLists);
// 上传时间
requestParam.put(WeChatPayParam.UPLOAD_TIME, new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()));
// 支付者
JSONObject payer = new JSONObject();
// 微信登录时生成的openid,创建订单时存入订单中
payer.put(WeChatPayParam.OPENID, openId);
requestParam.put(WeChatPayParam.PAYER, payer);
// 设置请求url
String url = StrFormatter.format(WxApiTypeEnum.GOOD_ENTERING.getValue(), accessToken);
// 发送请求
JSONObject object = WeChatUtil.getBodyAsJsonByPost(url, requestParam, config);
// 发货失败
if (!object.get(WeChatPayParam.ERR_CODE).equals(WeChatPayParam.NUM_ZERO)) {
log.error("发货失败:" + object.get(WeChatPayParam.ERR_CODE) + " " + object.get(WeChatPayParam.ERR_MSG));
return false;
}
} catch (Exception e) {
e.printStackTrace();
throw new CustomException(e.getMessage());
}
return true;
}
/**
* 更新订单,运单状态 已完成
*
* @param waybill 运单
* @param transactionId 微信支付订单号
*/
public void updateStatus(Waybill waybill, String transactionId) {
// 修改运单状态
waybill.setWaybillStatus(WaybillStatusEnum.COMPLETED);
waybill.setPayId(transactionId);
updateById(waybill);
// 新增散户流水
individualAddFlow(waybill);
// 查询订单
final OrderService orderService = SpringUtils.getBean(OrderService.class);
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Order::getId, waybill.getOrderId());
Order order = orderService.getOne(queryWrapper);
// 修改订单状态
LambdaUpdateWrapper<Order> orderWrapper = new LambdaUpdateWrapper<>();
orderWrapper.eq(Order::getId, waybill.getOrderId());
orderWrapper.set(Order::getOrderStatus, OrderStatusEnum.COMPLETED);
orderWrapper.set(Order::getGoodWeight, waybill.getNetWeight());
orderWrapper.set(Order::getGoodTotalPrice, waybill.getNetWeight().multiply(order.getGoodPrice()));
orderService.update(orderWrapper);
}
/**
* 修改运单确认发货
*
* @param waybill 运单
* @param shipment 确认发货枚举
*/
private void updateDeliverGood(JkwlWaybill waybill, ShipmentEnum shipment) {
waybill.setShipment(shipment);
updateById(waybill);
}
六,查询支付结果
1,控制层
/**
* 查询微信支付订单状态
*
* @param waybillId 运单id
*/
@GetMapping("/getPayOrder/{waybillId}")
@Operation(summary = "查询微信支付订单状态")
public AjaxResult<Void> getPayOrder(@PathVariable("waybillId") String waybillId) {
return waybillService.getPayOrder(waybillId);
}
2,业务层
/**
* 查询微信支付是否成功
*
* @param waybillId 运单id
*/
@Override
public AjaxResult<Void> getPayOrder(String waybillId) {
try {
// 查询微信支付信息
JSONObject jsonObject = payService.getPayState(waybillId, config);
// 返回查单结果信息
final String res = jsonObject.get(WeChatPayParam.TRADE_STATE).toString();
// 支付成功
if (WeChatPayParam.WX_BACK_OK.equals(res)) {
// 根据业务 该干嘛干嘛
// 例: 修改订单
// updateStatus(orderId);
return AjaxResult.success("SUCCESS");
} catch (Exception e) {
log.error("支付查单失败" + orderId);
e.printStackTrace();
}
return AjaxResult.error();
}
/**
* 获取支付状态
*
* @param waybillId 运单id
* @param config 配置
* @return 状态详情
*/
@Override
public JSONObject getPayState(String waybillId, WeChatToolConfig config) throws IOException {
// 查单 微信接口 编辑 微信订单号 + 商户号
String url = StrFormatter.format(WxApiTypeEnum.QUERY_CREATE_ORDER.getValue(), waybillId, config.getMchId());
// 调用微信接口
JSONObject jsonObject = WeChatUtil.getBodyAsJsonByGet(url, config);
// 异常情况
if (Objects.nonNull(jsonObject.get(WeChatPayParam.CODE))) {
throw new CustomException(jsonObject.get(WeChatPayParam.MESSAGE).toString());
}
return jsonObject;
}
七,微信退款
/**
* 微信退款
*
* @param waybillId 运单id
*/
@RepeatSubmit
@GetMapping("/weChatRefundOrder/{waybillId}/{amount}")
@Operation(summary = "微信退款")
@IgnoreAuth
public AjaxResult<Void> weChatRefundOrder(@PathVariable("waybillId") String waybillId, @PathVariable("amount") int amount) {
return waybillService.weChatRefundOrder(waybillId, amount);
}
/**
* 微信退款
*
* @param waybillId 运单id
*/
@Override
public AjaxResult<Void> weChatRefundOrder(String waybillId, int amount) {
// 非开发环境 输入多少退多少
if (!"dev".equals(SpringUtils.getActiveProfile())) {
// 从数据库获取运单退款金额
amount = getAmountByOrderNo(waybillId);
}
// 调用微信退款接口
JSONObject jsonObject = payService.refundOrder(waybillId, amount, config);
// 获取微信退款状态
String status = jsonObject.get(WeChatPayParam.STATUS).toString();
return switch (status) {
// 退款关闭
case "CLOSED" -> AjaxResult.success("退款关闭");
// 退款处理中
case "PROCESSING" -> AjaxResult.success("退款处理中 请稍后");
// 退款成功
case "SUCCESS" -> {
// 可拓展 退款成功后进行的操作 例:修改订单退款订单,运单状态 微信退款入数据库
// updateStatus(orderId);
yield AjaxResult.success("退款申请成功 注意退款查收");
}
// 退款异常
default -> AjaxResult.error("退款账户异常 请联系管理人员");
};
}
/**
* 微信退款
*
* @param waybillId 运单id
* @param refundAmount 退款金额
* @param config 配置
* @return 状态详情
*/
@Override
public JSONObject refundOrder(String waybillId, Integer refundAmount, WeChatToolConfig config) {
// 配置参数
JSONObject requestParam = new JSONObject();
// 商户订单号 orderId
requestParam.put(WeChatPayParam.OUT_TRADE_NO, waybillId);
// 商户退款单号
requestParam.put(WeChatPayParam.OUT_REFUND_NO, waybillId);
// 退款回调地址
requestParam.put(WeChatPayParam.NOTIFY_URL, config.getRefundNotifyUrl());
// 金额信息 amount (单位都是分)
JSONObject amount = new JSONObject();
// 原订单金额 total
amount.put(WeChatPayParam.AMOUNT_TOTAL, refundAmount);
// 退款金额 refund
amount.put(WeChatPayParam.AMOUNT_REFUND, refundAmount);
// 退款币种 CNY 人民币
amount.put(WeChatPayParam.AMOUNT_CURRENCY, WeChatPayParam.CURRENCY_RMB);
// 金额信息 amount: 原订单金额 total 退款金额 refund (单位都是分)
requestParam.put(WeChatPayParam.AMOUNT, amount);
try {
// 调用请求接口
JSONObject jsonObject = WeChatUtil.getBodyAsJsonByPost(WxApiTypeEnum.REFUND_ORDER.getValue(), requestParam, config);
// 异常情况
if (Objects.nonNull(jsonObject.get(WeChatPayParam.CODE))) {
throw new CustomException(jsonObject.get(WeChatPayParam.MESSAGE).toString());
}
log.info("微信申请退款返回结果" + "response:" + jsonObject);
return jsonObject;
} catch (Exception e) {
log.info("微信退款" + waybillId + "失败");
e.printStackTrace();
throw new CustomException("退款失败:" + e.getMessage());
}
}
八、退款回调
/**
* 微信退款回调接口
*/
@IgnoreAuth
@Operation(summary = "微信退款回调接口")
@PostMapping("/wechatRefundBack")
public Map<String, String> wechatRefundBack(HttpServletRequest request) {
Map<String, String> result = new HashMap<>(WeChatPayParam.NUM_TWO);
// 根据退款返回结果 更新订单状态 返回结果
if (waybillService.verifyRefundOrder(request)) {
log.info("==============================微信退款成功订单=====================================");
result.put(WeChatPayParam.CODE, WeChatPayParam.WX_BACK_OK);
} else {
result.put(WeChatPayParam.CODE, "FAIL");
result.put(WeChatPayParam.MESSAGE, "失败");
}
return result;
}
@Override
public Boolean verifyRefundOrder(HttpServletRequest request) {
// 获取微信通知详情
JSONObject decryptData = jkwlPayService.getDecryptData(request, config);
// 商户退款单号
String outRefundNo = decryptData.get(WeChatPayParam.OUT_REFUND_NO).toString();
log.info("微信退款成功订单:" + outRefundNo);
// 退款状态
String refundStatus = decryptData.get(WeChatPayParam.REFUND_STATUS).toString();
// 数据库核对 如果退款状态已退款说明已处理 直接返回即可
if (WaybillStatusEnum.CANCELED.equals(getStatus(outRefundNo))) {
return true;
}
return switch (refundStatus) {
// 退款关闭
case "CLOSED" -> Boolean.FALSE;
// 退款成功
case "SUCCESS" -> {
// 修改订单退款订单,运单状态 微信退款入数据库
// updateStatus(outRefundNo);
yield Boolean.TRUE;
}
// 退款异常
default -> Boolean.FALSE;
};
}
九,查询退款订单
/**
* 查询退款订单
*
* @param waybillId 运单id
*/
@Operation(summary = "查询退款订单")
@GetMapping("/getRefundOrder/{waybillId}")
public AjaxResult<Void> getRefundOrder(@PathVariable("waybillId") String waybillId) {
return waybillService.getRefundOrder(waybillId);
}
/**
* 查询退款订单
*
* @param waybillId 运单id
*/
@Override
public AjaxResult<Void> getRefundOrder(String waybillId) {
try {
// 调用微信查询退款订单接口
JSONObject jsonObject = payService.getRefundState(waybillId, config);
// 获取退款状态
String string = jsonObject.get(WeChatPayParam.STATUS).toString();
return switch (string) {
// 退款关闭
case "CLOSED" -> AjaxResult.success("退款关闭");
// 退款处理中
case "PROCESSING" -> AjaxResult.success("退款处理中 请稍后");
// 退款成功
case "SUCCESS" -> AjaxResult.success("退款申请成功 注意退款查收");
// 退款异常
default -> AjaxResult.error("退款账户异常 请联系管理人员");
};
} catch (Exception e) {
log.error("支付查单失败" + waybillId);
e.printStackTrace();
}
return AjaxResult.error();
}
/**
* 获取退款状态
*
* @param waybillId 运单id
* @param config 配置
* @return 状态详情
*/
@Override
public JSONObject getRefundState(String waybillId, WeChatToolConfig config) throws IOException {
// 查单 微信接口 拼接url
String url = StrFormatter.format(WxApiTypeEnum.QUERY_REFUND_ORDER.getValue(), waybillId);
// 调用微信接口
JSONObject jsonObject = WeChatUtil.getBodyAsJsonByGet(url, config);
// 异常情况
if (Objects.nonNull(jsonObject.get(WeChatPayParam.CODE))) {
throw new CustomException(jsonObject.get(WeChatPayParam.MESSAGE).toString());
}
return jsonObject;
}