天天看點

谷歌支付服務端服務賬号訂單校驗

谷歌支付服務端服務賬号訂單校驗

    • 整個開發背景是前端在調用完google play v3 支付流程後,需要背景驗證支付結果以及在自己的服務生成訂單相關資訊。對于服務賬号的文檔搜了度娘對于這塊的資料少,無從下手,踩了不少坑。網上的搜出來的大多數是針對OAuth 2.0 用戶端 ID的驗證,需要使用到refreshAccess,但是服務賬号不一樣,那不需要驗簽通過後直接能請求查詢訂單。
    • 用到的 maven 依賴jar包
    • 登入校驗
    • 訂單建立
    • 訂單成功校驗

整個開發背景是前端在調用完google play v3 支付流程後,需要背景驗證支付結果以及在自己的服務生成訂單相關資訊。對于服務賬号的文檔搜了度娘對于這塊的資料少,無從下手,踩了不少坑。網上的搜出來的大多數是針對OAuth 2.0 用戶端 ID的驗證,需要使用到refreshAccess,但是服務賬号不一樣,那不需要驗簽通過後直接能請求查詢訂單。

這邊服務端用的是java ,在接入googgle管道中與安卓端對接的過程就三接口,1、登入校驗 2、建立訂單号及校驗産品id後傳回訂單号給安卓端 3、支付完成訂單校驗,google支付那沒有想微信那邊支付成功後回調到我們服務端,它那邊是通過安卓端的請求來通知我們自己的服務端的支付成功通知,掉單的情況也是用戶端來通知。。

因這邊用到的是消耗型的産品,背景就省了一步acknowledge的确認,隻需要校驗訂單支付狀态。

登入前期準備以下隻是格式,具體的參數從谷歌背景下載下傳:

1、用戶端秘鑰,其中google_client_secret_data是我這邊自己加的

{"google_client_secret_data": {
		"web": {
			"client_id": "123456789-1li1d7k.apps.googleusercontent.com",
			"project_id": "project_id-0310",
			"auth_uri": "https://accounts.google.com/o/oauth2/auth",
			"token_uri": "https://oauth2.googleapis.com/token",
			"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs"
		}
	}
}
           

2、服務端秘鑰其中google_service_account_data是我這邊自己加的

{
	"google_service_account_data": {
		"type": "service_account",
		"project_id": "123456789526755524298661-783",
		"private_key_id": "1234567890ac89f9c70f245f1798587ae9b7",
		"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQASCBKcwggSjAgEAAoIBAQCX1kMC4Au/\n-----END PRIVATE KEY-----\n",
		"client_email": "test02661-783.iam.gserviceaccount.com",
		"client_id": "123456789",
		"auth_uri": "https://accounts.google.com/o/oauth2/auth",
		"token_uri": "https://oauth2.googleapis.com/token",
		"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
		"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/83.iam.gserviceaccount.com"
	}
}
           

用到的 maven 依賴jar包

<dependency>
        <groupId>com.google.api-client</groupId>
         <artifactId>google-api-client</artifactId>
         <version>1.32.1</version>
</dependency>
 <dependency>
        <groupId>com.google.auth</groupId>
        <artifactId>google-auth-library-oauth2-http</artifactId>
        <version>1.2.2</version>
</dependency>
<dependency>
        <groupId>com.google.apis</groupId>
        <artifactId>google-api-services-androidpublisher</artifactId>
        <version>v3-rev20211021-1.32.1</version>
 </dependency>
           

登入校驗

private ResultData<Object> googleIdTokenVerifier(JSONObject google_client_secret_dataJson,String idTokenString) throws IOException,java.security.GeneralSecurityException{
        
        
        GoogleClientSecrets clientSecrets = GsonFactory.getDefaultInstance().fromString(
                google_client_secret_dataJson.get("google_client_secret_data").toString(),
                GoogleClientSecrets.class);
       String CLIENT_ID=clientSecrets.getDetails().getClientId();
       logger.info("CLIENT_ID={}",CLIENT_ID);
        GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), GsonFactory.getDefaultInstance())
                // Specify the CLIENT_ID of the app that accesses the backend:
                .setAudience(Collections.singletonList(CLIENT_ID))
                // Or, if multiple clients access the backend:
                //.setAudience(Arrays.asList(CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3))
                .build();

        GoogleIdToken idToken = verifier.verify(idTokenString);
        logger.info("驗證結果idToken={}",idToken);
        if (idToken == null) {
            logger.error("IDtoken驗證失敗!,Invalid ID token.");
            return ResultData.fail("idToken驗證失敗");
        }
            Payload payload = idToken.getPayload();
            logger.info("驗證結果payload={}",payload);
            // Print user identifier
            String googleUserId = payload.getSubject();
            logger.info("googleUserId={}",googleUserId);
           
           
            return ResultData.success(googleUserId);
    }
           

訂單建立

略。。
           

訂單成功校驗

private ResultData googlePayNotify(JSONObject google_service_account_dataJson,HttpServletRequest request) {

        Map<String, String[]> parameterMap = request.getParameterMap();
       
        logger.info("支付回調傳回參數:{}", JSONObject.toJSONString(parameterMap));
        

        JSONObject google_service_account_data  = google_service_account_dataJson.getJSONObject("google_service_account_data");
        logger.info("google_service_account_data={}",google_service_account_data);
        String myOrderId = parameterMap.get("myOrderId")[0] ;  //自己的訂單号
        String purchaseToken = parameterMap.get("purchaseToken")[0];  //購買應用内産品時提供給使用者裝置的令牌。
        String productId = parameterMap.get("productId")[0];  //産品id
        String orderId = parameterMap.get("orderId")[0]; //谷歌訂單号
        String channelType = parameterMap.get("channelType")[0]; //支付管道
        String amount = parameterMap.get("amount")[0]; //金額
        String packageName = parameterMap.get("packageName")[0]; //包名 安卓端傳

        //擷取管道商品ID 校驗
        
		
        
        PayOrder payOrder =  payOrderDao.getPayOrder(myOrderId);


        /** 響應結果
         {
         "resource": {
         object (ProductPurchase)
         }
         }
         ProductPurchase:
         {
         "kind": string, //這種表示 androidpublisher 服務中的一個 inappPurchase 對象
         "purchaseTimeMillis": string,//購買産品的時間,自紀元(1970 年 1 月 1 日)以來的毫秒數。
         "purchaseState": integer,//訂單的購買狀态。可能的值為:0. 已購買 1. 已取消 2. 待定
         "consumptionState": integer,//inapp産品的消費狀态。可能的值為: 0. 尚未消耗 1. 已消耗
         "developerPayload": string,//包含有關訂單的補充資訊的開發人員指定的字元串。
         "orderId": string,//與購買 inapp 産品關聯的訂單 ID
         "purchaseType": integer,//inapp 産品的購買類型。僅當此購買不是使用标準應用内結算流程進行時才設定此字段。可能的值為: 0. 測試(即從許可測試帳戶購買) 1. 促銷(即使用促銷代碼購買) 2. 獎勵(即通過觀看視訊廣告而不是付費)
         "acknowledgementState": integer,//inapp 産品的确認狀态。可能的值為: 0. 尚未确認 1. 确認
         "purchaseToken": string,//為識别此次購買而生成的購買令牌。
         "productId": string,//inapp 産品 SKU。
         "quantity": integer,//與購買 inapp 産品相關的數量。
         "obfuscatedExternalAccountId": string,//與您應用中的使用者帳戶唯一關聯的 id 的混淆版本。僅在購買時使用https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setobfuscatedaccountid指定時出現。
         "obfuscatedExternalProfileId": string,//與您應用中的使用者個人資料唯一關聯的 id 的混淆版本。僅在購買時使用https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setobfuscatedprofileid指定時出現。
         "regionCode": string //授予産品時使用者的 ISO 3166-1 alpha-2 計費區域代碼。
         }
         */
        /**
         * google 驗征訂單
         */
          /*** 注意
         * 		秘鑰格式"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoI\n-----END PRIVATE KEY-----\n",
         * 添加秘鑰空格,存資料庫後轉json秘鑰空格被去掉,沒有空格時将導緻校驗錯誤
         */
        String credentialsJson = google_service_account_data.toJSONString();
        credentialsJson = credentialsJson.replace("BEGINPRIVATEKEY","BEGIN PRIVATE KEY");
        credentialsJson = credentialsJson.replace("ENDPRIVATEKEY","END PRIVATE KEY");
        logger.info("添加秘鑰空格,存資料庫後轉json秘鑰空格被去掉,沒有空格時将導緻校驗錯誤!!!,credentialsJson=",credentialsJson);
     
        InputStream stream = new ByteArrayInputStream(credentialsJson.getBytes());
        ProductPurchase purchase = null;
        try{
            GoogleCredentials credentials = GoogleCredentials.fromStream(stream);
            credentials = credentials.createScoped(Arrays.asList("https://www.googleapis.com/auth/androidpublisher")); //訂單查詢域
            logger.info("credentials ={}",credentials);

            credentials.refreshIfExpired();
            logger.info("credentials.refreshIfExpired={}",credentials);
            // AccessToken token = credentials.getAccessToken();
            // OR
            //  AccessToken refreshAccess = credentials.refreshAccessToken();
           //該方法已棄用 GoogleCredential googleCredential;
		   
            HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(credentials);
            AndroidPublisher publisher = new AndroidPublisher.Builder(new NetHttpTransport(),
                    GsonFactory.getDefaultInstance(), requestInitializer).build();
            AndroidPublisher.Purchases.Products products = publisher.purchases()
                    .products();
			//訂單查詢
            AndroidPublisher.Purchases.Products.Get product = products.get(packageName,productId,purchaseToken);

             purchase = product.execute();
            logger.info("查詢訂單結果purchase={}",purchase);

        }catch (Exception e){
            logger.error("查詢訂單失敗!",e);
            return  ResultData.fail("查詢訂單失敗!");
        }

        logger.info("訂單狀态為(0.已購買1.已取消2.待定)purchaseState={},消費狀态(0.尚未消耗1.已消耗)consumptionState={}," +
                "确認狀态(0.尚未确認1.确認)acknowledgementState={}",purchase.getPurchaseState(),
                purchase.getConsumptionState(),
                purchase.getAcknowledgementState());

        logger.info("訂單發貨狀态ayOrder.getDeliveryStatus={}",payOrder.getDeliveryStatus());
        // 訂單的購買狀态:(0.已購買1.已取消2.待定)已購買   非消耗産品不用校驗acknowledgementState狀态
        if (purchase.getPurchaseState() != 0) {
            logger.error("訂單未支付!");
            return  ResultData.fail("訂單未支付");
        }

		//儲存purchaseToken字段
        
        //ConsumptionState=1已消耗  發貨狀态為已發貨
        if(1 == purchase.getConsumptionState() && DELIVERY_STATUS_SUCCESS == payOrder.getDeliveryStatus()){
            return  ResultData.fail("重複回調!,已發貨!");
        }

      

        String issueOrderNo = gameOrderId;
        String snOrderNo = purchase.getOrderId();

        //.通知發貨...
       
        return ResultData.success();    

    }