Flutter集成谷歌内购与运用的全过程
我正在参加「启航方案」
前语
留意:本文包含许多的图片与代码,留意流量运用。
在之前的文章中,咱们了解到 Flutter 的页面开发,以及 Flutter 的架构搭建,结束对应的页面与逻辑是没什么问题了。
可是开发一个 App 又不是只要页面的基本逻辑,假如涉及到一些第三方的对接就显得比较费事,比方推送,付出,内购,IM 等等。
而假如第三方没有提供到 Flutter 的插件,那么咱们就需求经过 Channel 的办法自己去结束 Android 与 iOS 的具体结束。
还好,Flutter 推出这么些年,基本上常用的一些插件都现已有支撑,就算官方没出,也有民间大神出了相似的插件。
而本文所说的便是相对比较复杂的第三方集成,运用商城内购,大部分是讲谷歌内购,其实苹果内购比谷歌内购要更容易一下,兼容一下同样的代码就能结束相关的流程。
以谷歌内购为例,咱们需求做的一共为三步,每一步我会给出具体的相关设置对应的图片或代码。
- 需求在谷歌商场装备产品,设置测验途径,装备开发者账号,设置对应权限。
- 装备完产品之后,如安在 Flutter 中获取到产品,购买指定产品,耗费产品等。
- 购买成功之后,怎么到服务器校验是否付出成功,后台服务器怎么装备通行权限,谷歌商场与谷歌云的相关以及相关校验。
留意:因为谷歌商场版别老在改变,本文的谷歌商场以 2023-05-02 时刻为准,假如后期谷歌商场与谷歌云的相关网页布局 或 Tab 发生改变,咱们能够参阅运用。
好了,大致的过程了解了之后,咱们就开端一步步的走相关流程。
一、谷歌商场装备产品
按道理,咱们只需求在谷歌商场中,找到指定的运用,然后在内购的 Tab 中直接添加产品即可。可是并没有这么简略,会提示报错,没有设置付款账号。
1.1 定价模板
所以咱们要切换到整个账号的设置页面,而不是指定运用的选项。
找到付款概略之后,假如没有付款账号,咱们填写一些信息,名字,邮箱,账号,等等信息,创立结束之后咱们就能够设置定价的模板。
假如能创立模板阐明你付款账号没问题,定价模板对错必须的,可有可无,可是界说了模板之后会更加便利,到时分创立产品能够直接相关模板,账号下的每一个子运用的内购产品都能相关对应的模板,有一个一致的定价。
怎么创立定价模板如下:
咱们创立模板之后,就能够界说模板的价格与标题,挑选的金额会有对应的汇率转换,比方我创立的新加坡币,假如用港元付出的话,会依据汇率转换为对应的港元付出。
创立结束之后,咱们就能看到对应的定价模板如下图所示:
1.2 运用上架
当咱们的一些定价模板界说结束之后,咱们就能够设置运用的内购了,点击运用产品内购的 Tab ,成果是不能创立,因为你虽然创立了子运用,可是你没有上传APK包,并没有运用所以没有对应的运用内产品。
所以在此之前咱们还需求做一些装备。咱们需求创立一个内部测验途径,然后装备对应的开发者或测验人员。
哎,对对对,我知道是很费事,很SB,但谁叫谷歌便是这么定的流程呢,没有办法,按流程走把。
首要需求创立一个测验轨道:比方我挑选的是内部测验:
发布一个 APK / AAB 文件到测验途径,然后 挑选/创立 测验人员。
假如没有的话,创立一个测验者,输入对应的邮箱即可。
这样就能添加测验人员了,同时你还能够在底部约请链接去约请测验者,激活测验规矩等。
当一切都装备结束之后(会有一个进度条的,当结束一步会中划线标记的),咱们到发布中挑选审阅以及发布,谷歌审阅很快,一切正常2个小时内就能经过,然后咱们打开谷歌运用商场查找咱们的运用就能找到内部测验的途径运用:
点击下载之后,咱们装置的便是咱们之前创立的内部测验途径运用啦。
1.3 创立运用内购产品
运用虽然是上线了,可是咱们还没创立内购产品呢,此刻咱们再点击运用内购的 Tab 就能创立对应的产品了。
产品其实是分订阅类型与内购产品,内购产品又分耗费型与非耗费型,因为咱们的产品并不复杂,是输入耗费型的,所以我的代码都是以耗费性为例。
怎么差异各种类型?举个栗子:
- 订阅类型:爱奇艺的VIP月卡。
- 非耗费性:消消乐免费玩前10关卡,付费解锁后边的关卡内容。
- 耗费性:欢喜斗地主买100个欢喜豆。
咱们其实便是最简略的耗费性,花钱买虚拟币,可重复购买的那种。
下面开端创立产品,这一点反倒是蛮简略:
留意的是,创立运用id的时分,最好是包名加上产品id。防止冲突
com.xxgroup.whatsapp.coin10
随后界说对应的标题与描述,以及产品对应的价格,能够运用价格模板,也能够直接写。
创立结束之后别急,需求激活之后才干收效:
此刻列表上就有了,这样才是收效状况。
此刻就能买了吗?能够了,可是测验账号并没有相关信用卡,也不便利,咱们设置一下测验账号的测验购买授权:
下面的授权办法改为:LICENSED
好了到此,咱们的谷歌商场装备就结束了。
二、集成付出插件兼容Android与iOS付出
接下来咱们就在 Flutter 中运用插件集成运用内付出功用。
in_app_purchase: 3.1.5
文档地址:【传送门】
运用起来并不复杂,能够说是 Android 与 iOS 的逻辑是相同样的。
贴一下我的代码:
首要是生命周期的回调,页面创立结束初始化插件,并设置监听,当页面封闭的时刻销毁资源:
late StreamSubscription<List<PurchaseDetails>> _subscription;
late InAppPurchase _inAppPurchase;
List<ProductDetails>? _products; //内购的产品目标调集
@override
void onReady() {
fetchCoinList();
// 初始化in_app_purchase插件
_inAppPurchase = InAppPurchase.instance;
//监听购买的事情
final Stream<List<PurchaseDetails>> purchaseUpdated = _inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
error.printError();
SmartDialog.showToast("购买失利了");
});
//加载悉数的产品
loadProducts();
}
@override
void onClose() {
refreshController.dispose();
datas.clear();
if (Device.isIOS) {
final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition =
_inAppPurchase.getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
iosPlatformAddition.setDelegate(null);
}
_subscription.cancel();
}
加载悉数的产品,这儿实在开发环境是由后端接口回来的,我这儿作为测验就写死了:
/// 加载悉数的产品
void loadProducts() async {
final bool available = await _inAppPurchase.isAvailable();
if (!available) {
SmartDialog.showToast("无法衔接到商铺");
return;
}
//开端购买
SmartDialog.showToast("衔接成功-开端查询悉数产品");
const Set<String> _kIds = <String>{
'com.hongyegroup.whatsapp.android.coin100',
'com.hongyegroup.whatsapp.android.coin500',
'com.hongyegroup.whatsapp.android.coin1000',
'com.hongyegroup.whatsapp.android.coin3000',
};
final ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(_kIds);
if (response.notFoundIDs.isNotEmpty) {
SmartDialog.showToast("无法找到指定的产品");
return;
}
// 处理查询到的产品列表
List<ProductDetails> products = response.productDetails;
if (products.isNotEmpty) {
//赋值内购产品调集
_products = products;
}
SmartDialog.showToast("悉数产品加载结束了,能够发动购买了,一共产品数量为:${products.length}");
//先恢复可重复购买
await _inAppPurchase.restorePurchases();
}
查询到产品之后就能够在 ListView 显现产品的特点,如过 UI 设计的列表款式比较复杂,当咱们谷歌内购设置的标题与概况满意不了设计的需求,能够由咱们自己的后台回来对应接口,当产品加载成功之后的产品 id 去匹配对应后台的列表数据并展示出来。
当咱们点击对应的 Item 就能够拿到对应的产品ID,履行购买的逻辑:
// 调用此函数以发动购买过程
void startPurchase(String productId) async {
if (_products != null && _products!.isNotEmpty) {
SmartDialog.showToast("预备开端发动购买流程");
try {
ProductDetails productDetails = _getProduct(productId);
Log.d("一切正常,开端购买,信息如下:title: ${productDetails.title} desc:${productDetails.description} "
"price:${productDetails.price} currencyCode:${productDetails.currencyCode} currencySymbol:${productDetails.currencySymbol}");
_inAppPurchase.buyConsumable(purchaseParam: PurchaseParam(productDetails: productDetails));
} catch (e) {
e.printError();
Log.e("购买失利了");
}
} else {
SmartDialog.showToast("当时没有产品无法调用购买逻辑");
}
}
// 依据产品ID获取产品信息
ProductDetails _getProduct(String productId) {
return _products!.firstWhere((product) => product.id == productId);
}
咱们的产品都是耗费类型的,所以这儿咱们就写死了,假如你的产品有多种类型,你能够判别类型是输入哪一种类型,分别就行不同种类的购买办法。
当咱们点击购买之后,就能够走到购买监听中了,咱们在里边监听当时购买的状况。当确定购买结束之后咱们就能进行产品的耗费。假如购买结束不耗费,那么三天之后会主动退款的。
/// 内购的购买更新监听
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) async {
for (PurchaseDetails purchase in purchaseDetailsList) {
if (purchase.status == PurchaseStatus.pending) {
// 等候付出结束
_handlePending();
} else if (purchase.status == PurchaseStatus.error) {
// 购买失利
_handleError(purchase.error);
} else if (purchase.status == PurchaseStatus.purchased || purchase.status == PurchaseStatus.restored) {
//结束购买, 到服务器验证
if (Device.isAndroid) {
var googleDetail = purchase as GooglePlayPurchaseDetails;
print(purchase);
loadAndroidGetPayInfo(googleDetail);
} else if (Device.isIOS) {
var appstoreDetail = purchase as AppStorePurchaseDetails;
print(purchase);
loadAppleGetPayInfo(appstoreDetail);
}
}
}
}
/// 购买失利
void _handleError(IAPError? iapError) {
SmartDialog.showToast("购买失利啦:${iapError?.code} message${iapError?.message}");
}
/// 等候付出
void _handlePending() {
SmartDialog.showToast("等候付出的逻辑");
}
/// Android付出成功的校验
void loadAndroidGetPayInfo(GooglePlayPurchaseDetails googleDetail) async {
final originalJson = googleDetail.billingClientPurchase.originalJson;
Log.d("originalJson:$originalJson");
if (await coinRepositroy.checkGooglePaySuccess(originalJson)) {
//校验成功之后履行耗费
await _inAppPurchase.completePurchase(googleDetail);
}
}
/// Apple付出成功的校验
void loadAppleGetPayInfo(AppStorePurchaseDetails appstoreDetail) {
if (await coinRepositroy.checkApplyPaySuccess(appstoreDetail)) {
//校验成功之后履行耗费
await _inAppPurchase.completePurchase(appstoreDetail);
}
}
到这一步,咱们现已和测验账号管理了,所以咱们直接运行 Debug 包相同的能够测验付出,而且咱们授权测验付出,所以不需求绑定银行卡直接用测验付出卡就能结束付出流程。
截图:
当咱们付出成功之后就能够履行耗费操作。
三、服务器校验相关流程
我并没有把代码做区别,上面的代码就现已包含了付出成功之后的后端校验逻辑。
为什么要加后端校验?客户端付出成功了,服务端怎么知道,万一用接口的办法通信,假如被抓包岂不是能够无限加金币了。太不安全了,所以才有服务器校验这一步。
iOS的校验不用说,很简略,拿到付出结束的票据直接发起恳求即可,而 Android 的服务端校验就相对费事,需求装备谷歌云,以及对应的通行权限。
谷歌结算文档:【传送门】
谷歌付出校验AI:【传送门】
假如咱们直接在API中调用校验接口,那肯定是直接报错:
{
"error": {
"code": 403,
"message": "The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console.",
"errors": [
{
"message": "The project id used to call the Google Play Developer API has not been linked in the Google Play Developer Console.",
"domain": "androidpublisher",
"reason": "projectNotLinked"
}
]
}
}
没有授权,接下来开端授权:
3.1 Google Cloud相关
首要需求装备 Google Cloud 而且装备相关的账号,对应指定的运用。
点击项目的 API Access 中
假如这一步你没有 Google Cloud 账号,能够创立或相关已有的 Google Cloud 账号,这儿我没有就直接创立了Google Cloud 账号。相关之后咱们就能看到上图所示的画面。
咱们能够直接在谷歌商场控制台中的 API Access 中直接进入谷歌云后台,也能 直接输入网址 code.google.com/apis/consol… 是相同的效果。
网上许多教程教你去开通 Google Play Developer API 权限,其结束在是没必要了,因为咱们相关 Google Cloud 账号之后,默认就现已开通了。
所以咱们不需求再次去授权了。
假如觉得不保险,也能在里边查找 Billing ,然后发动相关的付出服务权限,反正我是开了,但我觉得没必要开这些,假如不可的你敞开这些服务也无所谓。
3.2 创立 web-OAuth 授权
网上许多教程比较老,或许要你手动的创立web授权,其实当咱们在谷歌商场的后台相关谷歌云的时分,就现已帮咱们初始化了许多装备,现已都有了。
咱们再谷歌云后台,在APIs & auth 项中找到 Credentials,直接查看即可:
咱们点击 Web 授权进去装备相关装备。
主要是装备左边的上下两个 URI 地址,上面的装备后台域名:
下面的是固定写法,callback的地址一定是可用域名 + /oauth2callback。
创立结束之后,记住记录你的三个重要字段,client_id 和 client_secret 以及 redirect_uri ,后边会用到。
经过拜访一下的网页获取到一个oauth2callback:
https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&
redirect_uri=https://api.whatsapp.sg/oauth2callback&client_id=816630003638-5p27m684jfpfa6sh6l9chbpreq2hg9ov.apps.googleusercontent.com
回来一个code:
https://api.whatsapp.sg/oauth2callback?code=4/CpVOd8CljO_gxTRE1M5jtwEFwf8gRD44vrmKNDi4GSS.kr-GHuseD-oZEnp6UADFXm0E0MD3FlAI
拿到后边的 code 字段。
code=4/CpVOd8CljO_gxTRE1M5jtwEFwf8gRD44vrmKNDi4GSS.kr-GHuseD-oZEnp6UADFXm0E0MD3FlAI
咱们手动的在 postman 之类的东西上,经过固定的参数,拿到 refresh_token(要点,后期全靠它)
{
'grant_type':'authorization_code',
'code':'4/CpVOd8CljO_gxTRE1M5jtwEFwf8gRD44vrmKNDi4GSS.kr-GHuseD-oZEnp6UADFXm0E0MD3FlAI',//上一步获取的,
'client_id':'816630003638-5p27m684jfpfa6sh6l9chbpreq2hg9ov.apps.googleusercontent.com',
'client_secret':'36WnPnojshgj56uhghj-xCo',
'redirect_uri':'https://api.whatsapp.sg/oauth2callback',
}
向以下的网址发起 Post 恳求。
https://accounts.google.com/o/oauth2/token
一定要保证网络畅通,只要一次时机,回来的json目标如下
{
"access_token" : "",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/zaaHNytlC3SEBX7F2cfrHcqJEa3KoAHYeXES6nmho"
}
refresh_token 就拿到了,留意一定要保存好,只要这一次时机,假如再次调用此接口 refresh_token 便是空了,不会回来了。
3.3 OAuth校验付出是否成功
拿到这个refresh_token就能够调用真实的校验接口了,例如咱们后端调用的是否付出成功:
https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/products/{productId}/tokens/{purchaseToken}?access_token={$access_token}"
这儿的packageName,productId,purchaseToken 咱们都很熟悉了,便是Android 付出成功之后回来给咱们的,直接传递给后端即可,而access_token其实便是咱们上面拿到的 refresh_token。
咱们需求拿到第一次回来的 refresh_token 保存起来,后续以改写的办法来获取新的 refresh_token ,用于拜访真实的API。
后台调用验证接口结束之后得到的目标如下:
{
"kind": string,
"purchaseTimeMillis": string,
"purchaseState": integer,
"consumptionState": integer,
"developerPayload": string,
"orderId": string,
"purchaseType": integer,
"acknowledgementState": integer,
"purchaseToken": string,
"productId": string,
"quantity": integer,
"obfuscatedExternalAccountId": string,
"obfuscatedExternalProfileId": string,
"regionCode": string
}
只需求验证状况即可:
consumptionState == 0 purchaseState == 0
阐明这个产品现已购买了,而且也没有被耗费,那么此刻就能够给移动端回来true,让移动端履行耗费操作。
后端PHP的校验谷歌内购是否成功示例代码:
public function checkGooglePay(){
$google_public_key = "你的公钥(google后台在你的运用下获取)";
$inapp_purchase_data = $_REQUEST['signtureTemp'];
$inapp_data_signature = $_REQUEST['signtureDataTemp'];
$key = "-----BEGIN PUBLIC KEY-----\n".chunk_split($google_public_key, 64,"\n").'-----END PUBLIC KEY-----';
$key = openssl_pkey_get_public($key);
$signature = base64_decode($inapp_data_signature);
$ok = openssl_verify($inapp_purchase_data,$signature,$key,OPENSSL_ALGO_SHA1);
if (1 == $ok) {
// 付出验证成功!
//进行二次验证,订单查询
// 1.获取access_token(3600秒有用期)
$access_token_url = "https://accounts.google.com/o/oauth2/token";
$data_tmp2 = array(
'grant_type'=>'refresh_token',
'refresh_token'=>'',//长效token
'client_id'=>'', //客户端id
'client_secret'=>'',//客户端密钥
);
$http = new http($access_token_url,'POST',5);
$http->setContent($data_tmp2);
$result = $http->exec();
$result = json_decode($contents,true);
$access_token = $result['access_token'];
//2.经过取得access_token 就能够恳求谷歌的API接口,取得订单状况
$packageName=""//包名
$productId="" //产品Id
$purchaseToken=""
$url = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/{packageName}/purchases/products/{productId}/tokens/{purchaseToken}?access_token={$access_token}";
$http = new http($url,'GET',5);
$http->setContent($data);
$contents = $http->exec();
$contents = json_decode($contents,true);
if($contents['consumptionState'] == 0 && $contents['purchaseState'] == 0){
//验证成功 购买成功而且没有耗费 google付出中客户端假如没有进行耗费是不能再次购买该产品
//处理游戏逻辑 发钻石,通知客户端进行耗费
}else{
//订单验证失利
}
}else{
//签名验证失利
}
}
第一步是可选的,校验APK的签名,当时运用是不是谷歌商场下载的,假如不是从谷歌商场下载的那么付出不收效。假如你想要的校验APK来历就加上,不想校验也能够。
第二步便是开端校验谷歌内购付出订单的状况,拿到本地长期保存的refresh_token 以及之前获取到的client_id 和 client_secret 就能够到哪授权的 access_token 。
第三部便是拿到 access_token 以及 客户端传递的包名,产品id,付出凭据,调用校验接口,拿到订单的当时状况。
然后便是依据订单的状况判别回来给客户端是否有用,让客户端履行耗费操作。
假如您觉得有必要,也能够耗费之后再次调用接口校验,是否已购买,是否已耗费。
3.3 创立Service Account的授权
其实之前的之前的 Web-OAuth 的办法来进行验证不是不可,可是过程相对比较复杂,而更推荐的办法则是创立服务的办法来进行校验。
咱们把视角拉回谷歌商场控制台,找到 Api Access 选项 (老熟人了)
其实咱们鄙人面的拜访权限就能够看到 Service Account 的选项。假如你已有 Service Account 就能够看到悉数的相关的 Service Account 。假如你没有此服务,那么就能够点击创立服务去谷歌云创立。当咱们到谷歌云里边点击创立 Service Account:
咱们点击创立 Service Account 会走到创立服务的流程:
第一步随意写,关键是第二步:
挑选人物为 Service Account Admin
第三步不填,直接提交:
你就能看到你创立的服务啦,接下来便是创立Key,Json的办法创立,然后下载到Json给到后台人员。
再下一步就回到谷歌商铺控制台的 Api Access 看 Service Account
是否现已相关上了:
假如有这样的信息,阐明相关上了,才是正确的流程,假如你创立了 Service Account
,可是这儿并没有展示,那么就肯定会错:
{
"code":401,
"errors":[{
"domain":"androidpublisher",
"message":"Thecurrentuserhasinsufficientpermissionstoperformtherequestedoperation.",
"reason":"permissionDenied"
}],
"message":"Thecurrentuserhasinsufficientpermissionstoperformtherequestedoperation."
}
之后正常显现了服务,阐明你的服务才干拜访到谷歌商场这一边,接下来便是点击授予拜访权限。
要点是要把财务信息的两项勾选上,这样才干拜访到运用内付出校验的相关权限,如图所示:
点击保存修改之后就结束了,因为咱们相关账号的时分现已勾选了 Google Play Android Developer API 权限,咱们现在直接就能用了。
后端的用法各渠道的运用办法不同,可是都是很简略的,直接集成谷歌的API,然后一共就两步,第一步设置Config特点把这个 Service Account
生成的Json文件传入,第二步直接调用 GoogleAPI 内置的校验办法即可,都是API内置了的更便利。
当咱们客户端把packageName ,prodectId,purchaseToken 三个字段传给后端,他们直接调用 API 就能直接校验,相比 Web-OAuth
的办法要更简略一些。
校验成果如下:
OK,两种办法 Web-OAuth
的授权办法,以及 Service Account
的授权办法,两种都能够达到效果。就看咱们的喜欢哪一种啦!
那么处处整体的谷歌内购悉数流程就完毕了,咱们觉得还算具体吗?
后记
其他的第三方插件我觉得 Flutter 都蛮简略的,比方极光推送,感觉比 Android 版别的还要简略,一些装备与代码都封装好了,开箱即用很便利。
对于内购的插件 in_app_purchase 其实内部在 Android 渠道也是用的 Google Billing ,仅仅封装之后运用起来也很简略。感觉比原生都好用
主要是内购的装备,谷歌的装备,运用装备,产品的装备,谷歌云装备,各种授权装备。只要其间一步卡住了就不能行,感觉真的是很复杂,网上的一些参阅资源许多都是过期的,所以才有了我一步步的踩坑的分享,假如有需求咱们能够保藏
一波,保藏等于学会,万一哪天踩坑了能够参阅参阅嘛。
关于内购,不知道咱们有没有遇到内购的一些坑呢,又是怎么处理的呢?欢迎咱们一起沟通一下哦。
那么本期内容就到这儿,如讲的不到位或讹夺的地方,希望同学们能够谈论区指出。
假如感觉本文对你有一点点点的启示,还望你能点赞
支撑一下,你的支撑是我最大的动力啦。
Ok,这一期就此结束。