0

IAP 開発では、まず、リンクの指示に従いました: http://troybrant.net/blog/2010/01/in-app-purchases-a-full-walkthrough/

幸いなことに、すぐに IAP ダイアログ ボックスが表示されました (XXX を 0.99 ドルで購入しますか?)。すべてが機能しているように見えました!その後、領収書の検証に取り組み始めました。

1 日か 2 日後、Invalid Product IDの件を受け取りました。ほとんどすべての場所にブレークポイントを追加し、最終的に、

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response

メソッドはまったく呼び出されませんでした。その後、すべてをグーグルで調べるのに約2週間かかりましたが、進歩はありませんでした。次に、startPayment メソッドを削除しました。

- (void)startPayment
{
   SKPayment *payment = [SKPayment paymentWithProduct: myProduct];
   [[SKPaymentQueue defaultQueue] addPayment:payment];
}

行にブレークポイントを追加しました

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response

3 ~ 5 秒待ってからメソッドが呼び出されたことがわかりました。無効な製品 IDの問題はありません。

したがって、productsRequest didReceiveResponseメソッド内でstartPayment メソッドを呼び出しました。はい、無効な製品 IDの問題は解決しましたが、別の問題が発生しました。

2 ~ 3 秒待ってアプリがクラッシュし (iPhone のメイン メニューに戻る)、1 ~ 2 秒後に IAP ダイアログ ボックスが表示されました。ダイアログボックスがiPhoneのメインメニューに直接ポップアップするようです。アプリのアイコンをもう一度クリックすると、ダイアログ ボックスが消え、アプリが最初から読み込まれました。

一体何が起こっているのか教えてくれる人はいますか?どうもありがとうございました!

4

1 に答える 1

0

アプリ内購入にはリクエストのキューがあります。リクエストが完了していない場合、リクエストはキューに残り、アプリの再起動時にリクエストを続行しようとします。適切なメソッドを呼び出してリクエストを完了するのは、開発者の責任です。

覚えておいてください: Apple のサンドボックス サービスは遅くなる可能性がありますが、ライブ サーバーははるかに高速である必要があります。

ちょうど今日、アプリ内購入をアプリに統合しました。教育目的でコードを下に投稿します。すべてのコードをシングルトンに入れることにしました。最善の解決策ではないかもしれません (そうかもしれませんが、そうでないかもしれません) が、私の目的では問題なく動作します。私の店は今のところ 1 つの商品しか扱っていないので、もっと多くの商品を販売する場合、コードは少し実用的ではないかもしれませんが、心に留めておくようにしています。

PS:以下のコードは進行中の作業であることに注意してください。私にはうまくいくようですが、問題の解決に役立つかもしれません。

ProductStore.h

@interface FSProductStore : NSObject

+ (FSProductStore *)defaultStore;

// AppDelegate should call this on app start (appDidFinishLoading) ...
- (void)registerObserver;

// handling product requests ...
- (void)startProductRequestWithIdentifier:(NSString *)productIdentifier
                        completionHandler:(void (^)(BOOL success, NSError *error))completionHandler;
- (void)cancelProductRequest;

@end

FSProductStore.m

#import "FSProductStore.h"
#import <StoreKit/StoreKit.h>
#import "NSData+Base64.h"
#import "AFNetworking.h"
#import "FSTransaction.h"


@interface FSProductStore () <SKProductsRequestDelegate, SKPaymentTransactionObserver>

- (void)startTransaction:(SKPaymentTransaction *)transaction;
- (void)completeTransaction:(SKPaymentTransaction *)transaction;
- (void)failedTransaction:(SKPaymentTransaction *)transaction;
- (void)restoreTransaction:(SKPaymentTransaction *)transaction;
- (void)validateReceipt:(NSData *)receiptData withCompletionHandler:(void (^)(BOOL success, NSError *error))completionHandler;

- (void)purchaseSuccess:(NSString *)productIdentifier;
- (void)purchaseFailedWithError:(NSError *)error;

@property (nonatomic, strong) SKProductsRequest *currentProductRequest;
@property (nonatomic, copy) void (^completionHandler)(BOOL success, NSError *error);

@end


@implementation FSProductStore

+ (FSProductStore *)defaultStore
{
    static FSProductStore *store;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!store)
        {
            store = [[FSProductStore alloc] init];
        }
    });

    return store;
}

- (void)registerObserver
{
    DLog(@"registering observer ...");

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}

#pragma mark - Products request delegate

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    if (!response.products || response.products.count == 0)
    {
        NSError *error = [NSError errorWithDomain:FSMyAppErrorDomain code:FSNoProductsAvailableError];
        [self purchaseFailedWithError:error];
    }
    else
    {
        SKProduct *product = response.products[0];
        SKPayment *payment = [SKPayment paymentWithProduct:product];

        if ([SKPaymentQueue canMakePayments])
        {
            [[SKPaymentQueue defaultQueue] addPayment:payment];
        }
        else
        {
            NSError *error = [NSError errorWithDomain:FSMyAppErrorDomain code:FSInAppPurchaseDisabledError];
            [self purchaseFailedWithError:error];
        }
        DLog(@"%@", response.products);
    }
}

#pragma mark - Payment transaction observer

// Sent when the transaction array has changed (additions or state changes).  Client should check state of transactions and finish as appropriate.
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    DLog(@"%@", transactions);

    for (SKPaymentTransaction *transaction in transactions)
    {
        DLog(@"%@", transaction);

        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchasing: [self startTransaction:transaction];    break;
            case SKPaymentTransactionStateFailed:     [self failedTransaction:transaction];   break;
            case SKPaymentTransactionStatePurchased:  [self completeTransaction:transaction]; break;
            case SKPaymentTransactionStateRestored:   [self restoreTransaction:transaction];  break;
            default: break;
        }
    }
}

// Sent when an error is encountered while adding transactions from the user's purchase history back to the queue.
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
    DLog(@"%@", error);

    [self purchaseFailedWithError:error];
}

// Sent when transactions are removed from the queue (via finishTransaction:).
- (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions
{
    DLog(@"%@", transactions);
}

// Sent when all transactions from the user's purchase history have successfully been added back to the queue.
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    DLog(@"%@", queue);
}

// Sent when the download state has changed.
- (void)paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads
{
    DLog(@"%@", downloads);
}


#pragma mark - Public methods

- (void)startProductRequestWithIdentifier:(NSString *)productIdentifier
                        completionHandler:(void (^)(BOOL success, NSError *error))completionHandler
{
    if ([productIdentifier isEqualToString:FS_PRODUCT_DISABLE_ADS] == NO)
    {
        DLog(@"ERROR: invalid product identifier!");

        NSError *error = [NSError errorWithDomain:FSMyAppErrorDomain code:FSInvalidProductIdentifier];

        if (completionHandler)
        {
            completionHandler(NO, error);
        }

        return;
    }


    // cancel any existing product request (if exists) ...

    [self cancelProductRequest];


    // start new  request ...

    self.completionHandler = completionHandler;

    NSSet *productIdentifiers = [NSSet setWithObject:productIdentifier];
    self.currentProductRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
    _currentProductRequest.delegate = self;
    [_currentProductRequest start];
}

- (void)cancelProductRequest
{
    if (_currentProductRequest)
    {
        DLog(@"cancelling existing request ...");

        [_currentProductRequest setDelegate:nil];
        [_currentProductRequest cancel];
    }
}

#pragma mark - Private methods

- (void)startTransaction:(SKPaymentTransaction *)transaction
{
    DLog(@"starting transaction: %@", transaction);
}

- (void)completeTransaction: (SKPaymentTransaction *)transaction
{
    [self validateReceipt:transaction.transactionReceipt withCompletionHandler:^ (BOOL success, NSError *error) {
        if (success)
        {
            // Your application should implement these two methods.
            [self recordTransaction:transaction];
            [self purchaseSuccess:transaction.payment.productIdentifier];
        }
        else
        {
            // deal with error ...
            [self purchaseFailedWithError:error];
        }

        [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
    }];
}

- (void)failedTransaction: (SKPaymentTransaction *)transaction
{
    if (transaction.error.code != SKErrorPaymentCancelled) {
        [self purchaseFailedWithError:transaction.error];
    }

    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

- (void)restoreTransaction: (SKPaymentTransaction *)transaction
{
    [self recordTransaction:transaction];
    [self purchaseSuccess:transaction.originalTransaction.payment.productIdentifier];

    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

- (void)recordTransaction:(SKPaymentTransaction *)transaction
{
    DLog(@"recording transaction: %@", transaction);


    // TODO: store for audit trail - perhaps on remote server?

    FSTransaction *transactionToRecord = [FSTransaction transactionWithIdentifier:transaction.transactionIdentifier receipt:transaction.transactionReceipt];
    [transactionToRecord store];
}

- (void)purchaseSuccess:(NSString *)productIdentifier
{
    // TODO: make purchase available to user - perhaps call completion block?

    DLog(@"transaction success for product: %@", productIdentifier);

    self.currentProductRequest = nil;

    if (_completionHandler)
    {
        _completionHandler(YES, nil);
    }
}

- (void)purchaseFailedWithError:(NSError *)error
{
    DLog(@"%@", error);

    self.currentProductRequest = nil;

    if (_completionHandler)
    {
        _completionHandler(NO, error);
    }
}

- (void)validateReceipt:(NSData *)receiptData withCompletionHandler:(void (^)(BOOL success, NSError *error))completionHandler
{
    DLog(@"validating receipt with Apple ...");

    NSString *body = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", [receiptData base64EncodedString]];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:FS_URL_APPLE_VERIFY_RECEIPT];
    request.HTTPMethod = @"POST";
    request.HTTPBody   = [body dataUsingEncoding:NSUTF8StringEncoding];

    [AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:@"text/plain"]];
    AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^ (NSURLRequest *request, NSURLResponse *response, id JSON) {

        DLog(@"%@", JSON);

        NSNumber *number  = [JSON objectForKey:@"status"];
        BOOL      success = number && (number.integerValue == 0);
        NSError  *error   = success ? nil : [NSError errorWithDomain:FSMyAppErrorDomain code:FSInvalidProductReceipt];

        if (completionHandler)
        {
            completionHandler(success, error);
        }

    } failure:^ (NSURLRequest *request, NSURLResponse *response, NSError *error, id JSON) {

        if (completionHandler)
        {
            completionHandler(NO, error);
        }

    }];
    [operation start];
}

@end

クラスのデザインの良いところは、アプリ内購入のブラック ボックスのようなものです。クラスの使用はかなり簡単です。たとえば、次のコードを使用して「広告を無効にする」製品を購入します。

- (void)disableAds
{
    [self showLoadingIndicator:YES forView:self.tableView];

    [[FSProductStore defaultStore] startProductRequestWithIdentifier:FS_PRODUCT_DISABLE_ADS completionHandler:^ (BOOL success, NSError *error) {

        [self showLoadingIndicator:NO forView:self.tableView];

        DLog(@"%d %@", success, error);

        if (success)
        {
            NSNumber *object = [NSNumber numberWithBool:YES];
            [[NSNotificationCenter defaultCenter] postNotificationName:FSShouldDisableAdsNotification object:object];

            NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
            if (indexPath)
            {
                [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
            }
        }
        else
        {
            [UIAlertView showAlertViewWithError:error delegate:self];
        }

    }];
}

PS:次のマクロは、レシート検証 URL に使用されます。デバッグ (開発)、リリース (テスト)、配布 (AppStore) の 3 つのスキームがあります。

// In-App purchase: we'll use the Sandbox environment for test versions ...

#if (DEBUG || RELEASE)
#define FS_URL_APPLE_VERIFY_RECEIPT [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"]
#else // DISTRIBUTION
#define FS_URL_APPLE_VERIFY_RECEIPT [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"]
#endif // (DEBUG || RELEASE)
于 2012-12-31T03:58:00.350 に答える