Recently, the company has encountered a lot of problems with the in-app purchase membership function. To summarize and record, first of all, it is important to distinguish between Apple Pay and IN-app purchase. Please go to the official documentation address for a detailed explanation of each step

This article is divided into: 1. Internal purchase payment process; 2. Development and integration steps; 3, the problem (in the pit) record before the solution did not see the official document took a lot of detours online blog system is not strongly recommended to go over the official document first

Take a look at the in-App Purchase payment process (official)

  1. The program sends a request to the server for a list of products.
  2. The server returns a list of product identifiers.
  3. The application sends a request to the App Store to get information about the product.
  4. The App Store returns product information.
  5. The App displays the returned product information to the user (App Store interface)
  6. The user selects a product
  7. The application sends a payment request to the App Store
  8. The App Store processes payment requests and returns transaction completion information.
  9. The program retrieves data from the information and sends it to the server.
  10. The server records the data and performs the audit (ours).
  11. The server sends the data to the App Store to verify the transaction.
  12. The App Store parses the received data and returns the data along with a token indicating whether it is valid.
  13. The server reads the returned data and determines what the user purchased.
  14. The server passes the purchased content to the program.

Step 1: Internal purchase account tax agreement, bank card binding related

Iaps,In App Purchases, are also available for purchase In the App. You may also purchase iaps, or in-app Purchases, during the course of your battery life

Step 2: Xcode Settings are relevant

Turning on the In-App Purchase switch should also show up as available status In the developer Certificate Center’s project certificate

Step 3: Add in-app purchase items in App Store Content -> My App

  1. On the home page, click “My App” and select the App associated with the in-app purchase item.
  2. In the toolbar, click “Features,” then click “Buy Items in App” in the left column.
  3. To add an in-app purchase item, go to “In-App Purchase Item” and click the “Add” button (+).

Select the function to add in-app purchase items

The purchased goods correspond to four types of official documents: consumable, non-consumable, automatic renewal and non-renewal

  1. Select Consumable Items, Non-Consumable Items, or Non-Renewed Subscription, and click Create. For information about automatic renewal subscriptions, see Creating automatic Renewal Subscriptions.
  2. Add the reference name, product ID, and localized display name.
  3. Click Save or Submit for review.
You can enter all the metadata when you create your in-app purchase item, or enter your in-app purchase item information later.Copy the code

The product ID must be carefully filled in the project. The product information needs to be obtained according to the ID. The price has different grades

Step 4: Sandbox the environment test account

Because money is involved, you can’t directly use money to pay, so you need to add a sandbox tech tester account (this account is virtual) and the payment will not be deducted. You can see the figure in step 3 in the App Store Content, select users and functions, go to the following page and select sandbox tech tester Adding a Test Account

Unknown Email XXXXXX Unknown Email XXXXXX Unknown Email XXXXXX Unknown Email XXXXXX Unknown Email XXXXXX Unknown Email XXXXXX

Step 5: Code implementation (let me know in the comments if you have any questions)

.h files

typedef void(^XSProductStatusBlock)(BOOL isStatus); @interface XSApplePayManager : NSObject + (instancetype)shareManager; */ + (void)checkOrderStatus; /** request payment information by item ID @param orderId orderId @param productId item ID @param statusBlock (void)requestProductWithOrderId:(NSString *)orderId productId:(NSString *)productId statusBlock:(XSProductStatusBlock)statusBlock;Copy the code

.m files

#import <StoreKit/StoreKit.h>
#import "APIManager.h"
#import "UIAlertView+AABlock.h"

@interface XSApplePayManager ()<SKProductsRequestDelegate,SKPaymentTransactionObserver>

@property (nonatomic, copy) NSString *orderId;
@property (nonatomic, copy) XSProductStatusBlock statusBlcok;

@end

@implementation XSApplePayManager

+ (instancetype)shareManager
{
    static dispatch_once_t onceToken;
    static XSApplePayManager *manager = nil;
    dispatch_once(&onceToken, ^{
        manager = [[XSApplePayManager alloc]init];
    });
    returnmanager; } /** checkOrderStatus {NSDictionary *orderInfo = [XSApplePayManager getReceiptData];} /** checkOrderStatus {NSDictionary *orderInfo = [XSApplePayManager getReceiptData];if(orderInfo ! = nil) { NSString *orderId = orderInfo[@"orderId"];
        NSString *receipt = orderInfo[@"receipt"]; [[XSApplePayManager shareManager] verifyPurchaseForServiceWithOrderId:orderId receipt:receipt]; }}#pragma Mark -- close the last unfinished transaction
-(void)removeAllUncompleteTransactionsBeforeNewPurchase{
    
    NSArray* transactions = [SKPaymentQueue defaultQueue].transactions;
    
    if (transactions.count >= 1) {
        
        for (SKPaymentTransaction* transaction in transactions) {
            if(transaction.transactionState == SKPaymentTransactionStatePurchased || transaction.transactionState == SKPaymentTransactionStateRestored) { [[SKPaymentQueue defaultQueue]finishTransaction:transaction]; }}}else{
        NSLog(@"No history of unconsumed orders"); }} / * * examination permission to add monitoring start payment process * / - (void) requestProductWithOrderId: (nsstrings *) orderId productId: (productId nsstrings *) statusBlock:(XSProductStatusBlock)statusBlock {if (orderId == nil || productId == nil) {
        [AAProgressManager showFinishWithStatus:@"Wrong order number/item number"];
        return;
    }
    
    if ([[XZDeviceManager didRoot] isEqualToString:@"didRoot"[AAProgressManager showFinishWithStatus:@]) {// write your own jailbreak judgment method"Jailbroken phones do not support in-app purchase."];
        return;
    }
    
    
    if([SKPaymentQueue canMakePayments]){
        
        [self removeAllUncompleteTransactionsBeforeNewPurchase];
        
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

        self.orderId = orderId;
        self.statusBlcok = statusBlock;
        [self requestProductData:productId];
        
    }else{
        [AAProgressManager showFinishWithStatus:L(@"Please enable in-app payments.")]; */ - (void)requestProductData:(NSString *)type{
    
    [AAProgressManager showWithStatus:@"Requesting..."];
    NSArray *product = [[NSArray alloc] initWithObjects:type,nil];
    
    NSSet *nsset = [NSSet setWithArray:product];
    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
    request.delegate = self;
    [request start];
}


#pragma mark -- SKProductsRequestDelegate- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{ NSArray *product = response.products;if([product count] == 0){
        [AAProgressManager showFinishWithStatus:L(@"Unable to obtain product information, please try again")];
        return;
    }
    
    NSLog(@"Product paid quantity :%ld",product.count); SKProduct *p = product.firstObject; SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:p]; payment.quantity = (NSInteger)p.price; // Number of purchases = priceif(payment.quantity == 0) { payment.quantity = 1; } payment.applicationUsername = self.orderId; //[NSString stringWithFormat:@"% @",[[AAUserManager shareManager] getUID]]; [[SKPaymentQueue defaultQueue] addPayment:payment]; } // request failed - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{NSLog(@)"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- wrong -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- : % @", error);
    if (self.statusBlcok) {
        self.statusBlcok(NO);
    }
    [AAProgressManager showFinishWithStatus:L(@"Failed to obtain product information from Apple")];

}

- (void)requestDidFinish:(SKRequest *)request{
    NSLog(@"------------ End of feedback -----------------%@",request);
}

#pragma mark -- monitor AppStore payment status
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{
    
    NSLog(@"Monitor AppStore payment status");
    dispatch_async(dispatch_get_main_queue(), ^{
        for(SKPaymentTransaction *tran in transaction){
            switch (tran.transactionState) {
                caseSKPaymentTransactionStatePurchased: {/ / sent to apple server authentication credentials [self verifyPurchaseWithPaymentTransaction]; [[SKPaymentQueue defaultQueue] finishTransaction:tran]; }break;
                case SKPaymentTransactionStatePurchasing:
                    NSLog(@"Add item to list");
                    break;
                case SKPaymentTransactionStateRestored:
                {
                    [AAProgressManager showFinishWithStatus:L(@"Goods already purchased")];
                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                }
                    break;
                case SKPaymentTransactionStateFailed:
                {
                    if (self.statusBlcok) {
                        self.statusBlcok(NO);
                    }
                    NSLog(@"Transaction failed.");

                    [[SKPaymentQueue defaultQueue] finishTransaction:tran];
                }
                    break;
                case SKPaymentTransactionStateDeferred:
                {
                    [AAProgressManager showFinishWithStatus:L(@"Final state not determined.")];
                }
                    break;
                default:
                    break; }}}); }#pragma mark -- verify/** Verify purchase, Avoid the jailbreak software simulation apple request to illegal to buy * / - (void) verifyPurchaseWithPaymentTransaction {/ / trade credentials and was obtained from the sandbox NSURL * receiptUrl = spliced into a request body data [[NSBundle mainBundle] appStoreReceiptURL]; NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl]; NSString *receiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; [self saveReceiptData:@{@"receipt":receiptString,
                            @"orderId":self.orderId}];
    
  
    [self verifyPurchaseForServiceWithOrderId:self.orderId
                                      receipt:receiptString];
}

- (void)verifyPurchaseForServiceWithOrderId:(NSString *)orderId
                                    receipt:(NSString *)receiptString
{
    if (orderId == nil && receiptString == nil) {
        if (self.statusBlcok) {
            self.statusBlcok(NO);
        }
        [AAProgressManager showFinishWithStatus:@"Order number/voucher is invalid"];
        return;
    }
    
    [self removeTransaction];

    [AAProgressManager showWithStatus:@"Verifying server..."];
    
    WS(weakSelf);
    [[APIManager sharedInstance] verifyPurchaseWithOrderID:orderId
                                                    params:@{@"ceceipt-data":receiptString}
                                                   success:^(id response)
     {
         dispatch_async(dispatch_get_main_queue(), ^{
             [AAProgressManager dismiss];
             [AAProgressManager showFinishWithStatus:L(@"Transaction closed.")];
             [weakSelf removeLocReceiptData];
             if(weakSelf.statusBlcok) { weakSelf.statusBlcok(YES); }}); } failure:^(NSError *error) { dispatch_async(dispatch_get_main_queue(), ^{ [CommonFunction showError:error]; [weakSelf verifyPurchaseFail]; }); }]; } - (void)verifyPurchaseFail { WS(weakSelf); UIAlertView *altert =[UIAlertView alertViewWithTitle:@"Server authentication failed"
                                                 message:@"There was an error in the process of validating the server, \n please check whether the network environment can be validated again \n If cancelled can be restarted under good network environment traveler can continue to validate the payment again"
                                       cancelButtonTitle:L(@"Cancel")
                                       otherButtonTitles:@[L(@"Double-check")]
                                               onDismiss:^(NSInteger buttonIndex)
                          {
                              dispatch_async(dispatch_get_main_queue(), ^
                                             {
                                                 [XSApplePayManager checkOrderStatus];
                                             });           ;
                              
                          } onCancel:^{
                              dispatch_async(dispatch_get_main_queue(), ^{
                                  
                                  if (weakSelf.statusBlcok) {
                                      weakSelf.statusBlcok(NO);
                                  }
                                  [PromptInfo showWithText:@"Can be restarted in a good network environment traveler can continue to verify payment again."]; }); }]; [altert show]; } // end of transaction - (void)completeTransaction:(SKPaymentTransaction *)transaction {[[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } - (void)removeTransaction { [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; }#pragma mark -- Saves one payment voucher locally
static NSString *const kSaveReceiptData = @"kSaveReceiptData";

- (void)saveReceiptData:(NSDictionary *)receiptData
{
    [[NSUserDefaults standardUserDefaults] setValue:receiptData forKey:kSaveReceiptData];
    [[NSUserDefaults standardUserDefaults]synchronize];
}

+ (NSDictionary *)getReceiptData
{
    return [[NSUserDefaults standardUserDefaults] valueForKey:kSaveReceiptData];
}

- (void)removeLocReceiptData
{
    [[NSUserDefaults standardUserDefaults] removeObjectForKey:kSaveReceiptData];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
Copy the code
Step 6: IAP payment process & server verification process

The whole payment process is as follows: 1. The client requests Appstore to purchase the product (assuming that the product information has been obtained), and after Appstore verifies the product successfully, the payment will be deducted from the balance of the user’s Apple account. 2.Appstore returns a receipt data to the client, which records the certificate and signature information of this transaction. 3. The client provides receipt data 4 to a server we can trust. The server does a base64 encoding of the receipt-data 5. The server sends the encoded receipt data to itunes.appstore for verification 6. Itunes. appstore returns the verification result to server 7. The server issues corresponding props and push data update notifications to the client for the status and type of goods purchased

Missed order handling ensures successful receipt-data submission and exception handling

It is based on IAP Server Model, and we know that the mobile network is unstable, so we cannot ensure that receipt data is always submitted to the Server after successful payment. If this happens, it means that the player is charged by the appstore, but doesn’t receive the item from the server. Missed receipt processing: The solution to this problem is to submit receipt data on the client side to our server and have our server send a validation request to the Apple server to validate the receipt. In the absence of a reply, the client must save the receipt-data and send requests to the server periodically or in a reasonable UI until the receipt- bill record is deleted. What if the client fails to submit receipt-data? The player has been charged and received a receipt from the appstore, but still hasn’t received the game item, so they complain to the game customer service.

This has happened in the past as well, the usual disputes between players and game operators. The game’s customer service asks the player for the game account and the appstore receipt number, and checks itunes-connect to see if the order exists. If the order exists, contact the developer to check the game server to see if the order number matches the player name and if it has already been used. The purpose of this check is to prevent malicious players from using the already used order number to trick (validated bills can be asked for validation again, once sent manually to the server for testing and successfully processed) and falsely claim that they did not receive the item. This is the purpose of the security logic mentioned in red in the IAP Server Model section above. Of course, if the order number is not found, it means that the order has not been used, manually to the player to replace the goods.

Check out this blog post for moreApple IAP secure payment and security receipt verification

In the pit of

Q: 21004 The shared Key you provided is inconsistent with the shared key of the account. What is the shared key? Where can I obtain the shared key?

To improve the security of your App’s transactions with Apple servers when verifying automatic renewal subscriptions, you can include A 32-bit randomly generated alphanumeric string in your receipt as A shared key. Generate the shared key in App Store Connect. You can generate a master shared key as a single code for all your apps, or as an App-specific shared key for a single App. You can also use the master shared key for some of your apps and use the App-specific shared key for other apps. Click expand below to see how the shared key is generated

Q: The sandbox technical tester is always prompted with mailbox error when adding failed

A: Sandbox technology test account for payment test Any email that has not created an Apple ID can be fake email as well as important password format must contain case as the official account registration rules (for example: Lh123456*)

####Q: What are the parameters for your own server to validate receipts/credentials to apple servers? What does it mean to verify the status code of the Apple IAP Sever to Status Code?

**A:**21002, 21003, 21004, 21005, 21006, 21007… Check out this document to verify receipts with the App Store

Q: The difference between Apple and IAP

**A:** IAps are in-app purchase services that link to the App Store and are generally used as channels for virtual goods (e.g. memberships). Apple Pay is A card package that works with major banks and is similar to paying with A credit card

Q: How can I view the specific order through itunes-Connect? The order information cannot be directly seen in itunes-Connect. You can use the following method to query

1. You can send bill verification to Apple through the bill. If it is valid, you can manually reissue 2. 3. Use third-party transaction functions such as TalkingData to automatically record billing data

There are a few more questions you can learn from this blog post on iOS, you must see the in-app purchase jailbreak

Feel helpful can follow me to continue to add….