package com.ruoyi.web.work.wxpay;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.AuthTypeEnum;
import com.ijpay.core.enums.RequestMethodEnum;
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.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
import com.ijpay.wxpay.model.v3.Amount;
import com.ijpay.wxpay.model.v3.UnifiedOrderModel;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.web.work.domain.Company;
import com.ruoyi.web.work.domain.Recharge;
import com.ruoyi.web.work.domain.Record;
import com.ruoyi.web.work.domain.dto.CompanyPayDto;
import com.ruoyi.web.work.domain.dto.PayStateDto;
import com.ruoyi.web.work.service.ICompanyService;
import com.ruoyi.web.work.service.IRechargeService;
import com.ruoyi.web.work.service.IRecordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/work/wxPay")
public class CompanyPayController extends BaseController {
    @Resource
    WxPayBean wxPayV3Bean;

    private String serialNo;

    @Autowired
    private IRechargeService rechargeService;

    @Autowired
    private ICompanyService companyService;

    @Autowired
    private IRecordService recordService;

    @PreAuthorize("@ss.hasPermi('work:recharge:add')")
    @Log(title = "微信充值", businessType = BusinessType.PAY)
    @PostMapping("/pay")
    public AjaxResult pay(@Validated @RequestBody CompanyPayDto dto) {
        try {
            String orderNum = PayKit.generateStr();
            UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel();
            unifiedOrderModel.setAppid(wxPayV3Bean.getAppId());
            unifiedOrderModel.setMchid(wxPayV3Bean.getMchId());
            unifiedOrderModel.setDescription("企业账户充值");
            unifiedOrderModel.setOut_trade_no(orderNum);
            unifiedOrderModel.setTime_expire(DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3));
            unifiedOrderModel.setAttach("企业账户充值");
            unifiedOrderModel.setNotify_url(wxPayV3Bean.getDomain().concat("/work/wxPay/notify"));
            unifiedOrderModel.setAmount(new Amount().setTotal(dto.getMoney().multiply(new BigDecimal(100)).intValue()));
            IJPayHttpResponse response = WxPayApi.v3(RequestMethodEnum.POST, WxDomainEnum.CHINA.toString(), BasePayApiEnum.NATIVE_PAY.toString(), wxPayV3Bean.getMchId(), getSerialNumber(), null, wxPayV3Bean.getKeyPath(), JSONUtil.toJsonStr(unifiedOrderModel), AuthTypeEnum.RSA.getCode());
            boolean verifySignature = WxPayKit.verifySignature(response, wxPayV3Bean.getPlatformCertPath());
            if (response.getStatus() == 200 && verifySignature) {
                //保存订单信息
                Recharge recharge = new Recharge();
                recharge.setCompanyId(SecurityUtils.getLoginUser().getUser().getCompanyId());
                recharge.setState(0);
                recharge.setType(1);
                recharge.setIdent(dto.getIdent());
                recharge.setNums(orderNum);
                recharge.setMoney(dto.getMoney());
                recharge.setVersion(System.currentTimeMillis());
                recharge.setOpBy(SecurityUtils.getLoginUser().getUser().getNickName());
                recharge.setUserId(SecurityUtils.getLoginUser().getUser().getUserId());
                if (!rechargeService.save(recharge)) {
                    throw new ServiceException("充值失败,请联系平台或者转账充值");
                }
                return AjaxResult.success("成功",response.getBody());
            }
            return AjaxResult.error("支付异常,请联系平台或者转账充值");
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
    }

    @PreAuthorize("@ss.hasPermi('work:recharge:add')")
    @GetMapping("/payState")
    public AjaxResult payState(@Validated PayStateDto dto) {
        return AjaxResult.success(rechargeService.selectByIdent(dto.getIdent()) == null ? false : true);
    }

    @PostMapping(value = "/notify")
    @Transactional(rollbackFor = Exception.class)
    public void notify(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 中有验证证书的序列号
            JSONObject res = JSONObject.parseObject(WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, wxPayV3Bean.getApiKey3(), wxPayV3Bean.getPlatformCertPath()));
            System.out.println("支付通知明文：" + res.toJSONString());
            if (res != null && res.getString("trade_state").equals("SUCCESS")) {
                //业务处理，更新订单信息
                Recharge recharge = rechargeService.selectByNums(res.getString("out_trade_no"));
                if (recharge != null && recharge.getState() == 0) {
                    //更新订单状态
                    recharge.setState(1);
                    if (!rechargeService.updateById(recharge)) {
                        throw new ServiceException("更新充值订单失败");
                    }
                    //更新企业账户余额
                    Company company = companyService.getById(recharge.getCompanyId());
                    company.setMoney(company.getMoney().add(recharge.getMoney()));
                    if (!companyService.updateById(company)) {
                        throw new ServiceException("更新企业账户金额失败");
                    }
                    //添加充值记录
                    Record record = new Record();
                    record.setType(0);
                    record.setCompanyId(recharge.getCompanyId());
                    record.setOpId(recharge.getId());
                    record.setMoney(recharge.getMoney());
                    if (!recordService.save(record)) {
                        throw new ServiceException("添加充值记录失败");
                    }
                    response.setStatus(200);
                    map.put("code", "SUCCESS");
                    map.put("message", "SUCCESS");
                }
            } else {
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "签名错误");
            }
            response.setHeader("Content-type", ContentType.JSON.toString());
            response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
            response.flushBuffer();
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        }
    }

    /**
     * 获取证书序列号
     *
     * @return
     */
    private String getSerialNumber() {
        if (StrUtil.isEmpty(serialNo)) {
            X509Certificate certificate = PayKit.getCertificate(wxPayV3Bean.getCertPath());
            if (certificate != null) {
                serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
            }
        }
        return serialNo;
    }
}
