package org.springblade.modules.api.controller;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethod;
import com.ijpay.core.kit.AesUtil;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.core.utils.DateTimeZoneUtil;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxApiType;
import com.ijpay.wxpay.enums.WxDomain;
import com.ijpay.wxpay.model.v3.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import jodd.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.common.constant.CommonConstant;
import org.springblade.core.log.logger.BladeLogger;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.modules.finance.pay.entity.WxPayV3Bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.*;

/**
 * <p>IJPay 让支付触手可及，封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p>
 *
 * <p>不依赖任何第三方 mvc 框架，仅仅作为工具使用简单快速完成支付模块的开发，可轻松嵌入到任何系统里。 </p>
 *
 * <p>IJPay 交流群: 723992875</p>
 *
 * <p>Node.js 版: https://gitee.com/javen205/TNWX</p>
 *
 * <p>微信支付 v3 接口示例</p>
 *
 * @author Javen
 */
@RestController
@RequestMapping(CommonConstant.API_URL + "/wxPay/v3")
@Api(value = "微信支付接口v3", tags = "98.微信支付v3")
@EnableAsync
public class WxPayV3Controller {
	private final static int OK = 200;
	private final Logger log = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private BladeLogger logger;

	@Resource
	WxPayV3Bean wxPayV3Bean;

	String serialNo;
	String platSerialNo;

	@GetMapping("")

	@ApiOperationSupport(order = 1)
	@ApiOperation(value = "证书pkcs12格式", notes = "证书pkcs12")
	public String index() {
		log.info(wxPayV3Bean.toString());
		try {
			String absolutePath = PayKit.getAbsolutePath("classpath:/cert/apiclient_cert.p12");
			log.info("absolutePath:{}", absolutePath);
		} catch (Exception e) {
			log.error("文件不存在", e);
		}
		return ("欢迎使用 IJPay 中的微信支付 Api-v3 -By Javen  <br/><br>  交流群：723992875");
	}

	@GetMapping("/getSerialNumber")
	@ApiOperation(value = "获取证书序列号", notes = "获取证书序列号")
	@ApiOperationSupport(order = 2)
	public String serialNumber() {
		return getSerialNumber();
	}

	@GetMapping("/getPlatSerialNumber")
	@ApiOperationSupport(order = 3)
	@ApiOperation(value = "获取平台证书序列号", notes = "获取平台证书序列号")
	public String platSerialNumber() {
		return getPlatSerialNumber();
	}

	private String getSerialNumber() {
		if (StrUtil.isEmpty(serialNo)) {
			// 获取证书序列号
			X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(wxPayV3Bean.getCertPath()));
			serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
            System.out.println("输出证书信息:" + certificate.toString());
            // 输出关键信息，截取部分并进行标记
            System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
            System.out.println("版本号:" + certificate.getVersion());
            System.out.println("签发者：" + certificate.getIssuerDN());
            System.out.println("有效起始日期：" + certificate.getNotBefore());
            System.out.println("有效终止日期：" + certificate.getNotAfter());
            System.out.println("主体名：" + certificate.getSubjectDN());
            System.out.println("签名算法：" + certificate.getSigAlgName());
            System.out.println("签名：" + certificate.getSignature().toString());
		}
		System.out.println("serialNo:" + serialNo);
		return serialNo;
	}

	private String getPlatSerialNumber() {
		if (StrUtil.isEmpty(platSerialNo)) {
			// 获取平台证书序列号
			X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(wxPayV3Bean.getPlatformCertPath()));
			platSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
		}
		System.out.println("platSerialNo:" + platSerialNo);
		return platSerialNo;
	}

	private String savePlatformCert(String associatedData, String nonce, String cipherText, String certPath) {
		try {
			AesUtil aesUtil = new AesUtil(wxPayV3Bean.getApiKey3().getBytes(StandardCharsets.UTF_8));
			// 平台证书密文解密
			// encrypt_certificate 中的  associated_data nonce  ciphertext
			String publicKey = aesUtil.decryptToString(
				associatedData.getBytes(StandardCharsets.UTF_8),
				nonce.getBytes(StandardCharsets.UTF_8),
				cipherText
			);
			// 保存证书
			FileWriter writer = new FileWriter(certPath);
			writer.write(publicKey);
			// 获取平台证书序列号
			X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
			return certificate.getSerialNumber().toString(16).toUpperCase();
		} catch (Exception e) {
			e.printStackTrace();
			return e.getMessage();
		}
	}


	@GetMapping("/platformCert")
	@ApiOperationSupport(order = 4)
	@ApiOperation(value = "保存平台证书", notes = "保存平台证书")
	public String platformCert() {
		try {
			String associatedData = "certificate";
			String nonce = "32692616fa03";
			String cipherText = "ilANqfeUTaqkGt72Gkb52L1+a2yfqtscyxa/TxbjtTVnwERj+5kNRyt4WbjjHK3FQ2D2n+PlL6No/KEBl8neyoPQx2a9/mO6zuHo2i/DRBk3u+NnYrL0shgToq5Ljrw8gF/7xFuQaIunIay3fOJpga7WOC0xNJOR+hJaZMAFbZmGUMglqMdlM6Ov4LPyAXJHqh8KiRBMwUrf6C+ZcQQx0DPcDAceqE/3E4AntUlQuAHZE6/JLkO7ABfbHd8ezzxnmSMXDL53tsVwGgz4sWmDvIoa6I42e6KfPQ7IiS8n8U+vl4xDUbI4QXivVphC9PiZIcUPd+WkDatuvFFqUuFPHxZx+fd5TWIEh+4MM3K9NeBOz59bKDJBgaAtHR3ktVcKCnU29zRil/BchWvxVmX3l7FKLaBbL9Iq08/UGc4ZNVkWavNSfS+gYB8E1atr0RaQ4OcX/i6jRVJw/Yc6XnBur1rUChnv8lMxRgnmQcLichUT3umNeE4McvO7isM0d/gcFRcNrbqP6RyjQIYTfbUdcI8M+aAI9uqyuZRfThCZ3kwzsdexg6fHtlYbCTz2natlPat0ar/y9nxewNdoG53vjTSFQGg3xtmBXSnarV9FfmawOYNSgicFmNfUI/HGwDCge0/lPS58RpeiJ+1mTT/09wG8LX1G8z5ir7ct+fgaX3WtGiZXvRwNRZbX7RIqdV5F6WLeaXPsxiqositBPHs9LYIv8B6/YGc399tBZlcMvv19ry+9aIFenr23e8zHYgpmkNbu9gN2iZ5hHs5x7nkuHBzSHfaYw0cllYwqMbg5JLaPuG/lJewKDj4xLKM6JSf8XgmHDiwD8raXB8AbRLni8cqJq2BhTGm0YOQXSYBMQpm/PTVvfAtu6SzmdMNokUFYYrozn854js0C9vZerob8hutlQ2vK43LsUzFlrdZD5QKQKne7r6RTpLbMYQ2ZBJZhE4Wpvz47zWtxzsGwsIj4QwKJbS4dpasvUrL92KI7WC8874587Cdt0UgnCjvrg1yMMJEzkg9HH1rLuneLIZLd8Zp3K4IRjWvi+ujYr9biaDTCkK9nx1dG2ril/EZeT9U6628FwjLq8esH+JRAi/nf0WMSC5kgiSE9t8Ze6Zw8updOhUyPK5jROjCLBDd073tl6PDOHABE77WKc3XgF5aVhSTPspor4Gj9IFNKFuPcU1aObFINUxS2j0EzhmPHSESiJGXYsl9naU9T6tbADZ1yGM9ym6QfRCU4zHBm5/PClMyBgRw+ouCMQjO0LNwKC88rzSc6f1YzsejWYOwGIAStgLXRJf/8SjQKw4akZ72rjDQtbiGOeisjZJAasP2Ea/XsjkOQwk8ju/6j0ZvKW/fCAGzi4gFcjBBtFoVKKouyTjivO0QpGEZwM5/yICNbJb/3+Vr75oRn+YDBQrL2xbv8VTx7NRcf4w38vrh+AsgPV9MYzFRBH1JSbpR5A7wTvoTYj+zXTTghfAqeiGj9V7kOgmMe/E+qI8IuHpoYP7lPTBpkYcw0sZaqZVOSEJGdQ2PaSv56HvTz4rCsvwCXmiDE+GjtwotOabbAwo1Pc+Q4ScaOS/GrPv7N6mu0vkwzm+UjbLDJqbS+U86cWYCKkPNG0Yvdd75YdnHwSXiXURXxmG5blMBq4ufesniZ1ChvcBMxPzEzYc0EfOmQJ6oUNQfvLjE9yDqvMTxTKSyAmgkvTUkgSDifuCxMyWtiCyYykoP/lBiLbqQKditJJ+hOd+q+CO3Jh6SCKXL0/gtkoKFkdy/QNKF0ppM5T0BQBPJjKVQgjY5VLf9oAkDB4wbRW9GmlTZElTVUQCme+AFXq4AWzbFQvKYTRdyXWviZpqx00AUAzehQf4WUkP7hwzNn4jDkOjb0bAB3LA==";
			return savePlatformCert(associatedData, nonce, cipherText, wxPayV3Bean.getPlatformCertPath());
		} catch (Exception e) {
			e.printStackTrace();
			return e.getMessage();
		}
	}

	@GetMapping("/get")
	@ApiOperation(value = "获取平台证书列表", notes = "获取平台证书列表")
	@ApiOperationSupport(order = 5)
	public R v3Get() {
		// 获取平台证书列表
		try {
			IJPayHttpResponse response = WxPayApi.v3(
				RequestMethod.GET,
				WxDomain.CHINA.toString(),
				WxApiType.GET_CERTIFICATES.toString(),
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				null,
				wxPayV3Bean.getKeyPath(),
				""
			);

			String timestamp = response.getHeader("Wechatpay-Timestamp");
			String nonceStr = response.getHeader("Wechatpay-Nonce");
			String serialNumber = response.getHeader("Wechatpay-Serial");
			String signature = response.getHeader("Wechatpay-Signature");

			String body = response.getBody();
			int status = response.getStatus();

			log.info("serialNumber: {}", serialNumber);
			log.info("status: {}", status);
			log.info("body: {}", body);
			int isOk = 200;
			if (status == isOk) {
				JSONObject jsonObject = JSONUtil.parseObj(body);
				JSONArray dataArray = jsonObject.getJSONArray("data");
				// 默认认为只有一个平台证书
				JSONObject encryptObject = dataArray.getJSONObject(0);
				JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
				String associatedData = encryptCertificate.getStr("associated_data");
				String cipherText = encryptCertificate.getStr("ciphertext");
				String nonce = encryptCertificate.getStr("nonce");
				String serialNo = encryptObject.getStr("serial_no");
				final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, wxPayV3Bean.getPlatformCertPath());
				log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
			}
			// 根据证书序列号查询对应的证书来验证签名结果
			boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Bean.getPlatformCertPath());
			System.out.println("verifySignature:" + verifySignature);
			return R.data(JSONUtil.parseObj(body));
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail("验证失败");
		}
	}

	@PostMapping("/nativePay")
	@ApiOperationSupport(order = 6)
	@ApiOperation(value = "Native下单API", notes = "Native下单API")
	public R nativePay() {
		try {
			String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
			UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
				.setAppid(wxPayV3Bean.getAppId())
				.setMchid(wxPayV3Bean.getMchId())
				.setDescription("IJPay 让支付触手可及")
				.setOut_trade_no(PayKit.generateStr())
				.setTime_expire(timeExpire)
				.setAttach("微信系开发脚手架 https://gitee.com/javen205/TNWX")
				.setNotify_url(wxPayV3Bean.getDomain().concat("/v3/payNotify"))
				.setAmount(new Amount().setTotal(1));

			log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
			IJPayHttpResponse response = WxPayApi.v3(
				RequestMethod.POST,
				WxDomain.CHINA.toString(),
				WxApiType.NATIVE_PAY.toString(),
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				null,
				wxPayV3Bean.getKeyPath(),
				JSONUtil.toJsonStr(unifiedOrderModel)
			);
			log.info("统一下单响应 {}", response);
			// 根据证书序列号查询对应的证书来验证签名结果
			boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Bean.getPlatformCertPath());
			log.info("verifySignature: {}", verifySignature);
			return R.data(JSONUtil.parseObj(response.getBody()));
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail(e.getMessage());
		}
	}

	@PostMapping("/jsApiPay")
	@ApiOperationSupport(order = 7)
	@ApiOperation(value = "JSAPI下单", notes = "JSAPI下单")
	public R jsApiPay(@RequestParam(value = "openId", required = false, defaultValue = "o5MXL5FIvhEz-_NrcKrz6xUmi_-A") String openId,
					  @RequestParam(value = "outTradeNo") String outTradeNo) {
		try {
			if (StringUtil.isEmpty(openId)){
				return R.fail("参数错误");
			}

			return R.data("支付失败");
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail("支付失败");
		}
	}

	@PostMapping("/put")
	@ApiOperationSupport(order = 8)
	@ApiOperation(value = "根据证书序列号查询对应的证书来验证签名结果", notes = "根据证书序列号查询对应的证书来验证签名结果")
	public R put() {
		try {
			Map<String, String> params = new HashMap<>();
			params.put("url", "https://gitee.com/javen205/IJPay");

			IJPayHttpResponse response = WxPayApi.v3(
				RequestMethod.PUT,
				WxDomain.CHINA.toString(),
				WxApiType.MERCHANT_SERVICE_COMPLAINTS_NOTIFICATIONS.toString(),
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				null,
				wxPayV3Bean.getKeyPath(),
				JSONUtil.toJsonStr(params)
			);
			// 根据证书序列号查询对应的证书来验证签名结果
			boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Bean.getPlatformCertPath());
			log.info("verifySignature: {}", verifySignature);
			log.info("响应 {}", response);
			return R.data(response.getBody());
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail(e.getMessage());
		}
	}


	@PostMapping("/getParams")
	@ApiOperationSupport(order = 9)
	@ApiOperation(value = "根据证书序列号查询对应的证书来验证签名结果", notes = "根据证书序列号查询对应的证书来验证签名结果")
	public R<IJPayHttpResponse> payScoreUserServiceState(@RequestParam(value = "openId", required = false, defaultValue = "o5MXL5FIvhEz-_NrcKrz6xUmi_-A") String openId) {
		try {
			Map<String, String> params = new HashMap<>();
			params.put("service_id", "500001");
			params.put("appid", wxPayV3Bean.getAppId());
			params.put("openid", openId);

			IJPayHttpResponse result = WxPayApi.v3(
				RequestMethod.GET,
				WxDomain.CHINA.toString(),
				WxApiType.PAY_SCORE_USER_SERVICE_STATE.toString(),
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				null,
				wxPayV3Bean.getKeyPath(),
				params
			);
			System.out.println(result);
			return R.data(result);
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail(e.getMessage());
		}
	}

	@PostMapping("/delete")
	@ApiOperationSupport(order = 10)
	@ApiOperation(value = "创建/查询/更新/删除投诉通知回调", notes = "创建/查询/更新/删除投诉通知回调")
	public R<IJPayHttpResponse> v3Delete() {
		// 创建/查询/更新/删除投诉通知回调
		try {
			HashMap<String, String> hashMap = new HashMap<>(12);
			hashMap.put("url", "https://qq.com");
			IJPayHttpResponse result = WxPayApi.v3(
				RequestMethod.POST,
				WxDomain.CHINA.toString(),
				WxApiType.MERCHANT_SERVICE_COMPLAINTS_NOTIFICATIONS.toString(),
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				null,
				wxPayV3Bean.getKeyPath(),
				JSONUtil.toJsonStr(hashMap)
			);
			System.out.println(result);

			result = WxPayApi.v3(
				RequestMethod.DELETE,
				WxDomain.CHINA.toString(),
				WxApiType.MERCHANT_SERVICE_COMPLAINTS_NOTIFICATIONS.toString(),
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				null,
				wxPayV3Bean.getKeyPath(),
				""
			);
			// 根据证书序列号查询对应的证书来验证签名结果
			boolean verifySignature = WxPayKit.verifySignature(result, wxPayV3Bean.getPlatformCertPath());
			System.out.println("verifySignature:" + verifySignature);
			// 如果返回的为 204 表示删除成功
			System.out.println(result);
			return R.data(result);
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail(e.getMessage());
		}
	}

	@PostMapping("/upload")
	@ApiOperationSupport(order = 11)
	@ApiOperation(value = "接口上传文件", notes = "接口上传文件")
	public R<IJPayHttpResponse> v3Upload() {
		// v3 接口上传文件
		try {
			String filePath = "/Users/Javen/Documents/pic/cat.png";

			File file = FileUtil.newFile(filePath);
			String sha256 = SecureUtil.sha256(file);

			HashMap<Object, Object> map = new HashMap<>();
			map.put("filename", file.getName());
			map.put("sha256", sha256);
			String body = JSONUtil.toJsonStr(map);

			System.out.println(body);

			IJPayHttpResponse result = WxPayApi.v3(
				WxDomain.CHINA.toString(),
				WxApiType.MERCHANT_UPLOAD_MEDIA.toString(),
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				null,
				wxPayV3Bean.getKeyPath(),
				body,
				file
			);
			// 根据证书序列号查询对应的证书来验证签名结果
			boolean verifySignature = WxPayKit.verifySignature(result, wxPayV3Bean.getPlatformCertPath());
			System.out.println("verifySignature:" + verifySignature);
			System.out.println(result);
			return R.data(result);
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail(e.getMessage());
		}
	}

	@PostMapping("/post")
	@ApiOperationSupport(order = 12)
	@ApiOperation(value = "支付有礼-终止活动", notes = "支付有礼-终止活动")
	public R<IJPayHttpResponse> payGiftActivity() {
		// 支付有礼-终止活动
		try {
			String urlSuffix = String.format(WxApiType.PAY_GIFT_ACTIVITY_TERMINATE.toString(), "10028001");
			System.out.println(urlSuffix);
			IJPayHttpResponse result = WxPayApi.v3(
				RequestMethod.POST,
				WxDomain.CHINA.toString(),
				urlSuffix,
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				null,
				wxPayV3Bean.getKeyPath(),
				""
			);
			System.out.println(result);
			return R.data(result);
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail(e.getMessage());
		}
	}

	@PostMapping("/sensitive")
	@ApiOperationSupport(order = 13)
	@ApiOperation(value = "带有敏感信息接口", notes = "带有敏感信息接口")
	public R sensitive() {
		// 带有敏感信息接口
		try {
			String body = "处理请求参数";

			IJPayHttpResponse result = WxPayApi.v3(
				RequestMethod.POST,
				WxDomain.CHINA.toString(),
				WxApiType.APPLY_4_SUB.toString(),
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				getPlatSerialNumber(),
				wxPayV3Bean.getKeyPath(),
				body
			);
			System.out.println(result);
			return R.data(result);
		} catch (Exception e) {
			e.printStackTrace();
			return  R.fail(e.getMessage());
		}
	}

	@PostMapping("/cipher")
	@ApiOperationSupport(order = 14)
	@ApiOperation(value = "敏感信息加密", notes = "敏感信息加密")
	public R cipher() {
		try {
			// 敏感信息加密
			X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream(wxPayV3Bean.getPlatformCertPath()));
			String encrypt = PayKit.rsaEncryptOAEP("IJPay", certificate);
			System.out.println(encrypt);
			// 敏感信息解密
			String encryptStr = "";
			PrivateKey privateKey = PayKit.getPrivateKey(wxPayV3Bean.getKeyPath());
			String decrypt = PayKit.rsaDecryptOAEP(encryptStr, privateKey);
			System.out.println(decrypt);
			R.data(decrypt);
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail(e.getMessage());
		}
		return null;
	}

	/**
	 * 申请交易账单
	 *
	 * @param billDate 2020-06-14 当天账单后一天出，不然会出现「账单日期格式不正确」错误
	 * @return 交易账单下载地址
	 */
	@PostMapping("/tradeBill")
	@ApiOperationSupport(order = 15)
	@ApiOperation(value = "申请交易账单", notes = "申请交易账单")
	public R tradeBill(@RequestParam(value = "billDate", required = false) String billDate) {
		try {
			if (StrUtil.isEmpty(billDate)) {
				Calendar calendar = Calendar.getInstance();
				calendar.setTime(new Date());
				calendar.add(Calendar.DATE, -1);
				billDate = DateUtil.format(calendar.getTime(), "YYYY-MM-dd");
			}
			Map<String, String> params = new HashMap<>(12);
			params.put("bill_date", billDate);
			params.put("bill_type", "ALL");
			params.put("tar_type", "GZIP");

			IJPayHttpResponse result = WxPayApi.v3(
				RequestMethod.GET,
				WxDomain.CHINA.toString(),
				WxApiType.TRADE_BILL.toString(),
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				null,
				wxPayV3Bean.getKeyPath(),
				params
			);
			// 根据证书序列号查询对应的证书来验证签名结果
			boolean verifySignature = WxPayKit.verifySignature(result, wxPayV3Bean.getPlatformCertPath());
			log.info("verifySignature: {}", verifySignature);
			log.info("result:{}", result);
			return R.data(result);
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail(e.getMessage());
		}
	}

	/**
	 * 申请资金账单
	 *
	 * @param billDate 2020-06-14 当天账单后一天出，不然会出现「账单日期格式不正确」错误
	 * @return 资金账单下载地址
	 */
	@PostMapping("/fundFlowBill")
	@ApiOperationSupport(order = 16)
	@ApiOperation(value = "申请资金账单", notes = "申请资金账单")
	public R fundFlowBill(@RequestParam(value = "billDate", required = false) String billDate) {
		try {
			if (StrUtil.isEmpty(billDate)) {
				Calendar calendar = Calendar.getInstance();
				calendar.setTime(new Date());
				calendar.add(Calendar.DATE, -1);
				billDate = DateUtil.format(calendar.getTime(), "YYYY-MM-dd");
			}
			Map<String, String> params = new HashMap<>(12);
			params.put("bill_date", billDate);
			params.put("account_type", "BASIC");

			IJPayHttpResponse result = WxPayApi.v3(
				RequestMethod.GET,
				WxDomain.CHINA.toString(),
				WxApiType.FUND_FLOW_BILL.toString(),
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				null,
				wxPayV3Bean.getKeyPath(),
				params
			);
			// 根据证书序列号查询对应的证书来验证签名结果
			boolean verifySignature = WxPayKit.verifySignature(result, wxPayV3Bean.getPlatformCertPath());
			log.info("verifySignature: {}", verifySignature);
			log.info("result:{}", result);
			return R.data(result);
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail(e.getMessage());
		}
	}

	@PostMapping("/billDownload")
	@ApiOperationSupport(order = 17)
	@ApiOperation(value = "下载账单", notes = "下载账单")
	public R billDownload(@RequestParam(value = "token") String token,
							   @RequestParam(value = "tarType", required = false) String tarType) {
		try {

			Map<String, String> params = new HashMap<>(12);
			params.put("token", token);
			if (StrUtil.isNotEmpty(tarType)) {
				params.put("tartype", tarType);
			}

			IJPayHttpResponse result = WxPayApi.v3(
				RequestMethod.GET,
				WxDomain.CHINA.toString(),
				WxApiType.BILL_DOWNLOAD.toString(),
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				null,
				wxPayV3Bean.getKeyPath(),
				params
			);
			log.info("result:{}", result);
			return R.data(result);
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail(e.getMessage());
		}
	}

	@PostMapping("/refund")
	@ApiOperationSupport(order = 18)
	@ApiOperation(value = "商户退款", notes = "商户退款")
	public R refund(@RequestParam(required = false) String transactionId,
						 @RequestParam(required = false) String outTradeNo) {
		try {
			String outRefundNo = PayKit.generateStr();
			log.info("商户退款单号: {}", outRefundNo);

			List<RefundGoodsDetail> list = new ArrayList<>();
			RefundGoodsDetail refundGoodsDetail = new RefundGoodsDetail()
				.setMerchant_goods_id("123")
				.setGoods_name("IJPay 测试")
				.setUnit_price(1)
				.setRefund_amount(1)
				.setRefund_quantity(1);
			list.add(refundGoodsDetail);

			RefundModel refundModel = new RefundModel()
				.setOut_refund_no(outRefundNo)
				.setReason("IJPay 测试退款")
				.setNotify_url(wxPayV3Bean.getDomain().concat("/v3/refundNotify"))
				.setAmount(new RefundAmount().setRefund(1).setTotal(1).setCurrency("CNY"))
				.setGoods_detail(list);

			if (StrUtil.isNotEmpty(transactionId)) {
				refundModel.setTransaction_id(transactionId);
			}
			if (StrUtil.isNotEmpty(outTradeNo)) {
				refundModel.setOut_trade_no(outTradeNo);
			}
			log.info("退款参数 {}", JSONUtil.toJsonStr(refundModel));
			IJPayHttpResponse response = WxPayApi.v3(
				RequestMethod.POST,
				WxDomain.CHINA.toString(),
				WxApiType.DOMESTIC_REFUNDS.toString(),
				wxPayV3Bean.getMchId(),
				getSerialNumber(),
				null,
				wxPayV3Bean.getKeyPath(),
				JSONUtil.toJsonStr(refundModel)
			);
			// 根据证书序列号查询对应的证书来验证签名结果
			boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Bean.getPlatformCertPath());
			log.info("verifySignature: {}", verifySignature);
			log.info("退款响应 {}", response);

			if (verifySignature) {
				return R.data(response.getBody());
			}
		} catch (Exception e) {
			e.printStackTrace();
			return R.fail(e.getMessage());
		}
		return null;
	}

	@RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
	@ApiOperationSupport(order = 19)
	@ApiOperation(value = "支付通知回调", notes = "支付通知回调")
	public void payNotify(HttpServletRequest request, HttpServletResponse response) {
		Map<String, String> map = new HashMap<>(12);
		try {
			String timestamp = request.getHeader("Wechatpay-Timestamp");
			String nonce = request.getHeader("Wechatpay-Nonce");
			String serialNo = request.getHeader("Wechatpay-Serial");
			String signature = request.getHeader("Wechatpay-Signature");
			String result = HttpKit.readData(request);
			// 需要通过证书序列号查找对应的证书，verifyNotify 中有验证证书的序列号
			String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
				wxPayV3Bean.getApiKey3(), wxPayV3Bean.getPlatformCertPath());
			JSONObject obj = JSONUtil.parseObj(plainText);
			String outTradeNo = obj.getStr("out_trade_no");
			if (StrUtil.isNotEmpty(plainText)) {
				response.setStatus(200);
				map.put("code", "SUCCESS");
				map.put("message", "SUCCESS");
				//支付成功
				logger.info("微信支付回调成功", plainText);
			} else {
				response.setStatus(500);
				map.put("code", "ERROR");
				map.put("message", "签名错误");
				//支付失败
				logger.error("微信支付回调失败", plainText);
			}
			response.setHeader("Content-type", ContentType.JSON.toString());
			response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
			response.flushBuffer();
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("支付回调异常", map.toString());
		}
	}
}
