// // IAPManager.m // YuMi // // Created by P on 2024/9/24. // #import "IAPManager.h" #import "Api+Mine.h" #import "YuMi-swift.h" #import "RechargeStorage.h" @interface IAPManager() @property (nonatomic, copy) NSString *orderID; @property (nonatomic, copy) NSString *transactionID; @property (nonatomic, copy) void(^successPurchase)(void); @property (nonatomic, copy) void(^failurePurchase)(NSError *error); @property (nonatomic, copy) void(^contactCustomerService)(NSString *uid); @end @implementation IAPManager + (instancetype)sharedManager { static IAPManager *proxy; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ proxy = [[self alloc] init]; }); return proxy; } - (void)purchase:(NSString *)productId success:(void(^)(void))success failure:(void(^)(NSError *error))failure contactCS:(void(^)(NSString *uid))contactCS { self.successPurchase = success; self.failurePurchase = failure; self.contactCustomerService = contactCS; [self handleIAPState]; [self requestAPPOrderData:productId]; } - (void)handleSuccessPurchase { if (self.successPurchase) { @kWeakify(self); dispatch_async(dispatch_get_main_queue(), ^{ @kStrongify(self); self.successPurchase(); }); } } - (void)handleFailurePurchase:(NSString *)errorMsg { if (self.failurePurchase) { @kWeakify(self); dispatch_async(dispatch_get_main_queue(), ^{ @kStrongify(self); if (errorMsg.length == 0) { self.failurePurchase(nil); } else { self.failurePurchase([NSError errorWithDomain:errorMsg code:-1 userInfo:nil]); } }); } } - (void)handleContactCS { @kWeakify(self); dispatch_async(dispatch_get_main_queue(), ^{ @kStrongify(self); TTAlertConfig *config = [[TTAlertConfig alloc]init]; config.title = YMLocalizedString(@"XPIAPRechargeViewController7"); config.message = YMLocalizedString(@"XPIAPRechargeViewController8"); TTAlertButtonConfig *confirmButtonConfig = [[TTAlertButtonConfig alloc]init]; confirmButtonConfig.title = YMLocalizedString(@"XPIAPRechargeViewController9"); UIImage *image = [UIImage gradientColorImageFromColors:@[UIColorFromRGB(0x13E2F5),UIColorFromRGB(0x9DB4FF),UIColorFromRGB(0xCC67FF)] gradientType:GradientTypeLeftToRight imgSize:CGSizeMake(200, 200)]; confirmButtonConfig.backgroundColor = [UIColor colorWithPatternImage:image]; confirmButtonConfig.cornerRadius = 38/2; config.confirmButtonConfig = confirmButtonConfig; @kWeakify(self); [TTPopup alertWithConfig:config confirmHandler:^{ @kStrongify(self); [self loadCSUid]; } cancelHandler:^{}]; }); } - (void)handleIAPState { if (@available(iOS 15.0, *)) { @kWeakify(self); [[PIIAPRegulate shared] setConditionBlock:^(enum StoreConditionResult state, NSDictionary * _Nullable param) { @kStrongify(self); switch (state) { case StoreConditionResultStart: NSLog(@"~~~```~~~ Purchase started"); break; case StoreConditionResultPay: NSLog(@"~~~```~~~ Processing payment"); break; case StoreConditionResultVerifiedServer: NSLog(@"~~~```~~~ Verified by server"); [self handleIAPSuccess:param]; break; case StoreConditionResultUserCancelled: NSLog(@"~~~```~~~ User cancelled purchase"); [self handleFailurePurchase:@""]; break; case StoreConditionResultNoProduct: NSLog(@"~~~```~~~ No product found"); [self handleFailurePurchase:[NSString stringWithFormat:@"%@ No product found", YMLocalizedString(@"XPIAPRechargeViewController1")]]; break; case StoreConditionResultFailedVerification: NSLog(@"~~~```~~~ Verification failed"); [self handleFailurePurchase:YMLocalizedString(@"XPIAPRechargeViewController1")]; break; case StoreConditionResultUnowned: NSLog(@"~~~```~~~ Result Unowned"); [self handleFailurePurchase:YMLocalizedString(@"XPIAPRechargeViewController1")]; break; default: [self handleFailurePurchase:YMLocalizedString(@"XPIAPRechargeViewController0")]; break; } }]; } } // 生成后端订单 - (void)requestAPPOrderData:(NSString *)productId { if (@available(iOS 15.0, *)) { @kWeakify(self); [Api requestIAPRecharge:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) { @kStrongify(self); if (code == 200) { NSString *orderId = (NSString *)data.data[@"recordId"]; NSString *uuid = (NSString *)data.data[@"appAccountToken"]; [self requestIAPOrder:orderId productID:productId uuid:uuid]; } else if (code == 50000) { [self handleContactCS]; [self handleFailurePurchase:@""]; } else { [self handleFailurePurchase:msg]; } } chargeProdId:productId uid:[AccountInfoStorage instance].getUid ticket:[AccountInfoStorage instance].getTicket deviceInfo:[YYUtility deviceID] clientIp:[YYUtility ipAddress]]; } else { [self handleFailurePurchase:YMLocalizedString(@"XPIAPRechargeViewController10")]; } } // 获取客服 UID - (void)loadCSUid { if (self.contactCustomerService) { [Api requestContactCustomerServiceCompletion:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) { if (code == 200) { NSString *uid = [NSString stringWithFormat:@"%@",data.data]; self.contactCustomerService(uid); } }]; } } // 开始进行内购,查询线上是否有对应 productID 的物品,如果有,会自动拉起付费弹窗(PIIAPRegulate - purchase) - (void)requestIAPOrder:(NSString *)orderID productID:(NSString *)productID uuid:(NSString *)uuid { self.orderID = orderID; if (@available(iOS 15.0, *)) { @kWeakify(self); [[PIIAPRegulate shared] demandCommodityThingWithProductId:productID uuid:uuid completionHandler:^(NSError * _Nullable error) { @kStrongify(self); if (error) { // 已在 ConditionBlock 中回调 } }]; } } // 处理内购付款成功 - (void)handleIAPSuccess:(NSDictionary *)param { self.transactionID = [param objectForKey:@"transactionId"]; if (self.transactionID.length == 0) { [self handleFailurePurchase:YMLocalizedString(@"XPIAPRechargeViewController1")]; return; } [self saveTransactionID]; [self checkReceiptWithTransactionID]; } // 内购付款成功后保存收据 id 到钥匙串 - (void)saveTransactionID { NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; NSString *encodedReceipt = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; // 初始化字典并逐步填充 NSMutableDictionary *receiptInfo = [NSMutableDictionary dictionary]; // 使用通用方法添加字典键值对,减少冗余代码 [self addValueIfNotNil:self.transactionID forKey:@"transactionId" toDictionary:receiptInfo]; [self addValueIfNotNil:encodedReceipt forKey:@"receipt" toDictionary:receiptInfo]; [self addValueIfNotNil:self.orderID forKey:@"orderId" toDictionary:receiptInfo]; // 如果字典为空,直接返回 if (receiptInfo.count == 0) { return; } // 保存数据到 RechargeStorage [RechargeStorage saveTranscationId:self.transactionID recipt:[receiptInfo toJSONString] uid:[AccountInfoStorage instance].getUid]; } // 通过苹果收据与后端订单进行验单与发货 // MARK: 必须等待结果 - (void)checkReceiptWithTransactionID { @kWeakify(self); [Api checkReceipt:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) { @kStrongify(self); if (code == 200) { [self handleSuccessPurchase]; [self handleCheckReceiptSuccess]; } else { // TODO: 重试逻辑 [self handleFailurePurchase:msg]; if (code == 1444) { // 参数异常 } } } chooseEnv:@"true" chargeRecordId:self.orderID transcationId:self.transactionID uid:[AccountInfoStorage instance].getUid ticket:[AccountInfoStorage instance].getTicket]; } // 查找在缓存中的 transactionID 并处理 - (void)handleCheckReceiptSuccess { if (@available(iOS 15.0, *)) { @kWeakify(self); [[PIIAPRegulate shared] verifyBusinessAccomplishWithTransactionID:self.transactionID completionHandler:^(BOOL success, NSError * _Nullable error) { @kStrongify(self); if (success) { // 移除全部缓存 if ([RechargeStorage delegateTranscationId:self.transactionID uid:[AccountInfoStorage instance].getUid]) { self.orderID = @""; self.transactionID = @""; } } else { // TODO: 补单 } }]; } } - (void)addValueIfNotNil:(id)value forKey:(NSString *)key toDictionary:(NSMutableDictionary *)dictionary { if (value != nil && key != nil) { dictionary[key] = value; } } #pragma mark - RETRY @end