瀏覽代碼

微信V2 通行费请求扣款

master
chenchaod 1 月之前
父節點
當前提交
f057114f48

+ 34
- 0
gly-base-core/src/main/java/cn/com/taiji/core/model/comm/protocol/ats/weiXin/AtsWxDeductResultV2Request.java 查看文件

@@ -0,0 +1,34 @@
package cn.com.taiji.core.model.comm.protocol.ats.weiXin;

import cn.com.taiji.core.model.comm.protocol.AbstractSignTypeRequest;
import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.NotBlank;

/**
* @Author:ChenChao
* @Date:2025/7/11 21:23
* @Filename:AtsWxDeductResultV2Request
* @description:
*/
@Getter
@Setter
public class AtsWxDeductResultV2Request extends AbstractSignTypeRequest<AtsWxDeductResultV2Response> {

protected AtsWxDeductResultV2Request() {
super(WeiXinServiceCmd.WEIXINDEDUCTRESULTV2);
}

//=========================配置表获取==========================
@NotBlank(message = "appid不能为空")
private String appId;
@NotBlank(message = "商户号不能为空")
private String mchId;
@NotBlank(message = "商户密钥不能为空")
private String key;
//========================配置表获取==========================

@NotBlank(message = "外部订单号不能为空-自定义")
private String outTradeNo;
}

+ 58
- 0
gly-base-core/src/main/java/cn/com/taiji/core/model/comm/protocol/ats/weiXin/AtsWxDeductResultV2Response.java 查看文件

@@ -0,0 +1,58 @@
package cn.com.taiji.core.model.comm.protocol.ats.weiXin;

import cn.com.taiji.core.model.comm.protocol.AbstractSignTypeResponse;
import lombok.Getter;
import lombok.Setter;

/**
* @Author:ChenChao
* @Date:2025/7/11 21:23
* @Filename:AtsWxDeductResultV2Response
* @description:
*/
@Getter
@Setter
public class AtsWxDeductResultV2Response extends AbstractSignTypeResponse {

/**
* 微信返回信息(未处理的)
*/
private String result;

private String appId;

private String mchId;

private String resultCode;//业务结果 SUCCESS/FAIL

private String errCode;//错误代码

private String errCodeDes;//错误代码描述

private String deviceInfo;//微信支付分配的终端设备号

private String openId;//用户在商户appid下的唯一标识

private String isSubscribe;//是否关注公众账号 Y-关注,N-未关注

private String tradeType;//交易类型PAP-微信委托代扣支付

/**
* SUCCESS—支付成功
* ACCEPT—已接收,等待扣款
* PAY_FAIL--支付失败(其他原因,如银行返回失败)
*/
private String tradeState;//交易状态

private String tradeStateDesc;//交易状态描述

private Long totalFee;//总金额 单位分

private String transactionId;//微信支付订单号

private String outTradeNo;//商户订单号(自定义)

private String attach;//附加数据

private String timeEnd;//支付完成时间 格式为yyyyMMddHHmmss
}

+ 69
- 0
gly-base-core/src/main/java/cn/com/taiji/core/model/comm/protocol/ats/weiXin/AtsWxDeductV2Request.java 查看文件

@@ -0,0 +1,69 @@
package cn.com.taiji.core.model.comm.protocol.ats.weiXin;

import cn.com.taiji.core.model.comm.protocol.AbstractSignTypeRequest;
import lombok.Getter;
import lombok.Setter;

import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

/**
* @Author:ChenChao
* @Date:2025/7/11 16:07
* @Filename:AtsWxDeductV2Request
* @description:
*/
@Getter
@Setter
public class AtsWxDeductV2Request extends AbstractSignTypeRequest<AtsWxDeductV2Response> {
public AtsWxDeductV2Request() {
super(WeiXinServiceCmd.WEIXINDEDUCTV2);
}

//=========================配置表获取==========================
@NotBlank(message = "appid不能为空")
private String appId;
@NotBlank(message = "子商户appid不能为空")
private String subAppId;
@NotBlank(message = "商户号不能为空")
private String mchId;
@NotBlank(message = "子商户号不能为空")
private String subMchId;
@NotBlank(message = "商户密钥不能为空")
private String key;
//========================配置表获取==========================

private String detail;//商品详情

private String goodsTag;//商品标记

private String attach;//附加数据 自定义参数

@NotBlank(message = "外部订单号不能为空-自定义")
private String outTradeNo;
@NotBlank(message = "商品描述不能为空")
private String body;
@NotNull(message = "扣款金额不能为空")
@Min(value = 1, message = "扣款金额不能小于1分")
private Integer totalFee;
@NotBlank(message = "终端IP不能为空")
private String spbillCreateIp;//调用微信支付API的机器IP
@NotBlank(message = "回调地址不能为空")
private String notifyUrl;
/**
* 委托代扣的交易场景值,目前支持 :
* 1.PARKING:车场停车场景
* 2.PARKING SPACE;车位停车场景
* 3.GAS 加油场景
* 4.HIGHWAY 高速场景
* 5.BRIDGE 路桥场景
*/
@NotBlank(message = "交易场景")
private String tradeScene;
@NotNull(message = "场景信息不能为空")
@Valid
private SceneInfo sceneInfo;

}

+ 39
- 0
gly-base-core/src/main/java/cn/com/taiji/core/model/comm/protocol/ats/weiXin/AtsWxDeductV2Response.java 查看文件

@@ -0,0 +1,39 @@
package cn.com.taiji.core.model.comm.protocol.ats.weiXin;

import cn.com.taiji.core.model.comm.protocol.AbstractSignTypeResponse;
import lombok.Getter;
import lombok.Setter;

/**
* @Author:ChenChao
* @Date:2025/7/11 16:07
* @Filename:AtsWxDeductV2Response
* @description:
*/
@Getter
@Setter
public class AtsWxDeductV2Response extends AbstractSignTypeResponse {

/**
* 微信返回信息(未处理的)
*/
private String result;

private String appId;//公众账号id

private String mchId;//商户号

private String deviceInfo;//设备号

/**
* 错误代码
* 扣款接口请求成功,返回success仅代表扣款申请受理成功,不代表扣款成功。扣款是否成功以支付通知的结果为准。
*/
private String resultCode;//业务结果 SUCCESS/FAIL

private String errCode;//错误代码

private String errCodeDes;//错误代码描述


}

+ 53
- 0
gly-base-core/src/main/java/cn/com/taiji/core/model/comm/protocol/ats/weiXin/SceneInfo.java 查看文件

@@ -0,0 +1,53 @@
package cn.com.taiji.core.model.comm.protocol.ats.weiXin;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;

import javax.validation.constraints.NotBlank;

/**
* @Author:ChenChao
* @Date:2025/7/11 17:02
* @Filename:SceneInfo
* @description: v2扣款订单场景信息(目前只支持告诉场景)
*/
@Data
public class SceneInfo {
@NotBlank(message = "入场时间不能为空")
@JsonProperty(value = "start_time")
private String startTime;
@NotBlank(message = "出场时间不能为空")
@JsonProperty(value = "end_time")
private String endTime;
@NotBlank(message = "车牌号不能为空")
@JsonProperty(value = "plate_number")
private String plateNumber;
/**
* 车辆的类型,可选值:客车、货车
**/
@NotBlank(message = "车辆类型不能为空")
@JsonProperty(value = "car_type")
private String carType;
@NotBlank(message = "入口站名称不能为空")
@JsonProperty(value = "entrance_name")
private String entranceName;
@NotBlank(message = "出口站名称不能为空")
@JsonProperty(value = "exit_name")
private String exitName;
/**
* 核载人数
**/
@JsonProperty(value = "carrying_capacity")
private String carryingCapacity;
/**
* 核载区间
**/
@JsonProperty(value = "carrying_range")
private String carryingRange;
/**
* 通道类型, 目前可选:ETC、MTC
**/
@NotBlank(message = "通道类型不能为空")
@JsonProperty(value = "channel_type")
private String channelType;
}

+ 2
- 0
gly-base-core/src/main/java/cn/com/taiji/core/model/comm/protocol/ats/weiXin/WeiXinServiceCmd.java 查看文件

@@ -18,6 +18,8 @@ public enum WeiXinServiceCmd implements SignServiceCommand {
VEHICLESIGNV3("微信V3获取预开通用户ETC扣费签约参数","vehicleSignV3", AtsVehicleSignV3Request.class),
VEHICLESIGNRESULTV3("微信V3ETC签约状态查询","vehicleSignResultV3", AtsVehicleSignV3ResultRequest.class),
GETWECHATOPENDI("微信小程序登录-获取用户openID","getWeChatOpenId", AtsGetWeChatOpenIdRequest.class),
WEIXINDEDUCTV2("微信V2扣款","weiXinDeDuctV2", AtsWxDeductV2Request.class),
WEIXINDEDUCTRESULTV2("微信V2扣款结果查询","weiXinDeDuctResultV2", AtsWxDeductResultV2Request.class),
MPSENDMESSAGE("微信公众号发送订阅信息","mpSendMessage", WxMpSendMessageRequest.class),
MPMESSAGELIST("微信公众号获取模板列表","mpMessageList", WxMpMessageTemplateListRequest.class),
MPISSUBSCRIBED("微信公众号是否订阅","isSubscribed", WxMpIsSubscribedRequest.class),

+ 8
- 0
zhywpt-service-ats/src/main/java/cn/com/taiji/ats/manager/handler/WinXinServiceHandler.java 查看文件

@@ -54,6 +54,10 @@ public class WinXinServiceHandler extends AbstractAtsServiceHandler<WeiXinServic
@Autowired
private VehicleSignV3ResultManager vehicleSignV3ResultManager;
@Autowired
private WeixinDeductV2Manager weiXinDeductV2Manager;
@Autowired
private WeixinDeductResultV2Manager weiXinDeductResultV2Manager;
@Autowired
private WxMessageManager wxMessageManager;


@@ -86,6 +90,10 @@ public class WinXinServiceHandler extends AbstractAtsServiceHandler<WeiXinServic
return vehicleSignV3ResultManager.serviceHandle((AtsVehicleSignV3ResultRequest) request);
case GETWECHATOPENDI:
return getWeChatOpenIdManager.serviceHandle((AtsGetWeChatOpenIdRequest) request);
case WEIXINDEDUCTV2:
return weiXinDeductV2Manager.serviceHandle((AtsWxDeductV2Request) request);
case WEIXINDEDUCTRESULTV2:
return weiXinDeductResultV2Manager.serviceHandle((AtsWxDeductResultV2Request) request);
case MPISSUBSCRIBED:
return wxMessageManager.isSubscribed((WxMpIsSubscribedRequest) request);
case MPSENDMESSAGE:

+ 87
- 0
zhywpt-service-ats/src/main/java/cn/com/taiji/ats/manager/weixin/WeixinDeductResultV2Manager.java 查看文件

@@ -0,0 +1,87 @@
package cn.com.taiji.ats.manager.weixin;

import cn.com.taiji.ats.tools.WxVehicleUtil;
import cn.com.taiji.common.manager.net.http.ServiceHandleException;
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.AtsWxDeductResultV2Request;
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.AtsWxDeductResultV2Response;
import cn.com.taiji.core.model.comm.protocol.valid.GlyServiceError;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpRequest;
import com.google.common.collect.Maps;
import org.springframework.stereotype.Service;

import java.util.Map;

/**
* @Author:ChenChao
* @Date:2025/7/11 21:29
* @Filename:WeixinDeductResultV2Manager
* @description: 微信V2扣款结果查询
*/
@Service
public class WeixinDeductResultV2Manager {

public static final String baseUrl = "https://api.mch.weixin.qq.com/transit/queryorder";
public AtsWxDeductResultV2Response serviceHandle(AtsWxDeductResultV2Request req) throws ServiceHandleException {
String paramXml = null;
try {
paramXml = WxVehicleUtil.generateSignedXml(getParam(req), req.getKey(), "HMACSHA256");
} catch (Exception e) {
throw GlyServiceError.BUSINESS_VALIDATE_ERR.toHandleException("请款结果查询签名失败");
}
String responseXml = HttpRequest.post(baseUrl)
.setConnectionTimeout(10000)
.setReadTimeout(10000)
.body(paramXml)
.execute()
.body();
return getAtsWxDeductResultV2Response(req, responseXml);
}

private static AtsWxDeductResultV2Response getAtsWxDeductResultV2Response(AtsWxDeductResultV2Request req, String responseXml) throws ServiceHandleException {
//返回值验签
Map<String, String> responseMap = WxVehicleUtil.xmlToMap(responseXml);
if ("FAIL".equals(responseMap.get("return_code"))) {
throw GlyServiceError.BUSINESS_VALIDATE_ERR.toHandleException("微信返回异常:"+responseMap.get("return_msg"));
}
String localSign = null;
try {
localSign = WxVehicleUtil.generateSignature(responseMap, req.getKey(), "HMACSHA256");
} catch (Exception e) {
throw GlyServiceError.BUSINESS_VALIDATE_ERR.toHandleException("微信返回生成签名异常");
}
if (!localSign.equals(responseMap.get("sign"))) {
throw GlyServiceError.BUSINESS_VALIDATE_ERR.toHandleException("微信返回验签失败");
}
AtsWxDeductResultV2Response response = new AtsWxDeductResultV2Response();
response.setResult(responseXml);
response.setAppId(responseMap.get("appid"));
response.setMchId(responseMap.get("mch_id"));
response.setResultCode(responseMap.get("result_code"));
response.setErrCode(responseMap.get("err_code"));
response.setErrCodeDes(responseMap.get("err_code_des"));
response.setDeviceInfo(responseMap.get("device_info"));
response.setOpenId(responseMap.get("openid"));
response.setIsSubscribe(responseMap.get("is_subscribe"));
response.setTradeType(responseMap.get("trade_type"));
response.setTradeState(responseMap.get("trade_state"));
response.setTradeStateDesc(responseMap.get("trade_state_desc"));
response.setTotalFee(Long.valueOf(responseMap.get("total_fee")));
response.setTransactionId(responseMap.get("transaction_id"));
response.setOutTradeNo(responseMap.get("out_trade_no"));
response.setAttach(responseMap.get("attach"));
response.setTimeEnd(responseMap.get("time_end"));
return response;
}


private Map<String, String> getParam(AtsWxDeductResultV2Request req){
Map<String, String> param = Maps.newHashMap();
param.put("appid", req.getAppId());
param.put("mch_id", req.getMchId());
param.put("out_trade_no", req.getOutTradeNo());
param.put("nonce_str", RandomUtil.randomStringUpper(31));
param.put("sign_type", "HMAC-SHA256");
return param;
}
}

+ 97
- 0
zhywpt-service-ats/src/main/java/cn/com/taiji/ats/manager/weixin/WeixinDeductV2Manager.java 查看文件

@@ -0,0 +1,97 @@
package cn.com.taiji.ats.manager.weixin;

import cn.com.taiji.ats.model.wx.PayApplyHighwayVo;
import cn.com.taiji.ats.tools.WxVehicleUtil;
import cn.com.taiji.common.manager.net.http.ServiceHandleException;
import cn.com.taiji.common.pub.json.JsonTools;
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.AtsWxDeductV2Request;
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.AtsWxDeductV2Response;
import cn.com.taiji.core.model.comm.protocol.valid.GlyServiceError;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.HttpRequest;
import com.google.common.collect.Maps;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Map;

/**
* @Author:ChenChao
* @Date:2025/7/11 16:13
* @Filename:WeixinDeductV2Manager
* @description: 微信V2代扣请款
*/
@Service
public class WeixinDeductV2Manager {

private static final String baseUrl = "https://api.mch.weixin.qq.com/vehicle/pay/payapply";

public AtsWxDeductV2Response serviceHandle(AtsWxDeductV2Request req) throws ServiceHandleException {
String paramXml = "";
try {
//封装请求参数并签名
paramXml = WxVehicleUtil.generateSignedXml(getParam(req), req.getKey(), "HMACSHA256");
} catch (Exception e) {
throw GlyServiceError.BUSINESS_VALIDATE_ERR.toHandleException("请款签名失败");
}
String responseXml = HttpRequest.post(baseUrl)
.setConnectionTimeout(10000)
.setReadTimeout(10000)
.body(paramXml)
.execute()
.body();
return getAtsWxDeductV2Response(req, responseXml);
}

//处理响应
private static AtsWxDeductV2Response getAtsWxDeductV2Response(AtsWxDeductV2Request req, String responseXml) throws ServiceHandleException {
//返回值验签
Map<String, String> responseMap = WxVehicleUtil.xmlToMap(responseXml);
if ("FAIL".equals(responseMap.get("return_code"))) {
throw GlyServiceError.BUSINESS_VALIDATE_ERR.toHandleException("微信返回异常:"+responseMap.get("return_msg"));
}
String localSign = null;
try {
localSign = WxVehicleUtil.generateSignature(responseMap, req.getKey(), "HMACSHA256");
} catch (Exception e) {
throw GlyServiceError.BUSINESS_VALIDATE_ERR.toHandleException("微信返回生成签名异常");
}
if (!localSign.equals(responseMap.get("sign"))) {
throw GlyServiceError.BUSINESS_VALIDATE_ERR.toHandleException("微信返回验签失败");
}
AtsWxDeductV2Response response = new AtsWxDeductV2Response();
response.setResult(responseXml);
response.setAppId(responseMap.get("appid"));
response.setMchId(responseMap.get("mch_id"));
response.setDeviceInfo(responseMap.get("device_info"));
response.setResultCode(responseMap.get("result_code"));
response.setErrCode(responseMap.get("err_code"));
response.setErrCodeDes(responseMap.get("err_code_des"));
return response;
}

private Map<String, String> getParam(AtsWxDeductV2Request req) throws IOException {
Map<String, String> param = Maps.newHashMap();
param.put("appid", req.getAppId());
param.put("sub_appid", req.getSubAppId());
param.put("mch_id", req.getMchId());
param.put("sub_mch_id", req.getSubMchId());
param.put("nonce_str", RandomUtil.randomStringUpper(31));
param.put("sign_type", "HMAC-SHA256");

param.put("body", req.getBody());
param.put("detail", req.getDetail());
param.put("attach", req.getAttach());
param.put("out_trade_no", req.getOutTradeNo());
param.put("total_fee", String.valueOf(req.getTotalFee()));
param.put("fee_type", "CNY");
param.put("spbill_create_ip", req.getSpbillCreateIp());
param.put("goods_tag", req.getGoodsTag());
param.put("notify_url", req.getNotifyUrl());
param.put("trade_type", "PAP");
param.put("version", "2.0");
param.put("trade_scene", req.getTradeScene());
param.put("scene_info", JsonTools.toJsonStr(new PayApplyHighwayVo(req.getSceneInfo())));
return param;

}
}

+ 4
- 2
zhywpt-service-ats/src/main/java/cn/com/taiji/ats/model/anXin/InterfaceRequest.java 查看文件

@@ -1,7 +1,8 @@
package cn.com.taiji.ats.model.anXin;

import cn.com.taiji.common.model.BaseModel;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.NotBlank;

@@ -10,7 +11,8 @@ import javax.validation.constraints.NotBlank;
* @ClassName InterfaceRequest
* @Description 公共请求参数实体
*/
@Data
@Setter
@Getter
public class InterfaceRequest extends BaseModel {

@NotBlank(message = "业务参数数据不能为空")

+ 4
- 2
zhywpt-service-ats/src/main/java/cn/com/taiji/ats/model/anXin/InterfaceResponse.java 查看文件

@@ -1,13 +1,15 @@
package cn.com.taiji.ats.model.anXin;

import cn.com.taiji.common.model.BaseModel;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

/**
* @ClassName InterfaceRequest
* @Description 公共响应参数实体
*/
@Data
@Getter
@Setter
public class InterfaceResponse extends BaseModel {

private String bizContent;

+ 33
- 0
zhywpt-service-ats/src/main/java/cn/com/taiji/ats/model/wx/PayApplyHighwayVo.java 查看文件

@@ -0,0 +1,33 @@
package cn.com.taiji.ats.model.wx;


import cn.com.taiji.common.model.BaseModel;
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.SceneInfo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;
import java.util.Map;

/***
* <p>
* 高速场景参数工具类
* </p>
* @author hou yi
* @date 2022/8/23 18:14
**/
@Getter
@Setter
public class PayApplyHighwayVo extends BaseModel {

@JsonProperty(value = "scene_info")
private SceneInfo sceneInfo;

public PayApplyHighwayVo() {
}

public PayApplyHighwayVo(SceneInfo sceneInfo) {
this.sceneInfo = sceneInfo;
}
}

Loading…
取消
儲存