iOS 애플리케이션에 인앱 구매를 어떻게 추가합니까?


257

iOS 앱에 인앱 구매를 어떻게 추가합니까? 모든 세부 사항은 무엇이며 샘플 코드가 있습니까?

이것은 iOS 앱에 인앱 구매를 추가하는 방법에 대한 모든 종류의 것입니다.


11
"인앱 구매 프로그래밍 가이드"를 읽으면 어떻습니까?
rmaddy

답변:


554

스위프트 사용자

Swift 사용자는 이 질문에 대한 My Swift Answer를 확인할 수 있습니다 .
또는 이 Objective-C 코드를 Swift로 변환 하는 Yedidya Reiss 's Answer를 확인하십시오 .

Objective-C 사용자

이 답변의 나머지 부분은 Objective-C로 작성되었습니다.

App Store Connect

  1. appstoreconnect.apple.com으로 이동하여 로그인
  2. My Apps구매를 추가하려는 앱을 클릭 한 다음 클릭
  3. Features머리글을 클릭 한 다음 In-App Purchases왼쪽에서
  4. +중간에 있는 아이콘을 클릭하십시오
  5. 이 자습서에서는 광고를 제거하기 위해 인앱 구매를 추가 할 것이므로을 선택합니다 non-consumable. 실제 항목을 사용자에게 보내거나 두 번 이상 구입할 수있는 항목을 제공하려는 경우을 선택합니다 consumable.
  6. 참조 이름으로 원하는 것을 입력하십시오 (그러나 그것이 무엇인지 알고 있는지 확인하십시오)
  7. 제품 ID의 tld.websitename.appname.referencename경우 가장 잘 작동하므로 예를 들어com.jojodmo.blix.removeads
  8. 선택 cleared for sale하고 가격 계층 등의 1 (99 ¢)을 선택합니다. 2 단계는 1.99 달러, 3 단계는 2.99 달러입니다. view pricing matrix계층 1을 사용하는 것이 좋습니다. 를 클릭하면 전체 목록을 볼 수 있습니다. 일반적으로 모든 사람이 광고를 제거하기 위해 지불하는 비용이 가장 많기 때문입니다.
  9. 파란색 add language버튼을 클릭하고 정보를 입력하십시오. 모두 고객에게 표시되므로 원하지 않는 것을 넣지 마십시오.
  10. 아니오hosting content with Apple선택하십시오
  11. 지금 은 검토 메모를 비워 둘 수 있습니다 .
  12. 건너 뛰기 screenshot for review 지금을 , 모든 우리는 우리가 다시 올 것이다 건너 뜁니다.
  13. '저장'을 클릭하십시오

제품 ID가에 등록하는 데 몇 시간이 걸릴 수 App Store Connect있으므로 인내심을 가지십시오.

프로젝트 설정

App Store Connect에서 인앱 구매 정보를 설정 했으므로 Xcode 프로젝트로 이동 한 다음 응용 프로그램 관리자 (메서드 및 헤더 파일이있는 맨 위에있는 파란색 페이지와 같은 아이콘)로 이동하십시오. 대상 아래의 앱 (첫 번째 대상이어야 함)을 일반으로 이동하십시오. 하단에 linked frameworks and libraries작은 더하기 기호를 클릭하고 프레임 워크를 추가하는 StoreKit.framework것이 표시됩니다. 이렇게하지 않으면 인앱 구매가 작동 하지 않습니다 !

앱의 언어로 Objective-C를 사용하는 경우이 5 단계를 건너 뛰어야합니다 . 그렇지 않으면 Swift를 사용 하는 경우이 질문에 대해 내 Swift 답변을 따르 거나 여기 에서 In-App 구매 코드에 Objective-C를 사용하고 싶지만 앱에서 Swift를 사용하려는 경우 다음을 수행 할 수 있습니다. :

  1. 새로운 만들기 .h로 이동하여 (헤더) 파일을 File> New> File...( Command ⌘+ N). 이 파일은 .h나머지 튜토리얼에서 "Your file" 이라고합니다.

  2. 프롬프트가 표시되면 브리징 헤더 작성을 클릭 하십시오 . 이것이 브리징 헤더 파일이 될 것입니다. 당신이 경우 되지 하라는 메시지가, 당신이 경우 3 단계로 이동 하는 메시지, 3 단계를 건너 뛰고 4 단계로 바로 이동합니다.

  3. 기본 프로젝트 폴더에 .h이름이 지정된 다른 파일 Bridge.h을 만든 다음 응용 프로그램 관리자 (파란색 페이지 모양 아이콘)로 이동 한 다음 Targets섹션 에서 앱을 선택하고을 클릭 Build Settings합니다. Swift Compiler-Code Generation 이라는 옵션을 찾은 다음 Objective-C Bridging Header 옵션을Bridge.h

  4. 브리징 헤더 파일에서, 행을 추가하십시오 #import "MyObjectiveCHeaderFile.h". 여기서 MyObjectiveCHeaderFile1 단계에서 작성한 헤더 파일의 이름입니다. 예를 들어 헤더 파일 이름을 InAppPurchase.h 로 지정하면 #import "InAppPurchase.h"브리지 헤더 파일에 행 을 추가 합니다.

  5. 새로운 목표 - C 방법 (만들기 .m로 이동하여) 파일을 File> New> File...( Command ⌘+ N). 1 단계에서 작성한 헤더 파일과 동일한 이름을 지정하십시오. 예를 들어, 1 단계 InAppPurchase.h 에서 파일을 호출 한 경우이 새 파일을 InAppPurchase.m이라고 합니다. 이 파일은 .m나머지 자습서에서 "사용자 파일" 이라고합니다 .

코딩

이제 실제 코딩을 시작하겠습니다. .h파일에 다음 코드를 추가 하십시오.

BOOL areAdsRemoved;

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

다음으로, 당신은 가져올 필요가 StoreKit당신에 프레임 워크를 .m잘 추가로, 파일 SKProductsRequestDelegateSKPaymentTransactionObserver귀하의 후 @interface선언 :

#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

이제 .m파일에 다음을 추가하면 이 부분이 복잡해 지므로 코드에서 주석을 읽는 것이 좋습니다.

//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;
        }
    }
}

이제 사용자가 트랜잭션을 완료 할 때 발생할 수있는 작업에 대한 코드를 추가하려고합니다.이 자습서에서는 추가 제거를 사용하므로 배너보기가로드 될 때 발생하는 작업에 대한 고유 코드를 추가해야합니다.

- (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];
}

응용 프로그램에 광고가 없으면 원하는 다른 것을 사용할 수 있습니다. 예를 들어 배경색을 파란색으로 만들 수 있습니다. 이를 위해 다음을 사용하려고합니다.

- (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];
}

이제 viewDidLoad메소드의 어딘가에 다음 코드를 추가하려고합니다.

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
}

이제 모든 코드를 추가 했으므로 파일 .xib또는 storyboard파일 로 이동하여 구매 버튼과 복원 버튼을 추가하십시오. 위 후크 tapsRemoveAds IBAction방금 만든 것을 구매 버튼과를 restore IBAction복원 버튼. 이 restore작업은 사용자가 이전에 인앱 구매를 구매했는지 확인하고 아직 구매하지 않은 경우 무료로 인앱 구매를 제공합니다.

검토를 위해 제출

다음으로 이동 앱 스토어에 연결 을 클릭 Users and Access클릭 한 후 Sandbox Testers헤더를 다음 클릭 +밝히는 왼쪽에 기호를 Testers. 이름과성에 임의의 항목을 넣을 수 있으며 전자 메일은 실제 일 필요는 없습니다. 전자 메일 만 기억하면됩니다. 암호를 입력하고 (기억해야 할) 나머지 정보를 입력하십시오. Date of Birth사용자를 18 세 이상으로 만드는 날짜를 만드는 것이 좋습니다 . App Store Territory HAS 올바른 국가에있을 수 있습니다. 다음으로, 기존 iTunes 계정에서 로그 아웃하십시오 (이 자습서 후에 다시 로그인 할 수 있음).

당신이 시뮬레이터를 실행하려고하면 지금, 당신의 iOS 장비에서 응용 프로그램을 실행 구입이됩니다 항상 오류, 당신은 해야 iOS 기기에서 실행합니다. 앱이 실행되면 구매 버튼을 누릅니다. iTunes 계정에 로그인하라는 메시지가 표시되면 방금 만든 테스트 사용자로 로그인하십시오. 다음으로 99 ¢의 구매 또는 가격 계층을 설정 한 것을 확인하라는 메시지가 표시되면 스크린 샷을 찍어screenshot for review App Store Connect에서 사용 하십시오. 이제 결제를 취소하십시오.

이제 이동 앱 스토어 연결 로 이동 한 다음, My Apps> the app you have the In-app purchase on> In-App Purchases. 그런 다음 인앱 구매를 클릭하고 인앱 구매 세부 사항에서 편집을 클릭하십시오. 그런 다음 iPhone에서 방금 찍은 사진을 컴퓨터로 가져 와서 검토 할 수 있도록 스크린 샷으로 업로드 한 다음 검토 메모에 테스트 사용자 이메일과 비밀번호를 입력하십시오. 이것은 검토 과정에서 사과를 도울 것입니다.

이 작업을 마친 후에는 iOS 장치의 응용 프로그램으로 돌아가서 여전히 테스트 사용자 계정으로 로그인 한 다음 구매 버튼을 클릭하십시오. 이번에는 지불 확인 걱정하지 마십시오. 계정에 돈이 청구되지 않습니다. 테스트 사용자 계정은 모든 인앱 구매를 무료로 얻습니다 . 결제를 확인한 후 사용자가 실제로 제품을 구입할 때 어떤 일이 발생하는지 확인하십시오 발생합니다. 그렇지 않은 경우 doRemoveAds메서드에 오류가 발생합니다 . 다시 한 번 인앱 구매를 테스트하기 위해 배경을 파란색으로 변경하는 것이 좋습니다. 실제 인앱 구매는 아닙니다. 모든 것이 효과가 있고 나면 좋을 것입니다! App Store Connect에 업로드 할 때 새로운 바이너리에 인앱 구매를 포함시켜야합니다!


일반적인 오류는 다음과 같습니다.

기록 : No Products Available

이것은 네 가지를 의미 할 수 있습니다.

  • 코드에 올바른 인앱 구매 ID를 입력하지 않았습니다 ( kRemoveAdsProductIdentifier위 코드 의 식별자)
  • App Store Connect 에서 판매 할 인앱 구매를 지우지 않았습니다.
  • 인앱 구매 ID가 App Store Connect에 등록 될 때까지 기다리지 않았습니다 . ID를 생성 한 후 몇 시간 정도 기다리면 문제가 해결됩니다.
  • 계약, 세금 및 은행 정보 작성을 완료하지 않았습니다.

처음으로 작동하지 않으면 좌절하지 마십시오! 포기하지 마십시오! 이 작업을 수행하기 전에 약 5 시간이 걸렸으며 올바른 코드를 검색하는 데 약 10 시간이 걸렸습니다! 위의 코드를 정확하게 사용하면 정상적으로 작동합니다. 질문이 있으시면 언제든지 의견을 보내 주십시오.

iOS 앱에 인앱 구매를 추가하려는 모든 사람들에게 도움이되기를 바랍니다. 건배!


1
그러나 그 줄을 추가하지 않으면 복원 버튼을 클릭해도 아무 일도 일어나지 않습니다. 어쨌든이 자습서에 대해 대단히 감사합니다;)
Ilario

1
"if ( * transaction * == SKPaymentTransactionStateRestored) {"는 다음과 같아야합니다 ( * transaction.transactionState * == SKPaymentTransactionStateRestored) {
Massmaker

13
Apple의 모범 사례에서는 View Controller 작업이 아니라 AppDelegate에 트랜잭션 옵저버를 추가 할 것을 권장합니다. developer.apple.com/library/ios/technotes/tn2387/_index.html
Craig Pickering

3
제품 수가 0 개이지만 이미 3 가지 이유를 확인했습니다. 아이튠즈 연결 내부의 "ios paid app contract"에 연락처 정보, 은행 정보 및 세금 정보를 설정하지 않은 경우 염두에 두어야 할 유일한 이유는 무엇입니까?
Christopher Francisco

4
9 단계에서 표시 이름이 사용자에게 표시되는 것임을 설명해야합니다. "0.99 달러에 하나의 표시 이름을 구매 하시겠습니까?"라는 방식으로 표시됩니다. 표시 이름을 '광고 제거'로 설정 한 후 팝업에 부적절한 문법을 ​​사용하여 앱이 거부 되었기 때문에 중요합니다. 표시 이름을 "광고 제거 패키지"로 변경해야했습니다.
Alan Scarpa

13

Jojodmo 코드를 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")

        }


    }
}
} 

6

신속한 답변

이것은 Objective-C 답변이 너무 커지지 않도록 Swift 사용자 를 위해 내 Objective-C 답변 을 보충하기위한 입니다.

설정

먼저 appstoreconnect.apple.com 에서 인앱 구매를 설정하십시오 . 이에 대한 지침은 Objective-C 답변 의 시작 부분 ( App Store Connect 헤더 아래의 1-13 단계 )을 따르십시오.

제품 ID가 App Store Connect에 등록 되려면 몇 시간이 걸릴 수 있으므로 기다려주십시오.

App Store Connect에서 인앱 구매 정보를 설정 했으므로 인앱 구매를위한 Apple의 프레임 워크를 앱에 추가해야 StoreKit합니다.

Xcode 프로젝트로 이동하여 응용 프로그램 관리자 (앱 파일이있는 왼쪽 막대 상단의 파란색 페이지와 같은 아이콘)로 이동하십시오. 왼쪽의 타겟 아래에서 앱을 클릭 한 다음 (첫 번째 옵션이어야 함) 상단의 "기능"으로 이동하십시오. 목록에 "앱 내 구매"옵션이 표시됩니다. 이 기능을 켜면 Xcode가 StoreKit프로젝트에 추가 됩니다.

코딩

이제 코딩을 시작하겠습니다!

먼저 모든 인앱 구매를 관리 할 새로운 신속한 파일을 만드십시오. 나는 그것을 부를 것이다 IAPManager.swift.

이 파일에서 우리는 새로운 클래스 인 create IAPManagera SKProductsRequestDelegateand 를 만들 것 SKPaymentTransactionObserver입니다. 상단에 반드시 가져 만든다 FoundationStoreKit

import Foundation
import StoreKit

public class IAPManager: NSObject, SKProductsRequestDelegate,
                         SKPaymentTransactionObserver {
}

다음으로 인앱 구매의 식별자를 정의하는 변수를 추가합니다 ( enum여러 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"

다음에 클래스의 이니셜 라이저를 추가합시다 :

// 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
}

이제, 우리는에 필요한 기능을 추가 할 겁니다 SKProductsRequestDelegateSKPaymentTransactionObserver작업에 :

RemoveAdsManager나중에 수업을 추가하겠습니다

// 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
        }
    }
}

이제 구매를 시작하거나 구매를 복원하는 데 사용할 수있는 몇 가지 기능을 추가해 보겠습니다.

// 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()
}

다음으로, IAP를 관리하기 위해 새로운 유틸리티 클래스를 추가하겠습니다. 이 코드는 모두 하나의 클래스에 속할 수 있지만 여러 개가 있으면 조금 더 깔끔해집니다. 나는이라는 새로운 클래스를 만들고 RemoveAdsManager몇 가지 함수를 넣을 것입니다.

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()
}

처음 세 개의 기능 removeAds, restoreRemoveAds그리고 areAdsRemoved, 당신이 특정 작업을 수행하는 전화거야 기능은 다음과 같습니다. 마지막 4 개는에 의해 호출 될 것 IAPManager입니다.

처음 두 함수에 코드를 추가 removeAds하고 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()
}

마지막으로 마지막 5 개의 함수에 코드를 추가해 봅시다.

// 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.
}

이를 종합하면 다음과 같은 결과를 얻습니다.

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
            }
        }
    }

}

마지막으로, 사용자가 구매 및 통화 RemoveAdsManager.removeAds()를 시작하고 RemoveAdsManager.restoreRemoveAds()어딘가에 버튼과 같은 복원 및 통화 를 시작할 수있는 방법을 추가해야합니다 ! App Store 지침에 따라 구매를 어딘가에 복원 할 수있는 버튼을 제공해야합니다.

검토를 위해 제출

마지막으로 App Store Connect에서 검토를 위해 IAP를 제출하십시오! 이를 수행하는 방법에 대한 자세한 지침은 Objective-C answer 의 마지막 부분 인 제출을 위해 제출 헤더 아래를 참조하십시오 .


4

RMStore 는 인앱 구매를위한 간단한 iOS 라이브러리입니다. StoreKit API를 래핑하고 비동기 요청을위한 편리한 블록을 제공합니다. 제품 구입은 단일 방법을 호출하는 것만 큼 쉽습니다.

고급 사용자를 위해이 라이브러리는 영수증 확인, 컨텐츠 다운로드 및 트랜잭션 지속성을 제공합니다.


-1

나는 이것을 게시하기에 꽤 늦었지만 IAP 모델의 로프를 배울 때 비슷한 경험을 공유합니다.

인앱 구매는 Storekit 프레임 워크로 구현 된 iOS에서 가장 포괄적 인 워크 플로우 중 하나입니다. 전체 문서는 당신이 인내심을 읽을 경우 매우 분명하다,하지만 다소 전문적 자연에서 진행된다.

요약:

1-제품 요청-SKProductRequest 및 SKProductRequestDelegate 클래스를 사용하여 제품 ID 요청을 발행하고 자신의 itunesconnect 상점에서 다시 수신하십시오.

이 SKProducts는 사용자가 특정 제품을 구매하는 데 사용할 수있는 상점 UI를 채우는 데 사용해야합니다.

2-지불 요청 발행-SKPayment & SKPaymentQueue를 사용하여 트랜잭션 큐에 지불을 추가하십시오.

3-상태 업데이트를 위해 트랜잭션 큐 모니터링-SKPaymentTransactionObserver Protocol의 updatedTransactions 메소드를 사용하여 상태를 모니터하십시오.

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

4-복원 단추 플로우-SKPaymentQueue의 restoreCompletedTransactions를 사용하여이를 수행하십시오.-3 단계는 SKPaymentTransactionObserver의 다음 메소드와 함께 나머지를 처리합니다.

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

다음 은 단계별 자습서 (자신의 이해 시도의 결과로 저에게 작성)를 설명하는 단계별 자습서입니다. 마지막으로 직접 사용할 수있는 코드 샘플도 제공합니다.

다음 은 텍스트 만 더 잘 설명 할 수있는 특정 사항을 설명하기 위해 만든 것입니다.


21
StackOverflow는 다른 사람들을 돕기위한 웹 사이트이며 다른 사람들을 위해 돈을 버는 것이 아닙니다. 마지막에서 두 번째 링크를 제거하거나 해당 자습서에서 수행 한 작업을 여기에 무료로 게시하십시오.
Jojodmo

@Jojodmo 귀하는 SO의 지침에 따라 귀하의 주장을 입증 할 수 있습니까? 나는 많은 사람들이 자신의 SDK (유료로 지불 한 것조차도)를 면책 조항으로 마케팅하는 것을 보았습니다.
Nirav Bhatt

12
이에 대한 지침은 없지만 돈을 벌기 위해 여기 있다면 잘못된 이유로 여기에있을 것입니다. IMO, 귀하의 답변은 사람들이 다른 사람을 돕는 것이 아니라 비디오 자습서에 등록하도록하는 데 초점을 둔 것 같습니다
Jojodmo

3
이것은 단지 성가신 일뿐입니다.
durazno
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.