说起iAP内购支付,一把心酸泪,我的项目需求是电子书阅读器的每月支付VIP会员费。一开始是使用苹果的原生代码来写,用代理接收支付返回结果。在真机测试后就提交上架,一开始支付还没什么问题,但后来陆续有会员反映支付付钱后,app却并没有开通会员。然后就手动在系统后台给付款的会员开通VIP,而且严重影响了用户使用体验。
后来寻思怎样解决问题,因为我自己用真机不能重现这个问题,然后就在每个支付环节插入可提交到服务端的日志,当用户反馈这个问题时,就通过他的id去分析日志记录,结果发现是到发起 SKPaymentQueue 的addPayment方法后,流程就断了,还是没能解决问题。
最后没办法,就使用了RMStore,上架新版本后就再没出现过问题。
怎样在itunesconnect上配置内网支付我这里就不说了。
RMStore的使用:
Using CocoaPods:
pod ‘RMStore’, ‘~> 0.7’
使用其实也很简单,因为采用block编程风格,只需两步或一步就可以走完购买流程。
1. 可以先通过在itunesconnect后台配置的product_id取出内购产品列表:
_products = @[@"com.xxx.MCIOS6",
@"com.xxx.MCIOS12"];
[SVProgressHUD show];
[[RMStore defaultStore] requestProducts:[NSSet setWithArray:_products] success:^(NSArray *products, NSArray *invalidProductIdentifiers) {
[SVProgressHUD dismiss];
[self.tableView reloadData];
} failure:^(NSError *error) {
}];
[[RMStore defaultStore] addPayment:productID success:^(SKPaymentTransaction *transaction) {
//生成购买凭证
NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt;
receipt = [NSData dataWithContentsOfURL:receiptUrl];
NSString* transReceipt = [receipt base64EncodedStringWithOptions:0];
NSLog(@"transReceipt = %@", transReceipt);
UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:NSLocalizedString(@"购买成功", @"") message:[NSString stringWithFormat:@"transReceipt = %@", transReceipt] delegate:nil cancelButtonTitle:NSLocalizedString(@"OK", @"") otherButtonTitles:nil];
[alerView show];
} failure:^(SKPaymentTransaction *transaction, NSError *error) {
[SVProgressHUD dismiss];
}];
最后再提一个新手容易踩的坑,就是支付结果的服务端验证。
上面代码中支付成功后,返回的transaction对象可以生成一个购买凭证字符串,提交到后台,后台必须拿这个凭证字符串再提交到苹果的验证服务器验证是否有效支付,要不接口很容易被黑客刷单,特别是越狱手机可以有方法绕过实际支付而又返回支付成功结果。
以下是php的验证代码:
function ifReceiptValid($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);
$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);
if (!is_object($data)) {
throw new Exception('Invalid response data');
}
if(isset($data->status) && $data->status == 0){
watchdog('services_vip_resource_update verifyReceipt', ' status='.$data->status.', result=true');
return true;
}else if(isset($data->status) && $data->status == 21007){
watchdog('services_vip_resource_update verifyReceipt', ' status='.$data->status.', result=to sandbox');
return ifReceiptValid($receipt, true);
}else{
watchdog('services_vip_resource_update verifyReceipt', ' status='.$data->status.', result=false');
return false;
}
}
支付凭证验证分沙盒模式和生产模式,自己沙箱测试的是提交到苹果的沙盒接口,生产就到生产接口,服务端因为比较难判断本次验证是沙盒还是生产,所以先缺省是正式模式,如果返回status不为0,再提交到沙盒接口验证,如果返回status还不为0,再确定验证失败。
代码的体验效果可参考此app:品读阅读