2017年9月18日月曜日

xcode とにかく簡単に課金処理したい


1. エッセンスだけ書き下すと、こんな感じです。

こんな感じと言いながら、これでリリースしているので、ちゃんと動くと思います。


- (void)viewDidLoad {
    [super viewDidLoad];
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    switch (indexPath.section) {
        case 0:
            if (indexPath.row == 0) {
                //In-App Purchases機能制限の確認
                NSString *productID = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"];
                if ([SKPaymentQueue canMakePayments]) {
                    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObjects:productID, nil]];
                    request.delegate = self;
                    [request start];
                    [self.indicator startAnimating];
                }
            }
            break;
    }
}

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    if (response == nil || [response.invalidProductIdentifiers count] > 0) {
        [_indicator stopAnimating];
        return;
    }
    _product = nil;
    for (_product in response.products) {
        break;
    }
    if (_product == nil) {
        [_indicator stopAnimating];
        return;
    }
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"title", nil) message:msg delegate:self cancelButtonTitle:NSLocalizedString(@"Restore", nil) otherButtonTitles:NSLocalizedString(@"Purchase", nil), nil];
    [alert show];
}

- (void)alertView:(UIAlertView *)alert clickedButtonAtIndex:(NSInteger)buttonIndex
{
    SKPayment *payment;
    switch (buttonIndex) {
        case 1:
            payment = [SKPayment paymentWithProduct:_product];
            [[SKPaymentQueue defaultQueue] addPayment:payment];
            break;
        case 0:
            [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
        default:
           break;
    }
}

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            default:
            case SKPaymentTransactionStatePurchasing:
                break;
            case SKPaymentTransactionStateFailed:
                [queue finishTransaction:transaction];
                [_indicator stopAnimating];
                break;
            case SKPaymentTransactionStatePurchased:
            case SKPaymentTransactionStateRestored:
                [queue finishTransaction:transaction];
                [_indicator stopAnimating];
                break;
        }
        break;
    }
}

- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    [_indicator stopAnimating];
}

- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
    [_indicator stopAnimating];
}

2. 問題は、各stopAnimatingのところに、何を埋め込むかです。


【失敗処理なら】
StoreKitが勝手に出すダイアログもあるので、整理が難しいのですが、ダイアログが足りないと感じた場合には、自前ダイアログを埋め込んでいます。

【成功処理なら】
成功した場合には、成功したことをDocumentに保存しておく必要があります。
次回、起動時の際にはこの情報を読み込んで、例えば購入したアイテムを表示するとか、購入した機能を生かすとか、処理をしなくてはいけません。
一番簡単なのはplistにBooleanで書き込むことですが、App Storeの審査は通りますが、簡単にハッキングされてしまうので、アプリ販売で稼ぎたいなら、暗号化は必須でしょう。

ちなみにstopAnimatingは6箇所ありますが、4箇所は失敗で、2箇所が成功です。

2 件のコメント:

  1. [[SKPaymentQueue defaultQueue] addTransactionObserver:self];を忘れていてえらいことになりました。
    これがないと、updatedTransactionsが呼び出されることがないので、常に課金が失敗するのですが、実はそれだけでは済みません。
    そのまま知らずに何度もデバッグを続けると、Apple Storeから戻ってくるキューが、完結しないまま溜まってしまいます。
    忘れていた[[SKPaymentQueue defaultQueue] addTransactionObserver:self];を追加しビルドしても手遅れで、溜まっていたキューを消費しない限り、Apple IDを求めるダイアログが出まくります。
    Apple IDを求めるダイアログが何度も出るアプリとは、課金をキャンセルした時の処理がまずく、キューを消費できていない場合と思われます。

    返信削除
  2. iTunes Connectのコンソールで、App内課金を登録する際、製品IDをバンドルIDと一致させないと、TestFlightで試験できなくなった。
    去年まではできていたような気がしたのだが…
    確かに今年になってから審査時に、Reviewerから課金処理が動作しないから、バンドルIDで登録し直せと指摘を受けたな〜。その時はあまり考えず言う通りにしたわけだが。

    返信削除