Làm cách nào để thêm giao dịch mua trong ứng dụng vào ứng dụng iOS?


257

Làm cách nào để thêm giao dịch mua trong ứng dụng vào ứng dụng iOS? Tất cả các chi tiết là gì và có mã mẫu nào không?

Điều này có nghĩa là tất cả các loại để biết cách thêm mua hàng trong ứng dụng vào ứng dụng iOS


11
Còn đọc "Hướng dẫn lập trình mua hàng trong ứng dụng" thì sao?
rmaddy

Câu trả lời:


554

Người dùng Swift

Người dùng Swift có thể kiểm tra Câu trả lời của tôi cho câu hỏi này .
Hoặc, xem Câu trả lời của Yedidya Reiss , bản dịch mã Objective-C này sang Swift.

Người dùng khách quan-C

Phần còn lại của câu trả lời này được viết bằng Objective-C

Kết nối cửa hàng ứng dụng

  1. Tới appstoreconnect.apple.com và đăng nhập
  2. Nhấp vào My Appssau đó nhấp vào ứng dụng bạn muốn thêm mua hàng vào
  3. Bấm vào Featurestiêu đề, và sau đó chọn In-App Purchasesở bên trái
  4. Nhấp vào +biểu tượng ở giữa
  5. Đối với hướng dẫn này, chúng tôi sẽ thêm một giao dịch mua trong ứng dụng để xóa quảng cáo, vì vậy hãy chọn non-consumable. Nếu bạn định gửi một món đồ vật lý cho người dùng, hoặc đưa cho họ thứ gì đó họ có thể mua nhiều lần, bạn sẽ chọn consumable.
  6. Đối với tên tham chiếu, đặt bất cứ thứ gì bạn muốn (nhưng hãy chắc chắn rằng bạn biết nó là gì)
  7. Đối với id sản phẩm, tld.websitename.appname.referencenameđiều này sẽ hoạt động tốt nhất, vì vậy, ví dụ, bạn có thể sử dụngcom.jojodmo.blix.removeads
  8. Chọn cleared for salevà sau đó chọn tầng giá là 1 (99). Cấp 2 sẽ là $ 1,99 và cấp 3 sẽ là $ 2,99. Danh sách đầy đủ có sẵn nếu bạn nhấp vào view pricing matrixTôi khuyên bạn nên sử dụng cấp 1, vì đó thường là số tiền nhiều nhất mọi người sẽ trả để xóa quảng cáo.
  9. Nhấp vào add languagenút màu xanh và nhập thông tin. Điều này sẽ TẤT CẢ được hiển thị cho khách hàng, vì vậy đừng đặt bất cứ thứ gì bạn không muốn họ nhìn thấy
  10. Để hosting content with Applechọn không
  11. Bạn có thể để trống các ghi chú đánh giá NGAY BÂY GIỜ .
  12. Bỏ qua screenshot for review NGAY BÂY GIỜ , mọi thứ chúng tôi bỏ qua chúng tôi sẽ quay lại.
  13. Nhấp vào để lưu'

Có thể mất vài giờ để ID sản phẩm của bạn đăng ký App Store Connect, vì vậy hãy kiên nhẫn.

Thiết lập dự án của bạn

Bây giờ bạn đã thiết lập thông tin mua trong ứng dụng của mình trên App Store Connect, hãy vào dự án Xcode của bạn và đi đến trình quản lý ứng dụng (biểu tượng giống như trang màu xanh ở đầu nơi chứa các phương thức và tệp tiêu đề của bạn) ứng dụng của bạn theo mục tiêu (nên là ứng dụng đầu tiên), sau đó đi đến chung. Ở phía dưới, bạn sẽ thấy linked frameworks and librariesnhấp vào biểu tượng dấu cộng nhỏ và thêm khung StoreKit.frameworkNếu bạn không làm điều này, giao dịch mua trong ứng dụng sẽ KHÔNG hoạt động!

Nếu bạn đang sử dụng Objective-C làm ngôn ngữ cho ứng dụng của mình, bạn nên bỏ qua năm bước này . Mặt khác, nếu bạn đang sử dụng Swift, bạn có thể theo dõi Câu trả lời của Swift cho câu hỏi này, tại đây hoặc, nếu bạn muốn sử dụng Objective-C cho mã Mua trong ứng dụng nhưng đang sử dụng Swift trong ứng dụng của mình, bạn có thể làm như sau :

  1. Tạo mới .h(tiêu đề) tập tin bằng cách vào File> New> File...( Command ⌘+ N). Tập tin này sẽ được gọi là " .hTập tin của bạn " trong phần còn lại của hướng dẫn

  2. Khi được nhắc, bấm Tạo Tiêu đề Cầu nối . Đây sẽ là tập tin tiêu đề bắc cầu của chúng tôi. Nếu bạn không được nhắc, hãy đến bước 3. Nếu bạn được nhắc, hãy bỏ qua bước 3 và chuyển trực tiếp đến bước 4.

  3. Tạo một .htệp khác có tên Bridge.htrong thư mục dự án chính, sau đó chuyển đến Trình quản lý ứng dụng (biểu tượng giống trang màu xanh), sau đó chọn ứng dụng của bạn trong Targetsphần và nhấp vào Build Settings. Tìm tùy chọn cho biết Trình biên dịch Swift - Tạo mã , sau đó đặt tùy chọn Tiêu đề cầu nối Objective-C thànhBridge.h

  4. Trong tệp tiêu đề bắc cầu của bạn, hãy thêm dòng #import "MyObjectiveCHeaderFile.h", MyObjectiveCHeaderFiletên của tệp tiêu đề mà bạn đã tạo ở bước một. Vì vậy, ví dụ, nếu bạn đặt tên tệp tiêu đề của mình là InAppPurchase.h , bạn sẽ thêm dòng #import "InAppPurchase.h"vào tệp tiêu đề cầu của mình.

  5. Tạo mới Objective-C Phương pháp ( .mtập tin) bằng cách vào File> New> File...( Command ⌘+ N). Đặt tên giống như tệp tiêu đề bạn đã tạo ở bước 1. Ví dụ: nếu bạn đã gọi tệp ở bước 1 InAppPurchase.h , bạn sẽ gọi tệp mới này là InAppPurchase.m . Tập tin này sẽ được gọi là " .mTập tin của bạn " trong phần còn lại của hướng dẫn.

Mã hóa

Bây giờ chúng ta sẽ đi vào mã hóa thực tế. Thêm mã sau vào .htệp của bạn :

BOOL areAdsRemoved;

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

Tiếp theo, bạn cần nhập StoreKitkhung vào .mtệp của mình , cũng như thêm SKProductsRequestDelegateSKPaymentTransactionObserversau khi @interfacekhai báo:

#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

và bây giờ thêm phần sau vào .mtệp của bạn , phần này trở nên phức tạp, vì vậy tôi khuyên bạn nên đọc các nhận xét trong 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;
        }
    }
}

Bây giờ bạn muốn thêm mã của mình cho những gì sẽ xảy ra khi người dùng kết thúc giao dịch, đối với hướng dẫn này, chúng tôi sử dụng loại bỏ thêm, bạn sẽ phải thêm mã của riêng mình cho những gì xảy ra khi tải banner xem.

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

Nếu bạn không có quảng cáo trong ứng dụng của mình, bạn có thể sử dụng bất kỳ thứ gì khác mà bạn muốn. Ví dụ, chúng ta có thể làm cho màu của nền xanh. Để làm điều này, chúng tôi muốn sử dụng:

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

Bây giờ, ở đâu đó trong viewDidLoadphương thức của bạn , bạn sẽ muốn thêm mã sau đây:

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
}

Bây giờ bạn đã thêm tất cả mã, đi vào tệp .xibhoặc storyboardtệp của bạn và thêm hai nút, một nút nói mua và nút còn lại nói khôi phục. Kết nối tapsRemoveAds IBActionvới nút mua hàng mà bạn vừa thực hiện và restore IBActionnút khôi phục. Các restorehành động sẽ kiểm tra xem người dùng trước đây đã mua mua trong ứng dụng, và cung cấp cho họ mua trong ứng dụng miễn phí nếu họ chưa có nó.

Đệ trình để xem xét

Tiếp theo, đi vào App Store Connect và nhấp vào Users and Accesssau đó nhấp vào Sandbox Testerstiêu đề và sau đó nhấp vào +biểu tượng ở bên trái nơi nó nói Testers. Bạn chỉ có thể đặt những thứ ngẫu nhiên cho tên và họ, và e-mail không phải là thật - bạn chỉ cần có thể nhớ nó. Đặt mật khẩu (mà bạn sẽ phải nhớ) và điền vào phần còn lại của thông tin. Tôi khuyên bạn nên thực hiện Date of Birthmột ngày sẽ khiến người dùng từ 18 tuổi trở lên. App Store Territory ĐÃ ở đúng quốc gia Tiếp theo, đăng xuất khỏi tài khoản iTunes hiện tại của bạn (bạn có thể đăng nhập lại sau hướng dẫn này).

Bây giờ, hãy chạy ứng dụng của bạn trên thiết bị iOS của bạn, nếu bạn cố gắng chạy nó trên mô phỏng, việc mua sẽ luôn báo lỗi, bạn PHẢI chạy nó trên thiết bị iOS của bạn. Khi ứng dụng đang chạy, chạm vào nút mua hàng. Khi bạn được nhắc đăng nhập vào tài khoản iTunes của mình, hãy đăng nhập với tư cách là người dùng thử nghiệm mà chúng tôi vừa tạo. Tiếp theo, khi nó yêu cầu bạn xác nhận việc mua 99 hoặc bất cứ điều gì bạn đặt mức giá quá, HÃY NHANH TAY MỘT NỀN TẢNG Đây là những gì bạn sẽ sử dụng cho screenshot for reviewKết nối App Store của mình. Bây giờ hủy thanh toán.

Bây giờ, đi đến App Store Kết nối , sau đó đi đến My Apps> the app you have the In-app purchase on> In-App Purchases. Sau đó nhấp vào mua trong ứng dụng của bạn và nhấp vào chỉnh sửa trong chi tiết mua trong ứng dụng. Khi bạn đã hoàn thành việc đó, hãy nhập ảnh mà bạn vừa chụp trên iPhone vào máy tính của mình và tải lên đó làm ảnh chụp màn hình để xem xét, sau đó, trong ghi chú đánh giá, hãy đặt e-mail và mật khẩu TEST USER của bạn . Điều này sẽ giúp táo trong quá trình xem xét.

Sau khi bạn đã hoàn thành việc này, hãy quay lại ứng dụng trên thiết bị iOS của bạn, vẫn đăng nhập với tư cách là tài khoản người dùng thử nghiệm và nhấp vào nút mua hàng. Lần này, hãy xác nhận thanh toán Đừng lo lắng, điều này sẽ KHÔNG tính phí tài khoản của bạn BẤT K money tiền nào, kiểm tra tài khoản người dùng được miễn phí tất cả các giao dịch mua trong ứng dụng Sau khi bạn đã xác nhận thanh toán, hãy chắc chắn rằng điều gì xảy ra khi người dùng thực sự mua sản phẩm của bạn xảy ra. Nếu không, thì đó sẽ là một lỗi với doRemoveAdsphương thức của bạn . Một lần nữa, tôi khuyên bạn nên sử dụng thay đổi nền thành màu xanh để kiểm tra mua trong ứng dụng, đây không phải là mua trong ứng dụng thực tế của bạn. Nếu mọi thứ hoạt động và bạn tốt để đi! Chỉ cần đảm bảo bao gồm giao dịch mua trong ứng dụng trong tệp nhị phân mới của bạn khi bạn tải nó lên App Store Connect!


Dưới đây là một số lỗi phổ biến:

Đã đăng nhập: No Products Available

Điều này có thể có nghĩa là bốn điều:

  • Bạn đã không đặt đúng ID mua hàng trong ứng dụng vào mã của mình (đối với mã định danh kRemoveAdsProductIdentifiertrong mã trên
  • Bạn đã không xóa giao dịch mua trong ứng dụng của mình để bán trên App Store Connect
  • Bạn đã không đợi ID mua hàng trong ứng dụng được đăng ký trong App Store Connect . Đợi một vài giờ từ khi tạo ID và vấn đề của bạn sẽ được giải quyết.
  • Bạn đã không hoàn thành điền thông tin Thỏa thuận, Thuế và Ngân hàng.

Nếu lần đầu tiên nó không hoạt động, đừng nản lòng! Đừng bỏ cuộc! Tôi mất khoảng 5 giờ liên tục trước khi tôi có thể làm việc này và khoảng 10 giờ để tìm đúng mã! Nếu bạn sử dụng mã ở trên một cách chính xác, nó sẽ hoạt động tốt. Hãy bình luận nếu bạn có bất cứ thắc mắc gì cả .

Tôi hy vọng điều này sẽ giúp tất cả những người hy vọng thêm giao dịch mua trong ứng dụng vào ứng dụng iOS của họ. Chúc mừng!


1
nhưng nếu tôi không thêm dòng đó, khi tôi nhấp vào nút khôi phục thì không có gì xảy ra .. dù sao cũng cảm ơn bạn rất nhiều vì hướng dẫn này;)
Ilario

1
"if ( * giao dịch * == SKPaymentTransactionStateRestored) {" nên là if ( * giao dịch.transactionState * == SKPaymentTransactionStateRestored) {
Massmaker 30/03/2015

13
Thực tiễn tốt nhất của Apple khuyên bạn nên thêm trình quan sát giao dịch vào AppDelegate, chứ không phải các hành động của trình điều khiển xem. developer.apple.com/l
Craig Pickering

3
Tôi đang nhận được 0 sản phẩm, nhưng tôi đã kiểm tra 3 lý do có thể bạn liệt kê. Điều duy nhất nảy ra trong đầu tôi là nếu tôi chưa thiết lập thông tin liên hệ, thông tin ngân hàng và thông tin thuế về "hợp đồng ứng dụng trả phí ios" bên trong itunes, đây có phải là lý do không?
Christopher Francisco

4
Bạn nên giải thích rằng trong Bước 9, tên Hiển thị là thứ được hiển thị cho người dùng. Và nó được trình bày theo cách này: "Bạn có muốn mua một TÊN HIỂN THỊ với giá 0,99 đô la không?". Điều này rất quan trọng vì tôi đã đặt tên hiển thị "Xóa quảng cáo" và sau đó ứng dụng của tôi bị từ chối vì tôi đang sử dụng ngữ pháp không đúng trong cửa sổ bật lên! Tôi đã phải thay đổi Tên hiển thị của mình thành "Gói loại bỏ quảng cáo".
Alan Scarpa

13

Chỉ cần dịch mã Jojodmo sang 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

Trả lời nhanh

Điều này có nghĩa là để bổ sung câu trả lời Objective-C của tôi cho người dùng Swift, để giữ cho câu trả lời Objective-C không quá lớn.

Thiết lập

Đầu tiên, hãy thiết lập giao dịch mua trong ứng dụng trên appstoreconnect.apple.com . Thực hiện theo phần đầu của câu trả lời Objective-C của tôi (các bước 1-13, dưới tiêu đề Kết nối App Store ) để được hướng dẫn thực hiện điều đó.

Có thể mất vài giờ để ID sản phẩm của bạn đăng ký trong App Store Connect, vì vậy hãy kiên nhẫn.

Bây giờ bạn đã thiết lập thông tin mua trong ứng dụng của mình trên App Store Connect, chúng tôi cần thêm khung của Apple để mua trong ứng dụng StoreKit, vào ứng dụng.

Đi vào dự án Xcode của bạn và đi đến trình quản lý ứng dụng (biểu tượng giống như trang màu xanh ở đầu thanh bên trái nơi chứa các tệp của ứng dụng của bạn). Nhấp vào ứng dụng của bạn dưới các mục tiêu ở bên trái (nó phải là tùy chọn đầu tiên), sau đó chuyển đến "Khả năng" ở trên cùng. Trong danh sách, bạn sẽ thấy tùy chọn "Mua trong ứng dụng". BẬT khả năng này, và Xcode sẽ thêm StoreKitvào dự án của bạn.

Mã hóa

Bây giờ, chúng ta sẽ bắt đầu viết mã!

Đầu tiên, tạo một tệp nhanh chóng mới sẽ quản lý tất cả các giao dịch mua trong ứng dụng của bạn. Tôi sẽ gọi nó IAPManager.swift.

Trong tệp này, chúng tôi sẽ tạo một lớp mới, được gọi IAPManagerlà một SKProductsRequestDelegateSKPaymentTransactionObserver. Ở đầu trang, đảm bảo bạn nhập FoundationStoreKit

import Foundation
import StoreKit

public class IAPManager: NSObject, SKProductsRequestDelegate,
                         SKPaymentTransactionObserver {
}

Tiếp theo, chúng tôi sẽ thêm một biến để xác định mã định danh cho giao dịch mua trong ứng dụng của chúng tôi (bạn cũng có thể sử dụng một biến enum, sẽ dễ duy trì hơn nếu bạn có nhiều 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"

Hãy thêm một trình khởi tạo cho lớp của chúng ta tiếp theo:

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

Bây giờ, chúng ta sẽ thêm các chức năng cần thiết cho SKProductsRequestDelegateSKPaymentTransactionObserverđể làm việc:

Chúng tôi sẽ thêm RemoveAdsManagerlớp sau

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

Bây giờ, hãy thêm một số chức năng có thể được sử dụng để bắt đầu mua hàng hoặc khôi phục mua hàng:

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

Tiếp theo, hãy thêm một lớp tiện ích mới để quản lý IAP của chúng tôi. Tất cả các mã này có thể nằm trong một lớp, nhưng việc có nhiều mã này làm cho nó sạch hơn một chút. Tôi sẽ tạo một lớp mới được gọi RemoveAdsManager, và trong đó, đặt một vài hàm

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

Ba chức năng đầu tiên removeAds,restoreRemoveAds , vàareAdsRemoved , là chức năng mà bạn sẽ gọi để làm một số hành động. Bốn người cuối cùng là một người sẽ được gọi bởi IAPManager.

Hãy thêm một số mã vào hai chức năng đầu tiên removeAdsrestoreRemoveAds:

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

Và cuối cùng, hãy thêm một số mã vào năm chức năng cuối cùng.

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

Đặt tất cả lại với nhau, chúng ta có được một cái gì đó như thế này:

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

}

Cuối cùng, bạn cần thêm một số cách để người dùng bắt đầu mua hàng và gọi RemoveAdsManager.removeAds() và bắt đầu khôi phục và gọiRemoveAdsManager.restoreRemoveAds() , như một nút ở đâu đó! Hãy nhớ rằng, theo hướng dẫn của App Store, bạn cần cung cấp một nút để khôi phục mua hàng ở đâu đó.

Đệ trình để xem xét

Điều cuối cùng cần làm là gửi IAP của bạn để xem xét trên App Store Connect! Để được hướng dẫn chi tiết về cách thực hiện điều đó, bạn có thể làm theo phần cuối của câu trả lời Mục tiêu-C của tôi , trong phần Gửi bài cho tiêu đề đánh giá .


4

RMStore là một thư viện iOS nhẹ dành cho Mua trong ứng dụng. Nó bao bọc API StoreKit và cung cấp cho bạn các khối tiện dụng cho các yêu cầu không đồng bộ. Mua một sản phẩm dễ dàng như gọi một phương pháp duy nhất.

Đối với người dùng nâng cao, thư viện này cũng cung cấp xác minh biên nhận, tải xuống nội dung và kiên trì giao dịch.


-1

Tôi biết tôi khá muộn để đăng bài này, nhưng tôi chia sẻ kinh nghiệm tương tự khi tôi học được các sợi dây của mô hình IAP.

Mua trong ứng dụng là một trong những quy trình làm việc toàn diện nhất trong iOS được triển khai bởi khung Storekit. Các toàn bộ tài liệu là khá rõ ràng nếu bạn kiên nhẫn để đọc nó, nhưng có phần tiên tiến trong bản chất của technicality.

Để tóm tắt:

1 - Yêu cầu các sản phẩm - sử dụng các lớp SK SẢNtRequest & SK SẢNtRequestDelegate để đưa ra yêu cầu cho ID sản phẩm và nhận lại chúng từ cửa hàng itunesconnect của riêng bạn.

Những SK Products này nên được sử dụng để điền vào UI cửa hàng của bạn mà người dùng có thể sử dụng để mua một sản phẩm cụ thể.

2 - Phát hành yêu cầu thanh toán - sử dụng SKPayment & SKPaymentQueue để thêm thanh toán vào hàng đợi giao dịch.

3 - Giám sát hàng đợi giao dịch để cập nhật trạng thái - sử dụng phương thức Cập nhật giao dịch của SKPaymentTransactionObserver Protocol để theo dõi trạng thái:

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

4 - Khôi phục luồng nút - sử dụng SKPaymentQueue restoreCompletedTransilities để thực hiện việc này - bước 3 sẽ xử lý phần còn lại, cùng với các phương pháp sau của SKPaymentTransactionObserver:

paymentQueueRestoreCompletedTransactionsFinished
restoreCompletedTransactionsFailedWithError

Đây là một hướng dẫn từng bước (do tác giả của tôi là kết quả của nỗ lực của tôi để hiểu nó) giải thích nó. Cuối cùng, nó cũng cung cấp mẫu mã mà bạn có thể trực tiếp sử dụng.

Đây là một cái khác tôi tạo ra để giải thích một số điều mà chỉ văn bản có thể mô tả theo cách tốt hơn.


21
StackOverflow là một trang web để giúp đỡ người khác, và không phải để cố gắng kiếm tiền từ họ. Bạn nên xóa liên kết thứ hai đến cuối cùng, hoặc chỉ đăng những gì được thực hiện trong hướng dẫn đó ở đây, miễn phí.
Jojodmo

@Jojodmo bạn có thể chứng minh yêu cầu của mình với bất kỳ hướng dẫn nào của SO không? Tôi thấy rất nhiều người tiếp thị SDK của riêng họ (thậm chí đã trả tiền) với một sự từ chối, điều mà tôi nghĩ cũng có rất nhiều ở đây.
Nirav Bhatt

12
Không có hướng dẫn chống lại điều đó, nhưng nếu bạn ở đây để kiếm tiền, có lẽ bạn ở đây vì những lý do sai lầm. IMO, câu trả lời của bạn dường như tập trung vào việc khiến mọi người đăng ký tham gia các video hướng dẫn của bạn chứ không phải giúp đỡ người khác
Jojodmo

3
Điều này chỉ là không có gì ngoài sự khó chịu.
durazno
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.