【Flutter】App內(nèi)購支付集成 Google和Apple支付和服務器驗證全流程

Flutter支付集成

前言:

以谷歌內(nèi)購為例,我們需要做的總共為三步

  1. 需要在谷歌市場配置商品,設置測試渠道,配置開發(fā)者賬號,設置對應權(quán)限。
  2. 配置完商品之后,如何在 Flutter 中獲取到商品,購買指定商品,消耗商品等。
  3. 購買成功之后,如何到服務器校驗是否支付成功,后臺服務器如何配置通行權(quán)限,谷歌市場與谷歌云的關聯(lián)以及相關校驗。

購買交易的生命周期
下面是一次性購買或訂閱的典型購買流程:

  1. 向用戶展示他們可以購買什么。
  2. 啟動購買流程,以便用戶接受購買交易。
  3. 在您的服務器上驗證購買交易。
  4. 向用戶提供內(nèi)容。
  5. 確認內(nèi)容已傳送給用戶。對于消耗型商品,用戶要先消耗掉已購商品,才能再次購買。

訂閱會自動續(xù)訂,直到被取消。訂閱可處于下面這幾種狀態(tài):

  • 有效:用戶信譽良好,可享用訂閱內(nèi)容。
  • 已取消:用戶已取消訂閱,但在到期前仍可享用訂閱內(nèi)容。
  • 處于寬限期:用戶遇到了付款問題,但仍可享用訂閱內(nèi)容,同時 Google 會重新嘗試通過相應的付款方式扣款。
  • 暫時保留:用戶遇到了付款問題,不能再享用訂閱內(nèi)容,同時 Google 會重新嘗試通過相應的付款方式扣款。
  • 已暫停:用戶暫停了其訂閱,在恢復之前不能享用訂閱內(nèi)容。
  • 已到期:用戶已取消且不能再享用訂閱內(nèi)容。用戶在訂閱到期時會被視為流失。

支付流程示意圖

image.png

一 、google開發(fā)者平臺配置

首先進入谷歌開發(fā)者平臺
https://developers.google.com/?hl=zh-cn

進入開發(fā)者平臺之后,點擊google play,創(chuàng)建我們的APP


image.png

點擊登錄管理中心


image.png

創(chuàng)建完我們的APP之后,就可以開始配置支付的功能。需要注意的是,在進行谷歌支付測試的時候,需要先提交一個封閉測試版本及以上等級(例如公開版本)的包,然后才可以去創(chuàng)建應用內(nèi)支付的商品,等這個包提交審核通過之后才可以開始進行谷歌支付的測試。

1.1、創(chuàng)建定價模板

在設置頁面


image.png

找到付款概況之后,如果沒有付款賬號,我們填寫一些信息,姓名,郵箱,賬號,等等信息,創(chuàng)建完成之后我們就可以設置定價的模板。
如果能創(chuàng)建模板說明你付款賬號沒問題,定價模板是非必須的,可有可無,但是定義了模板之后會更加方便,到時候創(chuàng)建商品可以直接關聯(lián)模板,賬號下的每一個子應用的內(nèi)購商品都能關聯(lián)對應的模板,有一個統(tǒng)一的定價。
如何創(chuàng)建定價模板如下:


image.png

我們創(chuàng)建模板之后,就可以定義模板的價格與標題,選擇的金額會有對應的匯率轉(zhuǎn)換,比如我創(chuàng)建的新加坡幣,如果用港元支付的話,會根據(jù)匯率轉(zhuǎn)換為對應的港元支付。


image.png

創(chuàng)建完成之后,我們就能看到對應的定價模板如下圖所示:


image.png
1.2、上架封閉測試App

點擊創(chuàng)建軌道


image.png

點擊創(chuàng)建新的發(fā)布版本


image.png

簽名選擇Google管理簽名,然后上傳aab格式的release版本的包,aab版本的包在這里生成
點開Build,選擇Generate Signed Bundle/APK


image.png

然后選擇app bundle


image.png

然后一路next,最后選擇release版本,然后finish


image.png

然后在輸出控制臺的build選項卡,即可找到剛剛打出來的aab包


image.png

然后上傳就可以了。

1.3、創(chuàng)建應用內(nèi)購商品

此時就可以配置應用內(nèi)商品了,點擊這里進行添加配置:


image.png

添加完成后記得激活,不然即使審核通過之后測試的時候也獲取不到該商品

點擊這里激活商品


image.png

這個時候商品的配置就完成了。

接下來添加測試賬戶,進入封閉測試頁面,切換到【測試用戶選項卡】,然后創(chuàng)建測試群組,在群組里添加測試人員賬戶即可


image.png

當你的APP審核通過之后,這個頁面下方的測試人員參與方式便會生效,如下所示:


image.png

就可以將這些鏈接發(fā)給測試人員,讓他們?nèi)グ惭b進行測試購買。

最后修改一下測試政策狀態(tài)


image.png

選中測試群組,然后將政策狀態(tài)改為LICENSED


image.png

OK,配置完成

二 、Apple開發(fā)者平臺添加內(nèi)購商品

首先使用蘋果開發(fā)者賬戶登錄蘋果開發(fā)者平臺

https://developer.apple.com/account

點擊【App】


image.png

添加新的蘋果內(nèi)購商品


image.png

添加的時候頁面的指引很清晰,就不贅述了,蘋果添加內(nèi)購商品比較簡單,加完就可以了。

然后去創(chuàng)建沙盒賬戶用來做蘋果支付測試,回到首頁,點擊【用戶和訪問】


image.png

點擊沙盒,然后添加一個蘋果測試賬戶,這個賬戶可以是個假的郵箱,不需要是正式的Apple id,比如你可以設置為8888888@qq.com類似之類的賬戶

image.png

添加完點擊創(chuàng)建即可


image.png

OK,配置完成

三、flutter 代碼集成

使用到的官方推出的應用內(nèi)購插件:

in_app_purchase: ^3.2.0

插件官網(wǎng)地址:https://pub.dev/packages/in_app_purchase

使用起來并不復雜,可以說是 Android 與 iOS 的邏輯是一樣樣的。

將插件添加至yaml文件,然后執(zhí)行flutter pub get


image.png

執(zhí)行完了記得去IOS和安卓端分別執(zhí)行pod install 和 gradle sync同步一下第三方插件

然后在項目中新建dart文件,命名為:BuyEngine.dart

然后將以下代碼放入:

import 'dart:async';
import 'dart:io';
 
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart';
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
 
class BuyEngin{
 
  StreamSubscription<List<PurchaseDetails>> _subscription;
  InAppPurchase _inAppPurchase;
  List<ProductDetails> _products; //內(nèi)購的商品對象集合
 
  //初始化購買組件
  void initializeInAppPurchase() {
 
    // 初始化in_app_purchase插件
    _inAppPurchase = InAppPurchase.instance;
 
    //監(jiān)聽購買的事件
    final Stream<List<PurchaseDetails>> purchaseUpdated = _inAppPurchase.purchaseStream;
    _subscription = purchaseUpdated.listen((purchaseDetailsList) {
      _listenToPurchaseUpdated(purchaseDetailsList);
    }, onDone: () {
      _subscription.cancel();
    }, onError: (error) {
      error.printError();
      print("購買失敗了");
    });
  }
 
  void resumePurchase(){
    _inAppPurchase.restorePurchases();
  }
 
  /// 加載全部的商品
  void buyProduct(String productId) async {
 
    print("請求商品id " + productId);
 
    List<String> _outProducts = [productId];
 
    final bool available = await _inAppPurchase.isAvailable();
    if (!available) {
      // ToastUtil.showToast("無法連接到商店");
      print("無法連接到商店");
      return;
    }
 
    //開始購買
    // ToastUtil.showToast("連接成功-開始查詢?nèi)可唐?);
    print("連接成功-開始查詢?nèi)可唐?);
    List<String> _kIds = _outProducts;
 
    final ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(_kIds.toSet());
    print("商品獲取結(jié)果  " + response.productDetails.toString());
    if (response.notFoundIDs.isNotEmpty) {
      // ToastUtil.showToast("無法找到指定的商品");
      print("無法找到指定的商品");
      // ToastUtil.showToast("無法找到指定的商品 數(shù)量 " + response.productDetails.length.toString());
 
      return;
    }
 
    // 處理查詢到的商品列表
    List<ProductDetails> products = response.productDetails;
    print("products ==== " + products.length.toString());
    if (products.isNotEmpty) {
      //賦值內(nèi)購商品集合
      _products = products;
    }
 
    print("全部商品加載完成了,可以啟動購買了,總共商品數(shù)量為:${products.length}");
 
    //先恢復可重復購買
    // await _inAppPurchase. ();
 
    startPurchase(productId);
  }
 
 
  // 調(diào)用此函數(shù)以啟動購買過程
  void startPurchase(String productId) async {
 
    print("購買的商品id為" + productId);
    if (_products != null && _products.isNotEmpty) {
      // ToastUtil.showToast("準備開始啟動購買流程");
      try {
        ProductDetails productDetails = _getProduct(productId);
 
        print("一切正常,開始購買,信息如下: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();
        print("購買失敗了");
      }
    } else {
      print("當前沒有商品無法調(diào)用購買邏輯");
    }
  }
 
  // 根據(jù)產(chǎn)品ID獲取產(chǎn)品信息
  ProductDetails _getProduct(String productId) {
    return _products.firstWhere((product) => product.id == productId);
  }
 
  /// 內(nèi)購的購買更新監(jiān)聽
  void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) async {
    for (PurchaseDetails purchase in purchaseDetailsList) {
      if (purchase.status == PurchaseStatus.pending) {
        // 等待支付完成
        _handlePending();
      } else if (purchase.status == PurchaseStatus.canceled) {
        // 取消支付
        _handleCancel(purchase);
      } else if (purchase.status == PurchaseStatus.error) {
        // 購買失敗
        _handleError(purchase.error);
      } else if (purchase.status == PurchaseStatus.purchased || purchase.status == PurchaseStatus.restored) {
        // ToastUtil.showToast(DataConfig.getShowName("Pay_Success_Tip"));
        //完成購買, 到服務器驗證
        if (Platform.isAndroid) {
          var googleDetail = purchase as GooglePlayPurchaseDetails;
          checkAndroidPayInfo(googleDetail);
        } else if (Platform.isIOS) {
          var appstoreDetail = purchase as AppStorePurchaseDetails;
          checkApplePayInfo(appstoreDetail);
        }
      }
    }
  }
 
  /// 購買失敗
  void _handleError(IAPError iapError) {
    // ToastUtil.showToast("${DataConfig.getShowName("Purchase_Failed")}:${iapError?.code} message${iapError?.message}");
  }
 
  /// 等待支付
  void _handlePending() {
    print("等待支付");
  }
 
  /// 取消支付
  void _handleCancel(PurchaseDetails purchase) {
    _inAppPurchase.completePurchase(purchase);
  }
 
  /// Android支付成功的校驗
  void checkAndroidPayInfo(GooglePlayPurchaseDetails googleDetail) async {
    _inAppPurchase.completePurchase(googleDetail);
    print("安卓支付交易ID為" + googleDetail.purchaseID);
    print("安卓支付驗證收據(jù)為" + googleDetail.verificationData.serverVerificationData);
  }
 
  /// Apple支付成功的校驗
  void  checkApplePayInfo(AppStorePurchaseDetails appstoreDetail) async {
    _inAppPurchase.completePurchase(appstoreDetail);
 
    print("Apple支付交易ID為" + appstoreDetail.purchaseID);
    print("Apple支付驗證收據(jù)為" + appstoreDetail.verificationData.serverVerificationData);
  }
 
 
  @override
  void onClose() {
    if (Platform.isIOS) {
      final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition =
      _inAppPurchase.getPlatformAddition<InAppPurchaseStoreKitPlatformAddition>();
      iosPlatformAddition.setDelegate(null);
    }
    _subscription.cancel();
  }
 
}

至此集成完畢,開始測試谷歌支付

三 、支付測試

在調(diào)用支付的地方提前初始化購買插件:

BuyEngin _buyEngin = BuyEngin();
_buyEngin.initializeInAppPurchase();

然后調(diào)用即可:

_buyEngin.buyProduct("應用內(nèi)商品ID");

應用內(nèi)商品ID就是你在google開發(fā)者中心或APP Store Connect 配置的應用內(nèi)購買商品的product ID

如果一切正常,則會正常喚醒谷歌或蘋果支付


image.png

支付完成后可以看到可以正常獲取到交易的ID和交易的驗證收據(jù),為了避免被第三方惡意刷購買接口來進行非法購買,建議將該收據(jù)上傳后端服務器進行驗證,驗證通過之后再去更新用戶的購買信息。

image.png

Ok ,集成完畢,功德+1

四、 服務器校驗相關流程

為什么要加后端校驗?客戶端支付成功了,服務端怎么知道,萬一用接口的方式通信,如果被抓包豈不是可以無限加金幣了。太不安全了,所以才有服務器校驗這一步。

iOS的校驗不用說,很簡單,拿到支付完成的票據(jù)直接發(fā)起請求即可,而 Android 的服務端校驗就相對麻煩,需要配置谷歌云,以及對應的通行權(quán)限。

谷歌結(jié)算文檔:https://developer.android.com/google/play/billing?hl=zh-cn
谷歌支付校驗AI:https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.products?hl=zh-cn
如果我們直接在API中調(diào)用校驗接口,那肯定是直接報錯:

{
  "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"
      }
    ]
  }
}


沒有授權(quán),接下來開始授權(quán)

4.1、Google Cloud關聯(lián)

首先需要配置 Google Cloud 并且配置相關的賬號,對應指定的應用。
點擊項目的 API Access 中


image.png

如果這一步你沒有 Google Cloud 賬號,可以創(chuàng)建或關聯(lián)已有的 Google Cloud 賬號,這里我沒有就直接創(chuàng)建了Google Cloud 賬號。關聯(lián)之后我們就能看到上圖所示的畫面。

我們可以直接在谷歌市場控制臺中的 API Access 中直接進入谷歌云后臺,也能 直接輸入網(wǎng)址 https://code.google.com/apis/console/ 是一樣的效果。

我們關聯(lián) Google Cloud 賬號之后,默認就已經(jīng)開通 Google Play Developer API 權(quán)限。


image.png

所以我們不需要再次去授權(quán)了。


image.png

如果覺得不保險,也能在里面搜索 Billing ,然后啟動相關的支付服務權(quán)限

4.2 、創(chuàng)建 web-OAuth 授權(quán)

當我們在谷歌市場的后臺關聯(lián)谷歌云的時候,就已經(jīng)幫我們初始化了很多配置,已經(jīng)都有了
我們再谷歌云后臺,在APIs & auth 項中找到 Credentials,直接查看即可:


image.png

我們點擊 Web 授權(quán)進去配置相關配置。
主要是配置左側(cè)的上下兩個 URI 地址,上面的配置后臺域名:


image.png

下面的是固定寫法,callback的地址一定是可用域名 + /oauth2callback。


image.png

創(chuàng)建完成之后,記得記錄你的三個重要字段,client_id 和 client_secret 以及 redirect_uri ,后面會用到。
通過訪問一下的網(wǎng)頁獲取到一個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 之類的工具上,通過固定的參數(shù),拿到 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',
}
    


向以下的網(wǎng)址發(fā)起 Post 請求。

https://accounts.google.com/o/oauth2/token

一定要保證網(wǎng)絡暢通,只有一次機會,返回的json對象如下

{
"access_token" : "",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/zaaHNytlC3SEBX7F2cfrHcqJEa3KoAHYeXES6nmho"
}

refresh_token 就拿到了,注意一定要保存好,只有這一次機會,如果再次調(diào)用此接口 refresh_token 就是空了,不會返回了。

4.3、web-OAuth校驗支付是否成功

拿到這個refresh_token就可以調(diào)用真正的校驗接口了,例如我們后端調(diào)用的是否支付成功:

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 保存起來,后續(xù)以刷新的方式來獲取新的 refresh_token ,用于訪問真正的API。

后臺調(diào)用驗證接口完成之后得到的對象如下:

{
  "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
}

只需要驗證狀態(tài)即可:

consumptionState == 0 purchaseState == 0

說明這個商品已經(jīng)購買了,并且也沒有被消耗,那么此時就可以給移動端返回true,讓移動端執(zhí)行消耗操作。
后端PHP的校驗谷歌內(nèi)購是否成功示例代碼:

   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接口,獲得訂單狀態(tài)
             $packageName=""http://包名
            $productId="" //產(chǎn)品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支付中客戶端如果沒有進行消耗是不能再次購買該商品
                 //處理游戲邏輯 發(fā)鉆石,通知客戶端進行消耗
             }else{
                 //訂單驗證失敗
             }             
         }else{  
             //簽名驗證失敗
 
         }           
     }

  • 第一步是可選的,校驗APK的簽名,當前應用是不是谷歌市場下載的,如果不是從谷歌市場下載的那么支付不生效。如果你想要的校驗APK來源就加上,不想校驗也可以。

  • 第二步就是開始校驗谷歌內(nèi)購支付訂單的狀態(tài),拿到本地長期保存的refresh_token 以及之前獲取到的client_id 和 client_secret 就可以到哪授權(quán)的 access_token 。

  • 第三部就是拿到 access_token 以及 客戶端傳遞的包名,產(chǎn)品id,支付憑證,調(diào)用校驗接口,拿到訂單的當前狀態(tài)。
    然后就是根據(jù)訂單的狀態(tài)判斷返回給客戶端是否有效,讓客戶端執(zhí)行消耗操作。
    如果您覺得有必要,也可以消耗之后再次調(diào)用接口校驗,是否已購買,是否已消耗。

4.4、 創(chuàng)建Service Account的授權(quán)

其實之前的之前的 Web-OAuth 的方式來進行驗證不是不行,但是步驟相對比較復雜,而更推薦的方式則是創(chuàng)建服務的方式來進行校驗。
我們把視角拉回谷歌市場控制臺,找到 Api Access 選項


image.png

其實我們在下面的訪問權(quán)限就可以看到 Service Account 的選項。如果你已有 Service Account 就可以看到全部的關聯(lián)的 Service Account 。如果你沒有此服務,那么就可以點擊創(chuàng)建服務去谷歌云創(chuàng)建。當我們到谷歌云里面點擊創(chuàng)建 Service Account:


image.png

我們點擊創(chuàng)建 Service Account 會走到創(chuàng)建服務的流程:


image.png

第一步隨便寫,關鍵是第二步:


image.png

選擇角色為 Service Account Admin


image.png

第三步不填,直接提交:


image.png

你就能看到你創(chuàng)建的服務啦,接下來就是創(chuàng)建Key,Json的方式創(chuàng)建,然后下載到Json給到后臺人員。


image.png

再下一步就回到谷歌商店控制臺的 Api Access 看 Service Account 是否已經(jīng)關聯(lián)上了:


image.png

如果有這樣的信息,說明關聯(lián)上了,才是正確的流程,如果你創(chuàng)建了 Service Account,但是這里并沒有展示,那么就肯定會錯:

{  
  "code" : 401,  
  "errors" : [ {  
    "domain" : "androidpublisher",  
    "message" : "The current user has insufficient permissions to perform the requested operation.",  
    "reason" : "permissionDenied"  
  } ],  
  "message" : "The current user has insufficient permissions to perform the requested operation."  
}

之后正常顯示了服務,說明你的服務才能訪問到谷歌市場這一邊,接下來就是點擊授予訪問權(quán)限。
重點是要把財務信息的兩項勾選上,這樣才能訪問到應用內(nèi)支付校驗的相關權(quán)限,如圖所示:


image.png

點擊保存修改之后就完成了,由于我們關聯(lián)賬號的時候已經(jīng)勾選了 Google Play Android Developer API 權(quán)限,我們現(xiàn)在直接就能用了。

后端的用法各平臺的使用方式不同,但是都是很簡單的,直接集成谷歌的API,然后總共就兩步,第一步設置Config屬性把這個 **Service Account **生成的Json文件傳入,第二步直接調(diào)用 GoogleAPI 內(nèi)置的校驗方法即可,都是API內(nèi)置了的更方便。

當我們客戶端把packageName ,prodectId,purchaseToken 三個字段傳給后端,他們直接調(diào)用 API 就能直接校驗,相比 Web-OAuth 的方式要更簡單一些。
校驗結(jié)果如下:

image.png

OK,兩種方法 Web-OAuth 的授權(quán)方式,以及 Service Account 的授權(quán)方式,兩種都可以達到效果,用哪種都可以。
至此谷歌內(nèi)購全部流程已結(jié)束。

丟單問題處理

使用
_inAppPurchase.purchaseStream是用來監(jiān)聽消息隊列的回調(diào)的,也就是所有訂單的狀態(tài)以及信息回調(diào),in_app_purchase這個屬性的文檔中這么說到:

IMPORTANT! You must subscribe to this stream as soon as your app launches,
preferably before returning your main App Widget in main(). Otherwise you
will miss purchase updated made before this stream is subscribed to.
重要!你必須在應用程序啟動后立即訂閱此流,
最好在main()中返回主應用程序小部件之前。
否則你將錯過訂閱此流之前更新的購買。

也就是說當我們的App在第一次啟動的時候可以訂閱此流來完成補單的操作,但是如果用戶是之前丟單了,然后把App又卸載了,再次下載打開App后并沒有進行登錄操作,那用戶的登錄信息都拿不到怎么進行補單操作呢?

補單解決方案

讓后端出一個補單的接口,在補單時只需要傳一個訂單號即可,那App都刪除了,之前的訂單號客戶端怎么獲取呢?使用flutter_keychain來實現(xiàn),flutter_keychain就是使用的iOS的鑰匙串來實現(xiàn)的,當用戶在蘋果服務器下單時,在鑰匙串中保存后端生成的訂單號,然后再商品成功發(fā)貨后刪除鑰匙串里面的訂單號,完成一個完整的購買過程,再購買時任何一環(huán)出了問題鑰匙串里面緩存的訂單號都不會被清空,這樣在App下一次啟動時,在首頁或者main函數(shù)中使用_inAppPurchase.purchaseStream 監(jiān)聽,在拿到flutter_keychain中保存的訂單號完成補單過程。

注意點

1.在完成蘋果服務器付款流程后通知到自己服務器接口也就是驗單的接口返回的是成功或者不成功都要調(diào)用_inAppPurchase.completePurchase(purchaseDetails)這個方法,不然下次就掉不起蘋果支付來了,當然肯定會在失敗的判斷里面寫明白讓用戶自己去走蘋果退款流程的文案(概率較小,但是也得考慮)

2.商品類型如果是非消耗品的話,在下單完之后一定寫一個按鈕供點擊調(diào)用復原的方法,要是不復原的話每次下的訂單,訂單號都是一樣的(需要注意下)。

參考:

Flutter插件:
pay 2.0.0 :https://pub.dev/packages/pay
in_app_purchase 3.2.0:https://pub.dev/packages/in_app_purchase

Google pay:
https://developers.google.com/pay/api/android/overview?hl=zh-cn
https://developer.android.com/google/play/billing

Apple pay:
https://developer.apple.com/documentation/passkit_apple_pay_and_wallet/apple_pay/setting_up_apple_pay
https://developer.apple.com/in-app-purchase/

教程:
https://juejin.cn/post/7290009513470623800
https://juejin.cn/post/7020651416276434958
https://blog.csdn.net/mumubumaopao/article/details/136112183
https://juejin.cn/post/7233310081809760317

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容