Files
peko-ios/YuMi/Modules/YMMine/View/Recharge/IAPManager.m
2024-09-27 11:17:04 +08:00

440 lines
16 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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, assign) BOOL isLogin;
@property (nonatomic, assign) NSInteger recheckInterval;
@property (nonatomic, assign) NSInteger recheckIndex;
@property (nonatomic, strong) NSTimer *recheckTimer;
@property (nonatomic, assign) BOOL isProcessing;
@property (nonatomic, copy) NSString *orderID;
@property (nonatomic, copy) NSString *transactionID;
@property (nonatomic, copy) void(^successPurchase)(NSString *transactionID, NSString *orderID);
@property (nonatomic, copy) void(^successRecheck)(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];
proxy.recheckIndex = 0;
proxy.recheckInterval = 1.0;
});
return proxy;
}
- (void)handleLogin {
self.isLogin = YES;
}
- (void)handleLogout {
self.isLogin = NO;
if (self.recheckTimer) {
[self.recheckTimer invalidate];
}
}
// 开始正常内购流程
- (void)purchase:(NSString *)productId
success:(void(^)(NSString *transactionID, NSString *orderID))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 isFroRecheck:NO];
}
// 定时轮训未完成的订单并进行验单
- (void)retryCheckAllReceipt {
self.recheckTimer = [NSTimer scheduledTimerWithTimeInterval:self.recheckInterval
target:self
selector:@selector(handleRetryCheckReceipt)
userInfo:nil
repeats:NO];
}
// 验单逻辑
- (void)handleRetryCheckReceipt {
NSArray *array = [RechargeStorage getAllReceiptsWithUid:[AccountInfoStorage instance].getUid];
NSLog(@" ------------.------------ 尝试:%@", array);
@synchronized (array) {
if (array.count == 0 || self.isProcessing) {
return;
}
if (!self.isLogin) {
return;
}
if (self.recheckIndex >= array.count) {
self.recheckIndex = 0;
}
// 尝试请求一下订单号,看看能否触发后端检查用户是否有未下发内容
[self requestAPPOrderData:@"com.hflighting.yumi.gold.1_7000" isFroRecheck:YES];
self.isProcessing = YES;
NSDictionary *dic = [array xpSafeObjectAtIndex:self.recheckIndex];
NSString *transactionId = dic[@"transactionId"];
[self backgroundCheckReceiptWithTransactionID:transactionId
orderID:dic[@"orderId"]
next:^(BOOL isSuccess){
if (isSuccess) {
[RechargeStorage delegateTransactionId:transactionId
uid:[AccountInfoStorage instance].getUid];
self.recheckInterval = 1.0;
} else {
self.recheckInterval = self.recheckInterval * 2;
}
self.recheckIndex += 1;
self.isProcessing = NO;
[self.recheckTimer invalidate];
[self retryCheckAllReceipt];
}];
}
}
// 内购成功并通知外部
- (void)handleSuccessPurchase:(NSString *)tID order:(NSString *)orderID {
if (self.successPurchase) {
@kWeakify(self);
dispatch_async(dispatch_get_main_queue(), ^{
@kStrongify(self);
self.successPurchase(tID, orderID);
});
}
if (self.successRecheck) {
@kWeakify(self);
dispatch_async(dispatch_get_main_queue(), ^{
@kStrongify(self);
self.successRecheck();
});
}
}
// 内购失败并通知外部
- (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:^{}];
});
}
// 获取客服 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);
}
}];
}
}
// 监听内购流程状态
- (void)handleIAPState {
if (@available(iOS 15.0, *)) {
@kWeakify(self);
[[PIIAPRegulate shared] setConditionBlock:^(enum StoreConditionResult state, NSDictionary<NSString *,NSString *> * _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 isFroRecheck:(BOOL)isFroRecheck {
if (@available(iOS 15.0, *)) {
@kWeakify(self);
[Api requestIAPRecharge:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
if (isFroRecheck) {
return;
}
@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")];
}
}
// 获取后端订单后,查询线上是否有对应 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 {
id tid = param[@"transactionId"];
self.transactionID = tid;
if (self.transactionID.length == 0) {
[self handleFailurePurchase:YMLocalizedString(@"XPIAPRechargeViewController1")];
return;
}
[self saveTransactionID];
[self checkReceiptWithTransactionID:self.transactionID
orderID:self.orderID];
}
// 内购付款成功后保存收据 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
@synchronized (self.transactionID) {
[RechargeStorage saveTransactionId:self.transactionID
receipt:[receiptInfo toJSONString]
uid:[AccountInfoStorage instance].getUid];
}
NSLog(@" ------------.------------ 保存账单内容:%@ ", receiptInfo);
}
// 通过苹果收据与后端订单进行验单与发货
// MARK: 必须等待结果
- (void)checkReceiptWithTransactionID:(NSString *)tID
orderID:(NSString *)orderID {
NSLog(@" ------------.------------ 后端验单:%@ | %@", tID, orderID);
@kWeakify(self);
[Api checkReceipt:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
@kStrongify(self);
if (code == 200) {
[self handleSuccessPurchase:tID order:orderID];
[self handleCheckReceiptSuccess:tID isFromRecheck:NO];
} else {
[self handleFailurePurchase:msg];
if (code == 1444) {
// 参数异常,暂不处理
}
}
NSLog(@" ------------.------------ 后端验单结果:%@ ",msg);
}
chooseEnv:@"true"
chargeRecordId:orderID
transcationId:tID
uid:[AccountInfoStorage instance].getUid
ticket:[AccountInfoStorage instance].getTicket];
}
// 通过苹果收据与后端订单进行验单与发货,此方法是对本地未删除的订单进行检测
- (void)backgroundCheckReceiptWithTransactionID:(NSString *)tID
orderID:(NSString *)orderID
next:(void(^)(BOOL isSuccess))next {
NSLog(@" ------------.------------ 尝试后端验单:%@ | %@", tID, orderID);
if (tID.length == 0 || orderID.length == 0) {
// 本地数据异常,不重试
[self handleCheckReceiptSuccess:tID isFromRecheck:YES];
next(NO);
return;
}
@kWeakify(self);
[Api checkReceipt:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
@kStrongify(self);
if (code == 200) {
[self handleCheckReceiptSuccess:tID isFromRecheck:YES];
} else if (code == 1701) {
// 交易已完成,無需重複請求
[self handleCheckReceiptSuccess:tID isFromRecheck:YES];
}
NSLog(@" ------------.------------ 尝试后端验单结果:%ld, %@ ", (long)code, msg);
if (next) {
next(code == 200 || code == 1701);
}
}
chooseEnv:@"true"
chargeRecordId:orderID
transcationId:tID
uid:[AccountInfoStorage instance].getUid
ticket:[AccountInfoStorage instance].getTicket];
}
// 查找在缓存中的 transactionID 并告知 apple 订单已完成
- (void)handleCheckReceiptSuccess:(NSString *)tID
isFromRecheck:(BOOL)isFromRecheck {
if (@available(iOS 15.0, *)) {
@kWeakify(self);
[[PIIAPRegulate shared] verifyBusinessAccomplishWithTransactionID:tID
completionHandler:^(BOOL success, NSError * _Nullable error) {
@kStrongify(self);
if (success) {
NSLog(@" ------------.------------ apple 验单成功");
// 流程完成,移除本地缓存账单
[RechargeStorage delegateTransactionId:tID
uid:[AccountInfoStorage instance].getUid];
} else {
// 出现异常
NSLog(@" ------------.------------ apple 验单成功:%@ ",error);
if (error == nil) {
// 该订单在 appstore 已无法找到,不必再重试
[RechargeStorage delegateTransactionId:tID
uid:[AccountInfoStorage instance].getUid];
} else {
[self retryCheckAllReceipt];
}
}
if (!isFromRecheck) {
// 正常购买情况,重置内存订单数据
self.orderID = @"";
self.transactionID = @"";
}
}];
}
}
- (void)addValueIfNotNil:(id)value
forKey:(NSString *)key
toDictionary:(NSMutableDictionary *)dictionary {
if (value != nil && key != nil) {
dictionary[key] = value;
}
}
@end