Commit bb2198cd by 程裕兵

feat:pay

parent 7e334cd7
package com.jiejing.fitness.finance.api.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author chengyubing
* @since 2024/2/27 12:00
*/
@Getter
@AllArgsConstructor
public enum BrandCashierTransStateEnums {
/**
* 交易状态:0-支付初始态;1-支付中;2-支付失败;3-入账中(支付成功);4-入账成功;5-退款中;6-退款成功;7-退款失败
*/
PAY_INIT(0, "支付初始态"),
PAYING(1, "支付中"),
PAY_FAIL(2, "支付失败"),
PAY_SUCCESS(3, "入账中(支付成功)"),
PAY_IN(4, "入账成功"),
REFUNDING(5, "退款中"),
REFUND_SUCCESS(6, "退款成功"),
REFUND_FAIL(7, "退款失败"),
;
@EnumValue
private final Integer code;
private final String desc;
}
package com.jiejing.fitness.finance.api.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author chengyubing
* @since 2024/2/27 12:00
*/
@Getter
@AllArgsConstructor
public enum BrandCashierTransTypeEnums {
/**
* 交易类型:PAY-收款;REFUND-退款;
*/
PAY("PAY"),
REFUND("REFUND"),
;
@EnumValue
private final String code;
}
......@@ -9,10 +9,13 @@ import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantGetApplyReq
import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantGetAuthSubChannelRequest;
import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantGetRequest;
import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantPageApplyRequest;
import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantPayRequest;
import com.jiejing.fitness.finance.api.merchant.vo.BrandMerchantApplyVO;
import com.jiejing.fitness.finance.api.merchant.vo.BrandMerchantAuthSubChannelVO;
import com.jiejing.fitness.finance.api.merchant.vo.BrandMerchantBindXcxAppIdVO;
import com.jiejing.fitness.finance.api.merchant.vo.BrandMerchantVO;
import com.jiejing.paycenter.api.pay.request.PayRequest;
import com.jiejing.paycenter.api.pay.vo.PayVO;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import org.springframework.cloud.openfeign.FeignClient;
......@@ -62,4 +65,8 @@ public interface BrandMerchantApi {
JsonResult<List<BrandMerchantAuthSubChannelVO>> listAuthSubChannel(
BrandMerchantGetAuthSubChannelRequest request);
@ApiOperation(value = "支付", tags = {TAG})
@PostMapping(value = "/private/brandMerchant/pay")
JsonResult<PayVO> pay(BrandMerchantPayRequest request);
}
......@@ -9,10 +9,12 @@ import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantGetApplyReq
import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantGetAuthSubChannelRequest;
import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantGetRequest;
import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantPageApplyRequest;
import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantPayRequest;
import com.jiejing.fitness.finance.api.merchant.vo.BrandMerchantApplyVO;
import com.jiejing.fitness.finance.api.merchant.vo.BrandMerchantAuthSubChannelVO;
import com.jiejing.fitness.finance.api.merchant.vo.BrandMerchantBindXcxAppIdVO;
import com.jiejing.fitness.finance.api.merchant.vo.BrandMerchantVO;
import com.jiejing.paycenter.api.pay.vo.PayVO;
import feign.hystrix.FallbackFactory;
import java.util.List;
import org.springframework.stereotype.Component;
......@@ -69,6 +71,11 @@ public class BrandMerchantApiFallback implements FallbackFactory<BrandMerchantAp
BrandMerchantGetAuthSubChannelRequest request) {
return JsonResult.rpcError();
}
@Override
public JsonResult<PayVO> pay(BrandMerchantPayRequest request) {
return JsonResult.rpcError();
}
};
}
}
package com.jiejing.fitness.finance.api.merchant.request;
import com.alibaba.fastjson.JSONObject;
import com.jiejing.paycenter.common.enums.merchant.SubChannelEnums;
import com.jiejing.paycenter.common.enums.pay.PayTypeEnums;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author chengyubing
* @since 2024/2/27 10:54
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "品牌商户支付Request")
public class BrandMerchantPayRequest {
@ApiModelProperty(value = "品牌ID", required = true)
@NotNull(message = "品牌ID不能为空")
private Long brandId;
@ApiModelProperty(name = "支付总金额(元)", required = true)
@NotNull(message = "支付总金额不能为空")
private BigDecimal amount;
@ApiModelProperty(name = "业务订单号", required = true)
@NotBlank(message = "业务订单号不能为空")
private String orderNo;
@ApiModelProperty(name = "业务订单类型", required = true)
@NotNull(message = "业务订单类型不能为空")
private Integer orderType;
@ApiModelProperty(name = "支付类型", required = true)
@NotNull(message = "支付类型不能为空")
private PayTypeEnums payType;
@ApiModelProperty(name = "正扫:三方支付账户ID", notes = "微信的openId,支付宝的userId,JS、MINI、NATIVE支付必传")
private String openId;
@ApiModelProperty(name = "反扫:支付授权码", notes = "扫码设备读出的条形码或者二维码信息,BARCODE支付必传")
private String authCode;
@ApiModelProperty(name = "支付子渠道", notes = "聚合支付时需要")
private SubChannelEnums subChannel;
@ApiModelProperty(name = "商品名称", required = true)
@NotBlank(message = "商品名称不能为空")
private String goods;
@ApiModelProperty(name = "appId", notes = "指定本次使用的appId")
private String appId;
@ApiModelProperty(name = "过期时间(单位:秒)", required = true)
@NotNull(message = "过期时间不能为空")
private Integer timeExpire;
@ApiModelProperty(name = "业务扩展信息")
private JSONObject extra;
}
......@@ -11,6 +11,7 @@ import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantGetApplyReq
import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantGetAuthSubChannelRequest;
import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantGetRequest;
import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantPageApplyRequest;
import com.jiejing.fitness.finance.api.merchant.request.BrandMerchantPayRequest;
import com.jiejing.fitness.finance.api.merchant.vo.BrandMerchantApplyVO;
import com.jiejing.fitness.finance.api.merchant.vo.BrandMerchantAuthSubChannelVO;
import com.jiejing.fitness.finance.api.merchant.vo.BrandMerchantBindXcxAppIdVO;
......@@ -18,6 +19,9 @@ import com.jiejing.fitness.finance.api.merchant.vo.BrandMerchantVO;
import com.jiejing.fitness.finance.service.merchant.BrandMerchantService;
import com.jiejing.fitness.finance.service.merchant.params.ApplyBrandMerchantParams;
import com.jiejing.fitness.finance.service.merchant.params.PageBrandMerchantApplyParams;
import com.jiejing.fitness.finance.service.pay.PayService;
import com.jiejing.fitness.finance.service.pay.params.BrandMerchantPayParams;
import com.jiejing.paycenter.api.pay.vo.PayVO;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import javax.annotation.Resource;
......@@ -36,6 +40,9 @@ public class BrandMerchantController implements BrandMerchantApi {
@Resource
private BrandMerchantService brandMerchantService;
@Resource
private PayService payService;
@ApiOperation(value = "品牌入驻商户", tags = {TAG})
@PostMapping(value = "/private/brandMerchant/apply")
@Override
......@@ -104,4 +111,12 @@ public class BrandMerchantController implements BrandMerchantApi {
}
@ApiOperation(value = "支付", tags = {TAG})
@PostMapping(value = "/private/brandMerchant/pay")
@Override
public JsonResult<PayVO> pay(@RequestBody @Valid BrandMerchantPayRequest request) {
BrandMerchantPayParams params = BeanUtil.map(request, BrandMerchantPayParams.class);
return JsonResult.success(payService.merchantPay(params));
}
}
/*
* Copyright © 2024 Hangzhou Jiejing Technology Co., Ltd. All rights reserved.
*
* The copyright of the company's program code belongs to Hangzhou Jiejing Technology Co., Ltd. No one can illegally copy it without the explicit permission of this website.
* Official website: www.xiaomai5.com
*
*
*
* Copyright © 2024 杭州杰竞科技有限公司 版权所有.
*
* 本公司程序代码的版权归杭州杰竞科技有限公司所有,未经本网站的明确许可,任何人不得非法复制。
* 官网: www.xiaomai5.com
*/
package com.jiejing.fitness.finance.repository.entity;
import com.jiejing.fitness.finance.api.enums.BrandCashierTransStateEnums;
import com.jiejing.fitness.finance.api.enums.BrandCashierTransTypeEnums;
import com.jiejing.paycenter.common.enums.merchant.SubChannelEnums;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* <p>
* 品牌收银流水
* </p>
*
* @author chengyubing, created on 2024-02-27
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
public class BrandCashierRecord implements Serializable {
private static final long serialVersionUID = -4723670005434909301L;
/**
* 是否允许为null: NO
*/
@TableId(value = "id", type = IdType.ID_WORKER)
private Long id;
/**
* 备注: 交易流水号 是否允许为null: NO
*/
private String transNo;
/**
* 备注: 业务订单号 是否允许为null: NO
*/
private String orderNo;
/**
* 备注: 业务订单类型 是否允许为null: YES
*/
private Integer orderType;
/**
* 备注: 交易类型:PAY-收款;REFUND-退款; 默认值: 空字符串 是否允许为null: YES
*/
private BrandCashierTransTypeEnums transType;
/**
* 备注: 品牌ID 是否允许为null: NO
*/
private Long brandId;
/**
* 备注: 场馆ID 默认值: -1 是否允许为null: NO
*/
private Long studioId;
/**
* 备注: 商户ID 是否允许为null: YES
*/
private Long merchantId;
/**
* 备注: 商户号 默认值: 空字符串 是否允许为null: YES
*/
private String merchantNo;
/**
* 备注: 渠道号 默认值: 空字符串 是否允许为null: YES
*/
private String channelNo;
/**
* 备注: 支付子渠道:ALI-支付宝、WX-微信 默认值: 空字符串 是否允许为null: YES
*/
private SubChannelEnums subChannel;
/**
* 备注: 交易金额(元) 默认值: 0.00 是否允许为null: NO
*/
private BigDecimal transAmount;
/**
* 备注: 交易手续费率(%) 默认值: 0.00 是否允许为null: NO
*/
private BigDecimal feeRate;
/**
* 备注: 交易手续费 默认值: 0.00 是否允许为null: NO
*/
private BigDecimal fee;
/**
* 备注: 实际金额(元) 默认值: 0.00 是否允许为null: NO
*/
private BigDecimal actualAmount;
/**
* 备注: 交易状态:0-支付初始态;1-支付中;2-支付失败;3-入账中(支付成功);4-入账成功;5-退款中;6-退款成功;7-退款失败 默认值: 1 是否允许为null: YES
*/
private BrandCashierTransStateEnums transState;
/**
* 备注: 付款人姓名 是否允许为null: YES
*/
private String buyerName;
/**
* 备注: 付款人手机号 是否允许为null: YES
*/
private String buyerPhone;
/**
* 备注: 商品名称 是否允许为null: YES
*/
private String goods;
/**
* 备注: 备注 是否允许为null: YES
*/
private String remark;
/**
* 备注: 是否存在关联交易 默认值: 0 是否允许为null: YES
*/
private Boolean existRelatedTrans;
/**
* 备注: 关联的交易单号 是否允许为null: NO
*/
private String relatedTransNo;
/**
* 备注: 三方交易单号 是否允许为null: NO
*/
private String thirdTransNo;
/**
* 备注: 错误信息 默认值: 空字符串 是否允许为null: YES
*/
private String failMessage;
/**
* 备注: 交易发生时间 是否允许为null: YES
*/
private Date tradingTime;
/**
* 备注: 交易成功时间 是否允许为null: YES
*/
private Date successTime;
/**
* 备注: 入账时间 是否允许为null: YES
*/
private Date inTime;
/**
* 备注: 创建时间 默认值: CURRENT_TIMESTAMP 是否允许为null: YES
*/
private Date createTime;
/**
* 备注: 更新时间 默认值: CURRENT_TIMESTAMP 是否允许为null: YES
*/
private Date updateTime;
public static final String ID = "id";
public static final String TRANS_NO = "trans_no";
public static final String ORDER_NO = "order_no";
public static final String ORDER_TYPE = "order_type";
public static final String TRANS_TYPE = "trans_type";
public static final String BRAND_ID = "brand_id";
public static final String STUDIO_ID = "studio_id";
public static final String MERCHANT_ID = "merchant_id";
public static final String MERCHANT_NO = "merchant_no";
public static final String CHANNEL_NO = "channel_no";
public static final String SUB_CHANNEL = "sub_channel";
public static final String TRANS_AMOUNT = "trans_amount";
public static final String FEE_RATE = "fee_rate";
public static final String FEE = "fee";
public static final String ACTUAL_AMOUNT = "actual_amount";
public static final String TRANS_STATE = "trans_state";
public static final String BUYER_NAME = "buyer_name";
public static final String BUYER_PHONE = "buyer_phone";
public static final String GOODS = "goods";
public static final String REMARK = "remark";
public static final String EXIST_RELATED_TRANS = "exist_related_trans";
public static final String RELATED_TRANS_NO = "related_trans_no";
public static final String THIRD_TRANS_NO = "third_trans_no";
public static final String FAIL_MESSAGE = "fail_message";
public static final String TRADING_TIME = "trading_time";
public static final String SUCCESS_TIME = "success_time";
public static final String IN_TIME = "in_time";
public static final String CREATE_TIME = "create_time";
public static final String UPDATE_TIME = "update_time";
}
......@@ -47,6 +47,11 @@ public class BrandToMerchant implements Serializable {
private Long id;
/**
* 渠道号
*/
private String channelNo;
/**
* 备注: 品牌ID 是否允许为null: YES
*/
private Long brandId;
......@@ -57,6 +62,11 @@ public class BrandToMerchant implements Serializable {
private Long merchantId;
/**
* 备注: 商户号 是否允许为null: YES
*/
private String merchantNo;
/**
* 备注: 创建时间 是否允许为null: YES
*/
private Date createTime;
......@@ -69,10 +79,14 @@ public class BrandToMerchant implements Serializable {
public static final String ID = "id";
public static final String CHANNEL_NO = "channel_no";
public static final String BRAND_ID = "brand_id";
public static final String MERCHANT_ID = "merchant_id";
public static final String MERCHANT_NO = "merchant_no";
public static final String CREATE_TIME = "create_time";
public static final String UPDATE_TIME = "update_time";
......
/*
* Copyright © 2024 Hangzhou Jiejing Technology Co., Ltd. All rights reserved.
*
* The copyright of the company's program code belongs to Hangzhou Jiejing Technology Co., Ltd. No one can illegally copy it without the explicit permission of this website.
* Official website: www.xiaomai5.com
*
*
*
* Copyright © 2024 杭州杰竞科技有限公司 版权所有.
*
* 本公司程序代码的版权归杭州杰竞科技有限公司所有,未经本网站的明确许可,任何人不得非法复制。
* 官网: www.xiaomai5.com
*/
package com.jiejing.fitness.finance.repository.mapper;
import com.jiejing.fitness.finance.repository.entity.BrandCashierRecord;
import com.jiejing.mbp.inject.XBaseMapper;
/**
* <p>
* 品牌收银流水 Mapper 接口
* </p>
*
* @author chengyubing, created on 2024-02-27
*/
public interface BrandCashierRecordMapper extends XBaseMapper<BrandCashierRecord> {
}
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright © 2024 Hangzhou Jiejing Technology Co., Ltd. All rights reserved.
~
~ The copyright of the company's program code belongs to Hangzhou Jiejing Technology Co., Ltd. No one can illegally copy it without the explicit permission of this website.
~ Official website: www.xiaomai5.com
~
~
~
~ Copyright © 2024 杭州杰竞科技有限公司 版权所有.
~
~ 本公司程序代码的版权归杭州杰竞科技有限公司所有,未经本网站的明确许可,任何人不得非法复制。
~ 官网: www.xiaomai5.com
-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jiejing.fitness.finance.repository.mapper.BrandCashierRecordMapper">
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, trans_no, order_no, order_type, trans_type, brand_id, studio_id, merchant_id, merchant_no, channel_no, sub_channel, trans_amount, fee_rate, fee, actual_amount, trans_state, buyer_name, buyer_phone, goods, remark, exist_related_trans, related_trans_no, third_trans_no, fail_message, trading_time, success_time, in_time, create_time, update_time
</sql>
</mapper>
......@@ -17,7 +17,7 @@
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, brand_id, merchant_id, create_time, update_time
id, channel_no, brand_id, merchant_id, merchant_no, create_time, update_time
</sql>
</mapper>
/*
* Copyright © 2024 Hangzhou Jiejing Technology Co., Ltd. All rights reserved.
*
* The copyright of the company's program code belongs to Hangzhou Jiejing Technology Co., Ltd. No one can illegally copy it without the explicit permission of this website.
* Official website: www.xiaomai5.com
*
*
*
* Copyright © 2024 杭州杰竞科技有限公司 版权所有.
*
* 本公司程序代码的版权归杭州杰竞科技有限公司所有,未经本网站的明确许可,任何人不得非法复制。
* 官网: www.xiaomai5.com
*/
package com.jiejing.fitness.finance.repository.service;
import com.jiejing.fitness.finance.repository.entity.BrandCashierRecord;
import com.jiejing.fitness.finance.repository.mapper.BrandCashierRecordMapper;
import com.jiejing.mbp.MapperRepoService;
import org.springframework.stereotype.Service;
/**
* <p>
* 品牌收银流水 服务实现类
* </p>
*
* @author chengyubing, created on 2024-02-27
*/
@Service
public class BrandCashierRecordRpService extends MapperRepoService<Long, BrandCashierRecord, BrandCashierRecordMapper> {
}
......@@ -51,10 +51,11 @@ public class GeneratorServiceEntity {
*/
private String author = "chengyubing";
private String[] tableNames = {
// "global_config",
// "brand_merchant_apply",
// "brand_to_merchant",
// "global_config",
"brand_bind_wx_app_id_apply"
// "brand_bind_wx_app_id_apply",
"brand_cashier_record"
};
/**
......
......@@ -19,6 +19,7 @@ public enum FinanceErrorEnums implements BaseBizError {
NOT_EXIST("数据不存在"),
NOT_SUPPORT_METHOD("不支持的方法"),
NOT_SUPPORT_TYPE("不支持的类型"),
CHANNEL_NOT_OPEN("未开通当前支付通道"),
NOT_SUPPORT_FILE_TYPE("不支持的文件类型"),
;
......
......@@ -5,8 +5,10 @@ import static org.springframework.integration.IntegrationMessageHeaderAccessor.D
import com.alibaba.fastjson.JSON;
import com.jiejing.fitness.finance.api.enums.MerchantTypeEnums;
import com.jiejing.fitness.finance.service.merchant.BrandMerchantService;
import com.jiejing.fitness.finance.service.pay.PayService;
import com.jiejing.paycenter.common.event.MerchantEvent;
import com.jiejing.paycenter.common.event.MerchantSubChannelConfigEvent;
import com.jiejing.paycenter.common.event.PayEvent;
import com.xiaomai.event.annotation.EventHandler;
import java.util.ArrayList;
import java.util.Optional;
......@@ -28,6 +30,10 @@ public class ListenerService {
@Resource
private BrandMerchantService brandMerchantService;
@Resource
private PayService payService;
@EventHandler(value = MerchantEvent.class, binder = "biz-kafka", maxAttempts = MAX_RETRY)
public void handleMerchantEvent(MerchantEvent event, @Header(DELIVERY_ATTEMPT) int retryNum) {
try {
......@@ -49,11 +55,22 @@ public class ListenerService {
}
}
@EventHandler(value = PayEvent.class, binder = "biz-kafka", maxAttempts = MAX_RETRY)
public void payEventCallback(PayEvent event, @Header(DELIVERY_ATTEMPT) int retryNum) {
try {
log.info("start process pay event {}", event.getTransNo());
payService.callback(event);
} catch (Exception e) {
log.info("process pay event fail {}", event.getTransNo(), e);
}
}
@EventHandler(value = MerchantSubChannelConfigEvent.class, binder = "biz-kafka", maxAttempts = MAX_RETRY)
public void handleMerchantSubChannelConfigEvent(MerchantSubChannelConfigEvent event,
@Header(DELIVERY_ATTEMPT) int retryNum) {
try {
log.info("start process merchant sub channel config event {}, {}, {}", event.getApplyNo(),
log.info("start process merchant sub channel config event {}, {}", event.getApplyNo(),
event.getMerchantId());
brandMerchantService.bindXcxAppIdCallback(event);
} catch (Exception e) {
......
package com.jiejing.fitness.finance.service.pay;
import com.jiejing.fitness.finance.service.pay.params.BrandMerchantPayParams;
import com.jiejing.paycenter.api.pay.vo.PayVO;
import com.jiejing.paycenter.common.event.PayEvent;
/**
* @author chengyubing
* @since 2024/2/27 11:05
*/
public interface PayService {
/**
* 支付
*
* @param params 请求参数
* @return 结果
*/
PayVO merchantPay(BrandMerchantPayParams params);
/**
* 支付回调
*
* @param event 事件
*/
void callback(PayEvent event);
}
package com.jiejing.fitness.finance.service.pay.convert;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.google.common.collect.Lists;
import com.jiejing.common.exception.BizException;
import com.jiejing.common.utils.convert.BeanUtil;
import com.jiejing.common.utils.text.StringUtil;
import com.jiejing.fitness.finance.api.enums.BrandCashierTransStateEnums;
import com.jiejing.fitness.finance.api.enums.BrandCashierTransTypeEnums;
import com.jiejing.fitness.finance.repository.entity.BrandCashierRecord;
import com.jiejing.fitness.finance.repository.entity.BrandToMerchant;
import com.jiejing.fitness.finance.service.enums.FinanceErrorEnums;
import com.jiejing.fitness.finance.service.pay.params.BrandMerchantPayParams;
import com.jiejing.fitness.finance.service.utils.FeeUtil;
import com.jiejing.fitness.finance.service.utils.MoneyUtil;
import com.jiejing.paycenter.api.merchant.vo.MerchantVO;
import com.jiejing.paycenter.api.pay.request.PayRequest;
import com.jiejing.paycenter.common.enums.common.TransStateEnums;
import com.jiejing.paycenter.common.enums.merchant.SubChannelEnums;
import com.jiejing.paycenter.common.enums.merchant.SubChannelOpenTypeEnums;
import com.jiejing.paycenter.common.enums.pay.PayStateEnums;
import com.jiejing.paycenter.common.enums.pay.PayTypeEnums;
import com.jiejing.paycenter.common.event.PayEvent;
import com.jiejing.paycenter.common.model.SubChannelInfo;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.Optional;
/**
* @author chengyubing
* @since 2024/2/27 11:08
*/
public class PayConvert {
private static final List<String> ALI_AUTH_CODE_PREFIX = Lists.newArrayList("25", "26", "27", "28", "29",
"30");
private static final List<String> WX_AUTH_CODE_PREFIX = Lists.newArrayList("10", "11", "12", "13", "14",
"15");
public static PayRequest convert(BrandMerchantPayParams params, BrandCashierRecord record,
MerchantVO merchant) {
PayRequest request = BeanUtil.map(params, PayRequest.class);
request.setTransNo(record.getTransNo());
request.setMerchantId(merchant.getId());
request.setExtra(Optional.ofNullable(params.getExtra()).orElse(new JSONObject())
.fluentPut("studioId", params.getStudioId())
.fluentPut("buyerName", params.getBuyerName())
.fluentPut("buyerPhone", params.getBuyerPhone())
.fluentPut("remark", params.getRemark())
);
request.setTransTime(new Date());
return request;
}
public static BrandCashierRecord convertInit(BrandMerchantPayParams params, BrandToMerchant relation,
MerchantVO merchant) {
setSubChannel(params);
Long id = IdWorker.getId();
BigDecimal feeRate = getFeeRate(params, merchant);
BigDecimal fee = FeeUtil.calPayFee(feeRate, params.getTransAmount());
return BrandCashierRecord.builder()
.id(id)
.transNo(id.toString())
.orderNo(params.getOrderNo())
.orderType(params.getOrderType())
.transType(BrandCashierTransTypeEnums.PAY)
.brandId(relation.getBrandId())
.studioId(params.getStudioId())
.merchantId(merchant.getId())
.merchantNo(merchant.getMerchantNo())
.channelNo(merchant.getChannelNo())
.subChannel(params.getSubChannel())
.transAmount(params.getTransAmount())
.feeRate(feeRate)
.fee(fee)
.actualAmount(MoneyUtil.subtract(params.getTransAmount(), fee))
.transState(BrandCashierTransStateEnums.PAY_INIT)
.buyerName(params.getBuyerName())
.buyerPhone(params.getBuyerPhone())
.goods(params.getGoods())
.remark(params.getRemark())
.existRelatedTrans(false)
.tradingTime(new Date())
.createTime(new Date())
.updateTime(new Date())
.build();
}
private static void setSubChannel(BrandMerchantPayParams params) {
if (PayTypeEnums.BARCODE != params.getPayType() || StringUtil.isBlank(params.getAuthCode())) {
// 未设置子渠道,不是付款码支付或付款码为空,直接返回
return;
}
// https://juejin.cn/post/7099730102203727902
// 支付宝支付码生成规则:25 - 30开头的长度为16~24位的数字,实际字符串长度以开发者获取的付款码长度为准
if (ALI_AUTH_CODE_PREFIX.stream().anyMatch(prefix -> params.getAuthCode().startsWith(prefix))) {
params.setSubChannel(SubChannelEnums.ALI);
}
// 微信支付码生成规则:18位纯数字,以10、11、12、13、14、15开头
if (WX_AUTH_CODE_PREFIX.stream().anyMatch(prefix -> params.getAuthCode().startsWith(prefix))) {
params.setSubChannel(SubChannelEnums.WX);
}
}
private static BigDecimal getFeeRate(BrandMerchantPayParams params, MerchantVO merchant) {
SubChannelOpenTypeEnums scene;
switch (params.getPayType()) {
case MINI:
scene = SubChannelEnums.WX == params.getSubChannel() ? SubChannelOpenTypeEnums.WX_XCX_OFFLINE
: SubChannelOpenTypeEnums.ALI_OFFLINE;
break;
case JS:
scene = SubChannelEnums.WX == params.getSubChannel() ? SubChannelOpenTypeEnums.WX_GZH_OFFLINE
: SubChannelOpenTypeEnums.ALI_OFFLINE;
break;
default:
throw new BizException(FinanceErrorEnums.NOT_SUPPORT_TYPE);
}
SubChannelInfo subChannelInfo = merchant.getSubChannels().stream()
.filter(e -> scene == e.getOpenType())
.findFirst()
.orElseThrow(() -> new BizException(FinanceErrorEnums.CHANNEL_NOT_OPEN));
return subChannelInfo.getFeeRate();
}
public static void main(String[] args) {
BigDecimal feeRate = new BigDecimal("0.38");
BigDecimal fee = MoneyUtil.defaultRound(MoneyUtil.multiply(
MoneyUtil.divide(feeRate, new BigDecimal("100"), 4, RoundingMode.HALF_UP.ordinal()),
new BigDecimal("168")));
System.out.println(fee);
}
public static BrandCashierRecord convertRecord(BrandCashierRecord record, PayEvent event) {
return BrandCashierRecord.builder()
.id(record.getId())
.successTime(event.getFinishTime())
.channelNo(event.getChannelNo())
.transState(convertTransState(event.getPayState()))
.failMessage(event.getFailMsg())
.thirdTransNo(event.getThirdTransNo())
.updateTime(new Date())
.build();
}
private static BrandCashierTransStateEnums convertTransState(PayStateEnums state) {
switch (state) {
case SUCCESS:
return BrandCashierTransStateEnums.PAY_SUCCESS;
case CLOSED:
case FAILED:
return BrandCashierTransStateEnums.PAY_FAIL;
default:
return null;
}
}
}
package com.jiejing.fitness.finance.service.pay.impl;
import com.jiejing.fitness.finance.repository.entity.BrandCashierRecord;
import com.jiejing.fitness.finance.repository.entity.BrandToMerchant;
import com.jiejing.fitness.finance.repository.service.BrandCashierRecordRpService;
import com.jiejing.fitness.finance.repository.service.BrandToMerchantRpService;
import com.jiejing.fitness.finance.service.pay.PayService;
import com.jiejing.fitness.finance.service.pay.convert.PayConvert;
import com.jiejing.fitness.finance.service.pay.params.BrandMerchantPayParams;
import com.jiejing.fitness.finance.service.rpc.MerchantRpcService;
import com.jiejing.fitness.finance.service.rpc.PayRpcService;
import com.jiejing.fitness.finance.service.rpc.StudioRpcService;
import com.jiejing.paycenter.api.merchant.vo.MerchantVO;
import com.jiejing.paycenter.api.pay.request.PayRequest;
import com.jiejing.paycenter.api.pay.vo.PayVO;
import com.jiejing.paycenter.common.event.PayEvent;
import com.jiejing.studio.api.studio.vo.StudioVO;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @author chengyubing
* @since 2024/2/27 11:06
*/
@Slf4j
@Service
public class PayServiceImpl implements PayService {
@Resource
private BrandToMerchantRpService brandToMerchantRpService;
@Resource
private PayRpcService payRpcService;
@Resource
private StudioRpcService studioRpcService;
@Resource
private BrandCashierRecordRpService brandCashierRecordRpService;
@Resource
private MerchantRpcService merchantRpcService;
@Override
public PayVO merchantPay(BrandMerchantPayParams params) {
StudioVO studio = studioRpcService.getStudio(params.getStudioId());
BrandToMerchant relation = brandToMerchantRpService.getByBrandId(studio.getBrandId());
MerchantVO merchant = merchantRpcService.getByMerchantId(relation.getMerchantId());
BrandCashierRecord record = PayConvert.convertInit(params, relation, merchant);
brandCashierRecordRpService.insert(record);
PayRequest request = PayConvert.convert(params, record, merchant);
return payRpcService.pay(request);
}
@Override
public void callback(PayEvent event) {
BrandCashierRecord record = brandCashierRecordRpService.getById(Long.parseLong(event.getTransNo()))
.orElse(null);
if (null == record) {
return;
}
BrandCashierRecord toModify = PayConvert.convertRecord(record, event);
brandCashierRecordRpService.updateById(toModify);
}
}
package com.jiejing.fitness.finance.service.pay.params;
import com.alibaba.fastjson.JSONObject;
import com.jiejing.paycenter.common.enums.merchant.SubChannelEnums;
import com.jiejing.paycenter.common.enums.pay.PayTypeEnums;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.math.BigDecimal;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author chengyubing
* @since 2024/2/27 10:54
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "品牌商户支付Params")
public class BrandMerchantPayParams {
@ApiModelProperty(value = "场馆ID", required = true)
@NotNull(message = "场馆ID不能为空")
private Long studioId;
@ApiModelProperty(name = "支付总金额(元)", required = true)
@NotNull(message = "支付总金额不能为空")
private BigDecimal transAmount;
@ApiModelProperty(name = "业务订单号", required = true)
@NotBlank(message = "业务订单号不能为空")
private String orderNo;
@ApiModelProperty(name = "业务订单类型", required = true)
@NotNull(message = "业务订单类型不能为空")
private Integer orderType;
@ApiModelProperty(name = "支付类型", required = true)
@NotNull(message = "支付类型不能为空")
private PayTypeEnums payType;
@ApiModelProperty(name = "正扫:三方支付账户ID", notes = "微信的openId,支付宝的userId,JS、MINI、NATIVE支付必传")
private String openId;
@ApiModelProperty(name = "反扫:支付授权码", notes = "扫码设备读出的条形码或者二维码信息,BARCODE支付必传")
private String authCode;
@ApiModelProperty(name = "支付子渠道", notes = "聚合支付时需要")
private SubChannelEnums subChannel;
@ApiModelProperty(name = "商品名称", required = true)
@NotBlank(message = "商品名称不能为空")
private String goods;
@ApiModelProperty(name = "付款人姓名", required = true)
@NotBlank(message = "付款人姓名不能为空")
@Size(max = 30)
private String buyerName;
@ApiModelProperty(name = "付款人手机号", required = true)
@NotBlank(message = "付款人手机号不能为空")
@Size(max = 11)
private String buyerPhone;
@ApiModelProperty(name = "备注")
private String remark;
@ApiModelProperty(name = "appId", notes = "指定本次使用的appId")
private String appId;
@ApiModelProperty(name = "过期时间(单位:秒)", required = true)
@NotNull(message = "过期时间不能为空")
private Integer timeExpire;
@ApiModelProperty(name = "业务扩展信息")
private JSONObject extra;
}
......@@ -83,7 +83,7 @@ public class MerchantRpcService {
if (!result.getSuccess()) {
return AuthSubChannelVO.builder()
.state(OpenStateEnums.FAIL)
.authPhase(AuthPhaseEnums.APPLY_COMMIT_FAIL)
.phase(AuthPhaseEnums.APPLY_COMMIT_FAIL)
.failMessage(result.getMessage()).build();
}
return result.getResult();
......
package com.jiejing.fitness.finance.service.rpc;
import com.jiejing.common.model.JsonResult;
import com.jiejing.paycenter.api.pay.PayApi;
import com.jiejing.paycenter.api.pay.request.PayRequest;
import com.jiejing.paycenter.api.pay.request.RefundPayRequest;
import com.jiejing.paycenter.api.pay.vo.PayVO;
import com.jiejing.paycenter.api.pay.vo.RefundVO;
import com.jiejing.paycenter.common.enums.pay.PayStateEnums;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
/**
* @author chengyubing
* @since 2024/2/27 11:12
*/
@Service
public class PayRpcService {
@Resource
private PayApi payApi;
public PayVO pay(PayRequest request) {
try {
JsonResult<PayVO> result = payApi.pay(request);
result.assertSuccess();
return result.getResult();
} catch (Exception e) {
return PayVO.builder().payState(PayStateEnums.FAILED).failMsg(e.getMessage()).build();
}
}
public RefundVO refund(RefundPayRequest request) {
JsonResult<RefundVO> result = payApi.refund(request);
result.assertSuccess();
return result.getResult();
}
}
package com.jiejing.fitness.finance.service.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* @author chengyubing
* @since 2024/2/27 14:16
*/
public class FeeUtil {
/**
* 计算支付手续费,保留小数点后两位,四舍五入
*
* @param feeRate 费率(%)
* @param amount 支付金额
* @return 手续费
*/
public static BigDecimal calPayFee(BigDecimal feeRate, BigDecimal amount) {
return MoneyUtil.defaultRound(MoneyUtil.multiply(
MoneyUtil.divide(feeRate, new BigDecimal("100"), 4, RoundingMode.HALF_UP.ordinal()),
amount));
}
}
package com.jiejing.fitness.finance.service.utils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
/**
* @author: raccoon
* @date: 2019-07-25 15:03
* @description: 金额工具类
*/
public class MoneyUtil {
/**
* 格式化金额,四舍五入,保留两位小数:00.00
*
* @param v 金额
* @return 格式化后的金额
*/
public static String format(String v) {
return format(new BigDecimal(v));
}
/**
* 格式化金额,四舍五入,保留两位小数:00.00
*
* @param v 金额
* @return 格式化后的金额
*/
public static String format(double v) {
return format(new BigDecimal(v));
}
/**
* 格式化金额,四舍五入,保留两位小数:00.00
*
* @param v 金额
* @return 格式化后的金额
*/
public static String format(BigDecimal v) {
return new DecimalFormat("#,##0.00").format(round(v, 2, RoundingMode.HALF_UP.ordinal()));
}
/**
* 格式化金额,四舍五入,保留三位小数:00.000
*
* @param v 金额
* @return 格式化后的金额
*/
public static String format3PtCa(String v) {
return format3PtCa(new BigDecimal(v));
}
/**
* 格式化金额,四舍五入,保留三位小数:00.000
*
* @param v 金额
* @return 格式化后的金额
*/
public static String format3PtCa(BigDecimal v) {
return new DecimalFormat("#,##0.000").format(round(v, 3, RoundingMode.HALF_UP.ordinal()));
}
/**
* 格式化金额,四舍五入,保留三位小数:00.000
*
* @param v 金额
* @return 格式化后的金额
*/
public static String format3Pt(String v) {
return format3Pt(new BigDecimal(v));
}
/**
* 格式化金额,四舍五入,保留三位小数:00.000
*
* @param v 金额
* @return 格式化后的金额
*/
public static String format3Pt(BigDecimal v) {
return new DecimalFormat("0.000").format(round(v, 3, RoundingMode.HALF_UP.ordinal()));
}
/**
* 加法运算
*
* @param v1 加数
* @param v2 加数
* @return 累加值
*/
public static BigDecimal add(String v1, String v2) {
return add(new BigDecimal(v1), new BigDecimal(v2));
}
/**
* 加法运算
*
* @param v1 加数
* @param v2 加数
* @return 累加值
*/
public static BigDecimal add(double v1, double v2) {
return add(new BigDecimal(String.valueOf(v1)), new BigDecimal(String.valueOf(v2)));
}
/**
* 加法运算
*
* @param v1 加数
* @param v2 加数
* @return 累加值
*/
private static BigDecimal add(BigDecimal v1, BigDecimal v2) {
return v1.add(v2);
}
/**
* 减法运算
*
* @param v1 减数
* @param v2 被减数
* @return 相减值
*/
public static BigDecimal subtract(String v1, String v2) {
return subtract(new BigDecimal(v1), new BigDecimal(v2));
}
/**
* 减法运算
*
* @param v1 减数
* @param v2 被减数
* @return 相减值
*/
public static BigDecimal subtract(double v1, double v2) {
return subtract(new BigDecimal(String.valueOf(v1)), new BigDecimal(String.valueOf(v2)));
}
/**
* 减法运算
*
* @param v1 减数
* @param v2 被减数
* @return 相减值
*/
public static BigDecimal subtract(BigDecimal v1, BigDecimal v2) {
return v1.subtract(v2);
}
/**
* 乘法运算
*
* @param v1 乘数
* @param v2 被乘数
* @return 相乘值
*/
public static BigDecimal multiply(String v1, String v2) {
return multiply(new BigDecimal(v1), new BigDecimal(v2));
}
/**
* 乘法运算
*
* @param v1 乘数
* @param v2 被乘数
* @return 相乘值
*/
public static BigDecimal multiply(Double v1, Double v2) {
return multiply(new BigDecimal(String.valueOf(v1)), new BigDecimal(String.valueOf(v2)));
}
/**
* 乘法运算
*
* @param v1 乘数
* @param v2 被乘数
* @return 相乘值
*/
public static BigDecimal multiply(BigDecimal v1, BigDecimal v2) {
return v1.multiply(v2);
}
/**
* 两数相除,默认保留小数点后2位(四舍五入)
*
* @param v1 被除数
* @param v2 除数
* @return 相除值
*/
public static BigDecimal divide(String v1, String v2) {
return divide(new BigDecimal(v1), new BigDecimal(v2), 2, RoundingMode.HALF_UP.ordinal());
}
/**
* 两数相除(四舍五入),小数点位数自定义
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留位数
* @return 相除值
*/
public static BigDecimal divide(String v1, String v2, int scale) {
return divide(new BigDecimal(v1), new BigDecimal(v2), scale, RoundingMode.HALF_UP.ordinal());
}
/**
* 两数相除,默认保留小数点后2位(四舍五入)
*
* @param v1 被除数
* @param v2 除数
* @return 相除值
*/
public static BigDecimal divide(double v1, double v2) {
return divide(new BigDecimal(String.valueOf(v1)), new BigDecimal(String.valueOf(v2)), 2,
RoundingMode.HALF_UP.ordinal());
}
/**
* 两数相除(四舍五入),小数点位数自定义
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留位数
* @return 相除值
*/
public static BigDecimal divide(double v1, double v2, int scale) {
return divide(new BigDecimal(String.valueOf(v1)), new BigDecimal(String.valueOf(v2)), scale,
RoundingMode.HALF_UP.ordinal());
}
/**
* 两数相除(四舍五入),小数点位数自定义
*
* @param v1 被除数
* @param v2 除数
* @return 相除值
*/
public static BigDecimal divide(BigDecimal v1, BigDecimal v2) {
return divide(new BigDecimal(String.valueOf(v1)), new BigDecimal(String.valueOf(v2)), 2,
RoundingMode.HALF_UP.ordinal());
}
/**
* 两数相除,进位规则自定义,小数点位数自定义
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留位数
* @param roundingMode 进位规则
* @return 相除值
*/
public static BigDecimal divide(BigDecimal v1, BigDecimal v2, int scale, int roundingMode) {
return v1.divide(v2, scale, roundingMode);
}
/**
* 针对浮点数进行四舍五入,返回整型
*
* @param v 浮点数
* @return 四舍五入后的整型
*/
public static int round(double v) {
return round(new BigDecimal(v), 0, RoundingMode.HALF_UP.ordinal()).intValue();
}
public static BigDecimal round(BigDecimal v, int scale, int roundingMode) {
if ((scale < 0) || (roundingMode < 0)) {
throw new IllegalArgumentException(
"The scale or roundingMode must be a positive integer or zero");
}
return v.divide(BigDecimal.ONE, scale, roundingMode);
}
public static BigDecimal defaultRound(String v) {
return defaultRound(new BigDecimal(v));
}
public static BigDecimal defaultRound(double v) {
return defaultRound(new BigDecimal(v));
}
/**
* 默认保留两位小数,四舍五入
*/
public static BigDecimal defaultRound(BigDecimal v) {
return round(v, 2, RoundingMode.HALF_UP.ordinal());
}
public static long format2Long(double v) {
return (long) v;
}
/**
* 数字金额大写转换,思想先写个完整的然后将如零拾替换成零 要用到正则表达式
*/
public static String digitUppercase(String v) {
return digitUppercase(Double.valueOf(v));
}
public static String digitUppercase(double n) {
String fraction[] = {"角", "分"};
String digit[] = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"};
String unit[][] = {{"元", "万", "亿"},
{"", "拾", "佰", "仟"}};
String head = n < 0 ? "负" : "";
n = Math.abs(n);
String s = "";
for (int i = 0; i < fraction.length; i++) {
s += (digit[(int) (Math.floor(n * 10 * Math.pow(10, i)) % 10)] + fraction[i])
.replaceAll("(零.)+", "");
}
if (s.length() < 1) {
s = "整";
}
int integerPart = (int) Math.floor(n);
for (int i = 0; i < unit[0].length && integerPart > 0; i++) {
String p = "";
for (int j = 0; j < unit[1].length && n > 0; j++) {
p = digit[integerPart % 10] + unit[1][j] + p;
integerPart = integerPart / 10;
}
s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s;
}
return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零")
.replaceAll("^整$", "零元整");
}
/**
* 返回100的整数倍
*/
public static Double formatHundred(Double inter) {
BigDecimal temp = divide(new BigDecimal(inter), new BigDecimal(100), 0,
RoundingMode.FLOOR.ordinal());
return multiply(temp, new BigDecimal(100)).doubleValue();
}
/**
* 放大100倍的整数值(d * 100)
*/
public static Integer formatEnlarge(String inter) {
return multiply(new BigDecimal(inter), new BigDecimal(100)).intValue();
}
public static void main(String[] args) {
System.out.println(format("12453634.2313"));
System.out.println(defaultRound(new BigDecimal("12453634.2313")).toString());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment