287 lines
10 KiB
Objective-C
287 lines
10 KiB
Objective-C
//
|
||
// 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<NSString *,id> * _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
|