在极少数情况下,我的某些用户似乎无法进行非消耗品购买。当他们尝试购买时,它不会激活“premium”,当他们从当前安装恢复或新安装时paymentQueue: updatedTransactions:
未被调用。
我专门添加了大量日志记录,以尝试确定还原未遵循预期流量的原因。在恢复失败期间,不会触发任何“RESTORE”类别事件。
仅供参考[self success];
显示内容视图,而[self fail:]
会向用户显示错误消息。
同样在[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
中调用viewDidLoad
,按下按钮时调用[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
。
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
// COMPLETION POINT - RESTORE COMPLETE***
[MBProgressHUD hideHUDForView:self.view animated:TRUE];
if ([SKPaymentQueue defaultQueue].transactions.count == 0) {
[self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"RESTORE"
action:@"failure_hard"
label:@"no_purchases"
value:nil] build]];
[self fail:@"There are no items available to restore at this time."];
} else {
[self success];
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
// COMPLETION POINT - RESTORE FAILED
[MBProgressHUD hideHUDForView:self.view animated:TRUE];
[self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"RESTORE"
action:@"failure_hard"
label:error.localizedDescription
value:nil] build]];
[self fail:error.localizedDescription];
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
// Make sure completion states call [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
// in order to prevent sign in popup
// http://stackoverflow.com/a/10853107/740474
[MBProgressHUD hideHUDForView:self.view animated:TRUE];
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
break;
case SKPaymentTransactionStateDeferred:
break;
case SKPaymentTransactionStateFailed:
// COMPLETION POINT - PURCHASE FAILED
[self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"PURCHASE"
action:@"failure_hard"
label:transaction.error.localizedDescription
value:nil] build]];
if (transaction.error.code != SKErrorPaymentCancelled) {
// only show error if not a cancel
[self fail:transaction.error.localizedDescription];
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStatePurchased:
// COMPLETION POINT - PURCHASE SUCCESS
if ([transaction.payment.productIdentifier isEqualToString:(NSString*)productID]) {
// premium purchase successful
[self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"PURCHASE"
action:@"success"
label:nil
value:nil] build]];
[Utils setPremium:YES];
[self success];
} else {
[self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"PURCHASE"
action:@"failure_hard"
label:@"no_id"
value:nil] build]];
[self fail:@"The item you purchased was not returned from Apple servers. Please contact us."];
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
if ([transaction.payment.productIdentifier isEqualToString:(NSString*)productID]) {
// premium purchase restored
[self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"RESTORE"
action:@"restore_success"
label:nil
value:nil] build]];
[Utils setPremium:YES];
} else {
[self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"RESTORE"
action:@"failure_hard"
label:@"no_id"
value:nil] build]];
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
default:
// For debugging
[self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"STORE"
action:@"transaction_weird"
label:[NSString stringWithFormat:@"Unexpected transaction state %@", @(transaction.transactionState)]
value:nil] build]];
break;
}
}
}
任何建议都将不胜感激
答案 0 :(得分:3)
您是否实施了以下方法:
它是SKRequestDelegate的可选方法之一。
我们也遇到了丢失恢复购买电话的同样问题。处理这个代表帮助了我们。由于任何原因,甚至没有被送到队列的所有请求都是在这个失败代表中提供的。
所以,我认为你可能面临同样的问题。
答案 1 :(得分:2)
开始恢复过程 -
-(void)restore{
isRestored = false;
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
如果成功恢复任何事务,则调用以下方法 -
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
case SKPaymentTransactionStateRestored:
DDLogVerbose(@"Restored");
//Check with your product id if it is the right product that you want to restore
if ([transaction.payment.productIdentifier isEqualToString:IAP_PRODUCT_ID]) {
isRestored = true;
// Successfully restored the payment, provide the purchased content to the user.
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
当支付队列完成发送已恢复的事务时,将调用以下方法(如果调用它意味着它已完成事务而不是该恢复成功) -
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{
DDLogVerbose(@"Restore completed");
if (isRestored) {
// Successfully restored
}else{
// No transaction to restore
}
}
paymentQueueRestoreCompletedTransactionsFinished
恢复事务时发生任何错误时,将调用以下方法 -
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error{
DDLogVerbose(@"Error in restoring:%@",error);
if (error.code == 0) {
// unable to connect to iTunes
}
}
答案 2 :(得分:2)
按预期使应用程序行为的几个步骤:
1。在AppDelegate
中添加事务观察器,跟踪延迟响应&amp;每当您的应用程序启动时,它都会更新&amp;完成队列中的交易
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
删除applicationWillTerminate
中的观察者[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
2. 第二步,在要求用户购买之前检查应用内收据
-(void)validateReceiptsFromAppStoreFor:(NSString *)productTag completionBlock:(void (^)(NSDictionary *receiptResponse,NSError *error))completion
{
//check if receipt exists in app bundle
//else request for refresh receipt data..
//if receipt exists,verify with server & check if product tag exists in receipt & send receipt response as success msg
//else wait for refresh request success
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt)
{ /* No local receipt -- handle the error. */
refreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
refreshRequest.delegate = self;
[refreshRequest start];
return;
}
/* ... Send the receipt data to your server ... */
NSError *error;
NSDictionary *requestContents = @{
@"password":@"Your shared secret key",
@"receipt-data": [receipt base64EncodedStringWithOptions:0]
};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
options:0
error:&error];
if (!requestData) { /* ... Handle error ... */ }
// Create a POST request with the receipt data.
NSString *storeURL = SANDBOX_VERIFY_RECEIPT_URL; //ITMS_PROD_VERIFY_RECEIPT_URL;
if ([[[FFGDefaults sharedDefaults] objectForKey:@"environmentType"] isEqualToString:@"prod"])
{
storeURL = PROD_VERIFY_RECEIPT_URL;
}
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:storeURL]];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];
// Make a connection to the iTunes Store on a background queue.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
/* ... Handle error ... */
if (completion) {
completion(nil,connectionError);
}
} else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (completion)
{
jsonResponse = jsonResponse[@"receipt"];
if ([jsonResponse[@"bundle_id"] isEqualToString:[NSBundle mainBundle].bundleIdentifier])
{
//check if product was purchased earlier..
NSString *str_productID = [CFCommonUtils productIDForPlanTag:productTag];
NSArray *receiptArr = jsonResponse[@"in_app"];
if (receiptArr && receiptArr.count>0)
{
NSArray *filteredArray = [receiptArr filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"product_id = %@",str_productID]];
if (filteredArray.count>0) {
completion(jsonResponse,error);
}
else
{
NSError *err = [NSError errorWithDomain:@"" code:100 userInfo:nil];
completion(nil,err);
}
}
else
{
NSError *err = [NSError errorWithDomain:@"" code:100 userInfo:nil];
completion(nil,err);
}
}
else
{
NSError *err = [NSError errorWithDomain:@"" code:100 userInfo:nil];
completion(nil,err);
}
}
}
}];
}
3. 处理收据刷新委托方法以检查更新后的收据
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
#ifdef DEBUG
NSLog(@"SKRequest : didFailWithError :%@",error);
#endif
if ([request isMemberOfClass:[SKReceiptRefreshRequest class]] && refreshRequest && delegate && [delegate respondsToSelector:@selector(receiptRefreshed:error:)])
{
[self receiptRefreshed:self error:error];
refreshRequest = nil;
}
else
{
}
}
- (void)requestDidFinish:(SKRequest *)request
{
#ifdef DEBUG
NSLog(@"SKRequest : requestDidFinish ");
#endif
if ([request isMemberOfClass:[SKReceiptRefreshRequest class]] && refreshRequest && delegate && [delegate respondsToSelector:@selector(receiptRefreshed:error:)])
{
[self receiptRefreshed:self error:nil];
refreshRequest = nil;
}
else
{
}
}
-(void) receiptRefreshed:(CFStorekitManager*)ebp error:(NSError *)error
{
if (error)
{
}
else
{
[self validateSubscriptionReceiptsFromAppStoreWithRefreshReceipt:YES completion:^(NSDictionary *receiptResponse, NSError *error)
{
dispatch_async(dispatch_get_main_queue(), ^{
if (error)
{
//show subscription for purchase
}
else
{
}
});
}];
}
}
答案 3 :(得分:1)
我不确定这是否与您的问题有关,但可能是原因。 (或者至少建议苹果做这件事。)
Apple建议您在AppDelegate中注册 SKPaymentQueue 作为观察者,而不是在特定类中注册(除非您在AppDelegate中调用此类)
这意味着:
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
实际上应该进入AppDelegate中的 applicationDidFinishLaunchingWithOptions 方法。
为什么这很重要?你提到过:
在恢复失败期间,没有“RESTORE”类别事件 烧成。
这让我相信你的听众没有正确注册或准时注册。 (或者在非常罕见的情况下,用户可能会被重定向到您的应用程序之外以正确登录或某些内容,并且由于内存问题,您的应用可能会被杀死?无论哪种方式,这都可以确保一旦他们返回到您的应用程序,将始终存在观察员准备处理Apple发送的任何通知
使用App Receipt恢复购买
文档here。
或者,通过刷新应用收据,然后根据用户购买的内容提供内容,实施还原购买逻辑要容易得多。
request = [[SKReceiptRefreshRequest alloc] init];
request.delegate = self;
[request start];
这将调用您的委托方法:
func requestDidFinish(SKRequest)
或
func request(SKRequest, didFailWithError: Error)
请求成功完成后,您可以解析收据以向用户授予之前购买的所有商品。收据解析指南的描述为here。
答案 4 :(得分:1)
您是否有可能在应用中使用Firebase分析?
https://firebase.google.com/docs/analytics/ios/start说
如果您要跟踪应用内购买,则必须初始化 应用程序中的事务观察器:didFinishLaunchingWithOptions: 在初始化Firebase之前,否则您的观察者可能不会收到全部 购买通知。请参阅Apple的应用内购买最佳做法,以了解 更多信息。
在这种情况下,建议在初始化Firebase分析之前初始化观察者。
以下是博客文章,其中包含其他详细信息:https://www.greensopinion.com/2017/03/22/This-In-App-Purchase-Has-Already-Been-Bought.html
答案 5 :(得分:0)
据我所知,如果restoreCompletedTransactions()
调用已完成,但并未导致任何交易被还原,则{strong>未调用
paymentQueue(_:, updateTransactions:)
因此我们可以检查func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
if queue.transactions.count == 0 {
// does NOT call paymentQueue:updatedTransactions:
} else {
// should call paymentQueue:updatedTransactions:
}
}
以确定是否将调用其他委托方法。