由于公司不能申请下来服务商支付通业务权限,最后找到一个商城服务商解决方案
商城下单只能下单一个商户的商品,因为商户的商品下单,需要合单支付,业务没有开通,所以唯一的弊端就是一次只能买一个店铺的商品,跨店买不支持
开发工具 idea maven jdk1.8
1.引入pom
<dependency>
<groupId>com.github.javen205</groupId>
<artifactId>IJPay-All</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.2</version>
</dependency>
2.微信配置文件 wxpay.properties
#小程序appid
wxpay.appId=xxxx
#小程序密钥
wxpay.appSecret=xxxx
#服务商商户号
wxpay.mchId=xxxx
#服务商商户密钥 也就是APIV2密钥
wxpay.partnerKey=xxxx
#apiv3密钥 v3需要
wxpay.apiV3=xxxx
#服务商商户证书证书序列号 v3 需要
wxpay.serialNo=xxxx
#证书路径,在微信商户后台下载 v2 需要
wxpay.certPath=c:/apiclient_cert.p12
#域名地址 线上得改
wxpay.domain=https://xxx.natapp4.cc/
#文件存房目录
wxpay.localdomain=D:/minio/data/
#账单存放目录
wxpay.localPath=D:/minio/data/filepath/temp/
3.创建微信参数实体
@Component
@PropertySource("classpath:/wxpay.properties")
@ConfigurationProperties(prefix = "wxpay")
@Data //lombok
public class WxPayBean {
private String appId;
private String appSecret;
private String mchId;
private String partnerKey;
private String certPath;
private String domain;
private String localdomain;
private String serialNo;
private String localPath;
private String apiV3;
}
4 ijapy 支付
@PostMapping("/pay.do")
public Result<?> saveOneOrder(HttpServletRequest request){
String ip = IpKit.getRealIp(request);
if (StrUtil.isBlank(ip)) {
ip = "127.0.0.1";
}
Map<String, String> params = UnifiedOrderModel
.builder()
.appid(wxPayBean.getAppId())
.mch_id(wxPayBean.getMchId())
.sub_mch_id(shop.getSubMchid())
.nonce_str(WxPayKit.generateStr())
.body("商品支付")
.attach("小程序商品支付")
.out_trade_no(order.getOrderNo())
.total_fee(multiplys.intValue()+"")
.spbill_create_ip(ip)
.notify_url(wxPayBean.getDomain().concat("yw/api/order/payNotifyOne"))
.trade_type(TradeType.JSAPI.getTradeType())
.openid(user.getOpenid())
.profit_sharing("Y")//是否分账标志
.build()
.createSign(wxPayBean.getPartnerKey(), SignType.HMACSHA256);
String xmlResult = WxPayApi.pushOrder(false, params);
Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
String returnCode = result.get("return_code");
String returnMsg = result.get("return_msg");
if (!WxPayKit.codeIsOk(returnCode)) {
return Result.error(returnMsg);
}
String resultCode = result.get("result_code");
if (!WxPayKit.codeIsOk(resultCode)) {
return Result.error(returnMsg);
}
// 以下字段在 return_code 和 result_code 都为 SUCCESS 的时候有返回
String prepayId = result.get("prepay_id");
Map<String, String> packageParams = WxPayKit.miniAppPrepayIdCreateSign(wxPayBean.getAppId(), prepayId,
wxPayBean.getPartnerKey(), SignType.HMACSHA256);
String jsonStr = JSON.toJSONString(packageParams);
return Result.OK(packageParams);
}
5 支付回调
@AutoLog(value = "直接支付回调")
@PostMapping(value = "/payNotifyOne")
public String payNotifyOne(HttpServletRequest request) {
String xmlMsg = HttpKit.readData(request);
System.out.println("支付通知");
System.out.println(xmlMsg);
Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
String returnCode = params.get("return_code");
// 微信支付订单号
String transaction_id = params.get("transaction_id");
// 商户订单号
String out_trade_no = params.get("out_trade_no");
// 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
Order order = orderService.getOne(new LambdaQueryWrapper<Order>()
.eq(Order::getOrderNo, out_trade_no)
);
if (null==order) {
System.err.println("订单不存在:" + out_trade_no);
return "failure";
}
String orderStatus = order.getOrderStatus();
if ("2".equals(orderStatus)) {
//发送通知等
Map<String, String> xml = new HashMap<>(16);
xml.put("return_code", "SUCCESS");
xml.put("return_msg", "OK");
return WxPayKit.toXml(xml);
}
// 注意此处签名方式需与统一下单的签名类型一致
if (WxPayKit.verifyNotify(params, wxPayBean.getPartnerKey(), SignType.HMACSHA256)) {
if (WxPayKit.codeIsOk(returnCode)) {
// 更新订单信息
order.setOrderStatus("2");
order.setPayTime(new Date());
order.setPayNo(transaction_id);
orderService.afterPay(order);
// 发送通知等
Map<String, String> xml = new HashMap<String, String>(2);
xml.put("return_code", "SUCCESS");
xml.put("return_msg", "OK");
return WxPayKit.toXml(xml);
}else{
// orderService.savePayFailTradeLog(order);
}
}
return null;
}
6.退款
public void noSendAllRefund(String orderId) {
String s = WxPayKit.generateStr();
Order order = orderMapper.selectById(orderId);
order.setRefundNo(s);
order.setRefundFlag("1");
orderMapper.updateById(order);
SxzBreakfastShop shop = shopMapper.selectById(order.getShopId());
BigDecimal totalPrice = order.getAmount().multiply(new BigDecimal(100));
String refundStr="";
try {
Map<String, String> params = RefundModel.builder()
.appid(wxPayBean.getAppId())
.mch_id(wxPayBean.getMchId())
.sub_mch_id(shop.getSubMchid())
.nonce_str(WxPayKit.generateStr())
.transaction_id(order.getPayNo())
//.out_trade_no(outTradeNo)
.out_refund_no(s)
.total_fee(totalPrice.intValue()+"")
.refund_fee(totalPrice.intValue()+"")
.notify_url(wxPayBean.getDomain().concat("yw/api/order/refundNotifyAll"))
.build()
.createSign(wxPayBean.getPartnerKey(), SignType.MD5);
refundStr = WxPayApi.orderRefund(false, params, wxPayBean.getCertPath(), wxPayBean.getMchId());
System.out.println("退款同步信息"+refundStr);
//orderService.saveFlowLog(order,"2","");
} catch (Exception e) {
orderService.saveRefundFailTradeLog(order);
throw new MyException(refundStr);
}
}
退款通知
@PostMapping(value = "/refundNotifyAll")
public String refundNotifyAll(HttpServletRequest request) {
String xmlMsg = HttpKit.readData(request);
Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
String returnCode = params.get("return_code");
String out_refund_no = params.get("out_refund_no");
Order one = orderService.getOne(new LambdaQueryWrapper<Order>()
.eq(Order::getRefundNo, out_refund_no)
);
if (null==one) {
System.err.println("订单不存在:" + out_refund_no);
return "failure";
}
String refundFlag = one.getRefundFlag();
if ("2".equals(refundFlag)) {
//发送通知等
Map<String, String> xml = new HashMap<>(16);
xml.put("return_code", "SUCCESS");
xml.put("return_msg", "OK");
return WxPayKit.toXml(xml);
}
// 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态
if (WxPayKit.codeIsOk(returnCode)) {
String reqInfo = params.get("req_info");
String decryptData = WxPayKit.decryptData(reqInfo, getWxPayApiConfig().getPartnerKey());
orderDetailService.noSendRefundCode(one.getId());
// 发送通知等
Map<String, String> xml = new HashMap<String, String>(2);
xml.put("return_code", "SUCCESS");
xml.put("return_msg", "OK");
return WxPayKit.toXml(xml);
}else{
//orderService.saveRefundFailTradeLog(one);
throw new MyException(xmlMsg);
}
}
7.添加分账方
public void profitSharingAddReceiver(String subMchId) {
try {
ReceiverModel receiver = ReceiverModel.builder()
.type("MERCHANT_ID")//商户号
.account(wxPayBean.getMchId())//分帐方商户号
.relation_type("SERVICE_PROVIDER")//服务商
.build();
Map<String, String> params = ProfitSharingModel.builder()
.mch_id(wxPayBean.getMchId())
.appid(wxPayBean.getAppId())
.sub_mch_id(subMchId)
.nonce_str(WxPayKit.generateStr())
.receiver(JSON.toJSONString(receiver))
.build()
.createSign(wxPayBean.getPartnerKey(), SignType.HMACSHA256);
String result = WxPayApi.profitSharingAddReceiver(params);
Map<String, String> stringStringMap = WxPayKit.xmlToMap(result);
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
8 请求分账 分两种 请求单次分账 请求多次分账
区别: 请求单次分账后,不可再分账,会把剩余的待分账的钱直接解冻给商家
请求多次分账,可以多次分账,最大分账金额不可超过最大分账比例,分账完成后,要请求解冻给分账方接口,剩余的分账金额才会解冻给商家
请求单次分账 分账结果需要调用分账查询接口
1.请求单次分账
public synchronized void profitSharing(Order order) {
//BigDecimal amount = order.getAmount();
SxzBreakfastShop shop = shopMapper.selectById(order.getShopId());
//BigDecimal wxRate = shop.getWxRate();//得除以100
//BigDecimal fyRate = shop.getFyRate();//得除以100
//BigDecimal wxMoney = amount.multiply(wxRate);
//BigDecimal fenzhangMoney = amount.multiply(new BigDecimal(1).subtract(wxRate));
//BigDecimal fymoney = fenzhangMoney.multiply(fyRate);
BigDecimal multiply = order.getCommission().multiply(new BigDecimal(100));
String s = WxPayKit.generateStr();//分账单号
//查询商户信息
List<ReceiverModel> list = new ArrayList<>();
list.add(ReceiverModel.builder()
.type("MERCHANT_ID")
.account(wxPayBean.getMchId())
.amount(multiply.intValue())
.description(shop.getName()+" 分账")
.build());
Map<String, String> params = ProfitSharingModel.builder()
.mch_id(wxPayBean.getMchId())
.sub_mch_id(shop.getSubMchid())
.appid(wxPayBean.getAppId())
.nonce_str(WxPayKit.generateStr())
.transaction_id(order.getPayNo())
.out_order_no(s)
.receivers(JSON.toJSONString(list))
.build()
.createSign(wxPayBean.getPartnerKey(), SignType.HMACSHA256);
String result = WxPayApi.multiProfitSharing(params, wxPayBean.getCertPath(), wxPayBean.getMchId());
System.out.println(result);
Map<String, String> map = WxPayKit.xmlToMap(result);
if("SUCCESS".equals(map.get("return_code"))&&"SUCCESS".equals(map.get("result_code"))){
String order_id = map.get("order_id");//微信分账单号
String out_order_no = map.get("out_order_no");//商户分账单号
String transaction_id = map.get("transaction_id");//微信订单号
String status = map.get("status");//分账单状态
order.setFyNo(s);
//order.setCommission(multiply);
//order.setWxCommission(wxMoney);
if("PROCESSING".equals(status)){
order.setFyFlag("2");
this.baseMapper.updateById(order);
}else if("FINISHED".equals(status)){
order.setFyFlag("3");
order.setFzTime(new Date());
this.baseMapper.updateById(order);
saveFzToShopLog(order);//解冻
saveFzLog(order);//分账
}
}else{
saveFzFailLog(order);
throw new MyException(result);
}
}
2.查询分账结果 最好是写个定时器去查询
public synchronized void profitSharingQuery(Order order) {
SxzBreakfastShop shop = shopMapper.selectById(order.getShopId());
Map<String, String> params = ProfitSharingModel.builder()
.mch_id(wxPayBean.getMchId())
.sub_mch_id(shop.getSubMchid())
.appid(wxPayBean.getAppId())
.nonce_str(WxPayKit.generateStr())
.transaction_id(order.getPayNo())
.out_order_no(order.getFyNo())
.build()
.createSign(wxPayBean.getPartnerKey(), SignType.HMACSHA256);
String result = WxPayApi.profitSharingQuery(params);
System.out.println(result);
Map<String, String> map = WxPayKit.xmlToMap(result);
if("SUCCESS".equals(map.get("return_code"))&&"SUCCESS".equals(map.get("result_code"))){
String status = map.get("status");//分账单状态
if("FINISHED".equals(status)){
if(!"3".equals(order.getFyFlag())){
order.setFyFlag("3");
order.setFzTime(new Date());
this.baseMapper.updateById(order);
saveFzToShopLog(order);//解冻
saveFzLog(order);//分账
}
}
}else{
throw new MyException(result);
}
}
多次分账就不写了,和单次分账基本一样,组装参数即可
9 对账
1.对账分账账单 写个定时器 每天十点以后查询前一天的分账账单
public void dowloadFzBill(String date) {
FzBillDataResp zdUrlFz = getZdUrlFz(date);
//保存交易分账数据
List<FzBillLineResp> fzBillLineRespList = zdUrlFz.getFzBillLineRespList();
FzBillTotalResp fzBillTotalResp = zdUrlFz.getFzBillTotalResp();
}
public static FzBillDataResp getZdUrlFz(String date) {
//分账账单
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/profitsharing/bills?bill_date="+date);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = getHttpClient().execute(httpGet);
String bodyAsString = EntityUtils.toString(response.getEntity());
System.out.println(bodyAsString);
JSONObject jsonObject1 = JSONObject.parseObject(bodyAsString);
//{"code":"NO_STATEMENT_EXIST","message":"账单文件不存在"}
String code = jsonObject1.getString("code");
if(StringUtils.isNotBlank(code)){
throw new MyException(code+"__"+jsonObject1.getString("message"));
// insertLog(req, e, "1");
}
String download_url = jsonObject1.getString("download_url");
FzBillDataResp zdUrlDowloadFz = getZdUrlDowloadFz(download_url);
return zdUrlDowloadFz;
}
public static FzBillDataResp getZdUrlDowloadFz(String dowloadUrl) {
HttpRequest get = HttpUtil.createGet(dowloadUrl);
HttpUrl parse = HttpUrl.parse(dowloadUrl);
get.header("Authorization", "WECHATPAY2-SHA256-RSA2048"+" "+SignUtils.getToken("GET",parse,""));
HttpResponse execute = get.execute();
InputStream inputStream = execute.bodyStream();
// File file = FileUtil.writeFromStream(inputStream, FileUtil.newFile("D:/zdBill"+DateUtils.getDate("yyyy-MM-dd")+System.currentTimeMillis()+".xlsx"));
//List<String> list = FileUtil.readLines(file, "utf-8");
FzBillDataResp fzBillDataResp = parseBodyFz(execute.body());
return fzBillDataResp;
}
private static FzBillDataResp parseBodyFz(String body) {
FzBillDataResp tradeBillDataResp = null;
List<FzBillLineResp> tradeBillLineRespList = new ArrayList<>();
try {
body = body.replace("`", "");
String[] billDataStrArray = body.split("\r\n");
if (billDataStrArray.length > 0) {
/* 解析行数据 */
for (int i = 1; i < billDataStrArray.length - 2; i++) {
// 填充空格,防止数组越界
String billData = billDataStrArray[i] + " ";
String[] data = billData.split(",");
FzBillLineResp tradeBillLineResp = FzBillLineResp.class.getDeclaredConstructor().newInstance();
for (Field field : FzBillLineResp.class.getDeclaredFields()) {
SplitIndex splitIndex = field.getAnnotation(SplitIndex.class);
if (splitIndex != null) {
int index = splitIndex.value();
setFieldValue(tradeBillLineResp, field, data[index]);
}
}
tradeBillLineRespList.add(tradeBillLineResp);
}
/* 解析汇总数据*/
// 填充空格,防止数组越界
String totalData = billDataStrArray[billDataStrArray.length - 1] + " ";
String[] totalDataArray = totalData.split(",");
FzBillTotalResp tradeBillTotalResp = FzBillTotalResp.class.getDeclaredConstructor().newInstance();
for (Field field : FzBillTotalResp.class.getDeclaredFields()) {
SplitIndex splitIndex = field.getAnnotation(SplitIndex.class);
if (splitIndex != null) {
int index = splitIndex.value();
setFieldValue(tradeBillTotalResp, field, totalDataArray[index]);
}
}
tradeBillDataResp = FzBillDataResp.builder()
.fzBillTotalResp(tradeBillTotalResp)
.fzBillLineRespList(tradeBillLineRespList)
.build();
}
return tradeBillDataResp;
} catch (Exception e) {
throw new RuntimeException("解析交易账单文件响应数据异常:" + e);
}
}
/**
* 使用反射设置对象的属性值。
*
* @param obj 要设置属性值的对象
* @param field 属性
* @param value 属性值
* @throws IllegalAccessException 如果没有足够的权限访问属性
*/
public static void setFieldValue(Object obj, Field field, String value)
throws IllegalAccessException, IntrospectionException, InvocationTargetException, ParseException {
Class<?> aClass = obj.getClass();
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), aClass);
Method method = propertyDescriptor.getWriteMethod();
String type = field.getGenericType()
.toString();
if (!org.springframework.util.StringUtils.hasText(value)) {
return;
}
value = value.trim();
switch (type) {
case "class java.util.Date":
method.invoke(obj, DateUtils.parseDate(value,"yyyy-MM-dd HH:mm:ss"));
break;
case "class java.lang.String":
method.invoke(obj, value);
break;
case "class java.lang.Integer":
method.invoke(obj, Double.valueOf(value)
.intValue());
break;
case "class java.lang.Long":
method.invoke(obj, Double.valueOf(value)
.longValue());
break;
case "class java.lang.Double":
method.invoke(obj, Double.valueOf(value));
break;
case "class java.lang.Float":
method.invoke(obj, Double.valueOf(value)
.floatValue());
break;
case "class java.lang.Character":
method.invoke(obj, value.toCharArray()[0]);
break;
case "class java.math.BigDecimal":
method.invoke(obj, new BigDecimal(value).setScale(4, RoundingMode.HALF_UP));
break;
default:
method.invoke(obj, (Object) null);
break;
}
}
//实体
@Data
@Builder
public class FzBillDataResp {
/**
* 账单数据(分条)
*/
private List<FzBillLineResp> fzBillLineRespList;
/**
* 分账账单汇总数据
*/
private FzBillTotalResp fzBillTotalResp;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FzBillLineResp {
/**
* 分账时间
*/
@SplitIndex(0)
private Date tradeDate;
/**
* 分账发起方
*/
@SplitIndex(1)
private String serviceMchId;
/**
* 分账方
*/
@SplitIndex(2)
private String specMchId;
/**
* 微信订单号
*/
@SplitIndex(3)
private String wechatOrderNo;
/**
* 微信分账/回退单号
*/
@SplitIndex(4)
private String wechatFzNo;
/**
* 分账明细单号
*/
@SplitIndex(5)
private String wechatFzDetailNo;
/**
* 商户分账/回退单号
*/
@SplitIndex(6)
private String wechatShopFzNo;
/**
* 订单金额
*/
@SplitIndex(7)
private BigDecimal orderMoney;
/**
* 分账接收方
*/
@SplitIndex(8)
private String acceptMchId;
/**
* 分账金额
*/
@SplitIndex(9)
private BigDecimal fzMoney;
/**
* 业务类型
*/
@SplitIndex(10)
private String type;
/**
* 处理状态
*/
@SplitIndex(11)
private String status;
/**
* 分账描述
*/
@SplitIndex(12)
private String fzRemark;
/**
* 备注
*/
@SplitIndex(13)
private String remark;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FzBillTotalResp {
/**
* 总条数
*/
@SplitIndex(0)
private Integer totalOrderCount;
/**
* 分账成功出资金
*/
@SplitIndex(1)
private BigDecimal totalNeedPayOrderAmount;
/**
* 分账失败已转回分账方
*/
@SplitIndex(2)
private BigDecimal totalFundAmount;
/**
* 解冻资金给分账方
*/
@SplitIndex(3)
private BigDecimal totalTopUpFundAmount;
/**
* 分账回退资金
*/
@SplitIndex(4)
private BigDecimal totalRequestFundAmount;
}
//注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SplitIndex {
/**
* 填充下标
*/
int value();
}
1.下载交易账单 写个定时器 每天十点以后查询前一天的分账账单
public void dowloadTradeBill(String date) {
TradeBillDataResp tradeBillDataResp = getZdUrl(date);
//保存交易天数据
List<TradeBillLineResp> tradeBillLineRespList =
tradeBillDataResp.getTradeBillLineRespList();
TradeBillTotalResp tradeBillTotalResp = tradeBillDataResp.getTradeBillTotalResp();
}
public static TradeBillDataResp getZdUrl(String date) throws MyException,URISyntaxException, GeneralSecurityException, IOException, NotFoundException, HttpCodeException {
//交易账单
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/bill/tradebill?bill_date="+date+"&bill_type=ALL");
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = getHttpClient().execute(httpGet);
String bodyAsString = EntityUtils.toString(response.getEntity());
JSONObject jsonObject1 = JSONObject.parseObject(bodyAsString);
String code = jsonObject1.getString("code");
if(StringUtils.isNotBlank(code)){
throw new MyException(code+"__"+jsonObject1.getString("message"));
}
String download_url = jsonObject1.getString("download_url");
TradeBillDataResp zdUrlDowload = getZdUrlDowload(download_url);
return zdUrlDowload;
}
/**
* 交易账单
* @param dowloadUrl
* @return
* @throws Exception
*/
public static TradeBillDataResp getZdUrlDowload(String dowloadUrl) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
HttpRequest get = HttpUtil.createGet(dowloadUrl);
HttpUrl parse = HttpUrl.parse(dowloadUrl);
get.header("Authorization", "WECHATPAY2-SHA256-RSA2048"+" "+SignUtils.getToken("GET",parse,""));
HttpResponse execute = get.execute();
InputStream inputStream = execute.bodyStream();
File file = FileUtil.writeFromStream(inputStream, FileUtil.newFile("D:/tradeBill"+DateUtils.getDate("yyyy-MM-dd")+System.currentTimeMillis()+".xlsx"));
//List<String> list = FileUtil.readLines(file, "utf-8");
TradeBillDataResp tradeBillDataResp = parseBody(execute.body());
return tradeBillDataResp;
}
private static TradeBillDataResp parseBody(String body) {
TradeBillDataResp tradeBillDataResp = null;
List<TradeBillLineResp> tradeBillLineRespList = new ArrayList<>();
try {
body = body.replace("`", "");
String[] billDataStrArray = body.split("\r\n");
if (billDataStrArray.length > 0) {
/* 解析行数据 */
for (int i = 1; i < billDataStrArray.length - 2; i++) {
// 填充空格,防止数组越界
String billData = billDataStrArray[i] + " ";
String[] data = billData.split(",");
TradeBillLineResp tradeBillLineResp = TradeBillLineResp.class.getDeclaredConstructor().newInstance();
for (Field field : TradeBillLineResp.class.getDeclaredFields()) {
SplitIndex splitIndex = field.getAnnotation(SplitIndex.class);
if (splitIndex != null) {
int index = splitIndex.value();
setFieldValue(tradeBillLineResp, field, data[index]);
}
}
tradeBillLineRespList.add(tradeBillLineResp);
}
/* 解析汇总数据*/
// 填充空格,防止数组越界
String totalData = billDataStrArray[billDataStrArray.length - 1] + " ";
String[] totalDataArray = totalData.split(",");
TradeBillTotalResp tradeBillTotalResp = TradeBillTotalResp.class.getDeclaredConstructor().newInstance();
for (Field field : TradeBillTotalResp.class.getDeclaredFields()) {
SplitIndex splitIndex = field.getAnnotation(SplitIndex.class);
if (splitIndex != null) {
int index = splitIndex.value();
setFieldValue(tradeBillTotalResp, field, totalDataArray[index]);
}
}
tradeBillDataResp = TradeBillDataResp.builder()
.tradeBillTotalResp(tradeBillTotalResp)
.tradeBillLineRespList(tradeBillLineRespList)
.build();
}
return tradeBillDataResp;
} catch (Exception e) {
throw new RuntimeException("解析交易账单文件响应数据异常:" + e);
}
}
/**
* 使用反射设置对象的属性值。
*
* @param obj 要设置属性值的对象
* @param field 属性
* @param value 属性值
* @throws IllegalAccessException 如果没有足够的权限访问属性
*/
public static void setFieldValue(Object obj, Field field, String value)
throws IllegalAccessException, IntrospectionException, InvocationTargetException, ParseException {
Class<?> aClass = obj.getClass();
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), aClass);
Method method = propertyDescriptor.getWriteMethod();
String type = field.getGenericType()
.toString();
if (!org.springframework.util.StringUtils.hasText(value)) {
return;
}
value = value.trim();
switch (type) {
case "class java.util.Date":
method.invoke(obj, DateUtils.parseDate(value,"yyyy-MM-dd HH:mm:ss"));
break;
case "class java.lang.String":
method.invoke(obj, value);
break;
case "class java.lang.Integer":
method.invoke(obj, Double.valueOf(value)
.intValue());
break;
case "class java.lang.Long":
method.invoke(obj, Double.valueOf(value)
.longValue());
break;
case "class java.lang.Double":
method.invoke(obj, Double.valueOf(value));
break;
case "class java.lang.Float":
method.invoke(obj, Double.valueOf(value)
.floatValue());
break;
case "class java.lang.Character":
method.invoke(obj, value.toCharArray()[0]);
break;
case "class java.math.BigDecimal":
method.invoke(obj, new BigDecimal(value).setScale(4, RoundingMode.HALF_UP));
break;
default:
method.invoke(obj, (Object) null);
break;
}
}
//实体
@Data
@Builder
public class TradeBillDataResp {
/**
* 账单数据(分条)
*/
private List<TradeBillLineResp> tradeBillLineRespList;
/**
* 账单汇总数据
*/
private TradeBillTotalResp tradeBillTotalResp;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TradeBillLineResp {
/**
* 交易时间
*/
@SplitIndex(0)
private Date tradeDate;
/**
* 公众账号ID
*/
@SplitIndex(1)
private String appId;
/**
* 商户号
*/
@SplitIndex(2)
private String mchId;
/**
* 特约商户号
*/
@SplitIndex(3)
private String specMchId;
/**
* 设备号
*/
@SplitIndex(4)
private String deviceId;
/**
* 微信订单号
*/
@SplitIndex(5)
private String wechatOrderNo;
/**
* 商户订单号
*/
@SplitIndex(6)
private String mchOrderNo;
/**
* 用户标识
*/
@SplitIndex(7)
private String userSign;
/**
* 交易类型
*/
@SplitIndex(8)
private String tradeType;
/**
* 交易状态
*/
@SplitIndex(9)
private String tradeStatus;
/**
* 付款银行
*/
@SplitIndex(10)
private String payBank;
/**
* 货币种类
*/
@SplitIndex(11)
private String payCurrency;
/**
* 应结订单金额
*/
@SplitIndex(12)
private BigDecimal needPayAmount;
/**
* 代金券金额
*/
@SplitIndex(13)
private BigDecimal voucherAmount;
/**
* 微信退款单号
*/
@SplitIndex(14)
private String refundWechatNo;
/**
* 商户退款单号
*/
@SplitIndex(15)
private String refundMchNo;
/**
* 退款金额
*/
@SplitIndex(16)
private BigDecimal refundAmount;
/**
* 充值券退款金额
*/
@SplitIndex(17)
private BigDecimal topUpRefundAmount;
/**
* 退款类型
*/
@SplitIndex(18)
private String refundType;
/**
* 退款状态
*/
@SplitIndex(19)
private String refundStatus;
/**
* 商品名称
*/
@SplitIndex(20)
private String productName;
/**
* 商户数据包
*/
@SplitIndex(21)
private String mchData;
/**
* 手续费
*/
@SplitIndex(22)
private BigDecimal handlingFee;
/**
* 费率
*/
@SplitIndex(23)
private String feeRate;
/**
* 订单金额
*/
@SplitIndex(24)
private BigDecimal orderAmount;
/**
* 申请退款金额
*/
@SplitIndex(25)
private BigDecimal requestRefundAmount;
/**
* 费率备注
*/
@SplitIndex(26)
private String feeRateRemark;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TradeBillTotalResp {
/**
* 总交易单数
*/
@SplitIndex(0)
private Integer totalOrderCount;
/**
* 应结订单总金额
*/
@SplitIndex(1)
private BigDecimal totalNeedPayOrderAmount;
/**
* 退款总金额
*/
@SplitIndex(2)
private BigDecimal totalFundAmount;
/**
* 充值券退款总金额
*/
@SplitIndex(3)
private BigDecimal totalTopUpFundAmount;
/**
* 手续费总金额
*/
@SplitIndex(4)
private BigDecimal totalHandlingFee;
/**
* 订单总金额
*/
@SplitIndex(5)
private BigDecimal totalOrderAmount;
/**
* 申请退款总金额
*/
@SplitIndex(6)
private BigDecimal totalRequestFundAmount;
}
获取 v3 httpClient 链接
引入pom
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
</dependency>
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.5</version>
</dependency>
创建httpClient链接,官网上有
@Slf4j
public class WechatPay {
private static final String appId = "xxxxxx"; // 服务商商户的 APPID 小程序等appid
private static final String merchantId = "xxxxx";// 服务商商户号
private static final String merchantSerialNumber = "xxxxx";//商户API证书的证书序列号
private static final String apiV3Key = "xxxxx";//apiv3密钥
/**
* 商户API私钥,如何加载商户API私钥请看 常见问题。 @link: https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient#%E5%A6%82%E4%BD%95%E5%8A%A0%E8%BD%BD%E5%95%86%E6%88%B7%E7%A7%81%E9%92%A5
*/
private static PrivateKey merchantPrivateKey = null;
static {
try {
merchantPrivateKey = PemUtil.loadPrivateKey(
new ClassPathResource("apiclient_key.pem").getInputStream());
String format = merchantPrivateKey.getFormat();
System.out.println(format);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 微信支付平台证书列表 证书第一次需要手动下载 可以运行下main方法
*/
private static List<X509Certificate> wechatPayCertificates = new ArrayList<>();
static {
try {
wechatPayCertificates.add(PemUtil.loadCertificate(new ClassPathResource("cert.pem").getInputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
getptcert();
}
/**
* 获取平台证书 只要首次执行
* @throws URISyntaxException
* @throws IOException
*/
public static void getptcert() throws URISyntaxException, IOException, NotFoundException, GeneralSecurityException, HttpCodeException {
URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/certificates");
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.addHeader("Accept", "application/json");
CloseableHttpResponse response = getHttpClient().execute(httpGet);
String bodyAsString = EntityUtils.toString(response.getEntity());
System.out.println(bodyAsString);
JSONObject resourceJsons = JSONObject.parseObject(bodyAsString);
JSONArray jsonArray = JSONArray.parseArray(resourceJsons.getString("data"));
JSONObject resourceJson2 = jsonArray.getJSONObject(0);
JSONObject resourceJson = resourceJson2.getJSONObject("encrypt_certificate");
String associated_data = resourceJson.getString("associated_data");
String nonce = resourceJson.getString("nonce");
String ciphertext = resourceJson.getString("ciphertext");
//解密,如果这里报错,就一定是APIv3密钥错误
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes());
String resourceData = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
System.out.println("解密后=" + resourceData);
//解密后的字符串替换掉 cert.pem里的内容即可 cert.pem 是自己创建的文件
}
/**
* 获取微信HttpClient
* @return
*/
public static CloseableHttpClient getHttpClient() throws HttpCodeException, GeneralSecurityException, IOException, NotFoundException {
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
// ... 若有多个商户号,可继续调用putMerchant添加商户信息
// 从证书管理器中获取verifier
Verifier verifier = certificatesManager.getVerifier(merchantId);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier))
.withWechatPay(wechatPayCertificates);
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
//WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
// .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
// .withWechatPay(wechatPayCertificates);
return builder.build();
}
}
总结:支付->支付回调->退款->退款回调->发起分账->添加分账方->分账->下载分账账单和交易账单->对账逻辑
另外还有特约商户进件接口,下一篇介绍 有什么疑问和错误欢迎私信我