ios-iap应用内购接入

ios-iap应用内购接入


前篇


前置条件

  1. 协议是 active 状态, https://appstoreconnect.apple.com/agreements/#/

  2. 产品 id 是 非 元数据丢失 状态

  3. 内购 item 截图, 分辨率: 640 x 920


添加内购产品

  1. 产品 id (Product ID) 就是代码中需要用的 id.
  2. 图片资源
    • iOS requires at least 640 x 920 pixels. 不能有 alpha 值.

流程

  1. 先创建个沙盒测试人员, 测试时不会真实扣钱. https://appstoreconnect.apple.com/access/testers

  2. 内购代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    #import "IapHelper.h"
    #import "NetTool.h"
    #import "NSString+MyExt.h"

    #import <Foundation/Foundation.h>
    #import <StoreKit/StoreKit.h>
    #import <YYModel/YYModel.h>

    @interface IapHelper()<SKProductsRequestDelegate, SKPaymentTransactionObserver>
    @property (strong, nonatomic) CodeMsgFn _Nullable cb;
    @end

    @implementation IapHelper

    static IapHelper *_sharedIns = nil;
    +(instancetype) shareInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    _sharedIns = [[self alloc] init] ;
    [[SKPaymentQueue defaultQueue] addTransactionObserver:_sharedIns];
    }) ;
    return _sharedIns ;
    }

    // 内扣接口
    - (void)getById:(NSString *)prodId cb:(CodeMsgFn)cb {
    if (![SKPaymentQueue canMakePayments]) {
    cb(ECodeSupportError, @"--- cant pay");
    return;
    }

    self.cb = cb;
    NSSet *set = [NSSet setWithObjects:prodId, nil];
    SKProductsRequest * request = [[SKProductsRequest alloc] initWithProductIdentifiers:set];
    request.delegate = self;
    [request start];
    }

    // 客户端校验 receipt, 测试使用
    -(void)verify:(NSString*)url cb:(CodeMsgFn)cb {
    NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];
    NSDictionary *requestContents = @{@"receipt-data": [receipt base64EncodedStringWithOptions:0]};
    [NetTool httpPostAsync:url json:[requestContents yy_modelToJSONString] callback:^(CHttpJsonRsp * _Nonnull rsp) {
    cb(ECodeOk, [rsp yy_modelToJSONString]);
    }];
    }

    -(void)doCallback:(ECode)code msg:(NSString*)msg{
    if (self.cb) {
    self.cb(code, msg);
    self.cb = nil;
    }
    }

    // ------------------------------- 实现 SKProductsRequestDelegate 的接口
    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    if (response.products.count == 0) {
    [self doCallback:ECodeTaskError msg:@"--- products count is 0"];
    return;
    }

    SKProduct* product = response.products[0];
    SKMutablePayment *payMent = [SKMutablePayment paymentWithProduct:product];
    payMent.quantity = 1;
    payMent.applicationUsername = @"orderId_001"; // 透传参数
    [[SKPaymentQueue defaultQueue] addPayment:payMent];
    }

    // ------------------------------- 实现 SKPaymentTransactionObserver 的接口
    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
    if (transactions.count == 0) {
    [self doCallback:ECodeLoginError msg:@"--- transactions count is 0"];
    return;
    }

    SKPaymentTransaction* transaction = transactions[0];
    switch (transaction.transactionState) {
    case SKPaymentTransactionStatePurchased:
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    [self doCallback:ECodeOk msg:@"--- pay success"];
    break;
    case SKPaymentTransactionStateFailed:
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    [self doCallback:ECodeCancel msg:@"--- SKPaymentTransactionStateFailed"];
    break;
    default:
    break;
    }
    }

    @end

验证 receipt

验证流程: 先使用生产环境 url 去验证, 如果返回的 status 为 0, 表示是生成环境产生的正式购买; 如果返回的 status 为 21007, 表示是 测试人员产生的测试购买, 弹窗会显示环境是 sandbox.

  • 返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    {
    "receipt":{
    "receipt_type":"ProductionSandbox",
    "adam_id":0,
    "app_item_id":0,
    "bundle_id":"aaa.bbb.com",
    "application_version":"1",
    "download_id":0,
    "version_external_identifier":0,
    "receipt_creation_date":"2021-03-29 09:46:49 Etc/GMT",
    "receipt_creation_date_ms":"1617011209000",
    "receipt_creation_date_pst":"2021-03-29 02:46:49 America/Los_Angeles",
    "request_date":"2021-03-29 09:46:54 Etc/GMT",
    "request_date_ms":"1617011214550",
    "request_date_pst":"2021-03-29 02:46:54 America/Los_Angeles",
    "original_purchase_date":"2013-08-01 07:00:00 Etc/GMT",
    "original_purchase_date_ms":"1375340400000",
    "original_purchase_date_pst":"2013-08-01 00:00:00 America/Los_Angeles",
    "original_application_version":"1.0",
    "in_app":[
    {
    "quantity":"1",
    "product_id":"chips90",
    "transaction_id":"1000000123123123",
    "original_transaction_id":"1000000123123123",
    "purchase_date":"2021-03-29 09:46:48 Etc/GMT",
    "purchase_date_ms":"1617011208000",
    "purchase_date_pst":"2021-03-29 02:46:48 America/Los_Angeles",
    "original_purchase_date":"2021-03-29 09:46:48 Etc/GMT",
    "original_purchase_date_ms":"1617011208000",
    "original_purchase_date_pst":"2021-03-29 02:46:48 America/Los_Angeles",
    "is_trial_period":"false"
    }
    ]
    },
    "environment":"Sandbox",
    "status":0 // 0 就是成功的标记
    }

踩坑

产品状态 元数据丢失

需要填写 审核信息, 包裹 截图 和 备注, 状态就会修改为 准备提交


拉取 产品 id 为空

可以以下几个原因

  1. 产品状态是 元数据丢失, 不是 准备提交

  2. 付费 app 协议没有同意, 需要同意后才行 (需要账户持有人才能同意, 协作伙伴不行), 状态是 有效 状态才行.

    https://appstoreconnect.apple.com/agreements/#/


付费 app 协议 地址错误

付费 app 协议 同意前可能遇到 地址错误要修正, 报错: The address entered appears to be invalid.

报这样的错只要提交三次就行了, 这是什么鬼操作.

第一填写比如: (第一行加个 # 号, 第三行不用填)

1
2
3
Address
# FLAT 501,MAPPLE CELESTIA,PLOT 49,
JAY ABHERI ENCLAVE,GACHIBOWLI,HYDERABAD,

参考: https://stackoverflow.com/questions/43922778/the-address-entered-appears-to-be-invalid-please-correct-your-address-and-resu