From 9bd5811918200b7af122bc993db955a403b4e58a Mon Sep 17 00:00:00 2001 From: HH Date: Tue, 21 Mar 2023 01:03:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=86=E6=8F=90=E7=8E=B0?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E8=A1=A8=EF=BC=8C=E4=BF=9D=E5=AD=98=E6=8F=90?= =?UTF-8?q?=E7=8E=B0=E8=AE=B0=E5=BD=95=EF=BC=8C=E5=AE=9A=E6=97=B6=E5=99=A8?= =?UTF-8?q?=E4=B8=BB=E5=8A=A8=E5=90=91Adapay=E6=9F=A5=E8=AF=A2=E6=8F=90?= =?UTF-8?q?=E7=8E=B0=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/OrderDetailServiceImpl.java | 5 +- .../service/impl/OrderMasterServiceImpl.java | 5 +- .../ghy/payment/domain/DrawCashRecord.java | 77 +++++++++++++++++++ .../payment/mapper/DrawCashRecordMapper.java | 19 +++++ .../ghy/payment/service/AdapayService.java | 44 +++++++---- .../ghy/payment/service/AdapaySyncTimer.java | 76 ++++++++++++++++++ .../service/impl/DrawCashCallbackService.java | 16 ++-- .../mapper/payment/DrawCashRecordMapper.xml | 63 +++++++++++++++ 8 files changed, 283 insertions(+), 22 deletions(-) create mode 100644 ghy-payment/src/main/java/com/ghy/payment/domain/DrawCashRecord.java create mode 100644 ghy-payment/src/main/java/com/ghy/payment/mapper/DrawCashRecordMapper.java create mode 100644 ghy-payment/src/main/java/com/ghy/payment/service/AdapaySyncTimer.java create mode 100644 ghy-payment/src/main/resources/mapper/payment/DrawCashRecordMapper.xml diff --git a/ghy-order/src/main/java/com/ghy/order/service/impl/OrderDetailServiceImpl.java b/ghy-order/src/main/java/com/ghy/order/service/impl/OrderDetailServiceImpl.java index a94b7fa5..f9deeb60 100644 --- a/ghy-order/src/main/java/com/ghy/order/service/impl/OrderDetailServiceImpl.java +++ b/ghy-order/src/main/java/com/ghy/order/service/impl/OrderDetailServiceImpl.java @@ -179,10 +179,11 @@ public class OrderDetailServiceImpl implements OrderDetailService { } @Override - public String createCode() { + public synchronized String createCode() { INDEX.compareAndSet(9999L, 1L); LocalDateTime now = LocalDateTime.now(); - return "od" + now.format(MINI_FORMATTER) + INDEX.getAndIncrement(); + // 得到的CODE长度相同 + return "od" + now.format(MINI_FORMATTER) + String.format("%04d", INDEX.getAndIncrement()); } @Override diff --git a/ghy-order/src/main/java/com/ghy/order/service/impl/OrderMasterServiceImpl.java b/ghy-order/src/main/java/com/ghy/order/service/impl/OrderMasterServiceImpl.java index b59c0545..a4fa59c8 100644 --- a/ghy-order/src/main/java/com/ghy/order/service/impl/OrderMasterServiceImpl.java +++ b/ghy-order/src/main/java/com/ghy/order/service/impl/OrderMasterServiceImpl.java @@ -65,9 +65,10 @@ public class OrderMasterServiceImpl implements OrderMasterService { private final static ThreadLocal dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmmss")); @Override - public String createOrderCode() { + public synchronized String createOrderCode() { INDEX.compareAndSet(9999L, 1L); - return "om" + dateFormat.get().format(new Date()) + INDEX.getAndIncrement(); + // 得到的CODE长度相同 + return "om" + dateFormat.get().format(new Date()) + String.format("%04d", INDEX.getAndIncrement()); } @Override diff --git a/ghy-payment/src/main/java/com/ghy/payment/domain/DrawCashRecord.java b/ghy-payment/src/main/java/com/ghy/payment/domain/DrawCashRecord.java new file mode 100644 index 00000000..167d9b73 --- /dev/null +++ b/ghy-payment/src/main/java/com/ghy/payment/domain/DrawCashRecord.java @@ -0,0 +1,77 @@ +package com.ghy.payment.domain; + +import lombok.Data; + +/** + * 提现记录 + */ +@Data +public class DrawCashRecord { + + /** + * 由Adapay生成的取现对象 id + */ + private String id; + + private Long dept_id; + /** + * 控制台 主页面应用的app_id + */ + private String app_id; + + /** + * 取现类型:T1-T+1取现;D1-D+1取现;D0-即时取现。 + */ + private String cash_type; + /** + * 取现对象创建时的 10 位时间戳 + */ + private String created_time; + /** + * 用户对象的member_id,若是商户本身取现时为0 + */ + private String member_id; + /** + * 取现对象,cash + */ + private String object; + /** + * 请求订单号,只能为英文、数字或者下划线的一种或多种组合,保证在app_id下唯一 + */ + private String order_no; + /** + * 取现金额,必须大于0,保留两位小数点,如0.10、100.05等 + */ + private String cash_amt; + /** + * 取现成功后的到账金额,值为取现金额 - 取现手续费金额。 + */ + private String real_amt; + /** + * 取现手续费金额 + */ + private String fee_amt; + /** + * 状态 + * pending 交易处理中 + * succeeded 交易成功 + * failed 交易失败 + */ + private String status; + /** + * 是否 prod模式,true 是 prod模式,false 是 mock模式 + */ + private String prod_mode; + /** + * 错误码 https://docs.adapay.tech/api/errors.html#id6 + */ + private String error_code; + /** + * 错误类型 + * invalid_request_error 请求错误,传入了不正确的地址、参数或值 + * api_error Adapay 服务器出现的异常错误 + * channel_error 第三方支付渠道出现的错误导致请求出现错误,通常您需要对这些可能出现的情况进行处理或者联系我们 + */ + private String error_type; + private String error_msg; +} diff --git a/ghy-payment/src/main/java/com/ghy/payment/mapper/DrawCashRecordMapper.java b/ghy-payment/src/main/java/com/ghy/payment/mapper/DrawCashRecordMapper.java new file mode 100644 index 00000000..999b5a06 --- /dev/null +++ b/ghy-payment/src/main/java/com/ghy/payment/mapper/DrawCashRecordMapper.java @@ -0,0 +1,19 @@ +package com.ghy.payment.mapper; + +import com.ghy.payment.domain.DrawCashRecord; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface DrawCashRecordMapper { + + int insert(DrawCashRecord record); + + int update(DrawCashRecord record); + + DrawCashRecord selectById(String id); + + List selectByStatus(String status); + + int updateStatus(@Param("id") String id, @Param("status") String status); +} diff --git a/ghy-payment/src/main/java/com/ghy/payment/service/AdapayService.java b/ghy-payment/src/main/java/com/ghy/payment/service/AdapayService.java index 203eec36..2af82381 100644 --- a/ghy-payment/src/main/java/com/ghy/payment/service/AdapayService.java +++ b/ghy-payment/src/main/java/com/ghy/payment/service/AdapayService.java @@ -10,9 +10,9 @@ import com.ghy.payment.service.impl.PayReverseCallbackService; import com.ghy.payment.service.impl.RefundCallbackService; import com.huifu.adapay.core.exception.BaseAdaPayException; import com.huifu.adapay.model.*; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -25,11 +25,12 @@ import java.util.List; /** * @author HH 2022/3/25 */ +@Slf4j @Service public class AdapayService { - private static final Logger logger = LoggerFactory.getLogger(AdapayService.class); - + @Resource + private ThreadPoolTaskExecutor executor; @Resource private PayCallbackService payCallbackService; @Resource @@ -100,9 +101,9 @@ public class AdapayService { confirmParams.put("div_members", divMembers); confirmParams.put("fee_mode", feeMode); confirmParams.put("description", description); - logger.info("发起支付确认 dept[{}] param:{}", deptId, confirmParams.toJSONString()); + log.info("发起支付确认 dept[{}] param:{}", deptId, confirmParams.toJSONString()); JSONObject response = (JSONObject) PaymentConfirm.create(confirmParams, deptId.toString()); - logger.info("支付确认结果 dept[{}] response:{}", deptId, response.toJSONString()); + log.info("支付确认结果 dept[{}] response:{}", deptId, response.toJSONString()); return response; } @@ -278,13 +279,30 @@ public class AdapayService { cashParam.put("notify_url", adapayProperties.getNotifyUrl()); cashParam.put("remark", remark); cashParam.put("fee_mode", feeMode); - logger.info("发起提现 dept[{}] param:{}", deptId, cashParam.toJSONString()); + log.info("发起提现 dept[{}] param:{}", deptId, cashParam.toJSONString()); JSONObject response = (JSONObject) Drawcash.create(cashParam, deptId.toString()); - logger.info("提现结果 dept[{}] response:{}", deptId, response.toJSONString()); - drawCashCallbackService.onResponse(response); + log.info("提现结果 dept[{}] response:{}", deptId, response.toJSONString()); + response.put("dept_id", deptId); + executor.execute(() -> drawCashCallbackService.onResponse(response)); return response; } + /** + * 通过该功能,可以查询已发起的取现交易状态。 + * + * @param deptId [必填]商户ID + * @param orderNo [必填项]请求订单号,只能为英文、数字或者下划线的一种或多种组合,保证在app_id下唯一 + * @return https://docs.adapay.tech/api/wallet.html#query-cash-response + * 失败示例: {"error_msg":"未查到相关取现信息","error_type":"invalid_request_error","prod_mode":"true","error_code":"cash_error","status":"failed"} + * 成功示例: {"cash_list":[{"cash_id":"0021110483925412449046528","trans_stat":"P","cash_amt":"19.87"}],"prod_mode":"true","status":"succeeded"} + */ + public JSONObject queryDrawCash(@NotNull Long deptId, @NotNull String orderNo) throws BaseAdaPayException { + Assert.hasText(orderNo, "orderNo is blank!"); + JSONObject queryCashParam = new JSONObject(); + queryCashParam.put("order_no", orderNo); + return (JSONObject) Drawcash.query(queryCashParam, deptId.toString()); + } + /** * 支付宝正扫支付 */ @@ -351,9 +369,9 @@ public class AdapayService { paymentParams.put("div_members", divMembers); paymentParams.put("device_info", deviceInfo); paymentParams.put("expend", expend); - logger.debug("paymentParams: {}", paymentParams.toJSONString()); + log.debug("paymentParams: {}", paymentParams.toJSONString()); JSONObject response = (JSONObject) Payment.create(paymentParams, deptId.toString()); - payCallbackService.onResponse(response); + executor.execute(() -> payCallbackService.onResponse(response)); return response; } @@ -380,7 +398,7 @@ public class AdapayService { refundParams.put("refund_amt", refundAmt); refundParams.put("refund_order_no", refundOrderNo); JSONObject response = (JSONObject) Refund.create(paymentId, refundParams, deptId.toString()); - refundCallbackService.onResponse(response); + executor.execute(() -> refundCallbackService.onResponse(response)); return response; } @@ -444,7 +462,7 @@ public class AdapayService { reverseParams.put("notify_url", adapayProperties.getNotifyUrl()); reverseParams.put("order_no", "PAYMENT_REVERSE" + System.currentTimeMillis()); JSONObject response = (JSONObject) PaymentReverse.create(reverseParams, deptId.toString()); - payReverseCallbackService.onResponse(response); + executor.execute(() -> payReverseCallbackService.onResponse(response)); return response; } diff --git a/ghy-payment/src/main/java/com/ghy/payment/service/AdapaySyncTimer.java b/ghy-payment/src/main/java/com/ghy/payment/service/AdapaySyncTimer.java new file mode 100644 index 00000000..4624816d --- /dev/null +++ b/ghy-payment/src/main/java/com/ghy/payment/service/AdapaySyncTimer.java @@ -0,0 +1,76 @@ +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 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); + } + + } + } +} diff --git a/ghy-payment/src/main/java/com/ghy/payment/service/impl/DrawCashCallbackService.java b/ghy-payment/src/main/java/com/ghy/payment/service/impl/DrawCashCallbackService.java index e9df8896..903fefa1 100644 --- a/ghy-payment/src/main/java/com/ghy/payment/service/impl/DrawCashCallbackService.java +++ b/ghy-payment/src/main/java/com/ghy/payment/service/impl/DrawCashCallbackService.java @@ -2,29 +2,35 @@ package com.ghy.payment.service.impl; import com.alibaba.fastjson.JSONObject; import com.ghy.common.adapay.model.Event; +import com.ghy.payment.domain.DrawCashRecord; +import com.ghy.payment.mapper.DrawCashRecordMapper; import com.ghy.payment.service.CallBackService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import javax.annotation.Resource; + /** * 提现回调 * * @author HH 2022/5/30 */ +@Slf4j @Service("drawCashCallbackService") public class DrawCashCallbackService implements CallBackService { - private static final Logger logger = LoggerFactory.getLogger(DrawCashCallbackService.class); + @Resource + private DrawCashRecordMapper drawCashRecordMapper; @Override public void onCallback(Event event) { - logger.info("提现 callback: {}", event.toString()); + log.info("提现 callback: {}", event.toString()); } @Override public void onResponse(JSONObject response) { - logger.info("提现 response: {}", response.toString()); + DrawCashRecord record = response.toJavaObject(DrawCashRecord.class); + drawCashRecordMapper.insert(record); } } diff --git a/ghy-payment/src/main/resources/mapper/payment/DrawCashRecordMapper.xml b/ghy-payment/src/main/resources/mapper/payment/DrawCashRecordMapper.xml new file mode 100644 index 00000000..70b5c5a0 --- /dev/null +++ b/ghy-payment/src/main/resources/mapper/payment/DrawCashRecordMapper.xml @@ -0,0 +1,63 @@ + + + + + + SELECT `id`, + `dept_id`, + `app_id`, + `cash_amt`, + `cash_type`, + `created_time`, + `fee_amt`, + `member_id`, + `object`, + `order_no`, + `real_amt`, + `status`, + `prod_mode`, + `error_code`, + `error_type`, + `error_msg` + FROM draw_cash_record + + + + INSERT INTO draw_cash_record(`id`, `dept_id`, `app_id`, `cash_amt`, `cash_type`, `created_time`, + `fee_amt`, `member_id`, `object`, `order_no`, + `real_amt`, `status`, `prod_mode`, `error_code`, `error_type`, `error_msg`) + VALUES (#{id}, #{dept_id}, #{app_id}, #{cash_amt}, #{cash_type}, #{created_time}, + #{fee_amt}, #{member_id}, #{object}, #{order_no}, + #{real_amt}, #{status}, #{prod_mode}, #{error_code}, #{error_type}, #{error_msg}) + + + + UPDATE draw_cash_record + + `real_amt` = #{real_amt}, + `fee_amt` = #{fee_amt}, + `status` = #{status}, + `error_code` = #{error_code}, + `error_type` = #{error_type}, + `error_msg` = #{error_msg}, + + WHERE `id` = #{id} + + + + UPDATE draw_cash_record + SET `status` = #{status} + WHERE `id` = #{id} + + + + + + + \ No newline at end of file