ios-iap应用内购接入
ios-iap应用内购接入
前篇
- 官方
- App 内购买项目信息 - https://help.apple.com/app-store-connect/#/dev84b80958f
- 接入Apple Pay流程 - https://juejin.cn/post/6844904001897512967
- iOS内购(IAP)模块总结 - https://www.jianshu.com/p/bf7e42a3c73b
- iOS的应用内支付In-app purchase的开发 - http://blog.hudongdong.com/ios/868.html
- 2019最新iOS内购流程基本配置篇 - https://www.jianshu.com/p/94fa6793b197
- apple苹果IOS内购申请教程协议、税务和银行业务配置
前置条件
协议是 active 状态, https://appstoreconnect.apple.com/agreements/#/
产品 id 是 非 元数据丢失 状态
内购 item 截图, 分辨率: 640 x 920
添加内购产品
- 产品 id (Product ID) 就是代码中需要用的 id.
- 图片资源
- iOS requires at least 640 x 920 pixels. 不能有 alpha 值.
流程
先创建个沙盒测试人员, 测试时不会真实扣钱. https://appstoreconnect.apple.com/access/testers
内购代码
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
- 参考: https://developer.apple.com/cn/documentation/storekit/in-app_purchase/validating_receipts_with_the_app_store/
- 生产环境 url: https://buy.itunes.apple.com/verifyReceipt
- 沙盒环境 url: https://sandbox.itunes.apple.com/verifyReceipt
验证流程: 先使用生产环境 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 为空
可以以下几个原因
产品状态是 元数据丢失, 不是 准备提交
付费 app 协议没有同意, 需要同意后才行 (需要账户持有人才能同意, 协作伙伴不行), 状态是 有效 状态才行.
https://appstoreconnect.apple.com/agreements/#/
付费 app 协议 地址错误
付费 app 协议 同意前可能遇到 地址错误要修正, 报错: The address entered appears to be invalid.
报这样的错只要提交三次就行了, 这是什么鬼操作.
第一填写比如: (第一行加个 # 号, 第三行不用填)
1 | Address |