当前位置: 首页 > news >正文

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代码实现)

http://www.cnnetsun.cn/news/4054006.html

相关文章:

  • Dify工作流快速上手指南:10分钟导入免费模板,1小时玩转图文自动化与AI对话
  • 被网盘限速折磨过的人,都应该试试这个开源直链下载工具
  • 珠海精锐增材智造双基地布局:珠海与东莞的机器人手板产能
  • 抖音视频下不动、带水印?用douyin-downloader批量下载高清原片
  • DFRDisplayKm驱动完整上手指南:让MacBook Pro的Touch Bar在Windows 10下恢复全部功力
  • 写论文的AI工具选择指南
  • 3分钟免费解锁WeMod高级功能:Wand-Enhancer零成本体验指南
  • linux内核原理--用户态线性地址空间,mmap,malloc,缺页异常
  • 六堡茶OEM合作案例:天俸茶业的技术参数与性能评测
  • 玉米种子色选的视觉识别逻辑:霉粒与破损粒如何稳定剔除
  • 开发一个 Agent,还需要哪些东西?
  • 免费的中国行政区划矢量地图数据:四级 Shapefile 一套拿齐,10 分钟从克隆到出图
  • Postman请求体注释全攻略:提升接口测试可读性与团队协作效率
  • 马哥2026云原生/微服务治理大厂冲刺班/名师亲授
  • Winscope如何让你的系统app在可以展示ViewCapture相关数据?
  • 深度拆解 Windows Hello:从红外成像到密钥全流程
  • 原神成就导出终极攻略:YaeAchievement 五分钟搞定全平台数据同步
  • OpenClaw+轻量云:自建AI知识库实现秒级检索与成本优化实战
  • 义乌出口退税公司怎么挑选?
  • 大模型推理轨迹窃取:原理、风险与防御指南
  • Windows平台VSCode+MinGW动态库开发实战:从环境搭建到部署调用
  • 老旧Mac告别淘汰:OpenCore Legacy Patcher免费升级最新macOS完整避坑指南
  • AI短视频自动化生成:从LLM脚本到TTS配音与SD画面的全流程解析
  • 金融专业大学期间考什么证?一份按阶段梳理的实用参考
  • WPS/Office关联EndNote全攻略:解决文献引用格式难题
  • 本地AI编程助手ClaudeCode部署指南:Ollama+VS Code实战避坑
  • YOLO+Qwen-VL+OpenClaw:破解农业视觉检测三大难题的协同架构
  • Kubectl命令实战指南:从基础查询到高级调试的完整工作流
  • OpenClaw开源AI智能体框架:从本地部署到30个落地场景全解析
  • 从Docker Compose到生产环境:复杂应用部署全流程实战指南