package com.ghy.payment.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ghy.common.adapay.AdapayConfig;
import com.ghy.common.adapay.AdapayProperties;
import com.ghy.common.adapay.model.*;
import com.ghy.common.enums.RefundType;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

/**
 * @author HH 2022/3/25
 */
@Slf4j
@Service
public class AdapayService {

    private ThreadPoolTaskExecutor executor;
    private AdapayProperties adapayProperties;
    private CallBackService payCallbackService;
    private CallBackService refundCallbackService;
    private CallBackService drawCashCallbackService;
    private CallBackService payReverseCallbackService;
    private CallBackService paymentConfirmCallbackService;

    @Autowired
    public void setExecutor(ThreadPoolTaskExecutor threadPoolTaskExecutor) {
        this.executor = threadPoolTaskExecutor;
    }

    @Autowired
    public void setAdapayProperties(AdapayProperties adapayProperties) {
        log.info("Adapay load properties: {}", JSON.toJSONString(adapayProperties));
        this.adapayProperties = adapayProperties;
    }

    @Autowired
    public void setPayCallbackService(CallBackService payCallbackService) {
        log.info("Adapay load callback: {}", payCallbackService.getClass().toString());
        this.payCallbackService = payCallbackService;
    }

    @Autowired
    public void setRefundCallbackService(CallBackService refundCallbackService) {
        log.info("Adapay load callback: {}", refundCallbackService.getClass().toString());
        this.refundCallbackService = refundCallbackService;
    }

    @Autowired
    public void setDrawCashCallbackService(CallBackService drawCashCallbackService) {
        log.info("Adapay load callback: {}", drawCashCallbackService.getClass().toString());
        this.drawCashCallbackService = drawCashCallbackService;
    }

    @Autowired
    public void setPayReverseCallbackService(CallBackService payReverseCallbackService) {
        log.info("Adapay load callback: {}", payReverseCallbackService.getClass().toString());
        this.payReverseCallbackService = payReverseCallbackService;
    }

    @Autowired
    public void setPaymentConfirmCallbackService(CallBackService paymentConfirmCallbackService) {
        log.info("Adapay load callback: {}", paymentConfirmCallbackService.getClass().toString());
        this.paymentConfirmCallbackService = paymentConfirmCallbackService;
    }

    /**
     * 查询支付对象
     *
     * @param deptId    [必填]商户ID
     * @param paymentId [必填] String(64) Adapay生成的支付对象id
     * @return 支付对象
     */
    public JSONObject getPayment(@NotNull Long deptId, @NotNull String paymentId) {
        try {
            return (JSONObject) Payment.query(paymentId, deptId.toString());
        } catch (BaseAdaPayException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 查询支付对象列表
     *
     * @param deptId    [必填]商户ID
     * @param paymentId [必填] String(64) Adapay生成的支付对象id
     * @return 支付对象 https://docs.adapay.tech/api/trade.html#id21
     */
    public JSONObject paymentList(@NotNull Long deptId, String paymentId) {
        try {
            // 获取商户的appId
            String appId = AdapayConfig.getAppId(deptId);
            HashMap<String, Object> param = new HashMap<>();
            param.put("app_id", appId);
            param.put("paymentId", paymentId);
            return (JSONObject) Payment.queryList(param, deptId.toString());
        } catch (BaseAdaPayException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 支付确认
     * 适用于延时分账的场景。只有已支付完成且延时分账的Payment对象，才支持调用创建支付确认对象。
     * 支持一次全额或多次部分确认，多次部分确认时，当前确认金额 + 已确认金额 + 已撤销金额不能大于原支付金额。
     *
     * @param deptId      [必填]商户ID
     * @param paymentId   [必填] String(64) Adapay生成的支付对象id
     * @param orderNo     [必填] String(64) 请求订单号，只能为英文、数字或者下划线的一种或多种组合，保证在app_id下唯一
     * @param confirmAmt  [必填] String(14) 确认金额，必须大于0，保留两位小数点，如0.10、100.05等。必须小于等于原支付金额-已确认金额-已撤销金额
     * @param divMembers  分账对象信息列表，一次请求最多仅支持7个分账方。json对象 形式，详见 分账对象信息列表
     * @param feeMode     String(1) 手续费收取模式：O-商户手续费账户扣取手续费，I-交易金额中扣取手续费；值为空时，默认值为I；若为O时，分账对象列表中不支持传入手续费承担方
     * @param description String(128) 附加说明
     * @return 成功时同步返回一个包含 支付确认对象的JSON https://docs.adapay.tech/api/trade.html#id54
     * 成功示例:{
     * "order_no": "TEST18888888888_001",
     * "created_time": "1670932918",
     * "confirm_amt": "10.00",
     * "confirmed_amt": "0.00",
     * "reserved_amt": "0.00",
     * "div_members": [{
     * "member_id": "W7D888",
     * "amount": "10.00",
     * "fee_flag": "Y"
     * }],
     * "refunded_amt": "0.00",
     * "prod_mode": "true",
     * "id": "0021120221213200158990448709336858888888",
     * "app_id": "app_01af32e7-6173-414f-88e5-79cae64d5e24",
     * "fee_amt": "0.08",
     * "object": "payment_confirm",
     * "status": "succeeded"
     * }
     */
    public JSONObject paymentConfirm(@NotNull Long deptId, @NotNull String paymentId, @NotNull String orderNo,
                                     @NotNull String confirmAmt, List<DivMember> divMembers,
                                     String feeMode, String description) throws BaseAdaPayException {
        JSONObject confirmParams = new JSONObject();
        confirmParams.put("payment_id", paymentId);
        confirmParams.put("order_no", orderNo);
        confirmParams.put("confirm_amt", confirmAmt);
        confirmParams.put("div_members", divMembers);
        confirmParams.put("fee_mode", feeMode);
        confirmParams.put("description", description);
        log.info("发起支付确认 dept[{}] param:{}", deptId, confirmParams.toJSONString());
        JSONObject response = (JSONObject) PaymentConfirm.create(confirmParams, deptId.toString());
        log.info("支付确认结果 dept[{}] response:{}", deptId, response.toJSONString());
        response.put("deptId", deptId);
        response.put("paymentId", paymentId);
        executor.execute(() -> paymentConfirmCallbackService.onResponse(response));
        return response;
    }

    /**
     * 创建余额支付请求
     * 商户利用该接口进行余额支付，支持同一商户下的商户-用户，用户-商户，用户-用户间的账户余额支付
     *
     * @param deptId      [必填]商户ID
     * @param orderNo     [必填]请求订单号，只能为英文、数字或者下划线的一种或多种组合，保证在app_id下唯一
     * @param outMemberId [必填]出账用户的member_id， 若为商户本身时，请传入0
     * @param inMemberId  [必填]入账用户的member_id， 若为商户本身时，请传入0
     * @param transAmt    [必填]交易金额，必须大于0，人民币为元，保留两位小数点，如"0.10"、"100.05"等
     * @param goodsTitle  [必填]商品名称
     * @param goodsDesc   [必填]商品描述
     * @param payMode     支付模式，delay- 延时分账模式（值为 delay 时，div_members 字段必须为空）；
     *                    值为空时并且div_mermbers不为空时，表示实时分账；
     *                    值为空时并且div_mermbers也为空时，表示不分账；
     * @param divMembers  分账对象信息列表，最多仅支持7个分账方
     * @return 成功时同步返回交易结果的 JSON
     */
    public JSONObject balancePay(@NotNull Long deptId, @NotNull String orderNo, @NotNull String outMemberId,
                                 @NotNull String inMemberId, @NotNull String transAmt, @NotNull String goodsTitle,
                                 @NotNull String goodsDesc, String payMode, Collection<DivMember> divMembers) throws BaseAdaPayException {
        // 获取商户的appId
        String appId = AdapayConfig.getAppId(deptId);
        JSONObject balanceParam = new JSONObject();
        balanceParam.put("app_id", appId);
        balanceParam.put("adapay_func_code", "settle_accounts.balancePay");
        balanceParam.put("order_no", orderNo);
        balanceParam.put("out_member_id", outMemberId);
        balanceParam.put("in_member_id", inMemberId);
        balanceParam.put("trans_amt", transAmt);
        balanceParam.put("goods_title", goodsTitle);
        balanceParam.put("goods_desc", goodsDesc);
        balanceParam.put("pay_mode", payMode);
        if (!CollectionUtils.isEmpty(divMembers)) {
            balanceParam.put("div_members", divMembers);
        }
        return (JSONObject) AdapayCommon.requestAdapay(balanceParam, deptId.toString());
    }

    /**
     * 创建结算账户对象 https://docs.adapay.tech/api/trade.html#settle-account-create
     * 创建结算账户对象是为一个已创建用户对象创建结算账户，用于对用户分账金额的结算，目前仅支持绑定银行卡结算账户。
     * 用户创建对私结算账户时，会对银行卡号、银行卡开户姓名、身份证号三要素认证，若认证失败，则创建结算账户失败。
     * 每个结算账户对象 Adapay 系统会生成一个唯一的 id，可用于查询结算账户对象，或者删除结算账户对象。
     *
     * @param deptId       [必填]商户ID
     * @param memberId     [必填]商户下的用户id，只能为英文、数字或者下划线的一种或多种组合，保证在app_id下唯一
     * @param cardId       [必填]银行卡号
     * @param cardName     [必填]银行卡对应的户名
     * @param telNo        [必填]手机号
     * @param bankAcctType [必填]银行账户类型：1-对公；2-对私
     * @param certId       证件号，银行账户类型为对私时必填
     * @param bankCode     银行编码，银行账户类型对公时必填，详见附录 银行代码 https://docs.adapay.tech/api/appendix.html#id3
     * @param provCode     银行账户开户银行所在省份编码 （省市编码），银行账户类型为对公时必填，省市编码详见area.json
     * @param areaCode     银行账户开户银行所在地区编码（省市编码），银行账户类型为对公时必填，省市编码详见area.json
     * @return 成功时同步返回一个包含 SettleAccount对象 的 JSON。
     */
    public JSONObject createSettleAccount(@NotNull Long deptId, @NotNull String memberId, @NotNull String cardId,
                                          @NotNull String cardName, @NotNull String bankAcctType, String certId, String telNo,
                                          String bankCode, String provCode, String areaCode) throws BaseAdaPayException {

        // 获取商户的appId
        String appId = AdapayConfig.getAppId(deptId);
        // 结算账户信息 参见结算账户信息(AccountInfo)对象 https://docs.adapay.tech/api/appendix.html#accountinfo
        JSONObject accountInfo = new JSONObject();

        switch (bankAcctType) {
            case "1":
                Assert.isTrue(StringUtils.isNoneBlank(bankCode, provCode, areaCode),
                        "[bankCode, provCode, areaCode] cannot be empty !");
                break;
            case "2":
                Assert.hasText(cardId, "cardId is blank !");
                accountInfo.put("cert_type", "00");
                accountInfo.put("cert_id", certId);
                break;
            default:
                throw new BaseAdaPayException("Wrong bankAcctType !");
        }

        accountInfo.put("card_id", cardId);
        accountInfo.put("card_name", cardName);
        accountInfo.put("tel_no", telNo);
        accountInfo.put("bank_code", bankCode);
        accountInfo.put("bank_acct_type", bankAcctType);
        accountInfo.put("prov_code", provCode);
        accountInfo.put("area_code", areaCode);

        JSONObject settleCountParams = new JSONObject();
        settleCountParams.put("member_id", memberId);
        settleCountParams.put("app_id", appId);
        // 目前仅支持：bank_account（银行卡）
        settleCountParams.put("channel", "bank_account");
        settleCountParams.put("account_info", accountInfo);
        log.info("创建结算账户请求,会员:{}, 内容:{}", memberId, settleCountParams);
        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());
    }

    /**
     * 提现钱包里的所有余额（仅用于商户下的用户）
     *
     * @param deptId          [必填]商户ID
     * @param memberId        [必填]商户用户对象 id，只能为英文、数字或者下划线的一种或多种组合
     * @param settleAccountId [必填]由Adapay生成的结算账户对象id
     * @return 可用余额不足时 return null;
     * 其它同 {@link AdapayService#drawCash(Long, String, String, String, String, String, String)}
     */
    public JSONObject drawCashAll(@NotNull Long deptId, @NotNull String memberId, @NotNull String settleAccountId) throws BaseAdaPayException {
        JSONObject balance = queryAccountBalance(deptId, memberId, settleAccountId, null);
        if (AdapayStatusEnum.succeeded.code.equals(balance.getString("status"))) {
            // 可用余额
            String avlBalance = balance.getString("avl_balance");
            if (StringUtils.isBlank(avlBalance) || "0.00".equals(avlBalance) || avlBalance.startsWith("-")) {
                // 可用余额不足
                return null;
            }
            return drawCash(deptId, "", "T1", avlBalance, memberId, "", null);
        } else {
            throw new BaseAdaPayException(balance.getString("error_code") + ": " + balance.getString("error_msg"));
        }
    }

    /**
     * 删除结算账户对象 https://docs.adapay.tech/api/trade.html#id50
     * 删除结算账户对象是对已创建完成的结算账户对象进行删除操作。
     * 删除结算账户成功后，支付时不再支持分账功能。您可再调用创建结算账户对象创建新的结算账户。
     * 删除结算账户对象需要 Adapay 系统生成的结算账户对象 id 进行删除。
     *
     * @param deptId          [必填]商户ID
     * @param memberId        [必填]商户下的用户id，只能为英文、数字或者下划线的一种或多种组合，保证在app_id下唯一
     * @param settleAccountId [必填]结算账户ID
     * @return { "status": "succeeded", "id":"0006440476699456", "prod_mode": "true" }
     */
    public JSONObject deleteSettleAccount(@NotNull Long deptId, @NotNull String memberId, @NotNull String settleAccountId) throws BaseAdaPayException {
        String appId = AdapayConfig.getAppId(deptId);
        JSONObject settleCountParams = new JSONObject();
        settleCountParams.put("app_id", appId);
        settleCountParams.put("settle_account_id", settleAccountId);
        settleCountParams.put("member_id", memberId);
        return (JSONObject) SettleAccount.delete(settleCountParams, deptId.toString());
    }

    /**
     * 创建实名用户 https://docs.adapay.tech/api/trade.html#member-realname
     *
     * @param deptId   [必填]商户ID
     * @param memberId [必填]商户下的用户id，只能为英文、数字或者下划线的一种或多种组合，保证在app_id下唯一
     * @param telNo    [必填]用户手机号
     * @param username [必填]用户姓名
     * @param certId   [必填]证件号
     * @return 成功时同步返回一个包含Member对象的JSON
     */
    public JSONObject createMember(@NotNull Long deptId, @NotNull String memberId, @NotNull String telNo,
                                   @NotNull String username, @NotNull String certId) throws BaseAdaPayException {
        // 获取商户的appId
        String appId = AdapayConfig.getAppId(deptId);
        JSONObject memberParams = new JSONObject();
        memberParams.put("member_id", memberId);
        memberParams.put("app_id", appId);
        memberParams.put("tel_no", telNo);
        memberParams.put("user_name", username);
        // 证件类型，仅支持：00-身份证
        memberParams.put("cert_type", "00");
        // 接口功能号
        memberParams.put("adapay_func_code", "members.realname");
        memberParams.put("cert_id", certId);
        return (JSONObject) AdapayCommon.requestAdapay(memberParams, deptId.toString());
    }

    /**
     * 对指定商户或者商户下用户的结算账户可用余额发起主动取现操作，金额从账户中提到绑定的结算银行卡中
     *
     * @param deptId   [必填]商户ID
     * @param orderNo  [必填项]请求订单号，只能为英文、数字或者下划线的一种或多种组合，保证在app_id下唯一
     * @param cashType [必填项]取现类型：T1-T+1取现；D1-D+1取现；D0-即时取现。
     * @param cashAmt  [必填项]取现金额，必须大于0，人民币为元，保留两位小数点，如"0.10"、"100.05"等
     * @param memberId [必填项]用户对象的member_id，若是商户本身取现时，请传入0
     * @param remark   备注
     * @param feeMode  手续费收取模式：O-商户手续费账户扣取手续费，I-交易金额中扣取手续费；值为空时，默认值为I；
     * @return https://docs.adapay.tech/api/wallet.html#cash-response
     */
    public JSONObject drawCash(@NotNull Long deptId, String orderNo, String cashType, String cashAmt,
                               String memberId, String remark, String feeMode) throws BaseAdaPayException {
        // 获取商户的appId
        String appId = AdapayConfig.getAppId(deptId);
        Assert.hasText(orderNo, "orderNo is blank!");
        Assert.hasText(cashType, "cashType is blank!");
        Assert.hasText(cashAmt, "cashAmt is blank!");
        Assert.hasText(memberId, "memberId is blank!");
        JSONObject cashParam = new JSONObject();
        cashParam.put("order_no", orderNo);
        cashParam.put("app_id", appId);
        cashParam.put("cash_type", cashType);
        cashParam.put("cash_amt", cashAmt);
        cashParam.put("member_id", memberId);
        cashParam.put("notify_url", adapayProperties.getNotifyUrl());
        cashParam.put("remark", remark);
        cashParam.put("fee_mode", feeMode);
        log.info("发起提现 dept[{}] param:{}", deptId, cashParam.toJSONString());
        JSONObject response = (JSONObject) Drawcash.create(cashParam, deptId.toString());
        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());
    }

    /**
     * 支付宝正扫支付
     */
    public JSONObject alipayQrPay(@NotNull Long deptId, PayParam payParam,
                                  AlipayExpend expend, DeviceInfo deviceInfo,
                                  Collection<DivMember> divMembers) throws BaseAdaPayException {
        return pay(deptId, "alipay_qr", payParam, expend, deviceInfo, divMembers);
    }

    /**
     * 微信公众号支付
     */
    public JSONObject wxPubPay(@NotNull Long deptId, PayParam payParam,
                               WxpayExpend expend, DeviceInfo deviceInfo,
                               Collection<DivMember> divMembers) throws BaseAdaPayException {
        return pay(deptId, "wx_pub", payParam, expend, deviceInfo, divMembers);
    }

    /**
     * 微信小程序支付
     */
    public JSONObject wxLitePay(@NotNull Long deptId, PayParam payParam,
                                WxpayExpend expend, DeviceInfo deviceInfo,
                                Collection<DivMember> divMembers) throws BaseAdaPayException {
        return pay(deptId, "wx_lite", payParam, expend, deviceInfo, divMembers);
    }

    /**
     * 聚合支付
     *
     * @param deptId     [必填]商户ID
     * @param payChannel [必填项]支付渠道，详见 https://docs.adapay.tech/api/appendix.html#id2
     * @param payParam   [必填项]支付参数
     * @param expend     支付渠道额外参数 https://docs.adapay.tech/api/appendix.html#expend
     * @param deviceInfo 前端设备信息 https://docs.adapay.tech/api/appendix.html#deviceinfo
     * @param divMembers 分账对象信息列表  https://docs.adapay.tech/api/appendix.html#divmembers
     * @return 同步返回一个 支付对象，详见 https://docs.adapay.tech/api/trade.html#id2
     * 成功示例: {
     * "order_no": "TEST18888888888_001",
     * "created_time": "1670932358",
     * "party_order_id": "02212212137155817188888",
     * "pay_amt": "10.00",
     * "expend": {
     * "qrcode_url": "https://qr.alipay.com/bax022139la4gqagxbti8888"
     * },
     * "prod_mode": "true",
     * "pay_channel": "alipay_qr",
     * "query_url": "https://api.adapay.tech/v1/expire/payments/1/cff1102a8a0d5d4be1d444acb7726fdd",
     * "id": "002112022121319523810448706986096888888",
     * "app_id": "app_01af32e7-6173-414f-88e5-79cae64d5e24",
     * "object": "payment",
     * "status": "succeeded"
     * }
     */
    public JSONObject pay(@NotNull Long deptId, @NotNull String payChannel, @NotNull PayParam payParam,
                          Expend expend, DeviceInfo deviceInfo,
                          Collection<DivMember> divMembers) throws BaseAdaPayException {
        // 获取商户的appId
        String appId = AdapayConfig.getAppId(deptId);
        JSONObject paymentParams = payParam.toJSONObject();
        paymentParams.put("app_id", appId);
        paymentParams.put("notify_url", adapayProperties.getNotifyUrl());
        paymentParams.put("pay_channel", payChannel);
        paymentParams.put("div_members", divMembers);
        paymentParams.put("device_info", deviceInfo);
        paymentParams.put("expend", expend);
        log.debug("paymentParams: {}", paymentParams.toJSONString());
        JSONObject response = (JSONObject) Payment.create(paymentParams, deptId.toString());
        response.put("deptId", deptId);
        payCallbackService.onResponse(response);
        return response;
    }

    /**
     * 发起退款
     * 当您的业务需要发起退款时，可通过 Adapay 系统提供的创建 Refund对象 方法创建一个退款对象，资金会原路退回用户的支付宝或微信中。
     * 支持一次全额或多次部分退款，退款次数最多不超过10次。多次部分退款时，当前退款金额 + 已退款金额不能大于原支付金额。
     * 对于每次撤销交易，Adapay 都会通过 异步消息通知 告知结果。
     * 退款对象同步返回成功，表示Adapay受理成功，退款结果以异步通知为准。支持退款最长时间为178天，
     * 若返回码是“order_id_not_exists 订单记录不存在”，既超过退款期限，无法退款成功。
     *
     * @param deptId        [必填]商户ID
     * @param paymentId     [必填项]支付确认对象的id
     * @param refundOrderNo [必填项]订单号
     * @param refundAmt     [必填项]退款金额，若退款金额小于原交易金额，则认为是部分退款，必须大于0，保留两位小数点，如0.10、100.05等
     * @return 同步返回一个 退款对象 https://docs.adapay.tech/api/trade.html#create-refund-params
     */
    public JSONObject refund(@NotNull Long deptId, @NotNull String paymentId,
                             @NotNull String refundOrderNo, @NotNull String refundAmt) throws BaseAdaPayException {
        Assert.hasText(paymentId, "paymentId is blank!");
        Assert.hasText(refundOrderNo, "refundOrderNo is blank!");
        Assert.hasText(refundAmt, "refundAmt is blank!");
        JSONObject refundParams = new JSONObject();
        refundParams.put("refund_amt", refundAmt);
        refundParams.put("refund_order_no", refundOrderNo);
        JSONObject response = (JSONObject) Refund.create(paymentId, refundParams, deptId.toString());
        executor.execute(() -> refundCallbackService.onResponse(response));
        return response;
    }

    /**
     * 支付关单
     * 针对已经创建的 支付对象，您可以调用关单接口进行交易的关闭。调用此接口后，该用户订单将不再能支付成功。 对于关单功能的使用有如下规则：
     * 1.存在关单记录，不能再次关单
     * 2.交易时间 1分钟 内无法关单成功
     * 3.正扫交易时间超过 2小时 无法关单成功
     * 4.支付宝正扫接口，如果用户没有扫码，订单不能关闭成功（二维码给用户展示，如果用户没有用手机去扫码，那这笔就不能关单； 如果用户扫过了的话（无需支付成功）就可以关单了）—-微信正扫无此条限制
     * 5.网银和快捷类交易都不支持关单操作
     *
     * @param deptId    [必填]商户ID
     * @param paymentId [必填项]由 Adapay 生成的支付对象 id
     * @param reason    关单描述
     * @param expend    扩展域
     * @return 关单的结果将通过一个 JSON 同步返回 https://docs.adapay.tech/api/trade.html#close-payment-response
     */
    public JSONObject close(@NotNull Long deptId, String paymentId, String reason, String expend) throws BaseAdaPayException {
        Assert.hasText(paymentId, "paymentId is blank!");
        JSONObject paymentParams = new JSONObject();
        paymentParams.put("payment_id", paymentId);
        paymentParams.put("reason", reason);
        paymentParams.put("expend", expend);
        return (JSONObject) Payment.close(paymentParams, deptId.toString());
    }

    /**
     * 查询已创建的单个用户对象
     * https://docs.adapay.tech/api/trade.html#id41
     *
     * @param deptId   [必填]商户ID
     * @param memberId [必填]商户下的用户id，只能为英文、数字或者下划线的一种或多种组合，保证在app_id下唯一
     * @return 成功时同步返回一个包含 Member对象的JSON。
     */
    public JSONObject queryMember(@NotNull Long deptId, @NotNull String memberId) throws BaseAdaPayException {
        JSONObject memberParams = new JSONObject();
        memberParams.put("member_id", memberId);
        memberParams.put("app_id", AdapayConfig.getAppId(deptId));
        return (JSONObject) Member.query(memberParams, deptId.toString());
    }

    /**
     * 支付撤销
     * 支付撤销对象适用于[延时分账]的场景。只有已支付完成且为延时分账的Payment对象，在没有创建支付确认对象成功之前，可以调用创建支付撤销对象。
     * 用来撤销支付，资金会原路退回用户的支付宝或微信中。 支持一次全额或多次部分撤销，撤销次数最多不超过10次。
     * 多次部分撤销时，当前撤销金额 + 已撤销金额 + 已确认金额不能大于原支付金额。
     * 对于每次撤销交易，Adapay 都会通过 异步消息通知 告知结果。
     * https://docs.adapay.tech/api/trade.html#payment-reverse-object
     *
     * @param deptId     [必填]商户ID
     * @param paymentId  [必填]Adapay生成的支付对象id
     * @param reverseAmt [必填]撤销金额，必须大于0，保留两位小数点，如0.10、100.05等。
     *                   撤销金额必须小于等于支付金额 - 已确认金额 - 已撤销（撤销成功+撤销中）金额。
     * @param type       退款类型 回调时根据它找到对应的财务单
     * @return 创建支付撤销对象同步返回成功，表示 Adapay 受理成功，撤销结果以异步通知为准
     */
    public JSONObject payReverse(@NotNull Long deptId, @NotNull String paymentId, @NotNull String reverseAmt, @NotNull RefundType type) throws BaseAdaPayException {
        JSONObject reverseParams = new JSONObject();
        reverseParams.put("app_id", AdapayConfig.getAppId(deptId));
        reverseParams.put("payment_id", paymentId);
        reverseParams.put("reverse_amt", reverseAmt);
        reverseParams.put("notify_url", adapayProperties.getNotifyUrl());
        reverseParams.put("order_no", type.code + System.currentTimeMillis());
        JSONObject response = (JSONObject) PaymentReverse.create(reverseParams, deptId.toString());
        response.put("deptId", deptId);
        executor.execute(() -> payReverseCallbackService.onResponse(response));
        return response;
    }

    /**
     * 通过 Payment对象 的 id 查询一个已创建的 Payment对象。
     * 仅支持查询支付交易发起之后3天内的交易状态，3天之后的交易状态请使用 对账单下载 对账，或登陆 控制台 查询。
     *
     * @param deptId    [必填]商户ID
     * @param paymentId [必填]Adapay生成的支付对象id
     * @return https://docs.adapay.tech/api/trade.html#id17
     */
    public JSONObject queryPayment(@NotNull Long deptId, @NotNull String paymentId) throws BaseAdaPayException {
        return (JSONObject) Payment.query(paymentId, deptId.toString());
    }
}