定时器同步订单提现状态

This commit is contained in:
HH 2023-03-21 12:46:38 +08:00
parent 9bd5811918
commit f12f982e2f
9 changed files with 247 additions and 131 deletions

View File

@ -0,0 +1,96 @@
package com.ghy.web.timer;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ghy.common.adapay.model.AdapayStatusEnum;
import com.ghy.order.service.OrderDetailService;
import com.ghy.payment.domain.DrawCashRecord;
import com.ghy.payment.mapper.DrawCashRecordMapper;
import com.ghy.payment.service.AdapayService;
import com.huifu.adapay.core.exception.BaseAdaPayException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
/**
* 定时器
* 主动与Adapay同步支付撤销支付提现订单的状态
*/
@Slf4j
@Component
@EnableScheduling
public class AdapaySyncTimer {
@Resource
private AdapayService adapayService;
@Resource
private OrderDetailService orderDetailService;
@Resource
private DrawCashRecordMapper drawCashRecordMapper;
@Scheduled(fixedRate = 5 * 60 * 1000L)
public void syncDrawCash() {
List<DrawCashRecord> records = drawCashRecordMapper.selectByStatus("pending");
if (CollectionUtils.isEmpty(records)) {
log.debug("No pending drawCashRecord.");
return;
}
for (DrawCashRecord record : records) {
Long deptId = record.getDept_id();
String orderNo = record.getOrder_no();
if (deptId == null || StringUtils.isBlank(orderNo)) {
continue;
}
try {
syncDrawCashRecord(record, deptId, orderNo);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
@Transactional(rollbackFor = Exception.class)
public void syncDrawCashRecord(DrawCashRecord record, Long deptId, String orderNo) throws BaseAdaPayException {
JSONObject response = adapayService.queryDrawCash(deptId, orderNo);
// 这个status代表API调用状态 不代表提现状态
if (AdapayStatusEnum.succeeded.code.equals(response.getString("status"))) {
JSONArray cashList = response.getJSONArray("cash_list");
if (!CollectionUtils.isEmpty(cashList)) {
JSONObject cash = cashList.getJSONObject(0);
// 这个才是提现状态
String transStat = cash.getString("trans_stat");
switch (transStat) {
// 提现成功
case "S":
log.info("DrawCashRecord[{}]提现成功", record.getId());
// 更新提现记录表状态
drawCashRecordMapper.updateStatus(record.getId(), "succeeded");
// 更新子订单表状态
orderDetailService.updateDrawCashStatus(record.getId(), 2, new Date());
break;
// 提现失败
case "F":
log.error("DrawCashRecord[{}]提现失败: {}", record.getId(), response.toJSONString());
// 更新提现记录表状态
drawCashRecordMapper.updateStatus(record.getId(), "failed");
// 更新子订单表状态
orderDetailService.updateDrawCashStatus(record.getId(), -1, null);
break;
default:
break;
}
}
} else {
log.error("DrawCash.query请求失败: {}", response.toJSONString());
}
}
}

View File

@ -93,18 +93,28 @@ public class OrderDetail extends BaseEntity {
private Integer ledgerAccountStatus; private Integer ledgerAccountStatus;
/**
* 发起提现后Adapay返回的对象ID draw_cash_id
*/
private String drawCashId;
/** /**
* draw_cash_status * draw_cash_status
* 0 未分账 * 0 未分账
* 1 已分账 * 1 已分账
* 2 已到账 * 2 已到账
* -1 提现失败
*/ */
private Integer drawCashStatus; private Integer drawCashStatus;
/** /**
* 发起提现时间 draw_cash_date * 发起提现时间 draw_cash_time
*/ */
private Date drawCashDate; private Date drawCashTime;
/**
* 提现到账时间 arrival_time
*/
private Date arrivalTime;
@Excel(name = "付款类型", cellType = Excel.ColumnType.NUMERIC) @Excel(name = "付款类型", cellType = Excel.ColumnType.NUMERIC)
private Integer payType; private Integer payType;

View File

@ -4,6 +4,7 @@ import com.ghy.order.domain.OrderDetail;
import com.ghy.order.domain.OrderStatusCount; import com.ghy.order.domain.OrderStatusCount;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List; import java.util.List;
/** /**
@ -89,4 +90,14 @@ public interface OrderDetailMapper {
List<OrderDetail> selectByStatus(List<Integer> status); List<OrderDetail> selectByStatus(List<Integer> status);
List<OrderStatusCount> statusCount(); List<OrderStatusCount> statusCount();
/**
* 用提现ID更新订单的提现状态
*
* @param drawCashId 发起提现后Adapay返回的对象ID
* @param drawCashStatus 提现状态见{@link OrderDetail#getDrawCashStatus()}
* @param arrivalTime 提现到账时间
* @return 1
*/
int updateDrawCashStatus(@Param("drawCashId") String drawCashId, @Param("drawCashStatus") int drawCashStatus, @Param("arrivalTime") Date arrivalTime);
} }

View File

@ -6,6 +6,7 @@ import com.ghy.payment.domain.FinancialChangeRecord;
import com.huifu.adapay.core.exception.BaseAdaPayException; import com.huifu.adapay.core.exception.BaseAdaPayException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -113,6 +114,7 @@ public interface OrderDetailService {
/** /**
* 统计符合条件的子单数量 * 统计符合条件的子单数量
*
* @param orderDetail * @param orderDetail
* @return 数量 * @return 数量
*/ */
@ -120,10 +122,11 @@ public interface OrderDetailService {
/** /**
* 子单改价接口 * 子单改价接口
*
* @param orderDetailId 子单id * @param orderDetailId 子单id
* @param changeMoney 改价金额 * @param changeMoney 改价金额
* @param type 报价类型 * @param type 报价类型
* @param remark 备注 * @param remark 备注
* @return 成功/失败 * @return 成功/失败
*/ */
int changePrice(Long orderDetailId, BigDecimal changeMoney, Integer type, String remark) throws Exception; int changePrice(Long orderDetailId, BigDecimal changeMoney, Integer type, String remark) throws Exception;
@ -133,4 +136,14 @@ public interface OrderDetailService {
FinancialChangeRecord getChangedPriceRecord(Long orderDetailId); FinancialChangeRecord getChangedPriceRecord(Long orderDetailId);
List<OrderStatusCount> statusCount(); List<OrderStatusCount> statusCount();
/**
* 用提现ID更新订单的提现状态
*
* @param drawCashId 发起提现后Adapay返回的对象ID
* @param drawCashStatus 提现状态见{@link OrderDetail#getDrawCashStatus()}
* @param arrivalTime 提现到账时间
* @return 1
*/
int updateDrawCashStatus(String drawCashId, int drawCashStatus, Date arrivalTime);
} }

View File

@ -34,6 +34,7 @@ import com.huifu.adapay.core.exception.BaseAdaPayException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -63,6 +64,8 @@ public class OrderDetailServiceImpl implements OrderDetailService {
.appendValue(DAY_OF_MONTH, 2).appendValue(HOUR_OF_DAY, 2) .appendValue(DAY_OF_MONTH, 2).appendValue(HOUR_OF_DAY, 2)
.appendValue(MINUTE_OF_HOUR, 2).appendValue(SECOND_OF_MINUTE, 2).toFormatter(); .appendValue(MINUTE_OF_HOUR, 2).appendValue(SECOND_OF_MINUTE, 2).toFormatter();
@Resource
private ThreadPoolTaskExecutor executor;
@Resource @Resource
private OrderDetailMapper orderDetailMapper; private OrderDetailMapper orderDetailMapper;
@Resource @Resource
@ -319,7 +322,7 @@ public class OrderDetailServiceImpl implements OrderDetailService {
} }
/** /**
* 子订单确认完成 * 子订单确认完成 暂时先用 synchronized 来避免重复提交
* 1.修改子订单状态为 OrderStatus.FINISH.code * 1.修改子订单状态为 OrderStatus.FINISH.code
* 2.将子订单对应的子财务单确认分账 * 2.将子订单对应的子财务单确认分账
* 3.提现至银行卡 * 3.提现至银行卡
@ -328,7 +331,6 @@ public class OrderDetailServiceImpl implements OrderDetailService {
*/ */
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
// 暂时先用 synchronized 来避免重复提交
public synchronized void finish(Long orderDetailId) throws BaseAdaPayException { public synchronized void finish(Long orderDetailId) throws BaseAdaPayException {
// 校验订单 // 校验订单
OrderDetail orderDetail = selectById(orderDetailId); OrderDetail orderDetail = selectById(orderDetailId);
@ -363,14 +365,6 @@ public class OrderDetailServiceImpl implements OrderDetailService {
fdUpdate.setPayMoney(platformFeeFD.getPayMoney()); fdUpdate.setPayMoney(platformFeeFD.getPayMoney());
logger.debug("子订单[code={}]的完单流程开始", odCode); logger.debug("子订单[code={}]的完单流程开始", odCode);
// 修改订单状态
OrderDetail odUpdate = new OrderDetail();
odUpdate.setId(orderDetailId);
odUpdate.setOrderStatus(OrderStatus.FINISH.code());
odUpdate.setDrawCashDate(new Date());
odUpdate.setDrawCashStatus(1);
orderDetailMapper.updateOrderDetail(odUpdate);
// 加价单手续费补偿成功与否 // 加价单手续费补偿成功与否
boolean compensate = false; boolean compensate = false;
// 加价单手续费 // 加价单手续费
@ -488,25 +482,48 @@ public class OrderDetailServiceImpl implements OrderDetailService {
} }
// 待提现金额 // 待提现金额
String cashAmt = AdapayUtils.bigDecimalToString(dtx); String cashAmt = AdapayUtils.bigDecimalToString(dtx);
String orderNo = AdapayUtils.createOrderNo(AdapayOrderType.DRAW_CASH); executor.execute(() -> drawCash(orderDetailId, financialDetail.getDeptId(), memberId, cashAmt));
try {
JSONObject drawCashResponse = adapayService.drawCash(financialDetail.getDeptId(), orderNo, "T1",
cashAmt, memberId, "订单结算", null);
boolean drawCashStatus = AdapayStatusEnum.pending.code.equals(drawCashResponse.getString("status")) ||
AdapayStatusEnum.succeeded.code.equals(drawCashResponse.getString("status"));
if (drawCashStatus) {
// 提现成功
logger.info("子订单[code={}]自动提现成功", odCode);
} else {
// 提现失败 把信息记录到error日志里
logger.error("自动发起提现失败: 子订单code={}, deptId={} 失败信息: {}", odCode, cashAmt, drawCashResponse.toJSONString());
}
} catch (Exception e) {
logger.error("自动发起提现失败: 子订单code={}, deptId={}, memberId={}, amount={}", odCode, financialDetail.getDeptId(), memberId, cashAmt, e);
}
// --------------------- 自动提现流程 end --------------------- // --------------------- 自动提现流程 end ---------------------
} }
/**
* 子订单分账后提现
*
* @param orderDetailId 子订单ID
* @param deptId 企业ID
* @param memberId Adapay用户ID
* @param cashAmt 提现金额
*/
private void drawCash(Long orderDetailId, Long deptId, String memberId, String cashAmt) {
String orderNo = AdapayUtils.createOrderNo(AdapayOrderType.DRAW_CASH);
JSONObject drawCashResponse;
try {
drawCashResponse = adapayService.drawCash(deptId, orderNo, "T1",
cashAmt, memberId, "订单结算", null);
} catch (BaseAdaPayException e) {
logger.error("自动发起提现失败: 子订单id={}, deptId={}", orderDetailId, cashAmt);
return;
}
boolean drawCashStatus = AdapayStatusEnum.pending.code.equals(drawCashResponse.getString("status")) ||
AdapayStatusEnum.succeeded.code.equals(drawCashResponse.getString("status"));
if (drawCashStatus) {
// 提现成功
logger.info("子订单[id={}]自动提现成功", orderDetailId);
// 修改订单状态
OrderDetail odUpdate = new OrderDetail();
odUpdate.setId(orderDetailId);
odUpdate.setOrderStatus(OrderStatus.FINISH.code());
odUpdate.setDrawCashTime(new Date());
odUpdate.setDrawCashStatus(1);
// 保存提现ID 用于同步Adapay提现状态
odUpdate.setDrawCashId(drawCashResponse.getString("id"));
orderDetailMapper.updateOrderDetail(odUpdate);
} else {
// 提现失败 把信息记录到error日志里
logger.error("自动发起提现失败: 子订单id={}, deptId={} 失败信息: {}", orderDetailId, cashAmt, drawCashResponse.toJSONString());
}
}
@Override @Override
public Long countOrderDetailList(OrderDetail orderDetail) { public Long countOrderDetailList(OrderDetail orderDetail) {
return orderDetailMapper.countOrderDetailList(orderDetail); return orderDetailMapper.countOrderDetailList(orderDetail);
@ -576,4 +593,9 @@ public class OrderDetailServiceImpl implements OrderDetailService {
public List<OrderStatusCount> statusCount() { public List<OrderStatusCount> statusCount() {
return orderDetailMapper.statusCount(); return orderDetailMapper.statusCount();
} }
@Override
public int updateDrawCashStatus(String drawCashId, int drawCashStatus, Date arrivalTime) {
return orderDetailMapper.updateDrawCashStatus(drawCashId, drawCashStatus, arrivalTime);
}
} }

View File

@ -18,7 +18,9 @@
<result property="expectTimeEnd" column="expect_time_end"/> <result property="expectTimeEnd" column="expect_time_end"/>
<result property="workBeginTime" column="work_begin_time"/> <result property="workBeginTime" column="work_begin_time"/>
<result property="workFinishTime" column="work_finish_time"/> <result property="workFinishTime" column="work_finish_time"/>
<result property="drawCashDate" column="draw_cash_date"/> <result property="drawCashId" column="draw_cash_id"/>
<result property="drawCashTime" column="draw_cash_time"/>
<result property="arrivalTime" column="arrival_time"/>
<result property="drawCashStatus" column="draw_cash_status"/> <result property="drawCashStatus" column="draw_cash_status"/>
<result property="clockInLocation" column="clock_in_location"/> <result property="clockInLocation" column="clock_in_location"/>
<result property="shelveStatus" column="shelve_status"/> <result property="shelveStatus" column="shelve_status"/>
@ -44,7 +46,9 @@
expect_time_end, expect_time_end,
work_begin_time, work_begin_time,
work_finish_time, work_finish_time,
draw_cash_date, draw_cash_id,
draw_cash_time,
arrival_time,
draw_cash_status, draw_cash_status,
clock_in_location, clock_in_location,
ledger_account_status, ledger_account_status,
@ -70,7 +74,9 @@
od.expect_time_end, od.expect_time_end,
od.work_begin_time, od.work_begin_time,
od.work_finish_time, od.work_finish_time,
od.draw_cash_date, od.draw_cash_id,
od.draw_cash_time,
od.arrival_time,
od.draw_cash_status, od.draw_cash_status,
od.clock_in_location, od.clock_in_location,
od.ledger_account_status, od.ledger_account_status,
@ -232,7 +238,9 @@
<if test="useTimeNotRange">expect_time_end = null,</if> <if test="useTimeNotRange">expect_time_end = null,</if>
<if test="workBeginTime != null">work_begin_time = #{workBeginTime},</if> <if test="workBeginTime != null">work_begin_time = #{workBeginTime},</if>
<if test="workFinishTime != null">work_finish_time = #{workFinishTime},</if> <if test="workFinishTime != null">work_finish_time = #{workFinishTime},</if>
<if test="drawCashDate != null">draw_cash_date = #{drawCashDate},</if> <if test="drawCashId != null">draw_cash_id = #{drawCashId},</if>
<if test="drawCashTime != null">draw_cash_time = #{drawCashTime},</if>
<if test="arrivalTime != null">arrival_time = #{arrivalTime},</if>
<if test="drawCashStatus != null">draw_cash_status = #{drawCashStatus},</if> <if test="drawCashStatus != null">draw_cash_status = #{drawCashStatus},</if>
<if test="clockInLocation != null and clockInLocation != ''">clock_in_location = #{clockInLocation},</if> <if test="clockInLocation != null and clockInLocation != ''">clock_in_location = #{clockInLocation},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>
@ -328,4 +336,10 @@
</foreach> </foreach>
</select> </select>
<update id="updateDrawCashStatus">
UPDATE order_detail
SET draw_cash_status = #{drawCashStatus},
arrival_time = #{arrivalTime}
WHERE draw_cash_id = #{drawCashId}
</update>
</mapper> </mapper>

View File

@ -203,6 +203,34 @@ public class AdapayService {
return (JSONObject) SettleAccount.create(settleCountParams, deptId.toString()); return (JSONObject) SettleAccount.create(settleCountParams, deptId.toString());
} }
/**
* 查询商户或商户下某个用户结算账户的余额
*
* @param deptId [必填]商户ID
* @param memberId [必填]商户用户对象 id只能为英文数字或者下划线的一种或多种组合若查询商户本身时传入值0
* @param settleAccountId 由Adapay生成的结算账户对象id若查询商户本身时可为空而当查询商户下的用户时必填
* @param acctType 账户类型01或者为空是基本户02是手续费账户03是过渡户
* @return https://docs.adapay.tech/api/wallet.html#id13
* 成功示例: {"acct_balance": "0.00",
* "app_id": "app_XXXXXXXX",
* "avl_balance": "0.00",
* "frz_balance": "0.00",
* "last_avl_balance": "0.00",
* "object": "account_balance",
* "status": "succeeded",
* "prod_mode": "true"}
*/
public JSONObject queryAccountBalance(@NotNull Long deptId, @NotNull String memberId, String settleAccountId, String acctType) throws BaseAdaPayException {
// 获取商户的appId
String appId = AdapayConfig.getAppId(deptId);
JSONObject queryParams = new JSONObject(5);
queryParams.put("settle_account_id", settleAccountId);
queryParams.put("member_id", memberId);
queryParams.put("app_id", appId);
queryParams.put("acct_type", acctType);
return (JSONObject) SettleAccount.balance(queryParams, deptId.toString());
}
/** /**
* 删除结算账户对象 https://docs.adapay.tech/api/trade.html#id50 * 删除结算账户对象 https://docs.adapay.tech/api/trade.html#id50
* 删除结算账户对象是对已创建完成的结算账户对象进行删除操作 * 删除结算账户对象是对已创建完成的结算账户对象进行删除操作

View File

@ -1,76 +0,0 @@
package com.ghy.payment.service;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ghy.common.adapay.model.AdapayStatusEnum;
import com.ghy.payment.domain.DrawCashRecord;
import com.ghy.payment.mapper.DrawCashRecordMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.List;
/**
* 定时器
* 主动与Adapay同步支付撤销支付提现订单的状态
*/
@Slf4j
@Component
@EnableScheduling
public class AdapaySyncTimer {
@Resource
private AdapayService adapayService;
@Resource
private DrawCashRecordMapper drawCashRecordMapper;
@Scheduled(fixedRate = 5 * 60 * 1000L)
public void syncDrawCash() {
List<DrawCashRecord> records = drawCashRecordMapper.selectByStatus("pending");
if (CollectionUtils.isEmpty(records)) {
log.debug("No pending drawCashRecord.");
return;
}
for (DrawCashRecord record : records) {
Long deptId = record.getDept_id();
String orderNo = record.getOrder_no();
if (deptId == null || StringUtils.isBlank(orderNo)) {
continue;
}
try {
JSONObject response = adapayService.queryDrawCash(deptId, orderNo);
// 这个status代表API调用状态 不代表提现状态
if (AdapayStatusEnum.succeeded.code.equals(response.getString("status"))) {
JSONArray cashList = response.getJSONArray("cash_list");
if (!CollectionUtils.isEmpty(cashList)) {
JSONObject cash = cashList.getJSONObject(0);
// 这个才是提现状态
String transStat = cash.getString("trans_stat");
switch (transStat) {
// 提现成功
case "S":
drawCashRecordMapper.updateStatus(record.getId(), "succeeded");
break;
// 提现失败
case "F":
drawCashRecordMapper.updateStatus(record.getId(), "failed");
break;
default:
break;
}
}
} else {
log.error("DrawCash.query请求失败: {}", response.toJSONString());
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
}

View File

@ -21,8 +21,6 @@ import org.springframework.util.CollectionUtils;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -182,24 +180,24 @@ public class OrderServiceImpl implements OrderService {
@Override @Override
public void autoChangeDrawCashStatus() { public void autoChangeDrawCashStatus() {
LocalDate now = LocalDate.now(); // LocalDate now = LocalDate.now();
DayOfWeek dayOfWeek = now.getDayOfWeek(); // DayOfWeek dayOfWeek = now.getDayOfWeek();
if (dayOfWeek.getValue() > 5) { // if (dayOfWeek.getValue() > 5) {
return; // return;
} // }
OrderDetail orderDetail = new OrderDetail(); // OrderDetail orderDetail = new OrderDetail();
orderDetail.setDrawCashStatus(1); // orderDetail.setDrawCashStatus(1);
List<OrderDetail> orderDetails = orderDetailService.selectOrderDetailList(orderDetail); // List<OrderDetail> orderDetails = orderDetailService.selectOrderDetailList(orderDetail);
long before24h = System.currentTimeMillis() - (24 * 60 * 60 * 1000); // long before24h = System.currentTimeMillis() - (24 * 60 * 60 * 1000);
for (OrderDetail od : orderDetails) { // for (OrderDetail od : orderDetails) {
if (od.getDrawCashDate().getTime() < before24h) { // if (od.getDrawCashDate().getTime() < before24h) {
OrderDetail update = new OrderDetail(); // OrderDetail update = new OrderDetail();
update.setId(od.getId()); // update.setId(od.getId());
update.setOrderStatus(2); // update.setOrderStatus(2);
orderDetailService.updateOrderDetail(update); // orderDetailService.updateOrderDetail(update);
logger.info("订单[{}]自动提现成功", od.getCode()); // logger.info("订单[{}]自动提现成功", od.getCode());
} // }
} // }
} }
} }