最近为公司的一款手游项目开发了一个苹果APP Store内购功能,参考CSDN上查找了一些资料,结合实际开发过程中所遇到的问题,做个总结。我做的是服务器端工作,关于前端的具体实现请参考其他博客。
功能需求:iPhone玩家通过app进入Apple App Store选择所购买的金币或者钻石,付费完成后给玩家充值。
验证流程:(采用服务器模式)
1.后台server获取APP Store中的商品配置(建议在前端也配置一份Products ID)
2.前端发送请求至App Store,获取Products的信息
3.App Store返回Product信息
4.前端使用这些信息,向用户显示一个商店界面,用户从中选择一种商品
5.前端向App Store发送购买请求
6.App Store处理该请求,并返回完成的购买t收据(receipt-data)
7.前端将receipt-data和玩家uid两个数据,用HttpPost方式发给后台的php服务器
8.后台写个php程序,接收前端的数据,并将receipt-data发给Apple Store(注:php代码见附录1)
9.App Store解析后台发过去的receipt,并返回receipt,以及验证结果(是否合法)
10.后台php程序读取返回的receipt,并确定用户购买的product_id,记录交易订单号transacton_id(发给server存档,避免同一单号重复充值)(注:receipt表单详情见附录2)
11.后台php程序将uid,product_id,transaction_id发给后台server,server负责给玩家充值,并通知前端充值成功
require_once("game.config.php"); //服务器配置 和 购买配置 ,请读者根据实际需要补充完整
require_once 'deliver.php'; //数据传送接口,负责PHP和游戏服务器之间的通信,请读者根据实际需要补充完整
$test_environment_url = 'https://sandbox.itunes.apple.com/verifyReceipt'; //测试环境URL
$production_environment_url = 'https://buy.itunes.apple.com/verifyReceipt'; //正式环境URL
$error_ios = 'verify success';
if(count($_POST) == 2 && !empty($_POST['receipt_data']) && !empty($_POST['uid']))
{
$error = 0;
$money = 0;
$isSandbox = false;
$post_url = null;
$item_res = $item_Result['iosPay'];
$item_id = 0;
$uid = intval($_POST['uid']);
if($isSandbox)
{
$post_url = $test_environment_url;
}
else
{
$post_url = $production_environment_url;
}
$timeout = 10;
$apple_receipt = $_POST['receipt_data'];
$jsonData = array('receipt-data'=>$apple_receipt);
$post_json = json_encode($jsonData);
$log = date(DATE_RFC2822).'apple_receipt:'.$apple_receipt.chr(10);
file_put_contents('./log/ios.log.'.date("Ymd"),$log,FILE_APPEND);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $post_url);
curl_setopt($ch, CURLOPT_POST, 1);//post提交方式
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_json);
curl_setopt($ch, CURLOPT_HEADER, 0);//设置header
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
$response = curl_exec($ch);
$con_error = curl_error($ch);
curl_close($ch);
$ret_data = json_decode($response, true);
// $log = date(DATE_RFC2822).'ret_data_size:'.count($ret_data).chr(10);
// file_put_contents('./log/ios.log.'.date("Ymd"),$log,FILE_APPEND);
$status = $ret_data['status'];
if($status != 0) //status 非0 购买失败
{
$error = $status;
$error_ios = 'wrong status';
$log = date(DATE_RFC2822).'status:'.$status.chr(10);
file_put_contents('./log/ios.log.'.date("Ymd"),$log,FILE_APPEND);
}
if($error == 0)
{
$receipt = $ret_data['receipt'];
$product_id = null;
$transaction_id = null;
if(array_key_exists('product_id',$receipt))
$product_id = $receipt['product_id'];
if(array_key_exists('transaction_id',$receipt))
$transaction_id = (string)$receipt['transaction_id'];
$log = date(DATE_RFC2822).'|uid:'.$uid.'|product_id:'.$product_id.'|transaction_id:'.$transaction_id.chr(10);
file_put_contents('./log/ios.log.'.date("Ymd"),$log,FILE_APPEND);
if(array_key_exists($product_id,$ios_product_config) == true)
{
$item_id = intval($product_id); //服务器根据item_id充值
$money = 1; //此处money为固定值1,代表单次购买数量
}
if($money == 0 || $uid < 10000000)
{
$error = -1;
$error_ios = "no such productId:".$product_id."uid:".$uid;
}
}
if($error == 0)
{
$payType = 'iosPay';
$rst = Deliver::deliver_to_server(date('U'), '102', $uid, $item_id, $money, $payType, $transaction_id); //发给服务器充值
if($rst != true)
{
$error_ios = "send post msg to Server failed";
}
}
$log = date(DATE_RFC2822).'/POST/'.$item_res.'/'.$error_ios.chr(10);
file_put_contents('./log/ios.log.'.date("Ymd"),$log,FILE_APPEND);
}
else
{
$error_ios = "error request format";
$log = date(DATE_RFC2822).'/POST/'.$error_ios.chr(10);
file_put_contents('./log/ios.log.'.date("Ymd"),$log,FILE_APPEND);
}
?>
original_purchase_date_pst
purchase_date_ms
unique_identifier
original_transaction_id
bvrs
transaction_id //交易订单号,可传给服务器存档
quantity
unique_vendor_identifier
item_id
version_external_identifier
bid
is_in_intro_offer_period
product_id //商品id,前端需在APP Store 的开发者账号中配置,后台根据商品id匹配到具体的钻石或者金币
purchase_date
is_trial_period
purchase_date_pst
original_purchase_date
original_purchase_date_ms
//其他字段的具体含义请读者参考苹果IOS 内购的使用指南