package com.sonic.lion.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sonic.lion.enums.BizType;
import com.sonic.lion.enums.PayChannel;
import com.sonic.lion.dao.*;
import com.sonic.lion.domain.entity.*;
import com.sonic.lion.domain.input.IOSReceiptInput;
import com.sonic.lion.service.*;
import com.sonic.lion.utils.IOSConsumptionUtil;
import com.sonic.lion.utils.JwtUtils;
import com.sonic.lion.utils.NetRetryUtils;
import com.sonic.lion.enums.ToastResultCode;
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.time.ZoneOffset;


/**
 * @author: code
 * @date: 2025/06/22
 * @Description:
 * @version: 1.0.0
 */
@Slf4j
@Service("iapV2Service")
public class IapV2ServiceImpl implements IapV2Service, InitializingBean {

    @Value("${spring.profiles.active}")
    private String active;
    @Value("${apple.boundId}")
    private String BUNDLE_ID;
    @Value("${apple.password}")
    private String password;
//    @Value("${apple.environment:Sandbox}")
    private String environment = "Sandbox";
    @Value("${apple.verifyReceiptUrl:}")
    private String verifyReceiptUrl;

    private String verifySubscriptionUrl = "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/subscriptions/";
//  private String verifySubscriptionUrl = "https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/";

    @Autowired
    private PayTradeService payTradeService;
    @Autowired
    private PayTradeDao payTradeDao;
    @Autowired
    private IapRecordService iapRecordService;
    @Autowired
    private UserSubscriptionService userSubscriptionService;
    @Autowired
    private UserSubscriptionDao userSubscriptionDao;
    @Autowired
    private NetRetryUtils netRetryUtils;
    @Autowired
    private AccountBuffBillDao accountBuffBillDao;
    @Autowired
    private AccountBuffService accountBuffService;
    @Autowired
    private IOSConsumptionUtil iosConsumptionUtil;
    @Autowired
    private AppleRefundRecordDao appleRefundRecordDao;

    @Autowired
    private UserSubscriptionNotifyDao userSubscriptionNotifyDao;

    @Autowired
    private ChannelBlacklistService channelBlacklistService;

    private static final int SUCC_STATUS = 0;

    private static final int SANDBOX_RECIPT = 21007;

    private static final String PRODUCT_VERIFY_RECEIPT_URL = "https://buy.itunes.apple.com/verifyReceipt";

    private static final String SANDBOX_VERIFY_RECEIPT_URL = "https://sandbox.itunes.apple.com/verifyReceipt";


    @Override
    public void afterPropertiesSet() {
        if ("product".equals(active)) {
            verifyReceiptUrl = PRODUCT_VERIFY_RECEIPT_URL;
        } else {
            verifyReceiptUrl = SANDBOX_VERIFY_RECEIPT_URL;
        }
    }

    //====通知类型====
    //SUBSCRIBED  如果subtype是INITIAL_BUY（首次购买），则用户首次通过“家人共享”购买或接收了对订阅的访问权限。如果是RESUBSCRIBE（重新购买/重新购买同一个组内的plan），则用户通过家庭共享重新订阅或接收了对同一订阅或同一订阅组内的另一个订阅的访问权限。
    //CONSUMPTION_REQUEST 一种通知类型，表明客户发起了应用内消费品(大师课/肤质检测次数购买)购买的退款请求
    //DID_RENEW 一种通知类型，与其一起subtype指示订阅已成功续订。如果subtype是BILLING_RECOVERY，则之前续订失败的过期订阅已成功续订。如果子状态为空（续订），则活动订阅已成功自动续订新的交易周期。为客户提供对订阅内容或服务的访问权限。
    //OFFER_REDEEMED 一种通知类型，与其 一起subtype指示用户兑换了促销优惠或优惠代码。如果subtype是INITIAL_BUY，则用户兑换了首次购买的优惠。如果是RESUBSCRIBE，则用户兑换了重新订阅非活动订阅的优惠。如果是UPGRADE，则用户兑换了升级其有效订阅的优惠，该优惠立即生效。如果是DOWNGRADE，则用户兑换了降级其有效订阅的优惠，该优惠将在下一个续订日期生效。
    //REFUND 一种通知类型，指示 AppStore已成功对消费品应用内购买、非消费品应用内购买、自动续订订阅或非续订订阅的交易进行退款。
    //DID_CHANGE_RENEWAL_STATUS 一种通知类型，与其一起subtype指示用户对订阅续订状态进行了更改。如果subtype=AUTO_RENEW_ENABLED，则用户重新启用订阅自动续订。如果是AUTO_RENEW_DISABLED，则用户禁用了订阅自动续费，或者用户申请退款后App Store禁用了订阅自动续费。
    //DID_CHANGE_RENEWAL_PREF 一种通知类型，与其一起subtype指示用户对其订阅计划进行了更改。如果subtype是UPGRADE，则用户升级了他们的订阅。升级立即生效，开始新的计费周期，用户将收到上一周期未使用部分的按比例退款。如果subtype是DOWNGRADE，则用户降级了他们的订阅。降级将在下一个续订日期生效，并且不会影响当前有效的计划。如果subtype为空，则用户将其续订首选项更改回当前订阅，从而有效地取消降级。
    //4. DID_FAIL_TO_RENEW 一种通知类型，与其一起subtype指示订阅由于计费问题而未能续订。订阅进入计费重试期。如果subtype是GRACE_PERIOD，则在宽限期内继续提供服务。如果为空，则说明订阅不在宽限期内，您可以停止提供订阅服务。
    //6. EXPIRED 如果subtype是VOLUNTARY，则订阅在用户禁用订阅续订后过期。如果subtype是BILLING_RETRY，则订阅已过期，因为计费重试期已结束，但没有成功的计费事务。如果是PRICE_INCREASE，则订阅已过期，因为用户不同意需要用户同意的价格上涨。如果是PRODUCT_NOT_FOR_SALE，则订阅已过期，因为在订阅尝试续订时该产品不可购买。
    //7. GRACE_PERIOD_EXPIRED 一种通知类型，指示计费宽限期已结束而无需续订订阅，因此您可以关闭对服务或内容的访问。通知用户他们的账单信息可能存在问题。
    //10. PRICE_INCREASE 一种通知类型，与其一起subtype表示系统已通知用户自动续订订阅价格上涨。
    //如果涨价需要用户同意，是subtype指PENDING用户没有对涨价做出回应，或者ACCEPTED用户已经同意涨价。
    //如果涨价不需要用户同意，那subtype就是ACCEPTED。
    //12. REFUND_DECLINED 一种通知类型，指示 AppStore 拒绝了应用开发者使用以下任一方法发起的退款请求
    //13. REFUND_REVERSED 一种通知类型，表明 App Store 由于客户提出的争议而撤销了之前授予的退款。如果您的应用因相关退款而撤销了内容或服务，则需要恢复它们。
    //此通知类型可适用于任何应用内购买类型：消耗型、非消耗型、非续订订阅和自动续订订阅。对于自动续订订阅，当 App Store 撤销退款时，续订日期保持不变。
    //14. RENEWAL_EXTENDED 一种通知类型，指示 App Store 延长了特定订阅的订阅续订日期。您可以通过调用App Store Server API中的延长订阅续订日期或为所有活跃订阅者延长订阅续订日期来请求订阅续订日期延期。
    //15. RENEWAL_EXTENSION 一种通知类型，与其一起subtype表示 AppStore 正在尝试通过调用为所有活跃订阅者延长订阅续订日期 来延长您请求的订阅续订日期。如果subtype是SUMMARY，则 AppStore 已完成为所有符合条件的订阅者延长续订日期。
    //16. REVOKE指示用户有权通过“家人共享”进行应用内购买的通知类型不再可通过共享进行。当购买者对其购买禁用“家庭共享”、购买者（或家庭成员）离开家庭群组或购买者收到退款时，AppStore 会发送此通知。您的应用程序也会收到呼叫。家庭共享适用于非消耗性应用内购买和自动续订订阅。有关家庭共享的更多信息，请参阅在应用程序中支持家庭共享。

    //====通知子类型====
    //1. ACCEPTED  适用于PRICE_INCREASE. 如果价格上涨需要客户同意，则带有此通知的通知表明客户同意订阅价格上涨；如果价格上涨不需要客户同意，则表明系统通知他们价格上涨。
    //2. AUTO_RENEW_DISABLED  适用于DID_CHANGE_RENEWAL_STATUS. 此类通知表明用户禁用了订阅自动续订，或者 App Store 在用户申请退款后禁用了订阅自动续订。
    //2. AUTO_RENEW_ENABLED  适用于DID_CHANGE_RENEWAL_STATUS. 包含此信息的通知表明用户启用了订阅自动续订。
    //3. BILLING_RECOVERY  适用于DID_RENEW. 出现此通知表示之前未能续订的过期订阅已成功续订。
    //4. BILLING_RETRY  适用于EXPIRED. 此类通知表明订阅已过期，因为订阅在计费重试期结束之前未能续订。
    //5. DOWNGRADE  适用于DID_CHANGE_RENEWAL_PREF. 包含此信息的通知表明用户降级了其订阅或交叉分级为具有不同持续时间的订阅。降级将在下一个续订日期生效。
    //6. FAILURE 适用于RENEWAL_EXTENSION. 包含此信息的通知表明单个订阅的订阅续订日期延期失败。有关详细信息，请参阅中的对象。有关请求的信息，请参阅延长所有活跃订阅者的订阅续订日期。
    //7. GRACE_PERIOD 适用于DID_FAIL_TO_RENEW. 包含此信息的通知表明订阅由于计费问题而无法续订。在宽限期内继续提供对订阅的访问。
    //8. INITIAL_BUY 适用于SUBSCRIBED. 包含此内容的通知表示用户首次购买订阅或用户首次通过家人共享获得对订阅的访问权限。
    //9. PENDING 适用于PRICE_INCREASE. 出现此通知表示系统已通知用户订阅价格上涨，但用户尚未接受。
    //10. PRICE_INCREASE 适用于EXPIRED. 此类通知表明订阅已过期，因为用户不同意涨价。
    //11. PRODUCT_NOT_FOR_SALE 适用于EXPIRED. 包含此内容的通知表明订阅已过期，因为在订阅尝试续订时无法购买该产品。
    //12. RESUBSCRIBE 适用于SUBSCRIBED. 带有此信息的通知表明用户通过家庭共享重新订阅或接收了对同一订阅或同一订阅组内的另一个订阅的访问权限。
    //13. SUMMARY 适用于RENEWAL_EXTENSION. 此通知表明 App Store 服务器已完成您为所有符合条件的订阅者延长订阅续订日期的请求。有关摘要详细信息，请参阅中的对象。有关请求的信息，请参阅延长所有活跃订阅者的订阅续订日期。 notificationTypesubtypesummaryresponseBodyV2DecodedPayload
    //14. UPGRADE 适用于DID_CHANGE_RENEWAL_PREF. 包含此信息的通知表明用户已升级其订阅或交叉分级为具有相同持续时间的订阅。升级立即生效。
    //15. VOLUNTARY 适用于EXPIRED. 此类通知表明订阅在用户禁用订阅自动续订后已过期。

    /**
     * https://developer.apple.com/documentation/appstoreservernotifications/notification_type 相关链接.
     * https://juejin.cn/post/7373944051294666778
     * @param receiptId
     * @param payload
     * @return
     */
    @Override
    public boolean handSubscribeWebhook(Long receiptId, String payload) {
        //将处理订阅的异常给吃掉，不能影响后面的业务逻辑的执行
        try {
            //修改是否自动续订的状态
            doWhenStatusChange(receiptId, payload);
        } catch (Exception e) {
            log.error("===> appleV2 webhook handler error notifyId2 : " + receiptId, e);
        }
        //处理 过期、取消订阅
        doWhenCancel(receiptId, payload);
        //处理 订阅和续订成功的场景
        doWhenPay(receiptId, payload);
        //处理 订阅失败的场景
        doWhenPayFail(receiptId, payload);
        //处理 内购回调的场景
        doWenOnTimeCharge(receiptId, payload);
        //处理 AppStore已成功对消费品应用内购买、非消费品应用内购买、自动续订订阅或非续订订阅的交易进行退款
        doWhenRefundV2(receiptId, payload);
        //处理 客户发起了应用内消费品购买的退款请求
        doWhenConsumptionRequest(receiptId, payload);
        return true;
    }

    //当状态变更的时候 更新套餐 和 续订状态。
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void doWhenStatusChange(Long receiptId, String payload) {
        JSONObject result = JSON.parseObject(payload);
        JSONObject renewalInfoJson = result.getJSONObject("data").getJSONObject("renewalInfo");
        //获取不到订阅信息，直接快速返回
        if(renewalInfoJson == null) {
            return;
        }

        String originalTransactionId = renewalInfoJson.getString("originalTransactionId");
        if (originalTransactionId == null) {
            return;
        }
        //获取并校验订阅信息，并重新设置最新的值
        renewalInfoJson = checkAndGetRenewalInfo(originalTransactionId);
        //当前订购的产品标识符
        String productId = renewalInfoJson.getString("productId");
        //获取是否自动续订的状态（0 自动续订已关闭。客户已关闭订阅的自动续订，并且在当前订阅期结束后不会续订。1 自动续订已开启。订阅将在当前订阅期结束时续订。）
        Boolean autoRenew = "1".equals(renewalInfoJson.getString("autoRenewStatus"));
        //将是否自动续订的状态更新到数据库中
        userSubscriptionService.updateStatusByThird(originalTransactionId, UserSubscription.Platform.APPLE, productId, autoRenew);
        if (!autoRenew) {
            userSubscriptionService.cancel(originalTransactionId, UserSubscription.Platform.APPLE, null);
        }
        userSubscriptionService.completeNotify(receiptId);
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void doWhenConsumptionRequest(Long receiptId, String payload) {
        JSONObject result = JSON.parseObject(payload);
        JSONObject transactionInfoJson = result.getJSONObject("data").getJSONObject("transactionInfo");
        //获取通知类型
        String notificationType = result.getString("notificationType");

        if (!"CONSUMPTION_REQUEST".equals(notificationType)) {
            return;
        }
        //环境不匹配，不允许处理
        String resultEvn = result.getString("environment");
        String resultBundleId = result.getString("bundleId");
        ToastResultCode.IAP_RECEIPT_STATUS_ERROR.check(!environment.equals(resultEvn));
        ToastResultCode.IAP_BUNDLE_ID_ERROR.check(!BUNDLE_ID.equals(resultBundleId));
        //校验是IOS服务器
        if ("PROD".equals(resultEvn) && !result.getString("password").equals(password)) {
            return;
        }
        String originalTransactionId = transactionInfoJson.getString("originalTransactionId");
        IapRecord record = iapRecordService.getByTransactionId(originalTransactionId);
        if (record == null) {
            log.info("===> doWhenConsumptionRequest IapRecord not found , receiptId={}", receiptId);
            return;
        }
        int count = appleRefundRecordDao.selectCount(Wrappers.<AppleRefundRecord>lambdaQuery()
                .eq(AppleRefundRecord::getTransactionId, originalTransactionId));
        //已经存在的数据 不再处理
        if (count > 0) {
            log.info("===> doWhenConsumptionRequest IapRecord data found , receiptId={}", receiptId);
            return;
        }
        String tradeNo = record.getTradeNo();
        PayTrade payTrade = payTradeService.getByTradeNo(tradeNo);

        int consumptionStatus = 0;
        //购买得到的buff
        Long getBuffAmount = payTrade.getAmount();
        //这期间 消费了的buff
        Long sumConsumptionTotal = accountBuffBillDao.sumConsumption(payTrade.getPayTime(), LocalDateTime.now(), payTrade.getSrcAccountId());
        if (sumConsumptionTotal == 0) {   //没有消费 扣除buff  并发送系统消息。
            consumptionStatus = 1;    //不消耗应用内购买
        } else if (sumConsumptionTotal > 0 && sumConsumptionTotal < getBuffAmount) {
            consumptionStatus = 2;    //应用内购买被部分消费
        } else {
            consumptionStatus = 3;    //应用内购买已完全消耗
        }


        /**
         * {"transaction_id":"200001174368839","original_purchase_date":"2021-12-25 15:42:33 Etc/GMT",
         * "in_app_ownership_type":"PURCHASED","quantity":"1","original_transaction_id":"200001174368839",
         * "purchase_date_pst":"2021-12-25 07:42:33 America/Los_Angeles","original_purchase_date_ms":
         * "1640446953000","purchase_date_ms":"1640446953000","product_id":"gg.epal.buff_0004",
         * "original_purchase_date_pst":"2021-12-25 07:42:33 America/Los_Angeles",
         * "is_trial_period":"false","purchase_date":"2021-12-25 15:42:33 Etc/GMT"}
         */

        JSONObject latestReceiptInfo = JSONObject.parseObject(record.getResult());
        //订购时间
        Long purchase_date_ms = Long.valueOf(latestReceiptInfo.getString("purchase_date_ms"));
        LocalDateTime purchaseDate = LocalDateTime.ofEpochSecond(purchase_date_ms / 1000, 0, ZoneOffset.ofHours(8));


        Integer totalBuy = payTradeDao.countTotalBuy(payTrade.getSrcAccountId());
        Integer totalRefund = payTradeDao.countTotalRefund(payTrade.getSrcAccountId());
        //发送IOS 消费.
        String json = iosConsumptionUtil.sendConsumption(originalTransactionId, 1, consumptionStatus, totalBuy, totalRefund, 1);

//        accountBuffService.fronzenBalanceAndSendMessage(user.getId(), getBuffAmount); //冻结 金额并 发送消息

        //开始处理 冻结
        AppleRefundRecord.FronzenStatus fronzenStatus = accountBuffService.fronzenBalanceAndSendMessage(payTrade.getSrcAccountId(), payTrade.getAmount());

        AppleRefundRecord appleRefundRecord = AppleRefundRecord.builder().productId(latestReceiptInfo.getString("product_id"))
                .platform(AppleRefundRecord.Platform.IOS).bizType(BizType.CHARGE)
                .content(payload)
                .transactionId(originalTransactionId).cancellationDate(LocalDateTime.now()).purchaseDate(purchaseDate).build();

        appleRefundRecord.setAmount(payTrade.getOccurAmount());
        appleRefundRecord.setUserId(payTrade.getSrcAccountId());
        appleRefundRecord.setTradeNo(tradeNo);
        appleRefundRecord.setBuff(payTrade.getAmount());

        appleRefundRecord.setFronzenStatus(fronzenStatus);
        AccountBuff accountBuff = accountBuffService.getByUid(payTrade.getSrcAccountId());
        appleRefundRecord.setAfterFronzenBuff(accountBuff.getBalance());
        appleRefundRecord.setInsertTime(LocalDateTime.now());
        appleRefundRecordDao.insert(appleRefundRecord);
        //将用户加入渠道黑名单中
        channelBlacklistService.addBlacklist(appleRefundRecord.getUserId(), PayChannel.STRIPE.name());

        userSubscriptionService.updateNotifyExtend(receiptId, appleRefundRecord.getId(), json);
        userSubscriptionService.completeNotify(receiptId);
    }


    private void doWhenRefund(Long receiptId, String playload) {
        JSONObject result = JSON.parseObject(playload);
        String notification_type = result.getString("notification_type");
        if (!"REFUND".equals(notification_type)) {
            return;
        }
        //校验是IOS服务器
        if (!result.getString("password").equals(password)) {
            return;
        }

        JSONObject latestReceiptInfo = result.getJSONObject("unified_receipt").getJSONArray("latest_receipt_info").getJSONObject(0);


        Long cancellation_date_ms = Long.valueOf(latestReceiptInfo.getString("cancellation_date_ms"));
        LocalDateTime cancellation_date = LocalDateTime.ofEpochSecond(cancellation_date_ms / 1000, 0, ZoneOffset.ofHours(8));


        Long purchase_date_ms = Long.valueOf(latestReceiptInfo.getString("purchase_date_ms"));
        LocalDateTime purchase_date = LocalDateTime.ofEpochSecond(purchase_date_ms / 1000, 0, ZoneOffset.ofHours(8));

        String transaction_id = latestReceiptInfo.getString("transaction_id");
        AppleRefundRecord appleRefundRecord = AppleRefundRecord.builder().productId(latestReceiptInfo.getString("product_id"))
                .platform(AppleRefundRecord.Platform.IOS).bizType(BizType.CHARGE)
                .content(playload)
                .transactionId(transaction_id).cancellationDate(cancellation_date).purchaseDate(purchase_date).build();


        IapRecord record = iapRecordService.getByTransactionId(transaction_id);
        if (record != null) {
            String tradeNo = record.getTradeNo();
            PayTrade payTrade = payTradeService.getByTradeNo(tradeNo);
            appleRefundRecord.setAmount(payTrade.getOccurAmount());
            appleRefundRecord.setUserId(payTrade.getSrcAccountId());
            appleRefundRecord.setTradeNo(tradeNo);
            appleRefundRecord.setBuff(payTrade.getAmount());
        }
        appleRefundRecord.setInsertTime(LocalDateTime.now());
        appleRefundRecordDao.insert(appleRefundRecord);
        //将用户加入渠道黑名单中
        channelBlacklistService.addBlacklist(appleRefundRecord.getUserId(), PayChannel.STRIPE.name());
        userSubscriptionService.completeNotify(receiptId);
    }

    private void doWenOnTimeCharge(Long receiptId, String payload) {
        try {
            JSONObject result = JSON.parseObject(payload);
            //获取通知类型
            String notificationType = result.getString("notificationType");

            if (!"ONE_TIME_CHARGE".equals(notificationType)) {
                return;
            }
            //环境不匹配，不允许处理
            String resultEvn = result.getString("environment");
            String resultBundleId = result.getString("bundleId");
            ToastResultCode.IAP_RECEIPT_STATUS_ERROR.check(!environment.equals(resultEvn));
            ToastResultCode.IAP_BUNDLE_ID_ERROR.check(!BUNDLE_ID.equals(resultBundleId));
            //生产环境密钥校验配置
            if ("PROD".equals(resultEvn) && !result.getString("password").equals(password)) {
                return;
            }
            JSONObject transactionInfoJson = result.getJSONObject("data").getJSONObject("transactionInfo");
            //获取退款对应的原始交易号
            String transactionId = transactionInfoJson.getString("transactionId");
            //更新请求，将交易号写到订阅id字段中
            userSubscriptionService.updateStatusAndSubscriptionId(receiptId, transactionId);
        } catch (Exception e) {
            log.info("===> doWenOnTimeCharge error");
            e.printStackTrace();
        }
    }

    /**
     * 处理收到渠道方的退款回调数据
     * @param payload
 */
    private void doWhenRefundV2(Long receiptId, String payload) {
        try {
            JSONObject result = JSON.parseObject(payload);
            JSONObject transactionInfoJson = result.getJSONObject("data").getJSONObject("transactionInfo");
            //获取通知类型
            String notificationType = result.getString("notificationType");

            if (!"REFUND".equals(notificationType)) {
                return;
            }
            //校验是IOS服务器
            if (!result.getString("password").equals(password)) {
                return;
            }
            //获取退款对应的原始交易号
            String originalTransactionId = transactionInfoJson.getString("originalTransactionId");
            //根据交易号查询出对应的退款数据
            AppleRefundRecord appleRefundRecord = appleRefundRecordDao.selectOne(Wrappers.<AppleRefundRecord>lambdaQuery()
                    .select(AppleRefundRecord::getId)
                    .eq(AppleRefundRecord::getTransactionId, originalTransactionId));
            if(appleRefundRecord == null) {
                log.info("===> doWhenRefundV2 data not found");
                return;
            }
            //将请求的数据给更新过来
            userSubscriptionService.updateNotifyExtend(receiptId, appleRefundRecord.getId(), null);
        } catch (Exception e) {
            log.info("===> doWhenRefundV2 error");
            e.printStackTrace();
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void doWhenPayFail(Long receiptId, String payload) {
        JSONObject result = JSON.parseObject(payload);
        JSONObject transactionInfoJson = result.getJSONObject("data").getJSONObject("transactionInfo");
        JSONObject renewalInfoJson = result.getJSONObject("data").getJSONObject("renewalInfo");
        //获取通知类型
        String notificationType = result.getString("notificationType");
        String subtype = result.getString("subtype");

        // DID_FAIL_TO_RENEW类型下：如果subtype是GRACE_PERIOD，则在宽限期内继续提供服务。如果为空，则说明订阅不在宽限期内，您可以停止提供订阅服务。
        if (!"DID_FAIL_TO_RENEW".equals(notificationType) || StringUtils.isNotEmpty(subtype)) {
            return;
        }
        //获取原始交易号
        String originalTransactionId = renewalInfoJson.getString("originalTransactionId");
        //获取并校验订阅信息，并重新设置最新的值
        renewalInfoJson = checkAndGetRenewalInfo(originalTransactionId);
        //修改当前通知的交易号为原始交易号
        userSubscriptionNotifyDao.setSubscriptionId(receiptId, originalTransactionId);
        //续订失败，发送系统消息
        userSubscriptionService.payFailSystemMessage(originalTransactionId, UserSubscription.Platform.APPLE);
        //更新通知日志数据的处理状态
        userSubscriptionService.completeNotify(receiptId);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void doWhenPay(Long receiptId, String payload) {
        JSONObject result = JSON.parseObject(payload);
        JSONObject transactionInfoJson = result.getJSONObject("data").getJSONObject("transactionInfo");
        JSONObject renewalInfoJson = result.getJSONObject("data").getJSONObject("renewalInfo");
        //获取通知类型
        String notificationType = result.getString("notificationType");

        //SUBSCRIBED 首次订阅、变更套餐（原来订阅1个月，换成订阅3个月的 会先失效掉1个月的，再订阅3个月的）、DID_RENEW 续订
        if (!"SUBSCRIBED".equals(notificationType) && !"DID_RENEW".equals(notificationType)) {
            return;
        }
        //获取原始交易号
        String originalTransactionId = renewalInfoJson.getString("originalTransactionId");
        //获取并校验订阅信息，并重新设置最新的值
        renewalInfoJson = checkAndGetRenewalInfo(originalTransactionId);
        //获取当前的交易号
        String transactionId = transactionInfoJson.getString("transactionId");
        //更新原始交易号
        userSubscriptionNotifyDao.setSubscriptionId(receiptId, originalTransactionId);

        //获取商品ID
        String productId = renewalInfoJson.getString("productId");
        //订购时间
        Long purchaseDateMs = Long.valueOf(renewalInfoJson.getString("recentSubscriptionStartDate"));
        //过期时间
        Long expiresDateMs = Long.valueOf(renewalInfoJson.getString("renewalDate"));

        //构造收据数据
        IOSReceiptInput iosReceiptInput = IOSReceiptInput.builder()
                .originalTransactionId(originalTransactionId)
                .transactionId(transactionId)
                .productId(productId)
                .expires_date_ms(expiresDateMs)
                .purchase_date_ms(purchaseDateMs).build();
        //绑定订阅关系，并发送mq消息通知业务系统
        userSubscriptionService.bind(iosReceiptInput.getSubscriptionId(), UserSubscription.Platform.APPLE, iosReceiptInput.getExpiresDate(), iosReceiptInput.getPurchaseDate(), productId);
        //更新订阅通知处理状态
        userSubscriptionService.completeNotify(receiptId);
    }


    //IOS 退订还没有调通
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void doWhenCancel(Long receiptId, String payload) {
        JSONObject result = JSON.parseObject(payload);
        JSONObject renewalInfoJson = result.getJSONObject("data").getJSONObject("renewalInfo");
        //获取通知类型
        String notificationType = result.getString("notificationType");

        //EXPIRED 出现在过期或从1个月切换成3个月时，先过期退款，然后再重新订阅
        if (!"EXPIRED".equals(notificationType)) {
            return;
        }
        //获取原始交易号
        String originalTransactionId = renewalInfoJson.getString("originalTransactionId");
        //获取并校验订阅信息，并重新设置最新的值
        renewalInfoJson = checkAndGetRenewalInfo(originalTransactionId);
        //更新原始交易号
        userSubscriptionNotifyDao.setSubscriptionId(receiptId, originalTransactionId);

        //过期时间
        Long expiresDateMs = Long.valueOf(renewalInfoJson.getString("renewalDate"));
        //更新过期时间，并发送mq消息通知业务系统
        userSubscriptionService.cancel(originalTransactionId, UserSubscription.Platform.APPLE, expiresDateMs);
        //更新日志数据处理状态
        userSubscriptionService.completeNotify(receiptId);
    }


    public JSONObject checkAndGeLatestReceiptInfoByEnv(String receipt, String environment) {
        String url;
        if ("Sandbox".equals(environment)) {
            url = SANDBOX_VERIFY_RECEIPT_URL;
        } else {
            url = PRODUCT_VERIFY_RECEIPT_URL;
        }
        JSONObject request = new JSONObject();
        request.put("receipt-data", receipt);
        request.put("password", password);
        String str = netRetryUtils.postForObject(url, request, String.class);
        log.info("iap verify receipt result: {}, receiptId: {}", str, receipt);
        JSONObject result = JSON.parseObject(str);
        int status = result.getInteger("status");

        ToastResultCode.IAP_RECEIPT_STATUS_ERROR.check(status != SUCC_STATUS);
        JSONObject receiptObj = result.getJSONObject("receipt");
        ToastResultCode.IAP_BUNDLE_ID_ERROR.check(!BUNDLE_ID.equals(receiptObj.getString("bundle_id")));
        JSONArray latestReceiptInfoArray = result.getJSONArray("latest_receipt_info");
        JSONObject latestReceiptInfo = latestReceiptInfoArray.getJSONObject(0);
        return latestReceiptInfo;
    }


    /**
     * 获取并校验订阅信息
     * @param originalTransactionId
     * @return
     */
    public JSONObject checkAndGetRenewalInfo(String originalTransactionId) {
        try {
            String token = JwtUtils.getAppleJwt(BUNDLE_ID);
            String url = verifySubscriptionUrl + originalTransactionId;
            HttpResponse<String> response = Unirest.get(url)
                    .header("Authorization", "Bearer " + token)
                    .asString();
            int status = response.getStatus();
            if (status != 200) {
                throw new RuntimeException("HTTP Error: " + status);
            }
            String result = response.getBody();
            log.info("===> checkAndGetRenewalInfo result : {}", result);
            JSONObject jsonObject = JSON.parseObject(result);
            //环境不匹配，不允许处理
            String resultEvn = jsonObject.getString("environment");
            String resultBundleId = jsonObject.getString("bundleId");
            ToastResultCode.IAP_RECEIPT_STATUS_ERROR.check(!environment.equals(resultEvn));
            ToastResultCode.IAP_BUNDLE_ID_ERROR.check(!BUNDLE_ID.equals(resultBundleId));
            //获取订阅基础信息
            String signedRenewalInfo = jsonObject.getJSONArray("data").getJSONObject(0).getJSONArray("lastTransactions").getJSONObject(0).getString("signedRenewalInfo");
            log.info("===> checkAndGetRenewalInfo signedRenewalInfo : {}", signedRenewalInfo);
            return JSONObject.parseObject(JwtUtils.getJwtBody(signedRenewalInfo));
        } catch (Exception e) {
            log.error("===> checkAndGetRenewalInfo error : ", e);
            ToastResultCode.SYSTEM_EXCEPTION.check(true);
        }
        return null;
    }

}
