peko-android-gms init
This commit is contained in:
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/build
|
||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
*.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.idea
|
||||||
|
.settings
|
||||||
|
*.apk
|
||||||
|
|
32
build.gradle
Normal file
32
build.gradle
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
apply from: "./module_standard.gradle"
|
||||||
|
android {
|
||||||
|
namespace 'com.example.module_google'
|
||||||
|
|
||||||
|
def gak = project.hasProperty("GOOGLE_APP_KEY") ? GOOGLE_APP_KEY :
|
||||||
|
"Define GOOGLE_APP_KEY in gradle.properties. Or './gradlew -PGOOGLE_APP_KEY=gak_value ... taskName'"
|
||||||
|
def gsci = project.hasProperty("GOOGLE_SERVER_CLIENT_ID") ? GOOGLE_SERVER_CLIENT_ID :
|
||||||
|
"Define GOOGLE_SERVER_CLIENT_ID in gradle.properties. Or 'gradle -PGOOGLE_SERVER_CLIENT_ID=gak_value ... taskName'"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
buildConfigField "String", "GOOGLE_APP_KEY", "\"$gak\""
|
||||||
|
buildConfigField "String", "GOOGLE_SERVER_CLIENT_ID", "\"$gsci\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kapt {
|
||||||
|
arguments {
|
||||||
|
arg("AROUTER_MODULE_NAME", project.getName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// google登录
|
||||||
|
implementation 'com.google.android.gms:play-services-auth:20.7.0'
|
||||||
|
|
||||||
|
// googleplay内购
|
||||||
|
implementation 'com.google.android.gms:play-services-wallet:19.2.1'
|
||||||
|
implementation 'com.android.billingclient:billing:6.0.1'
|
||||||
|
|
||||||
|
// fastjson
|
||||||
|
implementation "com.alibaba:fastjson:1.2.41"
|
||||||
|
}
|
0
consumer-rules.pro
Normal file
0
consumer-rules.pro
Normal file
53
module_base.gradle
Normal file
53
module_base.gradle
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 文件说明:module的基础配置
|
||||||
|
*/
|
||||||
|
apply plugin: "com.android.library"
|
||||||
|
apply plugin: "org.jetbrains.kotlin.android"
|
||||||
|
apply plugin: "kotlin-android"
|
||||||
|
apply plugin: "kotlin-kapt"
|
||||||
|
apply plugin: "com.alibaba.arouter"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion COMPILE_SDK_VERSION.toInteger()
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion MIN_SDK_VERSION.toInteger()
|
||||||
|
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0.0"
|
||||||
|
consumerProguardFiles 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
zipAlignEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
debuggable true
|
||||||
|
minifyEnabled false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_11
|
||||||
|
targetCompatibility JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '11'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'com.google.android.material:material:1.8.0'
|
||||||
|
implementation 'androidx.core:core-ktx:1.9.0'
|
||||||
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
|
|
||||||
|
// aRouter
|
||||||
|
api 'com.alibaba:arouter-api:1.4.0'
|
||||||
|
api 'com.alibaba:arouter-annotation:1.0.6'
|
||||||
|
kapt 'com.alibaba:arouter-compiler:1.5.2'
|
||||||
|
}
|
||||||
|
|
10
module_standard.gradle
Normal file
10
module_standard.gradle
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* 文件说明:业务module的标准配置(非base模块外的业务模块)
|
||||||
|
*/
|
||||||
|
apply from: "./module_base.gradle"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// Base
|
||||||
|
implementation project(path: ":modules:module_base")
|
||||||
|
}
|
||||||
|
|
21
proguard-rules.pro
vendored
Normal file
21
proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
4
src/main/AndroidManifest.xml
Normal file
4
src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
</manifest>
|
32
src/main/java/com/example/module_google/GoogleServiceImpl.kt
Normal file
32
src/main/java/com/example/module_google/GoogleServiceImpl.kt
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package com.example.module_google
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import com.alibaba.android.arouter.facade.annotation.Route
|
||||||
|
import com.example.module_base.config.RouterPath
|
||||||
|
import com.example.module_base.support.billing.IBillingService
|
||||||
|
import com.example.module_base.support.google.IGoogleService
|
||||||
|
import com.example.module_base.support.login.ILoginService
|
||||||
|
import com.example.module_google.billing.BillingService
|
||||||
|
import com.example.module_google.login.GoogleLoginService
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Max on 2023/11/22 15:56
|
||||||
|
* Desc:
|
||||||
|
**/
|
||||||
|
@Route(path = RouterPath.GOOGLE_SERVICE)
|
||||||
|
class GoogleServiceImpl : IGoogleService {
|
||||||
|
override fun newLoginService(): ILoginService {
|
||||||
|
return GoogleLoginService()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newBillingService(
|
||||||
|
activity: Activity,
|
||||||
|
listener: IBillingService.Listener
|
||||||
|
): IBillingService {
|
||||||
|
return BillingService(activity, listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun init(context: Context?) {
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,18 @@
|
|||||||
|
package com.example.module_google.billing
|
||||||
|
|
||||||
|
import com.android.billingclient.api.AccountIdentifiers
|
||||||
|
import com.example.module_base.support.billing.IAccountIdentifiers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Max on 2023/11/22 20:57
|
||||||
|
* Desc:
|
||||||
|
**/
|
||||||
|
data class AccountIdentifiersImpl(val data: AccountIdentifiers) : IAccountIdentifiers {
|
||||||
|
override fun getObfuscatedAccountId(): String? {
|
||||||
|
return data.obfuscatedAccountId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getObfuscatedProfileId(): String? {
|
||||||
|
return data.obfuscatedProfileId
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,20 @@
|
|||||||
|
package com.example.module_google.billing
|
||||||
|
|
||||||
|
import com.android.billingclient.api.BillingClient
|
||||||
|
import com.android.billingclient.api.BillingResult
|
||||||
|
import com.example.module_base.support.billing.IBillingResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Max on 2023/11/22 20:20
|
||||||
|
* Desc:
|
||||||
|
**/
|
||||||
|
data class BillingResultImpl(val data: BillingResult) : IBillingResult {
|
||||||
|
|
||||||
|
override fun getResponseCode(): Int {
|
||||||
|
return data.responseCode
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isResponseOk(): Boolean {
|
||||||
|
return getResponseCode() == BillingClient.BillingResponseCode.OK
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,221 @@
|
|||||||
|
package com.example.module_google.billing
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.util.Log
|
||||||
|
import com.alibaba.fastjson.JSONObject
|
||||||
|
import com.android.billingclient.api.BillingClient
|
||||||
|
import com.android.billingclient.api.BillingClient.BillingResponseCode
|
||||||
|
import com.android.billingclient.api.BillingClientStateListener
|
||||||
|
import com.android.billingclient.api.BillingFlowParams
|
||||||
|
import com.android.billingclient.api.BillingFlowParams.ProductDetailsParams
|
||||||
|
import com.android.billingclient.api.BillingResult
|
||||||
|
import com.android.billingclient.api.ConsumeParams
|
||||||
|
import com.android.billingclient.api.ProductDetails
|
||||||
|
import com.android.billingclient.api.Purchase
|
||||||
|
import com.android.billingclient.api.PurchasesUpdatedListener
|
||||||
|
import com.android.billingclient.api.QueryProductDetailsParams
|
||||||
|
import com.android.billingclient.api.QueryPurchasesParams
|
||||||
|
import com.example.module_base.support.billing.IBillingService
|
||||||
|
import com.example.module_base.support.billing.IProductDetails
|
||||||
|
import com.example.module_base.support.billing.IPurchase
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class BillingService(/*活动*/
|
||||||
|
private val activity: Activity, /*监听*/
|
||||||
|
private val listener: IBillingService.Listener
|
||||||
|
) : IBillingService, PurchasesUpdatedListener {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "BillingManager"
|
||||||
|
|
||||||
|
/*购买key*/
|
||||||
|
private const val BASE_64_ENCODED_PUBLIC_KEY =
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
/*客户端*/
|
||||||
|
private var billingClient: BillingClient?
|
||||||
|
|
||||||
|
/*是否连接成功*/
|
||||||
|
private var isServiceConnected = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
/*商品列表*/
|
||||||
|
private val purchaseList: MutableList<IPurchase> = ArrayList()
|
||||||
|
|
||||||
|
init {
|
||||||
|
billingClient =
|
||||||
|
BillingClient.newBuilder(activity).enablePendingPurchases().setListener(this).build()
|
||||||
|
startServiceConnection {
|
||||||
|
listener.onBillingClientSetupFinished()
|
||||||
|
onQueryPurchases()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isServiceConnected(): Boolean {
|
||||||
|
return isServiceConnected
|
||||||
|
}
|
||||||
|
|
||||||
|
/*开始连接Play*/
|
||||||
|
private fun startServiceConnection(executeOnSuccess: Runnable?) {
|
||||||
|
billingClient?.startConnection(object : BillingClientStateListener {
|
||||||
|
override fun onBillingSetupFinished(billingResult: BillingResult) {
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Setup finished. Response code: " + billingResult.debugMessage + " code = " + billingResult.responseCode
|
||||||
|
)
|
||||||
|
if (billingResult.responseCode == BillingResponseCode.OK) {
|
||||||
|
isServiceConnected = true
|
||||||
|
executeOnSuccess?.run()
|
||||||
|
} else {
|
||||||
|
isServiceConnected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBillingServiceDisconnected() {
|
||||||
|
isServiceConnected = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*请求商品库存*/
|
||||||
|
override fun onQueryPurchases() {
|
||||||
|
val queryToExecute = Runnable {
|
||||||
|
billingClient?.queryPurchasesAsync(
|
||||||
|
QueryPurchasesParams.newBuilder()
|
||||||
|
.setProductType(BillingClient.ProductType.INAPP)
|
||||||
|
.build()
|
||||||
|
) { billingResult: BillingResult, purchases: List<Purchase>? ->
|
||||||
|
onPurchasesUpdated(
|
||||||
|
billingResult,
|
||||||
|
purchases
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
executeServiceRequest(queryToExecute)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*更新商品*/
|
||||||
|
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
|
||||||
|
Log.i(TAG, "billingResult.getResponseCode()==" + billingResult.responseCode)
|
||||||
|
purchaseList.clear()
|
||||||
|
if (billingResult.responseCode == BillingResponseCode.OK) {
|
||||||
|
if (purchases != null) {
|
||||||
|
for (purchase in purchases) {
|
||||||
|
handlePurchase(PurchaseImpl(purchase))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
listener.onPurchasesUpdated(purchaseList)
|
||||||
|
} else {
|
||||||
|
if (billingResult.responseCode == BillingResponseCode.USER_CANCELED) {
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
listener.onFailedHandle(billingResult.responseCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*商品处理*/
|
||||||
|
private fun handlePurchase(purchase: PurchaseImpl) {
|
||||||
|
//验证签名数据
|
||||||
|
if (!verifyValidSignature(purchase.data.originalJson, purchase.data.signature)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
purchaseList.add(purchase)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*验证签名*/
|
||||||
|
private fun verifyValidSignature(signedData: String, signature: String): Boolean {
|
||||||
|
return try {
|
||||||
|
Security.verifyPurchase(BASE_64_ENCODED_PUBLIC_KEY, signedData, signature)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "Got an exception trying to validate a purchase: $e")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*执行服务请求*/
|
||||||
|
private fun executeServiceRequest(runnable: Runnable) {
|
||||||
|
if (isServiceConnected) {
|
||||||
|
runnable.run()
|
||||||
|
} else {
|
||||||
|
startServiceConnection(runnable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*查询内购商品详情*/
|
||||||
|
override fun querySkuDetailsAsync(
|
||||||
|
productIdList: List<String>,
|
||||||
|
listener: IBillingService.ProductDetailsResponseListener
|
||||||
|
) {
|
||||||
|
val queryRequest = Runnable {
|
||||||
|
val products = ArrayList<QueryProductDetailsParams.Product>()
|
||||||
|
for (productId in productIdList) {
|
||||||
|
products.add(
|
||||||
|
QueryProductDetailsParams.Product.newBuilder()
|
||||||
|
.setProductId(productId)
|
||||||
|
.setProductType(BillingClient.ProductType.INAPP)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val queryProductDetailsParams = QueryProductDetailsParams.newBuilder()
|
||||||
|
.setProductList(products)
|
||||||
|
.build()
|
||||||
|
billingClient?.queryProductDetailsAsync(
|
||||||
|
queryProductDetailsParams,
|
||||||
|
ProductDetailsResponseListenerAdapter(listener)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
executeServiceRequest(queryRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*启动购买,订购流程*/
|
||||||
|
override fun initiatePurchaseFlow(productDetails: IProductDetails, recordId: String) {
|
||||||
|
val details = productDetails.getData() as? ProductDetails ?: return
|
||||||
|
val purchaseFlowRequest = Runnable {
|
||||||
|
val p = ProductDetailsParams.newBuilder()
|
||||||
|
.setProductDetails(details)
|
||||||
|
.build()
|
||||||
|
val jsonObject = JSONObject()
|
||||||
|
val oneTimePurchaseOfferDetails = details.oneTimePurchaseOfferDetails
|
||||||
|
if (oneTimePurchaseOfferDetails != null) {
|
||||||
|
jsonObject["p"] = oneTimePurchaseOfferDetails.formattedPrice
|
||||||
|
jsonObject["a"] = oneTimePurchaseOfferDetails.priceAmountMicros / 10000
|
||||||
|
jsonObject["c"] = oneTimePurchaseOfferDetails.priceCurrencyCode
|
||||||
|
}
|
||||||
|
val purchaseParams = BillingFlowParams.newBuilder()
|
||||||
|
.setObfuscatedAccountId(recordId)
|
||||||
|
.setObfuscatedProfileId(jsonObject.toJSONString())
|
||||||
|
.setProductDetailsParamsList(java.util.List.of(p))
|
||||||
|
.build()
|
||||||
|
val billingResult = billingClient?.launchBillingFlow(activity, purchaseParams)
|
||||||
|
Log.i(
|
||||||
|
TAG,
|
||||||
|
" initiatePurchaseFlow billingResult=" + billingResult?.responseCode + " " + billingResult?.debugMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
executeServiceRequest(purchaseFlowRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun consumeAsync(purchaseToken: String) {
|
||||||
|
val consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken).build()
|
||||||
|
executeServiceRequest {
|
||||||
|
billingClient?.consumeAsync(consumeParams) { billingResult: BillingResult, s: String? ->
|
||||||
|
listener.onConsumeFinished(
|
||||||
|
purchaseToken,
|
||||||
|
billingResult.responseCode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁结算客户端并断开连接
|
||||||
|
*/
|
||||||
|
override fun destroy() {
|
||||||
|
Log.d(TAG, "Destroying the manager.")
|
||||||
|
if (billingClient != null && billingClient?.isReady == true) {
|
||||||
|
billingClient?.endConnection()
|
||||||
|
billingClient = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,23 @@
|
|||||||
|
package com.example.module_google.billing
|
||||||
|
|
||||||
|
import com.android.billingclient.api.ProductDetails.OneTimePurchaseOfferDetails
|
||||||
|
import com.example.module_base.support.billing.IOneTimePurchaseOfferDetails
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Max on 2023/11/22 21:05
|
||||||
|
* Desc:
|
||||||
|
**/
|
||||||
|
class OneTimePurchaseOfferDetailsImpl(val data: OneTimePurchaseOfferDetails) :
|
||||||
|
IOneTimePurchaseOfferDetails {
|
||||||
|
override fun getPriceAmountMicros(): Long {
|
||||||
|
return data.priceAmountMicros
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFormattedPrice(): String {
|
||||||
|
return data.formattedPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPriceCurrencyCode(): String {
|
||||||
|
return data.priceCurrencyCode
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,24 @@
|
|||||||
|
package com.example.module_google.billing
|
||||||
|
|
||||||
|
import com.android.billingclient.api.ProductDetails
|
||||||
|
import com.example.module_base.support.billing.IOneTimePurchaseOfferDetails
|
||||||
|
import com.example.module_base.support.billing.IProductDetails
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Max on 2023/11/22 20:12
|
||||||
|
* Desc:
|
||||||
|
**/
|
||||||
|
data class ProductDetailsImpl(val data: ProductDetails) : IProductDetails {
|
||||||
|
override fun getData(): Any {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getProductId(): String {
|
||||||
|
return data.productId
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOneTimePurchaseOfferDetails(): IOneTimePurchaseOfferDetails? {
|
||||||
|
val info = data.oneTimePurchaseOfferDetails ?: return null
|
||||||
|
return OneTimePurchaseOfferDetailsImpl(info)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,19 @@
|
|||||||
|
package com.example.module_google.billing
|
||||||
|
|
||||||
|
import com.android.billingclient.api.BillingResult
|
||||||
|
import com.android.billingclient.api.ProductDetails
|
||||||
|
import com.android.billingclient.api.ProductDetailsResponseListener
|
||||||
|
import com.example.module_base.support.billing.IBillingService
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Max on 2023/11/22 20:23
|
||||||
|
* Desc:
|
||||||
|
**/
|
||||||
|
class ProductDetailsResponseListenerAdapter(private val listener: IBillingService.ProductDetailsResponseListener) :
|
||||||
|
ProductDetailsResponseListener {
|
||||||
|
override fun onProductDetailsResponse(p0: BillingResult, p1: MutableList<ProductDetails>) {
|
||||||
|
listener.onProductDetailsResponse(BillingResultImpl(p0), p1.map {
|
||||||
|
ProductDetailsImpl(it)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,44 @@
|
|||||||
|
package com.example.module_google.billing
|
||||||
|
|
||||||
|
import com.android.billingclient.api.Purchase
|
||||||
|
import com.example.module_base.support.billing.IAccountIdentifiers
|
||||||
|
import com.example.module_base.support.billing.IPurchase
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Max on 2023/11/22 20:06
|
||||||
|
* Desc:
|
||||||
|
**/
|
||||||
|
data class PurchaseImpl(val data: Purchase) : IPurchase {
|
||||||
|
override fun getData(): Any {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPurchaseState(): Int {
|
||||||
|
return data.purchaseState
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isPurchasedState(): Boolean {
|
||||||
|
return getPurchaseState() == Purchase.PurchaseState.PURCHASED
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAccountIdentifiers(): IAccountIdentifiers? {
|
||||||
|
val info = data.accountIdentifiers ?: return null
|
||||||
|
return AccountIdentifiersImpl(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getProducts(): List<String> {
|
||||||
|
return data.products
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPackageName(): String {
|
||||||
|
return data.packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPurchaseToken(): String {
|
||||||
|
return data.purchaseToken
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOrderId(): String? {
|
||||||
|
return data.orderId
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,68 @@
|
|||||||
|
package com.example.module_google.billing;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
|
||||||
|
public class Security {
|
||||||
|
private static final String TAG = "GoogleIap/Security";
|
||||||
|
private static final String KEY_FACTORY_ALGORITHM = "RSA";
|
||||||
|
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
|
||||||
|
public static boolean verifyPurchase(String base64PublicKey, String signedData,
|
||||||
|
String signature) throws IOException {
|
||||||
|
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
|
||||||
|
|| TextUtils.isEmpty(signature)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PublicKey key = generatePublicKey(base64PublicKey);
|
||||||
|
return verify(key, signedData, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublicKey generatePublicKey(String encodedPublicKey) throws IOException {
|
||||||
|
try {
|
||||||
|
byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
|
||||||
|
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
// "RSA" is guaranteed to be available.
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
String msg = "Invalid key specification: " + e;
|
||||||
|
throw new IOException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
|
||||||
|
byte[] signatureBytes;
|
||||||
|
try {
|
||||||
|
signatureBytes = Base64.decode(signature, Base64.DEFAULT);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Signature signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM);
|
||||||
|
signatureAlgorithm.initVerify(publicKey);
|
||||||
|
signatureAlgorithm.update(signedData.getBytes());
|
||||||
|
if (!signatureAlgorithm.verify(signatureBytes)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
// "RSA" is guaranteed to be available.
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
} catch (SignatureException e) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,68 @@
|
|||||||
|
package com.example.module_google.login
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import com.example.module_base.support.login.ILoginService
|
||||||
|
import com.example.module_base.support.login.LoginSDKException
|
||||||
|
import com.example.module_base.support.login.PlatformInfo
|
||||||
|
import com.example.module_google.BuildConfig
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignInClient
|
||||||
|
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||||
|
import com.google.android.gms.common.api.ApiException
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Max on 2023/11/22 15:59
|
||||||
|
* Desc:Google登录
|
||||||
|
**/
|
||||||
|
class GoogleLoginService : ILoginService {
|
||||||
|
private val requestCode = Random.nextInt(10000, 20000)
|
||||||
|
|
||||||
|
private var listener: ILoginService.Listener? = null
|
||||||
|
|
||||||
|
private var googleSignInOptions: GoogleSignInOptions =
|
||||||
|
GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
||||||
|
.requestEmail()
|
||||||
|
.requestProfile()
|
||||||
|
.requestIdToken(BuildConfig.GOOGLE_SERVER_CLIENT_ID)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private var googleSignInClient: GoogleSignInClient? = null
|
||||||
|
|
||||||
|
override fun login(activity: Activity, listener: ILoginService.Listener) {
|
||||||
|
this.listener = listener
|
||||||
|
if (this.googleSignInClient == null) {
|
||||||
|
this.googleSignInClient =
|
||||||
|
GoogleSignIn.getClient(activity.applicationContext, googleSignInOptions)
|
||||||
|
}
|
||||||
|
activity.startActivityForResult(
|
||||||
|
googleSignInClient!!.signInIntent,
|
||||||
|
requestCode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun logout() {
|
||||||
|
googleSignInClient?.signOut()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
if (requestCode == this.requestCode) {
|
||||||
|
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
|
||||||
|
try {
|
||||||
|
val account = task.getResult(ApiException::class.java)
|
||||||
|
val info = PlatformInfo(
|
||||||
|
id = account!!.id!!,
|
||||||
|
name = account.displayName,
|
||||||
|
gender = null,
|
||||||
|
avatar = account.photoUrl?.toString()
|
||||||
|
)
|
||||||
|
listener?.onSuccess(info)
|
||||||
|
} catch (e: ApiException) {
|
||||||
|
listener?.onFailure(LoginSDKException(e.statusCode, e))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
listener?.onFailure(LoginSDKException(-100))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user