ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Unity接入IAP内购(Android,IOS)最新流程,第三篇 后台对购买结果的验证以及发货(Android)

Unity接入IAP内购(Android,IOS)最新流程,第三篇 后台对购买结果的验证以及发货(Android)

文章目录

  • 欢迎进入Unity内购系列
  • 第三篇 后台对购买结果的验证以及发货(Android)
  • 一、基本流程
  • 二、GooglePlay后台设置
  • 三、PHP代码实现
  • 总结

欢迎进入Unity内购系列

你好! 这将是一个系列的文章
第一篇 介绍客户端里支付的调起以及购买。
第二篇 介绍后台对购买结果的验证以及发货(IOS)。
第三篇 介绍后台对购买结果的验证以及发货(Android)。
第四篇 介绍后台对内购退单问题的处理(IOS欺诈检测以及欺诈信息反馈)。

第三篇 后台对购买结果的验证以及发货(Android)

本篇介绍PHP后台对购买结果的验证以及发货(Android)。
这是纯后端内容,不限于Unity项目,其他项目同样可用。

重要提示:
1、本人不会PHP,这是同事提供的代码,是项目实战代码,代码里有些遗漏的还请自行添加,万一有无法解决的可以留言,我再请教同事。
2、代码中涉及到sql和db相关的代码为数据库操作,需要根据自己的项目实现

一、基本流程

1、玩家在客户端内发起购买,并成功付款
2、Google返回购买凭证给客户端
3、客户端将购买凭证发送给服务器,由服务器向Google验证真伪
4、服务器根据验证结果给玩法发放相关虚拟商品

二、GooglePlay后台设置

1、在Google开发者后台中启用Google Play Android Developer项目
2、进入GoogleAPI控制台,创建一个Web 类型的客户端 ID,具体步骤可以参考我的另一篇文章《Unity接入Google登录超详细流程》
3、打开浏览器打开这个地址:

https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=http://localhost:8080&client_id=123122121

其中redirect_uri换成自己第二部生成的客户端 ID时填入的网址(可以随便写,但是如果是真实的地址,他会很快跳转,导致拿不到想要的东西,所以一般情况填写http://localhost:8080),client_id换成自己第二部生成的客户端 ID
访问这个地址后会叫你登录google账号,就选择开发者这个账号登录
4、登录成功后,会在浏览器的地址栏中包含code,只要拿出code的值即可:

Code=4/bAEGOfkpDnG5hhtv8E7FSkKPp-oFVQpTPeg8l_jWjKQd5BaOviZLyimEywJR9ptEoFtRqb95sZh4yXfRLI81BbM

5、获取到code后发送post请求,以下POST请求实例以postman工具来进行操作
(1)、请求头 Content-Type = application/x-www-form-urlencoded
(2)、请求地址 https://accounts.google.com/o/oauth2/token
(3)、参数

grant_type=authorization_code(为固定值) code=4步中获取到的code值, client_id=客户端ID,(第二步生成的客户端ID) client_secret=客户端密钥,(第二步生成的客户端ID,里面对应的密钥) redirect_uri=重定向链接(http://localhost:8080

6、请求结果有一个refresh_token,这个是永久的,而且只会返回一次,这个refresh_token需要保存给php,后续步骤需要用到。

如果出现Bad Request错误,拿不到Refresh Token,说明code已经失效了,要从头再来,删除web客户端,从第二步创建web客户端开始

以下3个步骤由php代码动态实现,不需要手动操作了
以下3个步骤由php代码动态实现,不需要手动操作了
以下3个步骤由php代码动态实现,不需要手动操作了
7、POST请求:携带refresh_token可获取access_token
(1)、请求头 Content-Type = application/x-www-form-urlencoded
(2)、请求地址 https://accounts.google.com/o/oauth2/token
(3)、参数

grant_type=refresh_token为固定值, client_id=客户端ID,(第二步生成的客户端ID下载下来直接交给php) client_secret=客户端密钥,(第二步生成的客户端ID下载下来直接交给php) refresh_token=5步中获取到的refresh_token值,

8、验证订单:GET请求(不再是POST)

GET地址:

https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/products/{productId}/tokens/{purchaseToken}?access_token=access_token

相关参数:

packageName=需要查询的应用ID(包名、gradle中的applicationId ) productId=开发者后台中创建好的商品IDpurchaseToken=客户端传过来的purchaseTokenaccess_token=7步中获得的access_token

三、PHP代码实现

unity客户端需要传给php的数据:

publicPurchaseProcessingResultProcessPurchase(PurchaseEventArgsargs){//支付成功返回给客户端的数据varproduct=args.purchasedProduct;Debug.Log("Purchase Complete - Product: "+product.definition.id);stringreceipt=args.purchasedProduct.receipt;//receipt 这个要发送给php/* 此处为自己客户端跟php通信的代码 */returnPurchaseProcessingResult.Complete;}

php验证订单真伪:

/********************google内购start**********************//** * 1.通过内购账户获得 code code 只能用一次 * 2.通过 code 获得 refresh_token 【刷新令牌时间一般比较长】 * 3.通过 refresh_token 获得 access_token 【一般3600s】 * 4.通过 access_token 访问 google api * * 参考: * https://developers.google.com/android-publisher/authorization * https://blog.csdn.net/alex_my/article/details/82984706#2__8 * * [check_google_buy_order_exits 检查 google 内欧订单 成功就发送发游戏服务器 加某些东西] * @param [int] $game_id [游戏 ID] * @param [int] $player_id [玩家 ID] * * @param [string] $packageName [出售inapp产品的应用程序的软件包名称(例如,“ com.some.thing”)] * @param [string] $productId [Inapp产品SKU(例如,“ com.some.thing.inapp1”)。] 产品id对应的钻石数=[1=>66,2=>268,3=>828,4=>1436,5=>3436,6=>7416] * @param [string] $token [购买inapp产品时,提供给用户设备的令牌。] * @return [] ['status'=>$code,'message'=>$msg,] */publicfunctioncheck_google_buy_order_exits(){// ini_set("display_errors","on");// IException::setDebugMode(true);// error_reporting(E_ALL | E_STRICT);/*1 获取参数*/$game_id=IFilter::act(IReq::get('game_id','post'),'int');$child_game_id=IFilter::act(IReq::get('child_game_id','post'),'int');//子包id$player_id=IFilter::act(IReq::get('player_id','post'),'int');if(empty($game_id)||empty($player_id))util::jsonError('缺少玩家参数!!');empty($child_game_id)&&$child_game_id=$game_id;// packageName / purchases /// products / productId /// tokens / 令牌$packageName=IFilter::act(IReq::get('packageName','post'),'string');$productId=IFilter::act(IReq::get('productId','post'),'string');$token=IFilter::act(IReq::get('token','post'),'string');$order_no=IFilter::act(IReq::get('order_no','post'),'string');if(empty($packageName)||empty($productId)||empty($token))util::jsonError('缺少查询参数!!');/*2 检查玩家真实性**************************************************/$player_info=info::getPlayerInfo($player_id,$game_id);// if(empty($player_info)) util::jsonError('玩家不存在!!');/*2 查找订单*******************************************************/// 2.1 获取 access_token$access_token=$this->get_google_pay_access_token($child_game_id);$url='https://www.googleapis.com/androidpublisher/v3/applications/'.$packageName.'/purchases/products/'.$productId.'/tokens/'.$token.'?access_token='.$access_token;set::newsetlogfile("====".$url,'check_google_buy_order_exits');$result=get::httpRequest($url,'','get');set::setlogfile($player_id.'谷歌内购支付'.$result);$zuan_sku=[53=>['gold1'=>[0.99,60,4],//每日首充送8,二次无赠送'gold2'=>[4.99,300,4],],];$zuan_sku=$zuan_sku[$child_game_id];$result_arr=json_decode($result,TRUE);//数组// 错误 400 订单无效 403 项目未关联if(!empty($result_arr['error'])||empty($access_token)){$obj=newIModel("player_order");/*3.2 成功后修改数据库*/$ordedr_data=['o_no'=>$order_no?$order_no:0,//系统订单号'o_game_id'=>$game_id,//游戏id'o_pid'=>$player_info['p_id'],//玩家表id// 'o_from_os' => 1, //1.android 2.ios'o_item_id'=>4,//道具表id'o_item_num'=>$zuan_sku[$productId][1],//道具数量'o_money'=>$zuan_sku[$productId][0],'o_status'=>0,//订单状态 0.未完成 1.已完成'o_pay_status'=>0,//支付状态 0.未支付 1.已支付'o_dopay_pid'=>$player_id,//实际进行支付的玩家id'o_order_type'=>0,//订单类型0直接付 1代付'o_date'=>time(),//创建日期'o_isChecked'=>0,//是否回执 0.否 1.是];$obj->setData($ordedr_data);$obj->add();util::jsonError('错误'.$result_arr['error']['code'].':'.$result_arr['error']['message']);}// 3、订单的购买状态 0已购买 1取消 2待定switch($result_arr['purchaseState']){case'0':$obj=newIModel("player_order");/*3.1数据库中存在充值订单*/$player_order_info=$obj->getObj('o_no="'.$result_arr['orderId'].'"');if(!empty($player_order_info))util::jsonError('此充值订单已完成');//当充值商品为第一档时查询订单判断当天是否充值过$first_sku=current(array_keys($zuan_sku));if($productId==$first_sku){$firstOrderCurrDay=$obj->query('o_pid='.$player_info['p_id'].' and o_date>='.strtotime(date('Y-m-d 00:00:00')),'o_id','o_id asc',1);if(empty($firstOrderCurrDay)){$zuan_sku[$first_sku][1]+=8;}}/*3.2 成功后修改数据库*/$ordedr_data=['o_no'=>$result_arr['orderId'],//系统订单号'o_game_id'=>$game_id,//游戏id'o_pid'=>$player_info['p_id'],//玩家表id// 'o_from_os' => 1, //1.android 2.ios'o_item_id'=>4,//道具表id'o_item_num'=>$zuan_sku[$productId][1],//道具数量'o_money'=>$zuan_sku[$productId][0],'o_status'=>1,//订单状态 0.未完成 1.已完成'o_pay_status'=>1,//支付状态 0.未支付 1.已支付'o_dopay_pid'=>$player_id,//实际进行支付的玩家id'o_order_type'=>0,//订单类型0直接付 1代付'o_date'=>time(),//创建日期'o_isChecked'=>0,//是否回执 0.否 1.是// 'o_apple_receipt'=>'' //苹果内验证数据];$obj->setData($ordedr_data);$add_id=$obj->add();break;case'1':util::jsonError('订单购买状态:取消');# code...break;case'2':util::jsonError('订单购买状态:待定');# code...break;}/*4 发送游戏服务器*/// $params='msg=set'.'&openId='.$player_id.'&cardType='.$password_new1.'&cardNum='.;$send_data=['msg'=>'chargeById','userId'=>$player_id,'cardType'=>$zuan_sku[$productId][2],// 2房卡 4钻石 6金币'cardNum'=>$zuan_sku[$productId][1],'operType'=>2,//为什么有这一笔,enum 充值 赠送 都是 2];$params=http_build_query($send_data,'','&');$result=set::setMsgToGameServer($game_id,1,$params,1);if($result){if($result['errorCode']>0){set::setlogfile("发送钻石失败,与游戏服务器通信失败:".$result['errorCode'].$result['errorMsg']);// util::jsonError('发送钻石失败,与游戏服务器通信失败:'.$result['errorCode'].$result['errorMsg']);}}else{set::setlogfile("发送钻石失败,与游戏服务器通信失败:未获取返回值".$result['errorCode']);// util::jsonError('发送钻石失败,与游戏服务器通信失败:未获取返回值'.$result['errorCode']);}util::jsonSuccess(['number'=>$ordedr_data['o_item_num']]);}/** * [get_google_pay_access_token 获取 access_token] * @param [string] $grant_type [参数类型] * @param [string] $client_id [客户端ID] * @param [string] $client_secret [客户端秘钥] * @param [string] $refresh_token [刷新token] 用户获取access_token * @return [string] [] */privatefunctionget_google_pay_access_token($game_id){// 判断 access_token 是否过期$access_token=Common::rget('access_token');if(!empty($access_token)){return$access_token;}// 1.获取 token 通过刷新token 获取 访问 token$config_arr=$this->get_google_pay_josn_config($game_id);//获取基本数据// 2.拼装 paeams and url$send_params=['grant_type'=>'refresh_token','client_id'=>$config_arr['web']['client_id'],'client_secret'=>$config_arr['web']['client_secret'],//'refresh_token' =>'1//06otA7GjQrGkZCgYIARAAGAYSNwF-L9IrYZR_WaEEvsCFDZZZ-5d4qlfovUSvllLxyxz1hlHJVVP4zCfGbOXaIqxcepaG2ojSiRo''refresh_token'=>$config_arr['web']['refresh_token'],];set::newsetlogfile("====".print_r($send_params,true),'check_google_buy_order_exits');$url='https://accounts.google.com/o/oauth2/token';$send_params_str=http_build_query($send_params,'','&');// 获取数据$response_data=get::http_post_data($url,$send_params_str);//对象 no arrayset::newsetlogfile("==access_token-response_data==".print_r($response_data,true),'check_google_buy_order_exits');// 存库 redisif(!empty($response_data)){Common::rset_ex('access_token',$response_data->access_token,$response_data->expires_in);}return!empty($response_data)?$response_data->access_token:'';}/** * [get_google_pay_josn_config 获取参数信息 ] * @return [type] [数组 或 null] */privatefunctionget_google_pay_josn_config($game_id){$file_path=IWeb::$app->getBasePath().'plugins/google_pay/client_'.$game_id.'.json.txt';if(file_exists($file_path)){//将整个文件内容读入到一个字符串中$arr=json_decode(file_get_contents($file_path),true);// var_dump(file_get_contents($file_path));// var_dump($arr);return$arr;}else{return;}}

总结

1、GooglePlay关联到GoogleApi项目(Google后台操作)
2、创建GoogleApi访问的客户端ID(Google后台操作)
3、获取refresh_token(Google后台操作)
4、获取access_token(PHP代码实现)
5、验证订单并发货(PHP代码实现)

返回列表