Jak dodać zakup w aplikacji do aplikacji na iOS?

Odpowiedzi:

554

Szybcy użytkownicy

Użytkownicy Swift mogą sprawdzić My Swift Answer na to pytanie .
Lub sprawdź odpowiedź Yedidya Reiss , która tłumaczy ten kod Celu C na Swift.

Użytkownicy C celu

Reszta tej odpowiedzi jest napisana w Celu C

App Store Connect

  1. Wejdź na appstoreconnect.apple.com i zaloguj się
  2. Kliknij, My Appsa następnie kliknij aplikację, do której chcesz dodać zakup
  3. Kliknij Featuresnagłówek, a następnie wybierz In-App Purchasespo lewej stronie
  4. Kliknij +ikonę pośrodku
  5. W tym samouczku dodamy zakup w aplikacji, aby usunąć reklamy, więc wybierz non-consumable. Jeśli chcesz wysłać element fizyczny do użytkownika lub dać mu coś, co może kupić więcej niż jeden raz, wybierzesz consumable.
  6. Jako nazwę odniesienia wpisz dowolne (ale upewnij się, że wiesz, co to jest)
  7. W przypadku umieszczenia identyfikatora produktu tld.websitename.appname.referencenamebędzie to działać najlepiej, na przykład możesz użyćcom.jojodmo.blix.removeads
  8. Wybierz, cleared for salea następnie wybierz warstwę ceny jako 1 (99 ¢). Poziom 2 wyniósłby 1,99 USD, a poziom 3 2,99 USD. Pełna lista jest dostępna po kliknięciu view pricing matrixPolecam skorzystanie z poziomu 1, ponieważ zwykle jest to najwyższa kwota, jaką kiedykolwiek ktoś zapłaci za usunięcie reklam.
  9. Kliknij niebieski add languageprzycisk i wprowadź informacje. Zostanie to WSZYSTKIE pokazane klientowi, więc nie umieszczaj niczego, czego nie chciałbyś widzieć
  10. Do hosting content with Applewybierz nie
  11. Możesz zostawić notatki z recenzji NA TERAZ .
  12. Pomiń screenshot for review TERAZ , wszystko, co pomijamy, wrócimy.
  13. Kliknij „zapisz”

Rejestracja identyfikatora produktu może potrwać kilka godzin App Store Connect, więc bądź cierpliwy.

Konfiguracja twojego projektu

Po skonfigurowaniu informacji o zakupach w aplikacji w App Store Connect przejdź do projektu Xcode i przejdź do menedżera aplikacji (niebieska ikona przypominająca stronę u góry miejsca, w którym znajdują się twoje metody i pliki nagłówkowe), kliknij Twoja aplikacja pod celami (powinna być pierwsza), a następnie przejdź do ogólnych. Na dole powinieneś zobaczyć linked frameworks and librariesmały symbol plus i dodać ramkę. StoreKit.frameworkJeśli tego nie zrobisz, zakup w aplikacji NIE zadziała!

Jeśli używasz Objective-C jako języka dla swojej aplikacji, powinieneś pominąć te pięć kroków . W przeciwnym razie, jeśli używasz Swift, możesz postępować zgodnie z My Swift Answer na to pytanie tutaj lub, jeśli wolisz używać Objective-C dla kodu zakupu w aplikacji, ale używasz Swift w swojej aplikacji, możesz wykonać następujące czynności :

  1. Utwórz nowy .hplik (nagłówek), przechodząc do File> New> File...( Command ⌘+ N). Ten plik będzie nazywany „Twoim .hplikiem” w dalszej części samouczka

  2. Po wyświetleniu monitu kliknij opcję Utwórz nagłówek pomostowy . To będzie nasz mostkowy plik nagłówkowy. Jeśli nie zostanie wyświetlony monit, przejdź do kroku 3. Jeśli poproszony, pomiń krok 3 i przejść bezpośrednio do kroku 4.

  3. Utwórz inny .hplik o nazwie Bridge.hw głównym folderze projektu, a następnie przejdź do Menedżera aplikacji (niebieska ikona przypominająca stronę), a następnie wybierz aplikację w Targetssekcji i kliknij Build Settings. Znajdź opcję z napisem Swift Compiler - Generowanie kodu , a następnie ustaw opcję Header-C Bridging Header naBridge.h

  4. W łączącym pliku nagłówka dodaj wiersz #import "MyObjectiveCHeaderFile.h", w którym MyObjectiveCHeaderFileznajduje się nazwa pliku nagłówka utworzonego w kroku pierwszym. Na przykład, jeśli nazwiesz swój plik nagłówkowy InAppPurchase.h , dodasz linię #import "InAppPurchase.h"do pliku nagłówkowego mostu.

  5. Tworzenie Metody Objective-C (nowy .m) plik, przechodząc do File> New> File...( Command ⌘+ N). Nazwij go tak samo, jak plik nagłówka utworzony w kroku 1. Na przykład, jeśli plik został wywołany w kroku 1 InAppPurchase.h , nazwałbyś ten nowy plik InAppPurchase.m . Ten plik będzie nazywany „Twoim .mplikiem” w dalszej części samouczka.

Kodowanie

Teraz zajmiemy się kodowaniem. Dodaj następujący kod do swojego .hpliku:

BOOL areAdsRemoved;

- (IBAction)restore;
- (IBAction)tapsRemoveAds;

Następnie musisz zaimportować StoreKitframework do swojego .mpliku, a także dodać SKProductsRequestDelegatei SKPaymentTransactionObserverpo @interfacedeklaracji:

#import <StoreKit/StoreKit.h>

//put the name of your view controller in place of MyViewController
@interface MyViewController() <SKProductsRequestDelegate, SKPaymentTransactionObserver>

@end

@implementation MyViewController //the name of your view controller (same as above)
  //the code below will be added here
@end

a teraz dodaj do .mpliku następujące elementy, ta część komplikuje się, więc sugeruję przeczytanie komentarzy w kodzie:

//If you have more than one in-app purchase, you can define both of
//of them here. So, for example, you could define both kRemoveAdsProductIdentifier
//and kBuyCurrencyProductIdentifier with their respective product ids
//
//for this example, we will only use one product

#define kRemoveAdsProductIdentifier @"put your product id (the one that we just made in App Store Connect) in here"

- (IBAction)tapsRemoveAds{
    NSLog(@"User requests to remove ads");

    if([SKPaymentQueue canMakePayments]){
        NSLog(@"User can make payments");
    
        //If you have more than one in-app purchase, and would like
        //to have the user purchase a different product, simply define 
        //another function and replace kRemoveAdsProductIdentifier with 
        //the identifier for the other product

        SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:kRemoveAdsProductIdentifier]];
        productsRequest.delegate = self;
        [productsRequest start];
    
    }
    else{
        NSLog(@"User cannot make payments due to parental controls");
        //this is called the user cannot make payments, most likely due to parental controls
    }
}

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    SKProduct *validProduct = nil;
    int count = [response.products count];
    if(count > 0){
        validProduct = [response.products objectAtIndex:0];
        NSLog(@"Products Available!");
        [self purchase:validProduct];
    }
    else if(!validProduct){
        NSLog(@"No products available");
        //this is called if your product id is not valid, this shouldn't be called unless that happens.
    }
}

- (void)purchase:(SKProduct *)product{
    SKPayment *payment = [SKPayment paymentWithProduct:product];

    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

- (IBAction) restore{
    //this is called when the user restores purchases, you should hook this up to a button
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

- (void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
    NSLog(@"received restored transactions: %i", queue.transactions.count);
    for(SKPaymentTransaction *transaction in queue.transactions){
        if(transaction.transactionState == SKPaymentTransactionStateRestored){
            //called when the user successfully restores a purchase
            NSLog(@"Transaction state -> Restored");

            //if you have more than one in-app purchase product,
            //you restore the correct product for the identifier.
            //For example, you could use
            //if(productID == kRemoveAdsProductIdentifier)
            //to get the product identifier for the
            //restored purchases, you can use
            //
            //NSString *productID = transaction.payment.productIdentifier;
            [self doRemoveAds];
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            break;
        }
    }   
}

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
    for(SKPaymentTransaction *transaction in transactions){
        //if you have multiple in app purchases in your app,
        //you can get the product identifier of this transaction
        //by using transaction.payment.productIdentifier
        //
        //then, check the identifier against the product IDs
        //that you have defined to check which product the user
        //just purchased            

        switch(transaction.transactionState){
            case SKPaymentTransactionStatePurchasing: NSLog(@"Transaction state -> Purchasing");
                //called when the user is in the process of purchasing, do not add any of your own code here.
                break;
            case SKPaymentTransactionStatePurchased:
            //this is called when the user has successfully purchased the package (Cha-Ching!)
                [self doRemoveAds]; //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                NSLog(@"Transaction state -> Purchased");
                break;
            case SKPaymentTransactionStateRestored:
                NSLog(@"Transaction state -> Restored");
                //add the same code as you did from SKPaymentTransactionStatePurchased here
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                //called when the transaction does not finish
                if(transaction.error.code == SKErrorPaymentCancelled){
                    NSLog(@"Transaction state -> Cancelled");
                    //the user cancelled the payment ;(
                }
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
        }
    }
}

Teraz chcesz dodać kod tego, co się stanie, gdy użytkownik zakończy transakcję, w tym samouczku używamy usuwania dodatków, musisz dodać własny kod, co się stanie, gdy załaduje się widok banera.

- (void)doRemoveAds{
    ADBannerView *banner;
    [banner setAlpha:0];
    areAdsRemoved = YES;
    removeAdsButton.hidden = YES;
    removeAdsButton.enabled = NO;
    [[NSUserDefaults standardUserDefaults] setBool:areAdsRemoved forKey:@"areAdsRemoved"];
    //use NSUserDefaults so that you can load whether or not they bought it
    //it would be better to use KeyChain access, or something more secure
    //to store the user data, because NSUserDefaults can be changed.
    //You're average downloader won't be able to change it very easily, but
    //it's still best to use something more secure than NSUserDefaults.
    //For the purpose of this tutorial, though, we're going to use NSUserDefaults
    [[NSUserDefaults standardUserDefaults] synchronize];
}

Jeśli nie masz reklam w swojej aplikacji, możesz użyć dowolnej innej rzeczy. Na przykład możemy ustawić kolor tła na niebieski. Aby to zrobić, chcielibyśmy użyć:

- (void)doRemoveAds{
    [self.view setBackgroundColor:[UIColor blueColor]];
    areAdsRemoved = YES
    //set the bool for whether or not they purchased it to YES, you could use your own boolean here, but you would have to declare it in your .h file

    [[NSUserDefaults standardUserDefaults] setBool:areAdsRemoved forKey:@"areAdsRemoved"];
    //use NSUserDefaults so that you can load wether or not they bought it
    [[NSUserDefaults standardUserDefaults] synchronize];
}

Teraz, gdzieś w swojej viewDidLoadmetodzie, będziesz chciał dodać następujący kod:

areAdsRemoved = [[NSUserDefaults standardUserDefaults] boolForKey:@"areAdsRemoved"];
[[NSUserDefaults standardUserDefaults] synchronize];
//this will load wether or not they bought the in-app purchase

if(areAdsRemoved){
    [self.view setBackgroundColor:[UIColor blueColor]];
    //if they did buy it, set the background to blue, if your using the code above to set the background to blue, if your removing ads, your going to have to make your own code here
}

Po dodaniu całego kodu przejdź do pliku .xiblub storyboardpliku i dodaj dwa przyciski, jeden z napisem „kup”, a drugi „przywróć”. Podłącz utworzony przed tapsRemoveAds IBActionchwilą przycisk zakupu i restore IBActionprzycisk przywracania. restoreAkcja będzie sprawdzać, czy użytkownik został wcześniej zakupiony zakupu w aplikacji, i dać im zakupu w aplikacji za darmo jeśli nie masz go.

Przesyłanie do recenzji

Następnie przejdź do App Store Connect , kliknij, a Users and Accessnastępnie kliknij Sandbox Testersnagłówek, a następnie kliknij +symbol po lewej stronie, w którym jest napisane Testers. Możesz po prostu wpisać losowe imię i nazwisko, a e-mail nie musi być prawdziwy - musisz tylko pamiętać. Wpisz hasło (które będziesz musiał zapamiętać) i wypełnij resztę informacji. Radziłbym ustalić Date of Birthdatę, która oznaczałaby, że użytkownik ma 18 lat lub więcej. App Store Territory MUSI być we właściwym kraju. Następnie wyloguj się z istniejącego konta iTunes (możesz ponownie zalogować się po tym samouczku).

Teraz uruchom aplikację na urządzeniu z systemem iOS, jeśli spróbujesz uruchomić ją na symulatorze, zakup zawsze będzie błąd, MUSISZ uruchomić ją na urządzeniu z systemem iOS. Po uruchomieniu aplikacji dotknij przycisku zakupu. Po wyświetleniu monitu o zalogowanie się na konto iTunes zaloguj się jako użytkownik testowy, który właśnie utworzyliśmy. Następnie, gdy pojawi się prośba o potwierdzenie zakupu 99 centów lub cokolwiek innego, ustawisz także warstwę cenową, ZRÓB EKRAN SNAPSHOT TO, to będzie to, czego zamierzasz użyć screenshot for revieww App Store Connect. Teraz anuluj płatność.

Teraz przejdź do App Store Połącz , a następnie przejść do My Apps> the app you have the In-app purchase on> In-App Purchases. Następnie kliknij zakup w aplikacji i edytuj pod szczegółami zakupu w aplikacji. Gdy to zrobisz, zaimportuj zdjęcie, które właśnie zrobiłeś iPhone'em na komputer, i prześlij je jako zrzut ekranu do recenzji, a następnie w notatkach z recenzji podaj swój adres e-mail i hasło TESTU UŻYTKOWNIKA . Pomoże to Apple w procesie przeglądu.

Po wykonaniu tej czynności wróć do aplikacji na urządzeniu z systemem iOS, nadal zalogowanego jako konto użytkownika testowego, i kliknij przycisk zakupu. Tym razem potwierdź płatność Nie martw się, to NIE obciąży Twojego konta ŻADNYMI pieniędzmi, przetestuj konta użytkowników otrzymuj wszystkie zakupy w aplikacji za darmo Po potwierdzeniu płatności upewnij się, że co się stanie, gdy użytkownik kupi Twój produkt dzieje się. Jeśli nie, oznacza to błąd w Twojej doRemoveAdsmetodzie. Ponownie zalecam zmianę tła na niebieski w celu przetestowania zakupu w aplikacji, ale nie powinien to być faktyczny zakup w aplikacji. Jeśli wszystko działa i możesz już iść! Pamiętaj tylko, aby uwzględnić zakup w aplikacji w nowym pliku binarnym podczas przesyłania go do App Store Connect!


Oto kilka typowych błędów:

Zalogowany: No Products Available

Może to oznaczać cztery rzeczy:

  • Nie wstawiłeś prawidłowego identyfikatora zakupu w aplikacji w kodzie (dla identyfikatora kRemoveAdsProductIdentifierw powyższym kodzie
  • Nie wyczyściłeś zakupu w aplikacji na sprzedaż w App Store Connect
  • Nie czekałeś na zarejestrowanie identyfikatora zakupu w aplikacji w App Store Connect . Poczekaj kilka godzin od utworzenia identyfikatora, a problem powinien zostać rozwiązany.
  • Nie wypełniłeś informacji o umowach, podatkach i bankowości.

Jeśli to nie zadziała za pierwszym razem, nie denerwuj się! Nie poddawaj się! Zajęło mi to około 5 godzin, zanim mogłem zacząć działać, i około 10 godzin na znalezienie odpowiedniego kodu! Jeśli dokładnie użyjesz powyższego kodu, powinien on działać poprawnie. Zapraszam do komentowania, jeśli masz jakiekolwiek pytania w ogóle .

Mam nadzieję, że to pomoże wszystkim tym, którzy chcą dodać zakup w aplikacji do swojej aplikacji na iOS. Twoje zdrowie!

Jojodmo
źródło
1
ale jeśli nie dodam tego wiersza, po kliknięciu przycisku przywracania nic się nie stanie .. w każdym razie bardzo dziękuję za ten samouczek;)
Ilario
1
„if ( * transakcja * == SKPaymentTransactionStateRestored) {” powinno być if ( * transakcja.transactionState * == SKPaymentTransactionStateRestored) {
Massmaker
13
Najlepsze praktyki Apple zalecają dodanie obserwatora transakcji do AppDelegate, a nie działań kontrolera widoku. developer.apple.com/library/ios/technotes/tn2387/_index.html
Craig Pickering
3
Otrzymuję 0 produktów, ale sprawdziłem już 3 możliwe przyczyny, które wymieniłeś. Jedyne, co przychodzi mi na myśl, jeśli nie skonfigurowałem danych kontaktowych, bankowych i podatkowych w „umowie o płatną aplikację na iOS” w iTunes, czy może to być powód?
Christopher Francisco,
4
Należy wyjaśnić, że w kroku 9 wyświetlana nazwa jest prezentowana użytkownikowi. I wygląda tak: „Czy chcesz kupić NAZWĘ WYŚWIETLACZA za 0,99 USD?”. Jest to ważne, ponieważ nazwa wyświetlana to „Usuń reklamy”, a następnie moja aplikacja została odrzucona, ponieważ używałem niewłaściwej gramatyki w wyskakującym okienku! Musiałem zmienić nazwę wyświetlaną na „Pakiet usuwania reklam”.
Alan Scarpa
13

Przetłumacz kod Jojodmo na Swift:

class InAppPurchaseManager: NSObject , SKProductsRequestDelegate, SKPaymentTransactionObserver{





//If you have more than one in-app purchase, you can define both of
//of them here. So, for example, you could define both kRemoveAdsProductIdentifier
//and kBuyCurrencyProductIdentifier with their respective product ids
//
//for this example, we will only use one product

let kRemoveAdsProductIdentifier = "put your product id (the one that we just made in iTunesConnect) in here"

@IBAction func tapsRemoveAds() {

    NSLog("User requests to remove ads")

    if SKPaymentQueue.canMakePayments() {
        NSLog("User can make payments")

        //If you have more than one in-app purchase, and would like
        //to have the user purchase a different product, simply define
        //another function and replace kRemoveAdsProductIdentifier with
        //the identifier for the other product
        let set : Set<String> = [kRemoveAdsProductIdentifier]
        let productsRequest = SKProductsRequest(productIdentifiers: set)
        productsRequest.delegate = self
        productsRequest.start()

    }
    else {
        NSLog("User cannot make payments due to parental controls")
        //this is called the user cannot make payments, most likely due to parental controls
    }
}


func purchase(product : SKProduct) {

    let payment = SKPayment(product: product)
    SKPaymentQueue.defaultQueue().addTransactionObserver(self)
    SKPaymentQueue.defaultQueue().addPayment(payment)
}

func restore() {
    //this is called when the user restores purchases, you should hook this up to a button
    SKPaymentQueue.defaultQueue().addTransactionObserver(self)
    SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}


func doRemoveAds() {
    //TODO: implement
}

/////////////////////////////////////////////////
//////////////// store delegate /////////////////
/////////////////////////////////////////////////
// MARK: - store delegate -


func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {

    if let validProduct = response.products.first {
        NSLog("Products Available!")
        self.purchase(validProduct)
    }
    else {
        NSLog("No products available")
        //this is called if your product id is not valid, this shouldn't be called unless that happens.
    }
}

func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {


    NSLog("received restored transactions: \(queue.transactions.count)")
    for transaction in queue.transactions {
        if transaction.transactionState == .Restored {
            //called when the user successfully restores a purchase
            NSLog("Transaction state -> Restored")

            //if you have more than one in-app purchase product,
            //you restore the correct product for the identifier.
            //For example, you could use
            //if(productID == kRemoveAdsProductIdentifier)
            //to get the product identifier for the
            //restored purchases, you can use
            //
            //NSString *productID = transaction.payment.productIdentifier;
            self.doRemoveAds()
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
            break;
        }
    }
}


func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

    for transaction in transactions {
        switch transaction.transactionState {
        case .Purchasing: NSLog("Transaction state -> Purchasing")
            //called when the user is in the process of purchasing, do not add any of your own code here.
        case .Purchased:
            //this is called when the user has successfully purchased the package (Cha-Ching!)
            self.doRemoveAds() //you can add your code for what you want to happen when the user buys the purchase here, for this tutorial we use removing ads
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
            NSLog("Transaction state -> Purchased")
        case .Restored:
            NSLog("Transaction state -> Restored")
            //add the same code as you did from SKPaymentTransactionStatePurchased here
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
        case .Failed:
            //called when the transaction does not finish
            if transaction.error?.code == SKErrorPaymentCancelled {
                NSLog("Transaction state -> Cancelled")
                //the user cancelled the payment ;(
            }
            SKPaymentQueue.defaultQueue().finishTransaction(transaction)
        case .Deferred:
            // The transaction is in the queue, but its final status is pending external action.
            NSLog("Transaction state -> Deferred")

        }


    }
}
} 
Yedidya Reiss
źródło
6

Szybka odpowiedź

Ma to na celu uzupełnienie mojej odpowiedzi Objective-C dla użytkowników Swift, aby nie dopuścić do zbyt dużej odpowiedzi Objective-C.

Ustawiać

Najpierw skonfiguruj zakup w aplikacji na appstoreconnect.apple.com . Postępuj zgodnie z początkową częścią mojej odpowiedzi Cel C (kroki 1-13, pod nagłówkiem App Store Connect ), aby uzyskać instrukcje na ten temat.

Rejestracja identyfikatora produktu w App Store Connect może potrwać kilka godzin, więc bądź cierpliwy.

Po skonfigurowaniu informacji o zakupach w aplikacji w App Store Connect musimy dodać do aplikacji strukturę Apple do zakupów StoreKitw aplikacji.

Przejdź do projektu Xcode i przejdź do menedżera aplikacji (niebieska ikona przypominająca stronę u góry lewego paska, w którym znajdują się pliki aplikacji). Kliknij aplikację pod celami po lewej stronie (powinna to być pierwsza opcja), a następnie przejdź do „Możliwości” u góry. Na liście powinna być widoczna opcja „Zakup w aplikacji”. Włącz tę funkcję, a Xcode doda StoreKitdo twojego projektu.

Kodowanie

Teraz zaczniemy kodować!

Najpierw utwórz nowy szybki plik, który będzie zarządzał wszystkimi Twoimi zakupami w aplikacji. Nazwie to IAPManager.swift.

W tym pliku utworzymy nową klasę o nazwie IAPManagerto a SKProductsRequestDelegatei SKPaymentTransactionObserver. U góry upewnij się, że importujesz FoundationiStoreKit

import Foundation
import StoreKit

public class IAPManager: NSObject, SKProductsRequestDelegate,
                         SKPaymentTransactionObserver {
}

Następnie dodamy zmienną, aby zdefiniować identyfikator naszego zakupu w aplikacji (możesz także użyć zmiennej enum, która byłaby łatwiejsza do utrzymania, jeśli masz wiele IAP).

// This should the ID of the in-app-purchase you made on AppStore Connect.
// if you have multiple IAPs, you'll need to store their identifiers in
// other variables, too (or, preferably in an enum).
let removeAdsID = "com.skiplit.removeAds"

Dodajmy teraz inicjalizator dla naszej klasy:

// This is the initializer for your IAPManager class
//
// A better, and more scaleable way of doing this
// is to also accept a callback in the initializer, and call
// that callback in places like the paymentQueue function, and
// in all functions in this class, in place of calls to functions
// in RemoveAdsManager (you'll see those calls in the code below).

let productID: String
init(productID: String){
    self.productID = productID
}

Teraz dodamy wymagane funkcje SKProductsRequestDelegatei SKPaymentTransactionObserverdo pracy:

Dodamy RemoveAdsManagerklasę później

// This is called when a SKProductsRequest receives a response
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse){
    // Let's try to get the first product from the response
    // to the request
    if let product = response.products.first{
        // We were able to get the product! Make a new payment
        // using this product
        let payment = SKPayment(product: product)

        // add the new payment to the queue
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().add(payment)
    }
    else{
        // Something went wrong! It is likely that either
        // the user doesn't have internet connection, or
        // your product ID is wrong!
        //
        // Tell the user in requestFailed() by sending an alert,
        // or something of the sort

        RemoveAdsManager.removeAdsFailure()
    }
}

// This is called when the user restores their IAP sucessfully
private func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue){
    // For every transaction in the transaction queue...
    for transaction in queue.transactions{
        // If that transaction was restored
        if transaction.transactionState == .restored{
            // get the producted ID from the transaction
            let productID = transaction.payment.productIdentifier

            // In this case, we have only one IAP, so we don't need to check
            // what IAP it is. However, this is useful if you have multiple IAPs!
            // You'll need to figure out which one was restored
            if(productID.lowercased() == IAPManager.removeAdsID.lowercased()){
                // Restore the user's purchases
                RemoveAdsManager.restoreRemoveAdsSuccess()
            }

            // finish the payment
            SKPaymentQueue.default().finishTransaction(transaction)
        }
    }
}

// This is called when the state of the IAP changes -- from purchasing to purchased, for example.
// This is where the magic happens :)
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
    for transaction in transactions{
        // get the producted ID from the transaction
        let productID = transaction.payment.productIdentifier

        // In this case, we have only one IAP, so we don't need to check
        // what IAP it is.
        // However, if you have multiple IAPs, you'll need to use productID
        // to check what functions you should run here!

        switch transaction.transactionState{
        case .purchasing:
            // if the user is currently purchasing the IAP,
            // we don't need to do anything.
            //
            // You could use this to show the user
            // an activity indicator, or something like that
            break
        case .purchased:
            // the user successfully purchased the IAP!
            RemoveAdsManager.removeAdsSuccess()
            SKPaymentQueue.default().finishTransaction(transaction)
        case .restored:
                // the user restored their IAP!
                IAPTestingHandler.restoreRemoveAdsSuccess()
                SKPaymentQueue.default().finishTransaction(transaction)
        case .failed:
                // The transaction failed!
                RemoveAdsManager.removeAdsFailure()
                // finish the transaction
                SKPaymentQueue.default().finishTransaction(transaction)
        case .deferred:
                // This happens when the IAP needs an external action
                // in order to proceeded, like Ask to Buy
                RemoveAdsManager.removeAdsDeferred()
                break
        }
    }
}

Dodajmy teraz funkcje, których można użyć do rozpoczęcia zakupu lub przywrócenia zakupów:

// Call this when you want to begin a purchase
// for the productID you gave to the initializer
public func beginPurchase(){
    // If the user can make payments
    if SKPaymentQueue.canMakePayments(){
        // Create a new request
        let request = SKProductsRequest(productIdentifiers: [productID])
        // Set the request delegate to self, so we receive a response
        request.delegate = self
        // start the request
        request.start()
    }
    else{
        // Otherwise, tell the user that
        // they are not authorized to make payments,
        // due to parental controls, etc
    }
}

// Call this when you want to restore all purchases
// regardless of the productID you gave to the initializer
public func beginRestorePurchases(){
    // restore purchases, and give responses to self
    SKPaymentQueue.default().add(self)
    SKPaymentQueue.default().restoreCompletedTransactions()
}

Następnie dodajmy nową klasę narzędzi do zarządzania naszymi IAP. Cały ten kod może należeć do jednej klasy, ale jego wielokrotność sprawia, że ​​jest trochę czystszy. Mam zamiar stworzyć nową klasę o nazwie RemoveAdsManager, a w niej umieścić kilka funkcji

public class RemoveAdsManager{

    class func removeAds()
    class func restoreRemoveAds()

    class func areAdsRemoved() -> Bool

    class func removeAdsSuccess()
    class func restoreRemoveAdsSuccess()
    class func removeAdsDeferred()
    class func removeAdsFailure()
}

Pierwsze trzy funkcje, removeAds, restoreRemoveAds, i areAdsRemovedsą funkcje, które musisz zadzwonić, aby wykonać pewne czynności. Cztery ostatnie to te, które będą wywoływane przez IAPManager.

Dodajmy kod do pierwszych dwóch funkcji removeAdsi restoreRemoveAds:

// Call this when the user wants
// to remove ads, like when they
// press a "remove ads" button
class func removeAds(){
    // Before starting the purchase, you could tell the
    // user that their purchase is happening, maybe with
    // an activity indicator

    let iap = IAPManager(productID: IAPManager.removeAdsID)
    iap.beginPurchase()
}

// Call this when the user wants
// to restore their IAP purchases,
// like when they press a "restore
// purchases" button.
class func restoreRemoveAds(){
    // Before starting the purchase, you could tell the
    // user that the restore action is happening, maybe with
    // an activity indicator

    let iap = IAPManager(productID: IAPManager.removeAdsID)
    iap.beginRestorePurchases()
}

Na koniec dodajmy kod do pięciu ostatnich funkcji.

// Call this to check whether or not
// ads are removed. You can use the
// result of this to hide or show
// ads
class func areAdsRemoved() -> Bool{
    // This is the code that is run to check
    // if the user has the IAP.

    return UserDefaults.standard.bool(forKey: "RemoveAdsPurchased")
}

// This will be called by IAPManager
// when the user sucessfully purchases
// the IAP
class func removeAdsSuccess(){
    // This is the code that is run to actually
    // give the IAP to the user!
    //
    // I'm using UserDefaults in this example,
    // but you may want to use Keychain,
    // or some other method, as UserDefaults
    // can be modified by users using their
    // computer, if they know how to, more
    // easily than Keychain

    UserDefaults.standard.set(true, forKey: "RemoveAdsPurchased")
    UserDefaults.standard.synchronize()
}

// This will be called by IAPManager
// when the user sucessfully restores
//  their purchases
class func restoreRemoveAdsSuccess(){
    // Give the user their IAP back! Likely all you'll need to
    // do is call the same function you call when a user
    // sucessfully completes their purchase. In this case, removeAdsSuccess()

    removeAdsSuccess()
}

// This will be called by IAPManager
// when the IAP failed
class func removeAdsFailure(){
    // Send the user a message explaining that the IAP
    // failed for some reason, and to try again later
}

// This will be called by IAPManager
// when the IAP gets deferred.
class func removeAdsDeferred(){
    // Send the user a message explaining that the IAP
    // was deferred, and pending an external action, like
    // Ask to Buy.
}

Podsumowując, otrzymujemy coś takiego:

import Foundation
import StoreKit

public class RemoveAdsManager{

    // Call this when the user wants
    // to remove ads, like when they
    // press a "remove ads" button
    class func removeAds(){
        // Before starting the purchase, you could tell the
        // user that their purchase is happening, maybe with
        // an activity indicator

        let iap = IAPManager(productID: IAPManager.removeAdsID)
        iap.beginPurchase()
    }

    // Call this when the user wants
    // to restore their IAP purchases,
    // like when they press a "restore
    // purchases" button.
    class func restoreRemoveAds(){
        // Before starting the purchase, you could tell the
        // user that the restore action is happening, maybe with
        // an activity indicator

        let iap = IAPManager(productID: IAPManager.removeAdsID)
        iap.beginRestorePurchases()
    }

    // Call this to check whether or not
    // ads are removed. You can use the
    // result of this to hide or show
    // ads
    class func areAdsRemoved() -> Bool{
        // This is the code that is run to check
        // if the user has the IAP.

        return UserDefaults.standard.bool(forKey: "RemoveAdsPurchased")
    }

    // This will be called by IAPManager
    // when the user sucessfully purchases
    // the IAP
    class func removeAdsSuccess(){
        // This is the code that is run to actually
        // give the IAP to the user!
        //
        // I'm using UserDefaults in this example,
        // but you may want to use Keychain,
        // or some other method, as UserDefaults
        // can be modified by users using their
        // computer, if they know how to, more
        // easily than Keychain

        UserDefaults.standard.set(true, forKey: "RemoveAdsPurchased")
        UserDefaults.standard.synchronize()
    }

    // This will be called by IAPManager
    // when the user sucessfully restores
    //  their purchases
    class func restoreRemoveAdsSuccess(){
        // Give the user their IAP back! Likely all you'll need to
        // do is call the same function you call when a user
        // sucessfully completes their purchase. In this case, removeAdsSuccess()
        removeAdsSuccess()
    }

    // This will be called by IAPManager
    // when the IAP failed
    class func removeAdsFailure(){
        // Send the user a message explaining that the IAP
        // failed for some reason, and to try again later
    }

    // This will be called by IAPManager
    // when the IAP gets deferred.
    class func removeAdsDeferred(){
        // Send the user a message explaining that the IAP
        // was deferred, and pending an external action, like
        // Ask to Buy.
    }

}

public class IAPManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver{

    // This should the ID of the in-app-purchase you made on AppStore Connect.
    // if you have multiple IAPs, you'll need to store their identifiers in
    // other variables, too (or, preferably in an enum).
    static let removeAdsID = "com.skiplit.removeAds"

    // This is the initializer for your IAPManager class
    //
    // An alternative, and more scaleable way of doing this
    // is to also accept a callback in the initializer, and call
    // that callback in places like the paymentQueue function, and
    // in all functions in this class, in place of calls to functions
    // in RemoveAdsManager.
    let productID: String
    init(productID: String){
        self.productID = productID
    }

    // Call this when you want to begin a purchase
    // for the productID you gave to the initializer
    public func beginPurchase(){
        // If the user can make payments
        if SKPaymentQueue.canMakePayments(){
            // Create a new request
            let request = SKProductsRequest(productIdentifiers: [productID])
            request.delegate = self
            request.start()
        }
        else{
            // Otherwise, tell the user that
            // they are not authorized to make payments,
            // due to parental controls, etc
        }
    }

    // Call this when you want to restore all purchases
    // regardless of the productID you gave to the initializer
    public func beginRestorePurchases(){
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().restoreCompletedTransactions()
    }

    // This is called when a SKProductsRequest receives a response
    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse){
        // Let's try to get the first product from the response
        // to the request
        if let product = response.products.first{
            // We were able to get the product! Make a new payment
            // using this product
            let payment = SKPayment(product: product)

            // add the new payment to the queue
            SKPaymentQueue.default().add(self)
            SKPaymentQueue.default().add(payment)
        }
        else{
            // Something went wrong! It is likely that either
            // the user doesn't have internet connection, or
            // your product ID is wrong!
            //
            // Tell the user in requestFailed() by sending an alert,
            // or something of the sort

            RemoveAdsManager.removeAdsFailure()
        }
    }

    // This is called when the user restores their IAP sucessfully
    private func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue){
        // For every transaction in the transaction queue...
        for transaction in queue.transactions{
            // If that transaction was restored
            if transaction.transactionState == .restored{
                // get the producted ID from the transaction
                let productID = transaction.payment.productIdentifier

                // In this case, we have only one IAP, so we don't need to check
                // what IAP it is. However, this is useful if you have multiple IAPs!
                // You'll need to figure out which one was restored
                if(productID.lowercased() == IAPManager.removeAdsID.lowercased()){
                    // Restore the user's purchases
                    RemoveAdsManager.restoreRemoveAdsSuccess()
                }

                // finish the payment
                SKPaymentQueue.default().finishTransaction(transaction)
            }
        }
    }

    // This is called when the state of the IAP changes -- from purchasing to purchased, for example.
    // This is where the magic happens :)
    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
        for transaction in transactions{
            // get the producted ID from the transaction
            let productID = transaction.payment.productIdentifier

            // In this case, we have only one IAP, so we don't need to check
            // what IAP it is.
            // However, if you have multiple IAPs, you'll need to use productID
            // to check what functions you should run here!

            switch transaction.transactionState{
            case .purchasing:
                // if the user is currently purchasing the IAP,
                // we don't need to do anything.
                //
                // You could use this to show the user
                // an activity indicator, or something like that
                break
            case .purchased:
                // the user sucessfully purchased the IAP!
                RemoveAdsManager.removeAdsSuccess()
                SKPaymentQueue.default().finishTransaction(transaction)
            case .restored:
                // the user restored their IAP!
                RemoveAdsManager.restoreRemoveAdsSuccess()
                SKPaymentQueue.default().finishTransaction(transaction)
            case .failed:
                // The transaction failed!
                RemoveAdsManager.removeAdsFailure()
                // finish the transaction
                SKPaymentQueue.default().finishTransaction(transaction)
            case .deferred:
                // This happens when the IAP needs an external action
                // in order to proceeded, like Ask to Buy
                RemoveAdsManager.removeAdsDeferred()
                break
            }
        }
    }

}

Na koniec musisz dodać sposób, w jaki użytkownik może rozpocząć zakup i zadzwonić RemoveAdsManager.removeAds()oraz rozpocząć przywracanie i połączenie RemoveAdsManager.restoreRemoveAds(), jak gdzieś przycisk! Pamiętaj, że zgodnie ze wskazówkami App Store musisz podać przycisk, aby przywrócić gdzieś zakupy.

Przesyłanie do recenzji

Ostatnią rzeczą do zrobienia jest przesłanie IAP do sprawdzenia w App Store Connect! Aby uzyskać szczegółowe instrukcje na ten temat, możesz postępować zgodnie z ostatnią częścią mojej odpowiedzi na Cel C , pod nagłówkiem Przesyłanie do recenzji .

Jojodmo
źródło
4

RMStore to lekka biblioteka iOS do zakupów w aplikacji. Zawiera API StoreKit i zapewnia przydatne bloki dla żądań asynchronicznych. Zakup produktu jest tak prosty, jak wywołanie jednej metody.

Dla zaawansowanych użytkowników ta biblioteka zapewnia również weryfikację pokwitowań, pobieranie treści i utrwalanie transakcji.

Vladimir Grigorov
źródło
-1

Wiem, że spóźniłem się z opublikowaniem tego, ale dzielę się podobnym doświadczeniem, kiedy poznałem liny modelu IAP.

Zakup w aplikacji to jeden z najbardziej kompleksowych przepływów pracy w systemie iOS realizowany przez platformę Storekit. Cała dokumentacja jest dość oczywiste, jeśli cierpliwość, aby ją przeczytać, ale jest nieco rozszerzone w naturze technicyzacji.

Podsumowując:

1 - Zamów produkty - skorzystaj z klas SKProductRequest & SKProductRequestDelegate, aby wysłać zapytanie o identyfikatory produktu i otrzymać je z powrotem z własnego sklepu itunesconnect.

Tych produktów SKProducts należy użyć do wypełnienia interfejsu użytkownika sklepu, którego użytkownik może użyć do zakupu określonego produktu.

2 - Wyślij prośbę o płatność - użyj SKPayment i SKPaymentQueue, aby dodać płatność do kolejki transakcji.

3 - Monitoruj kolejkę transakcji w celu aktualizacji statusu - użyj zaktualizowanej metody SKPaymentTransactionObserver protokołu do monitorowania statusu:

SKPaymentTransactionStatePurchasing - don't do anything
SKPaymentTransactionStatePurchased - unlock product, finish the transaction
SKPaymentTransactionStateFailed - show error, finish the transaction
SKPaymentTransactionStateRestored - unlock product, finish the transaction

4 - Przywróć przepływ przycisków - użyj polecenia SKPaymentQueue restoreCompletedTransactions, aby to zrobić - krok 3 zajmie się resztą, wraz z następującymi metodami SKPaymentTransactionObserver:

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

Oto samouczek krok po kroku (autorstwa autora będący wynikiem moich własnych prób jego zrozumienia), który to wyjaśnia. Na koniec zawiera również przykładowy kod, którego można użyć bezpośrednio.

Oto kolejna, którą stworzyłem, aby wyjaśnić pewne rzeczy, które tylko tekst mógłby opisać w lepszy sposób.

Nirav Bhatt
źródło
21
StackOverflow to strona internetowa pomagająca innym, a nie próbująca zarabiać na nich pieniądze. Powinieneś albo usunąć link od drugiego do ostatniego, albo po prostu opublikować tutaj, co zrobiono w tym samouczku, za darmo.
Jojodmo
@Jojodmo, czy możesz uzasadnić swoje roszczenie wytycznymi SO? Widzę wiele osób sprzedających swój własny pakiet SDK (nawet płatny) z zastrzeżeniem, które moim zdaniem jest tutaj bardzo obecne.
Nirav Bhatt
12
Nie ma żadnych wytycznych przeciwko temu, ale jeśli jesteś tutaj, aby zarabiać pieniądze, prawdopodobnie jesteś tutaj z niewłaściwych powodów. IMO, twoja odpowiedź wydaje się koncentrować na zachęcaniu ludzi do zapisywania się na twoje samouczki wideo, a nie na pomaganiu innym
Jojodmo
3
To tylko irytacja.
durazno