package com.ruoyi.common.weixin.utils;

import cn.hutool.core.net.URLEncodeUtil;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.common.weixin.config.WeiXinH5PayConfig;
import com.ruoyi.common.weixin.config.WeiXinPayConfig;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.cipher.SignatureResult;
import com.wechat.pay.java.core.cipher.Signer;
import com.wechat.pay.java.core.http.*;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.service.payments.h5.model.H5Info;
import com.wechat.pay.java.service.payments.h5.model.SceneInfo;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.wechat.pay.java.service.payments.h5.H5Service;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.Status;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;

/**
 * 微信V3支付
 *
 * @author Wzp
 */
@Component
public class WeiXinPayV3NewUtils {

    protected final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    private WeiXinH5PayConfig weiXinH5PayConfig;


    /***
     * 请求JSAPI下单
     * @param total 金额
     * @param openId 支付用户
     * @param orderSn 订单号
     * @param description 描述
     * @param notifyApi 回调接口名称
     * @return 结果
     */
    public JSONObject createPayParam(BigDecimal total, String openId, String orderSn, String description, String notifyApi) {

        //更新证书配置，这里用官方sdk，不需要考虑时效性问题
        Config config = new RSAAutoCertificateConfig.Builder()
                .merchantId(weiXinH5PayConfig.getMchId())
                .privateKey(weiXinH5PayConfig.getApiClientKey())//微信v3-私钥
                .merchantSerialNumber(weiXinH5PayConfig.getMchSerialNum())//微信v3证书序列号
                .apiV3Key(weiXinH5PayConfig.getApiV3Key())//微信v3-apiV3Key
                .build();

        Amount amount = new Amount();
        amount.setTotal(total.multiply(new BigDecimal("100")).intValue());
        amount.setCurrency("CNY");

        Payer payer = new Payer();
        payer.setOpenid(openId);

        PrepayRequest prepayRequest = new PrepayRequest();
        prepayRequest.setMchid(weiXinH5PayConfig.getMchId());
        prepayRequest.setOutTradeNo(orderSn);
        prepayRequest.setAppid(weiXinH5PayConfig.getAppId());
        prepayRequest.setDescription(description);
        prepayRequest.setNotifyUrl(weiXinH5PayConfig.getNotifyUrl() + notifyApi);

        prepayRequest.setAmount(amount);
        prepayRequest.setPayer(payer);
        logger.info("JSAPI下单-参数 => {}", prepayRequest.toString());

        // 构建请求
        JsapiService jsapiService = new JsapiService.Builder().config(config).build();
        PrepayResponse prepay = jsapiService.prepay(prepayRequest);
        logger.info("JSAPI下单-结果 => {}", prepay.toString());
        String prepayId = prepay.getPrepayId();

        JSONObject payInfo = new JSONObject();
        if (StringUtils.isNotBlank(prepayId)) {
            // 小程序ID
            payInfo.put("appId", weiXinH5PayConfig.getAppId());
            // 时间戳
            payInfo.put("timeStamp", String.valueOf((int) (System.currentTimeMillis() / 1000)));
            // 随机字符串
            payInfo.put("nonceStr", IdUtils.fastSimpleUUID());
            // 订单详情扩展字符串
            payInfo.put("package", "prepay_id=" + prepayId);
            // 签名方式
            payInfo.put("signType", "RSA");
            // 加密字符串，每行一个参数
            String paySignParam = payInfo.get("appId") + "\n" + payInfo.get("timeStamp") + "\n" + payInfo.get("nonceStr") + "\n" + payInfo.get("package") + "\n";
            // 签名
            String paySign = this.signRSA(paySignParam, config);
            payInfo.put("paySign", paySign);
            payInfo.put("orderNumber", orderSn);

            return payInfo;
        } else {
            throw new ServiceException("JSAPI下单调起失败");
        }
    }

    /***
     * 请求H5下单
     * @param total 金额
     * @param orderSn 订单号
     * @param description 描述
     * @return 结果
     */
    public String createH5PayParam(BigDecimal total, String orderSn, String description) {

        //更新证书配置，这里用官方sdk，不需要考虑时效性问题
        Config config = new RSAAutoCertificateConfig.Builder()
                .merchantId(weiXinH5PayConfig.getMchId())
                .privateKey(weiXinH5PayConfig.getApiClientKey())//微信v3-私钥
                .merchantSerialNumber(weiXinH5PayConfig.getMchSerialNum())//微信v3证书序列号
                .apiV3Key(weiXinH5PayConfig.getApiV3Key())//微信v3-apiV3Key
                .build();

        com.wechat.pay.java.service.payments.h5.model.PrepayRequest prepayRequest = new com.wechat.pay.java.service.payments.h5.model.PrepayRequest();
        prepayRequest.setMchid(weiXinH5PayConfig.getMchId());
        prepayRequest.setOutTradeNo(orderSn);
        prepayRequest.setAppid(weiXinH5PayConfig.getAppId());
        prepayRequest.setDescription("学信考试");
        prepayRequest.setNotifyUrl(weiXinH5PayConfig.getH5NotifyUrl());
        SceneInfo sceneInfo = new SceneInfo();
        sceneInfo.setPayerClientIp(IpUtils.getIpAddr());
        H5Info h5Info = new H5Info();
        h5Info.setType("Wap");
        sceneInfo.setH5Info(h5Info);
        prepayRequest.setSceneInfo(sceneInfo);
        com.wechat.pay.java.service.payments.h5.model.Amount amount = new com.wechat.pay.java.service.payments.h5.model.Amount();
        amount.setTotal(total.multiply(new BigDecimal("100")).intValue());
        amount.setCurrency("CNY");
        prepayRequest.setAmount(amount);
        logger.info("H5下单-参数 => {}", prepayRequest);
        // 构建请求
        H5Service h5Service = new H5Service.Builder().config(config).build();
        com.wechat.pay.java.service.payments.h5.model.PrepayResponse response = h5Service.prepay(prepayRequest);
        String h5Url = response.getH5Url();
        if (StringUtils.isNotBlank(h5Url)) {
            // h5Url += "&redirect_url="+ URLEncodeUtil.encode("https://h5.chsie.com.cn");
            return h5Url;
        } else {
            throw new ServiceException("H5下单调起失败");
        }
    }

    /**
     * 微信支付v3签名 RSA签名
     *
     * @param message 需要签名的内容
     * @return 结果
     */
    public String signRSA(String message, Config config) {
        try {
            Signer signer = config.createSigner();
            SignatureResult sign = signer.sign(message);
            return sign.getSign();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * 微信退款（app和小程序通用）
     *
     * @param out_trade_no  商户订单号
     * @param out_refund_no 商户退款单号
     * @param refund        退款金额
     * @param total         原订单金额
     * @return 结果
     */
    public String weiXinRefund(String out_trade_no, String out_refund_no, int refund, int total) {

        try {

            //更新证书配置，这里用官方sdk，不需要考虑时效性问题
            Config wxConfig = new RSAAutoCertificateConfig.Builder()
                    .merchantId(weiXinH5PayConfig.getMchId())
                    .privateKey(weiXinH5PayConfig.getApiClientKey())
                    .merchantSerialNumber(weiXinH5PayConfig.getMchSerialNum())
                    .apiV3Key(weiXinH5PayConfig.getApiV3Key())
                    .build();


            AmountReq amountReq = new AmountReq();
            amountReq.setRefund((long) refund);
            amountReq.setTotal((long) total);
            amountReq.setCurrency("CNY");

            CreateRequest createRequest = new CreateRequest();
            createRequest.setAmount(amountReq);

            createRequest.setOutRefundNo(out_refund_no);
            createRequest.setOutTradeNo(out_trade_no);

            logger.info("JSAPI退款-参数 => " + createRequest.toString());
            // 发送请求
            HttpHeaders headers = new HttpHeaders();
            headers.addHeader(com.wechat.pay.java.core.http.Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
            headers.addHeader(com.wechat.pay.java.core.http.Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
            HttpRequest httpRequest =
                    new HttpRequest.Builder()
                            .httpMethod(HttpMethod.POST)
                            .url("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds")
                            .headers(headers)
                            .body(new JsonRequestBody.Builder().body(com.wechat.pay.java.core.util.GsonUtil.toJson(createRequest)).build())
                            .build();

            HttpClient httpClient = new DefaultHttpClientBuilder().config(wxConfig).build();

            HttpResponse<Refund> httpResponse = httpClient.execute(httpRequest, Refund.class);
            Refund serviceResponse = httpResponse.getServiceResponse();

            Status status = serviceResponse.getStatus();
            String name = status.name();

            if (("SUCCESS".equals(name) || "PROCESSING".equals(name))) {
                return "SUCCESS";
            } else {
                return "FAIL";
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "FAIL";
        }
    }

    /**
     * 解析支付回调参数
     *
     * @param httpServletRequest 请求
     * @return 结果
     */
    public Transaction getNotifyData(HttpServletRequest httpServletRequest) {

        try {
            // 请求头Wechatpay-Signature
            String signature = httpServletRequest.getHeader("Wechatpay-Signature");
            // 请求头Wechatpay-nonce
            String nonce = httpServletRequest.getHeader("Wechatpay-Nonce");
            // 请求头Wechatpay-Timestamp
            String timestamp = httpServletRequest.getHeader("Wechatpay-Timestamp");
            // 微信支付证书序列号
            String serial = httpServletRequest.getHeader("Wechatpay-Serial");
            // 签名方式
            String signType = httpServletRequest.getHeader("Wechatpay-Signature-Type");

            // 获取报文
            String body = getRequestBody(httpServletRequest);

            // 1.构造 RequestParam
            com.wechat.pay.java.core.notification.RequestParam requestParam = new com.wechat.pay.java.core.notification.RequestParam.Builder()
                    .serialNumber(serial)
                    .nonce(nonce)
                    .signature(signature)
                    .timestamp(timestamp)
                    .signType(signType)
                    .body(body)
                    .build();

            // 2. 如果你仍在使用微信支付平台证书，则使用 RSAAutoCertificateConfig
            NotificationConfig wxConfig = new RSAAutoCertificateConfig.Builder()
                    .merchantId(weiXinH5PayConfig.getMchId())
                    .privateKey(weiXinH5PayConfig.getApiClientKey())
                    .merchantSerialNumber(weiXinH5PayConfig.getMchSerialNum())
                    .apiV3Key(weiXinH5PayConfig.getApiV3Key())
                    .build();

            // 初始化 NotificationParser
            NotificationParser parser = new NotificationParser(wxConfig);
            // 以支付通知回调为例，验签、解密并转换成 Transaction
            return parser.parse(requestParam, Transaction.class);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 读取请求数据流
     *
     * @param request 请求
     * @return 结果
     */
    private String getRequestBody(HttpServletRequest request) {

        StringBuilder sb = new StringBuilder();

        try (ServletInputStream inputStream = request.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        ) {
            String line;

            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

        return sb.toString();
    }

}
