iOS内购买IAP和服务器交互问题
早期同时兼顾 iOS和PHP任务功能记录
warning 此处不讲解IA如何P配置了,网上挺全面的而且还写的都不错的。但是前提是你有沙盒测试账号和itunesconnect设置过了内购买所需虚拟物品和银行协议的一堆配置
此处只讲解两点
- 我碰见的问题
- 和服务器的交互问题(此处以PHP为例子,本人PHP刚入门也是copy来的,根据自己的需求做出小小改变)
问题
配置消耗型物品重复购买却显示此项目免费恢复
需要在
1
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
的每个状态后面都需要添加
1
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
交互
先解释一下OC代码和PHP代码
第一步拾掇拾掇需要的东西
- #import <StoreKit/StoreKit.h>
- <SKPaymentTransactionObserver, SKProductsRequestDelegate> 两个协议方法你也得实现吧
第二步注册观察者,并且判断该用户能否使用内购买
1
2
3
4
5
6
7
8
9
10[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
if([SKPaymentQueue canMakePayments]){
weakSelf.currentProId = productID;
[weakSelf requestProductData:productID];
}else{
UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:@"提示"
message:@"您的手机没有打开程序内付费购买"
delegate:self cancelButtonTitle:NSLocalizedString(@"关闭",nil) otherButtonTitles:NSLocalizedString(@"提示",nil), nil];
[alerView show];
}第三步查询 => 你传一个你在itunesconnect 中App中定义的内购买Product_ID
1
2
3
4
5
6NSArray *product = [[NSArray alloc] initWithObjects:type, nil];
NSSet *nsset = [NSSet setWithArray:product];
// 请求动作
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
request.delegate = self;
[request start];第四步 收到你在itunesconnect 中定义的Product_ID的详细数据,顺便把购买请求发送了()
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- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSLog(@"--------------收到产品反馈消息---------------------");
NSArray *product = response.products;
if([product count] == 0){
//[SVProgressHUD dismiss];
NSLog(@"--------------没有商品------------------");
return;
}
NSLog(@"productID:%@", response.invalidProductIdentifiers);
NSLog(@"产品付费数量:%lu",(unsigned long)[product count]);
SKProduct *p = nil;
for (SKProduct *pro in product) {
NSLog(@"%@", [pro description]);
NSLog(@"%@", [pro localizedTitle]);
NSLog(@"%@", [pro localizedDescription]);
NSLog(@"%@", [pro price]);
NSLog(@"%@", [pro productIdentifier]);
if([pro.productIdentifier isEqualToString:_currentProId]){
p = pro;
}
}
SKPayment *payment = [SKPayment paymentWithProduct:p];
NSLog(@"发送购买请求");
[[SKPaymentQueue defaultQueue] addPayment:payment];
}第五步收到信息给你返回数据了
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- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
for(SKPaymentTransaction *tran in transactions){
switch (tran.transactionState) {
case SKPaymentTransactionStatePurchased:{
NSLog(@"交易完成");
//方法在下面
[self completeTransaction:tran];
}
break;
case SKPaymentTransactionStatePurchasing:
NSLog(@"商品添加进列表");
break;
case SKPaymentTransactionStateRestored:{
NSLog(@"已经购买过商品");
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
}
break;
case SKPaymentTransactionStateFailed:{
NSLog(@"交易失败");
[[SKPaymentQueue defaultQueue] finishTransaction:tran];
// [SVProgressHUD showErrorWithStatus:@"购买失败"];
startConntentService = false;
}
break;
default:
break;
}
}
}此处是最后的方法还有我和服务器交互的地方,但是此处我也不懂为什么这个方法会调用两次如果您明白,请在下面留言告诉我谢谢,所以我此处加了一个全局的判断,来约束它和服务器交互的次数。
!!!看好我发送数据请求的方法其实就是AFNetworing的封装,都会用对吧。。。网址看好,别光顾着复制哦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- (void)completeTransaction:(SKPaymentTransaction *)transaction {
NSLog(@"交易结束");
[SVProgressHUD dismiss];
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) { /* No local receipt -- handle the error. */ }
//因为此处可能会多次调用原因不明所以加判断只调用一次
else if (receipt && startConntentService) {
/**
服务器要做的事情:
接收ios端发过来的购买凭证。
判断凭证是否已经存在或验证过,然后存储该凭证。
将该凭证发送到苹果的服务器验证,并将验证结果返回给客户端。
如果需要,修改用户相应的会员权限
*/
startConntentService = false;
//字典中第二个参数是为了debug准备的,正常你不用写
NSDictionary *requestContents = @{
@"receipt-data": [receipt base64EncodedStringWithOptions:0],
@"XDEBUG_SESSION_START":@"12477"
};
[HTTPClient postWithURLString:@"你的后台网址" parameters:requestContents success:^(id returnValue) {
id name = returnValue;
} failure:^(id failureValue) {
}];
}
//这个千万别忘了 ,要不你就会犯第一条问题(配置消耗型物品重复购买却显示此项目免费恢复)
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}下面是PHP代码,我用的是ThinkPHP框架(会php都会框架,像我只会框架暂时还不会php.咳咳…)
只有两个方法
1 外部调用的方法,此处我的逻辑都系在了Controller中,是为了大家方便,我建议还是写在Model中(MVC哦,其实感觉都一样哈哈)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public function iosIAPPay() {
$status = array('status'=>-1);
//获取 App 发送过来的数据,设置时候是沙盒状态
$receipt = $_POST['receipt-data'];
$isSandbox = true;
//开始执行验证
try
{
$info = $this->getReceiptData($receipt, $isSandbox);
// 通过product_id 来判断是下载哪个资源
switch($info['product_id']){
case '你的Product_ID':
$status['status'] = 1;
Header("Location:xxxx.zip");
break;
}
return $status;
}
//捕获异常
catch(\Exception $e)
{
$this->ajaxReturn($status);
}
}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//服务器二次验证代码
function getReceiptData($receipt, $isSandbox = false)
{
if ($isSandbox) {
$endpoint = 'https://sandbox.itunes.apple.com/verifyReceipt';
}
else {
$endpoint = 'https://buy.itunes.apple.com/verifyReceipt';
}
$postData = json_encode(array("receipt-data" => $receipt));;
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0); //这两行一定要加,不加会报SSL 错误
curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$errmsg = curl_error($ch);
curl_close($ch);
//判断时候出错,抛出异常
if ($errno != 0) {
throw new \Exception($errmsg, $errno);
}
$data = json_decode($response);
//此处是看到先人们的指导,又看到apple的官方说法改的。否则会审核不过貌似是审核也会走沙盒测试者,
//此处先判断一次返回的status是否=21007 这数据是从测试环境,但它发送到生产环境中进行验证。它发送到测试环境来代替。
if ($data->status == 21007) {
$this->getReceiptData($receipt,true);
return;
}
//判断返回的数据是否是对象
if (!is_object($data)) {
throw new \Exception('Invalid response data');
}
//判断购买时候成功
if (!isset($data->status) || $data->status != 0) {
throw new \Exception('Invalid receipt');
}
$in_app = $data->receipt->in_app;
//返回产品的信息
$status['data'] = array(
'quantity' => $in_app->quantity,
'product_id' => $in_app->product_id,
'transaction_id' => $in_app->transaction_id,
'purchase_date' => $in_app->purchase_date,
'app_item_id' => $data->receipt->app_item_id,
);
return(
$status;
}我一直都困在和后台交互的时候苹果返回的数据中status=21002,哭死。。。因为status=0才是购买成功.
- OC中将苹果所需的凭证其实就是
1
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
base64EncodedStringWithOptions一次
在PHP在将这个数据json_encode(array(“receipt-data” => $receipt));一次在传给苹果服务器,自己写百度谷歌一起来真心好难。
一遍一遍的改OC和PHP代码,还好公司太小两个都是我在做,我有大把时间和不用麻烦别人。总结一下
帮大家理理思路也是我目前知道的最好的app和服务器交互的方法:
在支付之前在后台服务器记录一次你的数据,包括product_id,之后才开始去支付支付成功后再和服务器进行比对product_id可以预防app本地破解支付和防止用户篡改(比如他买的518却只支付6块)等.
还有就是漏单问题,就是用户买东西成功了,但是和自己服务器交互出现了意外(不会怀孕),我现在并未尝试但觉得可行(在更新这个博客之前,因为今天周五要回家了所以下周测试),就是讲xcode的凭证保存到本地,这样用户就可以自己重新点击一次,在和服务器交互一次,将虚拟物品重新不给他一次。
参考:
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!