import * as crypto from 'crypto'
const x509_1 = require('@fidm/x509')

import {
    Ipay,
    Ih5,
    Inative,
    Ijsapi,
    Iquery1,
    Iquery2,
    Itradebill,
    Ifundflowbill,
    Iapp,
    Ioptions,
    Irefunds1,
    Irefunds2,
    ICertificates
} from './lib/interface'
import { IcombineH5, IcombineNative, IcombineApp, IcombineJsapi, IcloseSubOrders } from './lib/combine_interface'
import { BatchesTransfer } from './lib/interface-v2'
import { Base } from './lib/base'

export class Pay extends Base {
    private appid: string //  直连商户申请的公众号或移动应用appid。
    private mchid: string // 商户号
    private serial_no = '' // 证书序列号
    private publicKey?: Buffer // 公钥
    private privateKey?: Buffer // 密钥
    private authType = 'WECHATPAY2-SHA256-RSA2048' // 认证类型，目前为WECHATPAY2-SHA256-RSA2048

    private key?: string // APIv3密钥
    private static certificates: { [key in string]: string } = {} // 微信平台证书 key 是 serialNo, value 是 publicKey
    /**
     * 构造器
     * @param appid 直连商户申请的公众号或移动应用appid。
     * @param mchid 商户号
     * @param publicKey 公钥
     * @param privateKey 密钥
     * @param optipns 可选参数 object 包括下面参数
     *
     * @param serial_no  证书序列号
     * @param authType 可选参数 认证类型，目前为WECHATPAY2-SHA256-RSA2048
     * @param userAgent 可选参数 User-Agent
     * @param key 可选参数 APIv3密钥
     */
    public constructor(appid: string, mchid: string, publicKey: Buffer, privateKey: Buffer, optipns?: Ioptions)
    /**
     * 构造器
     * @param obj object类型 包括下面参数
     *
     * @param appid 直连商户申请的公众号或移动应用appid。
     * @param mchid 商户号
     * @param serial_no  可选参数 证书序列号
     * @param publicKey 公钥
     * @param privateKey 密钥
     * @param authType 可选参数 认证类型，目前为WECHATPAY2-SHA256-RSA2048
     * @param userAgent 可选参数 User-Agent
     * @param key 可选参数 APIv3密钥
     */
    public constructor(obj: Ipay)
    public constructor(
        arg1: Ipay | string,
        mchid?: string,
        publicKey?: Buffer,
        privateKey?: Buffer,
        optipns?: Ioptions
    ) {
        super()

        if (arg1 instanceof Object) {
            this.appid = arg1.appid
            this.mchid = arg1.mchid
            if (arg1.serial_no) this.serial_no = arg1.serial_no
            this.publicKey = arg1.publicKey
            if (!this.publicKey) throw new Error('缺少公钥')
            this.privateKey = arg1.privateKey
            if (!arg1.serial_no) this.serial_no = this.getSN(this.publicKey)

            this.authType = arg1.authType || 'WECHATPAY2-SHA256-RSA2048'
            this.userAgent = arg1.userAgent || '127.0.0.1'
            this.key = arg1.key
        } else {
            const _optipns = optipns || {}
            this.appid = arg1
            this.mchid = mchid || ''
            this.publicKey = publicKey
            this.privateKey = privateKey

            this.authType = _optipns.authType || 'WECHATPAY2-SHA256-RSA2048'
            this.userAgent = _optipns.userAgent || '127.0.0.1'
            this.key = _optipns.key
            this.serial_no = _optipns.serial_no || ''
            if (!this.publicKey) throw new Error('缺少公钥')
            if (!this.serial_no) this.serial_no = this.getSN(this.publicKey)
        }
    }
    /**
     * 获取微信平台key
     * @param apiSecret APIv3密钥
     * @returns
     */
    public async get_certificates(apiSecret: string): Promise<ICertificates[]> {
        const url = 'https://api.mch.weixin.qq.com/v3/certificates'
        const authorization = this.init('GET', url)
        const result = await this.getRequest(url, authorization)

        if (result.status === 200) {
            const data = result.data as ICertificates[]

            for (const item of data) {
                const decryptCertificate = this.decipher_gcm<string>(
                    item.encrypt_certificate.ciphertext,
                    item.encrypt_certificate.associated_data,
                    item.encrypt_certificate.nonce,
                    apiSecret
                )
                item.publicKey = x509_1.Certificate.fromPEM(Buffer.from(decryptCertificate)).publicKey.toPEM()
            }

            return data
        } else {
            throw new Error('拉取平台证书失败')
        }
    }
    /**
     * 拉取平台证书到 Pay.certificates 中
     * @param apiSecret APIv3密钥
     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/wechatpay5_1.shtml
     */
    private async fetchCertificates(apiSecret?: string) {
        const url = 'https://api.mch.weixin.qq.com/v3/certificates'
        const authorization = this.init('GET', url)
        const result = await this.getRequest(url, authorization)

        if (result.status === 200) {
            const data = result.data as {
                effective_time: string
                expire_time: string
                serial_no: string
                encrypt_certificate: {
                    algorithm: string
                    associated_data: string
                    ciphertext: string
                    nonce: string
                }
            }[]

            const newCertificates = {} as { [key in string]: string }

            data.forEach((item) => {
                const decryptCertificate = this.decipher_gcm<string>(
                    item.encrypt_certificate.ciphertext,
                    item.encrypt_certificate.associated_data,
                    item.encrypt_certificate.nonce,
                    apiSecret
                )

                newCertificates[item.serial_no] = x509_1.Certificate.fromPEM(
                    Buffer.from(decryptCertificate)
                ).publicKey.toPEM()
            })

            Pay.certificates = {
                ...Pay.certificates,
                ...newCertificates
            }
        } else {
            throw new Error('拉取平台证书失败')
        }
    }
    /**
     * 验证签名，提醒：node 取头部信息时需要用小写，例如：req.headers['wechatpay-timestamp']
     * @param params.timestamp HTTP头Wechatpay-Timestamp 中的应答时间戳
     * @param params.nonce HTTP头Wechatpay-Nonce 中的应答随机串
     * @param params.body 应答主体（response Body），需要按照接口返回的顺序进行验签，错误的顺序将导致验签失败。
     * @param params.serial HTTP头Wechatpay-Serial 证书序列号
     * @param params.signature HTTP头Wechatpay-Signature 签名
     * @param params.apiSecret APIv3密钥，如果在 构造器 中有初始化该值(this.key)，则可以不传入。当然传入也可以
     */
    public async verifySign(params: {
        timestamp: string | number
        nonce: string
        body: Record<string, any> | string
        serial: string
        signature: string
        apiSecret?: string
    }) {
        const { timestamp, nonce, body, serial, signature, apiSecret } = params

        let publicKey = Pay.certificates[serial]

        if (!publicKey) {
            await this.fetchCertificates(apiSecret)
        }

        publicKey = Pay.certificates[serial]

        if (!publicKey) {
            throw new Error('平台证书序列号不相符，未找到平台序列号')
        }

        const bodyStr = typeof body === 'string' ? body : JSON.stringify(body)
        const data = `${timestamp}\n${nonce}\n${bodyStr}\n`
        const verify = crypto.createVerify('RSA-SHA256')
        verify.update(data)

        return verify.verify(publicKey, signature, 'base64')
    }
    /**
     * 敏感信息加密
     * @param str 敏感信息字段（如用户的住址、银行卡号、手机号码等）
     * @returns
     */
    public publicEncrypt(str: string, wxPublicKey: Buffer, padding = crypto.constants.RSA_PKCS1_OAEP_PADDING) {
        if (![crypto.constants.RSA_PKCS1_PADDING, crypto.constants.RSA_PKCS1_OAEP_PADDING].includes(padding)) {
            throw new Error(
                `Doesn't supported the padding mode(${padding}), here's only support RSA_PKCS1_OAEP_PADDING or RSA_PKCS1_PADDING.`
            )
        }
        const encrypted = crypto
            .publicEncrypt({ key: wxPublicKey, padding, oaepHash: 'sha1' }, Buffer.from(str, 'utf8'))
            .toString('base64')
        return encrypted
    }
    /**
     * 敏感信息解密
     * @param str 敏感信息字段（如用户的住址、银行卡号、手机号码等）
     * @returns
     */
    public privateDecrypt(str: string, padding = crypto.constants.RSA_PKCS1_OAEP_PADDING) {
        if (![crypto.constants.RSA_PKCS1_PADDING, crypto.constants.RSA_PKCS1_OAEP_PADDING].includes(padding)) {
            throw new Error(
                `Doesn't supported the padding mode(${padding}), here's only support RSA_PKCS1_OAEP_PADDING or RSA_PKCS1_PADDING.`
            )
        }
        const decrypted = crypto.privateDecrypt(
            { key: this.privateKey as Buffer, padding, oaepHash: 'sha1' },
            Buffer.from(str, 'base64')
        )
        return decrypted.toString('utf8')
    }
    /**
     * 构建请求签名参数
     * @param method Http 请求方式
     * @param url 请求接口 例如/v3/certificates
     * @param timestamp 获取发起请求时的系统当前时间戳
     * @param nonceStr 随机字符串
     * @param body 请求报文主体
     */
    public getSignature(
        method: string,
        nonce_str: string,
        timestamp: string,
        url: string,
        body?: string | Record<string, any>
    ): string {
        let str = method + '\n' + url + '\n' + timestamp + '\n' + nonce_str + '\n'
        if (body && body instanceof Object) body = JSON.stringify(body)
        if (body) str = str + body + '\n'
        if (method === 'GET') str = str + '\n'
        return this.sha256WithRsa(str)
    }
    // jsapi 和 app 支付参数签名 加密自动顺序如下 不能错乱
    // 应用id
    // 时间戳
    // 随机字符串
    // 预支付交易会话ID
    private sign(str: string) {
        return this.sha256WithRsa(str)
    }
    // 获取序列号
    public getSN(fileData?: string | Buffer): string {
        if (!fileData && !this.publicKey) throw new Error('缺少公钥')
        if (!fileData) fileData = this.publicKey
        if (typeof fileData == 'string') {
            fileData = Buffer.from(fileData)
        }

        const certificate = x509_1.Certificate.fromPEM(fileData)
        return certificate.serialNumber
    }
    /**
     * SHA256withRSA
     * @param data 待加密字符
     * @param privatekey 私钥key  key.pem   fs.readFileSync(keyPath)
     */
    public sha256WithRsa(data: string): string {
        if (!this.privateKey) throw new Error('缺少秘钥文件')
        return crypto.createSign('RSA-SHA256').update(data).sign(this.privateKey, 'base64')
    }
    /**
     * 获取授权认证信息
     * @param nonceStr  请求随机串
     * @param timestamp 时间戳
     * @param signature 签名值
     */
    public getAuthorization(nonce_str: string, timestamp: string, signature: string): string {
        const _authorization =
            'mchid="' +
            this.mchid +
            '",' +
            'nonce_str="' +
            nonce_str +
            '",' +
            'timestamp="' +
            timestamp +
            '",' +
            'serial_no="' +
            this.serial_no +
            '",' +
            'signature="' +
            signature +
            '"'
        return this.authType.concat(' ').concat(_authorization)
    }
    /**
     * 回调解密
     * @param ciphertext  Base64编码后的开启/停用结果数据密文
     * @param associated_data 附加数据
     * @param nonce 加密使用的随机串
     * @param key  APIv3密钥
     */
    public decipher_gcm<T extends any>(ciphertext: string, associated_data: string, nonce: string, key?: string): T {
        if (key) this.key = key
        if (!this.key) throw new Error('缺少key')

        const _ciphertext = Buffer.from(ciphertext, 'base64')

        // 解密 ciphertext字符  AEAD_AES_256_GCM算法
        const authTag: any = _ciphertext.slice(_ciphertext.length - 16)
        const data = _ciphertext.slice(0, _ciphertext.length - 16)
        const decipher = crypto.createDecipheriv('aes-256-gcm', this.key, nonce)
        decipher.setAuthTag(authTag)
        decipher.setAAD(Buffer.from(associated_data))
        const decoded = decipher.update(data, undefined, 'utf8')
        decipher.final()

        try {
            return JSON.parse(decoded)
        } catch (e) {
            return decoded as T
        }
    }
    /**
     * 参数初始化
     */
    private init(method: string, url: string, params?: Record<string, any>) {
        const nonce_str = Math.random().toString(36).substr(2, 15),
            timestamp = parseInt(+new Date() / 1000 + '').toString()

        const signature = this.getSignature(
            method,
            nonce_str,
            timestamp,
            url.replace('https://api.mch.weixin.qq.com', ''),
            params
        )
        const authorization = this.getAuthorization(nonce_str, timestamp, signature)
        return authorization
    }
    // ---------------支付相关接口--------------//
    /**
     * h5支付
     * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_1.shtml
     */
    public async transactions_h5(params: Ih5): Promise<Record<string, any>> {
        // 请求参数
        const _params = {
            appid: this.appid,
            mchid: this.mchid,
            ...params
        }
        const url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/h5'

        const authorization = this.init('POST', url, _params)

        return await this.postRequest(url, _params, authorization)
    }
    /**
     * 合单h5支付
     * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_2.shtml
     */
    public async combine_transactions_h5(params: IcombineH5): Promise<Record<string, any>> {
        // 请求参数
        const _params = {
            combine_appid: this.appid,
            combine_mchid: this.mchid,
            ...params
        }
        const url = 'https://api.mch.weixin.qq.com/v3/combine-transactions/h5'

        const authorization = this.init('POST', url, _params)

        return await this.postRequest(url, _params, authorization)
    }
    /**
     * native支付
     * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_4_1.shtml
     */
    public async transactions_native(params: Inative): Promise<Record<string, any>> {
        // 请求参数
        const _params = {
            appid: this.appid,
            mchid: this.mchid,
            ...params
        }
        const url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/native'

        const authorization = this.init('POST', url, _params)

        return await this.postRequest(url, _params, authorization)
    }
    /**
     * 合单native支付
     * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_5.shtml
     */
    public async combine_transactions_native(params: IcombineNative): Promise<Record<string, any>> {
        // 请求参数
        const _params = {
            combine_appid: this.appid,
            combine_mchid: this.mchid,
            ...params
        }
        const url = 'https://api.mch.weixin.qq.com/v3/combine-transactions/native'

        const authorization = this.init('POST', url, _params)

        return await this.postRequest(url, _params, authorization)
    }
    /**
     * app支付
     * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml
     */
    public async transactions_app(params: Iapp): Promise<Record<string, any>> {
        // 请求参数
        const _params = {
            appid: this.appid,
            mchid: this.mchid,
            ...params
        }
        const url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/app'

        const authorization = this.init('POST', url, _params)

        const result: any = await this.postRequest(url, _params, authorization)
        if (result.status === 200 && result.prepay_id) {
            const data = {
                status: result.status,
                appid: this.appid,
                partnerid: this.mchid,
                package: 'Sign=WXPay',
                timestamp: parseInt(+new Date() / 1000 + '').toString(),
                noncestr: Math.random().toString(36).substr(2, 15),
                prepayid: result.prepay_id,
                sign: ''
            }
            const str = [data.appid, data.timestamp, data.noncestr, data.prepayid, ''].join('\n')
            data.sign = this.sign(str)
            return data
        }
        return result
    }
    /**
     * 合单app支付
     * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_1.shtml
     */
    public async combine_transactions_app(params: IcombineApp): Promise<Record<string, any>> {
        // 请求参数
        const _params = {
            combine_appid: this.appid,
            combine_mchid: this.mchid,
            ...params
        }
        const url = 'https://api.mch.weixin.qq.com/v3/combine-transactions/app'

        const authorization = this.init('POST', url, _params)

        const result: any = await this.postRequest(url, _params, authorization)
        if (result.status === 200 && result.prepay_id) {
            const data = {
                status: result.status,
                appid: this.appid,
                partnerid: this.mchid,
                package: 'Sign=WXPay',
                timestamp: parseInt(+new Date() / 1000 + '').toString(),
                noncestr: Math.random().toString(36).substr(2, 15),
                prepayid: result.prepay_id,
                sign: ''
            }
            const str = [data.appid, data.timestamp, data.noncestr, data.prepayid, ''].join('\n')
            data.sign = this.sign(str)
            return data
        }
        return result
    }
    /**
     * JSAPI支付 或者 小程序支付
     * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml
     */
    public async transactions_jsapi(params: Ijsapi): Promise<Record<string, any>> {
        // 请求参数
        const _params = {
            appid: this.appid,
            mchid: this.mchid,
            ...params
        }
        const url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi'

        const authorization = this.init('POST', url, _params)

        const result: any = await this.postRequest(url, _params, authorization)
        if (result.status === 200 && result.prepay_id) {
            const data = {
                status: result.status,
                appId: this.appid,
                timeStamp: parseInt(+new Date() / 1000 + '').toString(),
                nonceStr: Math.random().toString(36).substr(2, 15),
                package: `prepay_id=${result.prepay_id}`,
                signType: 'RSA',
                paySign: ''
            }
            const str = [data.appId, data.timeStamp, data.nonceStr, data.package, ''].join('\n')
            data.paySign = this.sign(str)
            return data
        }
        return result
    }
    /**
     * 合单JSAPI支付 或者 小程序支付
     * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_3.shtml
     */
    public async combine_transactions_jsapi(params: IcombineJsapi): Promise<Record<string, any>> {
        // 请求参数
        const _params = {
            combine_appid: this.appid,
            combine_mchid: this.mchid,
            ...params
        }
        const url = 'https://api.mch.weixin.qq.com/v3/combine-transactions/jsapi'

        const authorization = this.init('POST', url, _params)

        const result: any = await this.postRequest(url, _params, authorization)
        if (result.status === 200 && result.prepay_id) {
            const data = {
                status: result.status,
                appId: this.appid,
                timeStamp: parseInt(+new Date() / 1000 + '').toString(),
                nonceStr: Math.random().toString(36).substr(2, 15),
                package: `prepay_id=${result.prepay_id}`,
                signType: 'RSA',
                paySign: ''
            }
            const str = [data.appId, data.timeStamp, data.nonceStr, data.package, ''].join('\n')
            data.paySign = this.sign(str)
            return data
        }
        return result
    }
    /**
     * 查询订单
     * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_2.shtml
     */
    public async query(params: Iquery1 | Iquery2): Promise<Record<string, any>> {
        let url = ''
        if (params.transaction_id) {
            url = `https://api.mch.weixin.qq.com/v3/pay/transactions/id/${params.transaction_id}?mchid=${this.mchid}`
        } else if (params.out_trade_no) {
            url = `https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${params.out_trade_no}?mchid=${this.mchid}`
        } else {
            throw new Error('缺少transaction_id或者out_trade_no')
        }

        const authorization = this.init('GET', url)
        return await this.getRequest(url, authorization)
    }
    /**
     * 合单查询订单
     * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_11.shtml
     */
    public async combine_query(combine_out_trade_no: string): Promise<Record<string, any>> {
        if (!combine_out_trade_no) throw new Error('缺少combine_out_trade_no')
        const url = `https://api.mch.weixin.qq.com/v3/combine-transactions/out-trade-no/${combine_out_trade_no}`

        const authorization = this.init('GET', url)
        return await this.getRequest(url, authorization)
    }
    /**
     * 关闭订单
     * @param out_trade_no 请求参数 商户订单号 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_3_3.shtml
     */
    public async close(out_trade_no: string): Promise<Record<string, any>> {
        if (!out_trade_no) throw new Error('缺少out_trade_no')

        // 请求参数
        const _params = {
            mchid: this.mchid
        }
        const url = `https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${out_trade_no}/close`
        const authorization = this.init('POST', url, _params)

        return await this.postRequest(url, _params, authorization)
    }
    /**
     * 合单关闭订单
     * @param combine_out_trade_no 请求参数 总订单号 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter5_1_12.shtml
     * @param sub_orders array 子单信息
     */
    public async combine_close(
        combine_out_trade_no: string,
        sub_orders: IcloseSubOrders[]
    ): Promise<Record<string, any>> {
        if (!combine_out_trade_no) throw new Error('缺少out_trade_no')

        // 请求参数
        const _params = {
            combine_appid: this.appid,
            sub_orders
        }
        const url = `https://api.mch.weixin.qq.com/v3/combine-transactions/out-trade-no/${combine_out_trade_no}/close`
        const authorization = this.init('POST', url, _params)

        return await this.postRequest(url, _params, authorization)
    }
    /**
     * 申请交易账单
     * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_6.shtml
     */
    public async tradebill(params: Itradebill): Promise<Record<string, any>> {
        let url = 'https://api.mch.weixin.qq.com/v3/bill/tradebill'
        const _params: any = {
            ...params
        }
        const querystring = Object.keys(_params)
            .filter((key) => {
                return !!_params[key]
            })
            .sort()
            .map((key) => {
                return key + '=' + _params[key]
            })
            .join('&')
        url = url + `?${querystring}`
        const authorization = this.init('GET', url)
        return await this.getRequest(url, authorization)
    }
    /**
     * 申请资金账单
     * @param params 请求参数 object 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_7.shtml
     */
    public async fundflowbill(params: Ifundflowbill): Promise<Record<string, any>> {
        let url = 'https://api.mch.weixin.qq.com/v3/bill/fundflowbill'
        const _params: any = {
            ...params
        }
        const querystring = Object.keys(_params)
            .filter((key) => {
                return !!_params[key]
            })
            .sort()
            .map((key) => {
                return key + '=' + _params[key]
            })
            .join('&')
        url = url + `?${querystring}`
        const authorization = this.init('GET', url)
        return await this.getRequest(url, authorization)
    }
    /**
     * 下载账单
     * @param download_url 请求参数 路径 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_8.shtml
     */
    public async downloadbill(download_url: string) {
        const authorization = this.init('GET', download_url)
        return await this.getRequest(download_url, authorization)
    }
    /**
     * 申请退款
     * @param params 请求参数 路径 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_9.shtml
     */
    public async refunds(params: Irefunds1 | Irefunds2): Promise<Record<string, any>> {
        const url = 'https://api.mch.weixin.qq.com/v3/refund/domestic/refunds'
        // 请求参数
        const _params = {
            ...params
        }

        const authorization = this.init('POST', url, _params)

        return await this.postRequest(url, _params, authorization)
    }
    /**
     * 查询单笔退款
     * @documentation 请求参数 路径 参数介绍 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_10.shtml
     */
    public async find_refunds(out_refund_no: string): Promise<Record<string, any>> {
        if (!out_refund_no) throw new Error('缺少out_refund_no')
        const url = `https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/${out_refund_no}`

        const authorization = this.init('GET', url)
        return await this.getRequest(url, authorization)
    }
    /**
     * 发起商家转账零钱
     * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_1.shtml
     */
    public async batches_transfer(params: BatchesTransfer.Input): Promise<BatchesTransfer.IOutput> {
        const url = 'https://api.mch.weixin.qq.com/v3/transfer/batches'
        // 请求参数
        const _params = {
            appid: this.appid,
            ...params
        }

        const serial_no = _params?.wx_serial_no
        delete _params.wx_serial_no
        const authorization = this.init('POST', url, _params)

        return await this.postRequestV2(url, _params, authorization, {
            'Wechatpay-Serial': serial_no || this.serial_no
        })
    }
    /**
     * 微信批次单号查询批次单API
     * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_2.shtml
     */
    public async query_batches_transfer_list_wx(
        params: BatchesTransfer.QueryBatchesTransferByWx.Input
    ): Promise<BatchesTransfer.QueryBatchesTransferByWx.IOutput> {
        const baseUrl = `https://api.mch.weixin.qq.com/v3/transfer/batches/batch-id/${params.batch_id}`
        const url = baseUrl + this.objectToQueryString(params, ['batch_id'])
        const authorization = this.init('GET', url)
        return await this.getRequestV2(url, authorization)
    }
    /**
     * 微信明细单号查询明细单API
     * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_3.shtml
     */
    public async query_batches_transfer_detail_wx(
        params: BatchesTransfer.QueryBatchesTransferDetailByWx.Input
    ): Promise<BatchesTransfer.QueryBatchesTransferDetailByWx.IOutput> {
        const baseUrl = `https://api.mch.weixin.qq.com/v3/transfer/batches/batch-id/${params.batch_id}/details/detail-id/${params.detail_id}`
        const url = baseUrl + this.objectToQueryString(params, ['batch_id', 'detail_id'])
        const authorization = this.init('GET', url)
        return await this.getRequestV2(url, authorization)
    }
    /**
     * 商家批次单号查询批次单API
     * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_5.shtml
     */
    public async query_batches_transfer_list(
        params: BatchesTransfer.QueryBatchesTransferList.Input
    ): Promise<BatchesTransfer.QueryBatchesTransferList.IOutput> {
        const baseUrl = `https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no/${params.out_batch_no}`
        const url = baseUrl + this.objectToQueryString(params, ['out_batch_no'])
        const authorization = this.init('GET', url)
        return await this.getRequestV2(url, authorization)
    }
    /**
     * 商家明细单号查询明细单API
     * @documentation 请看文档https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_6.shtml
     */
    public async query_batches_transfer_detail(
        params: BatchesTransfer.QueryBatchesTransferDetail.Input
    ): Promise<BatchesTransfer.QueryBatchesTransferDetail.IOutput> {
        const baseUrl = `https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no/${params.out_batch_no}/details/out-detail-no/${params.out_detail_no}`
        const url = baseUrl + this.objectToQueryString(params, ['out_batch_no', 'out_detail_no'])
        const authorization = this.init('GET', url)
        return await this.getRequestV2(url, authorization)
    }
}

exports = Pay
