增加google play支付

This commit is contained in:
CounterFire2023 2023-07-09 18:52:12 +08:00
parent 26097c441c
commit 55d58c86e5
8 changed files with 342 additions and 29 deletions

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

File diff suppressed because one or more lines are too long

View File

@ -491,3 +491,20 @@ function nftMallBuy(funId, currency, addresses, ids, amounts, values, signature,
}
// end of NFT mall
// begin of google pay
function queryGoogleProducts(funId, productIds) {
let ids = JSON.parse(productIds);
console.log('queryGoogleProducts:: ' + productIds);
promiseCb(funId, jc.wallet.paySvr.queryGoogleProducts(ids));
}
function queryGooglePurchases(funId) {
promiseCb(funId, jc.wallet.paySvr.queryGooglePurchases());
}
function beginGoogleBuy(funId, productId, orderId) {
promiseCb(funId, jc.wallet.paySvr.buyGoogleProduct(productId, orderId));
}
// end of google pay

View File

@ -17,6 +17,9 @@
<meta-data
android:name="android.app.lib_name"
android:value="cocos2djs" />
<meta-data
android:name="firebase_crashlytics_collection_enabled"
android:value="true" />
<meta-data
android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id" />

View File

@ -29,8 +29,8 @@ android {
applicationId "com.cege.games.release"
minSdkVersion PROP_MIN_SDK_VERSION
targetSdkVersion PROP_TARGET_SDK_VERSION
versionCode 12
versionName "1.0.10"
versionCode 46
versionName "1.0.46"
ndk{
abiFilters 'armeabi-v7a','arm64-v8a'
}
@ -124,11 +124,11 @@ android.applicationVariants.all { variant ->
variant.mergeAssetsProvider.get().doLast {
def sourceDir = rootProject.ext.cfgs.jsFilePath
// copy{
// from "${sourceDir}"
// include "Data/js/**"
// into outputDir
// }
copy{
from "${sourceDir}"
include "Data/js/**"
into outputDir
}
copy {
from "${sourceDir}/cert/cacert.pem"
into outputDir
@ -169,4 +169,6 @@ dependencies {
implementation 'com.google.firebase:firebase-analytics'
implementation 'com.google.firebase:firebase-crashlytics-ndk'
// end of firebase
// google pay
implementation "com.android.billingclient:billing:6.0.1"
}

View File

@ -55,6 +55,7 @@ import com.google.firebase.analytics.FirebaseAnalytics;
import com.jc.jcfw.JcSDK;
import com.jc.jcfw.appauth.AuthStateManager;
import com.jc.jcfw.appauth.JConfiguration;
import com.jc.jcfw.google.PayClient;
import com.jc.jcfw.util.FileUtils;
import com.jc.jcfw.util.JsonUtils;
import com.king.zxing.CameraScan;
@ -188,6 +189,9 @@ public class MainActivity extends UnityPlayerActivity
// Uri appLinkData = appLinkIntent.getData();
tiktokOpenApi = TikTokOpenApiFactory.create(this);
PayClient payClient = PayClient.getInstance();
payClient.init(this);
}
@Override

View File

@ -1,20 +1,30 @@
package com.jc.jcfw;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
import com.cege.games.release.MainActivity;
import com.jc.jcfw.google.PayClient;
import org.cocos2dx.lib.CocosJSHelper;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class JcSDK {
private static final String TAG = JcSDK.class.getSimpleName();
private static UnityCallback commonCB;
@SuppressLint("StaticFieldLeak")
private static PayClient payClient;
private static native int runJS(final String funId, final String methodName, final String params);
public static void initCommonCB(UnityCallback callBack) {
@ -24,7 +34,7 @@ public class JcSDK {
/**
* @Deprecated
* 不使用该方法, 直接由unity调用cpp方法
* 不使用该方法, 直接由unity调用cpp方法
* @param password
*/
public static void initWallet(String password) {
@ -35,12 +45,9 @@ public class JcSDK {
/**
* 回调至c#
*
* @param funId
* @param msg
*/
public static void csCallback(String funId, String msg) {
if (funId != "" && funId.indexOf("js_") == 0) {
if (!Objects.equals(funId, "") && funId.indexOf("js_") == 0) {
commonCB.nativeCallback(funId, msg, 1);
} else {
commonCB.nativeCallback(funId, msg, 0);
@ -51,8 +58,8 @@ public class JcSDK {
* check if metamask installed and jump to metamask
*
* @param url
* sample:
* "https://metamask.app.link/wc?uri="+ExampleApplication.config.toWCUri();
* sample:
* "https://metamask.app.link/wc?uri="+ExampleApplication.config.toWCUri();
*/
public static void toWallet(String url) {
@ -116,7 +123,40 @@ public class JcSDK {
MainActivity.app.logEvent(content);
}
public static void nativeCb(String funId, String error, String idToken) {
public static void queryProducts(String funid, String skuListStr) {
Log.i(TAG, "queryProducts with: " + skuListStr);
if (payClient == null) {
payClient = PayClient.getInstance();
}
List<String> skuList = new ArrayList<>();
if (skuListStr.contains(",")) {
String[] skuArr = skuListStr.split(",");
skuList.addAll(Arrays.asList(skuArr));
} else {
skuList.add(skuListStr);
}
payClient.queryProductList(funid, skuList);
}
public static void buyProduct(String funid, String productId, String orderId) {
Log.i(TAG, "buyProduct with: " + productId);
if (payClient == null) {
payClient = PayClient.getInstance();
}
payClient.buyOne(funid, productId, orderId);
}
public static void queryPurchase(String funid) {
Log.i(TAG, "queryPurchase");
if (payClient == null) {
payClient = PayClient.getInstance();
}
payClient.queryPurchase(funid);
}
/**
* 回调至js
*/
public static void nativeCb(String funId, String error, String dataStr) {
JSONObject result = new JSONObject();
try {
if (error != null && !error.isEmpty()) {
@ -124,7 +164,7 @@ public class JcSDK {
result.put("errmessage", error);
} else {
result.put("errcode", 0);
result.put("data", idToken);
result.put("data", dataStr);
}
} catch (JSONException e) {
Log.e(TAG, "JSONException: " + e.getMessage());
@ -132,7 +172,6 @@ public class JcSDK {
if (funId == null || funId.isEmpty()) {
funId = MainActivity.app.getFunId();
}
JcSDK.runJS(funId, "jniCallback", result.toString());
}
}

View File

@ -0,0 +1,243 @@
package com.jc.jcfw.google;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
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.cege.games.release.MainActivity;
import com.jc.jcfw.JcSDK;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class PayClient extends Activity implements PurchasesUpdatedListener {
private static final String TAG = "GooglePayClient";
private static volatile PayClient mInstance = null;
private static BillingClient billingClient;
private static ConcurrentHashMap<String, ProductDetails> skuDetailsMap;
private Context mContext = null;
private String mFunId;
public static PayClient getInstance() {
if (null == mInstance) {
synchronized (PayClient.class) {
if (null == mInstance) {
mInstance = new PayClient();
}
}
}
return mInstance;
}
public void init(Context context) {
this.mContext = context;
skuDetailsMap = new ConcurrentHashMap<>();
billingClient = BillingClient.newBuilder(context).enablePendingPurchases().setListener(this).build();
connectToPlay();
}
private void connectToPlay() {
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
Log.i(TAG, "onBillingSetupFinished, response code: " + billingResult.getResponseCode());
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
Log.i(TAG, "BillingClient is ready");
}
}
@Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
connectToPlay();
}
});
}
JSONArray handlePurchase(JSONArray dataArr, Purchase purchase) throws JSONException {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED
// || purchase.getPurchaseState() == Purchase.PurchaseState.PENDING
) {
// Acknowledge purchase and grant the item to the user
// Grant entitlement to the user.
Log.i(TAG, "handlePurchase:" + purchase.getOriginalJson());
Log.i(TAG, "purchase sign:" + purchase.getSignature());
if (!purchase.isAcknowledged() && purchase.getProducts().size() > 0) {
// consumables 消耗型
JSONObject data = new JSONObject();
data.put("id", purchase.getProducts().get(0));
data.put("token", purchase.getPurchaseToken());
dataArr.put(data);
}
}
return dataArr;
}
@Override
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
Log.i(TAG, "onPurchasesUpdated with status: " + billingResult.getResponseCode());
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& purchases != null) {
final JSONArray dataArr = new JSONArray();
boolean hasErr = false;
for (Purchase purchase : purchases) {
try {
handlePurchase(dataArr, purchase);
} catch (JSONException e) {
hasErr = true;
break;
}
}
if (hasErr) {
purchaseUpdateCb("error parse purchase data", null);
} else {
purchaseUpdateCb(null, dataArr.toString());
}
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
purchaseUpdateCb("user cancel buy", null);
} else {
String errmsg = billingResult.getDebugMessage();
if (errmsg.isEmpty()) {
errmsg = "other error";
}
purchaseUpdateCb(errmsg, null);
}
}
private void purchaseUpdateCb(String error, String dataStr) {
if (mFunId != null && !mFunId.isEmpty()) {
JcSDK.nativeCb(mFunId, error, dataStr);
mFunId = null;
}
}
private boolean parseProductDetails(JSONArray dataArr, ProductDetails skuDetails) {
JSONObject data = new JSONObject();
try {
data.put("name", skuDetails.getTitle());
data.put("description", skuDetails.getDescription());
data.put("id", skuDetails.getProductId());
data.put("type", skuDetails.getProductType());
if (skuDetails.getProductType().equals(BillingClient.ProductType.INAPP)) {
data.put("currencyCode",
skuDetails.getOneTimePurchaseOfferDetails().getPriceCurrencyCode());
data.put("priceValue", skuDetails.getOneTimePurchaseOfferDetails().getPriceAmountMicros());
data.put("priceShow", skuDetails.getOneTimePurchaseOfferDetails().getFormattedPrice());
}
dataArr.put(data);
return true;
} catch (JSONException e) {
e.printStackTrace();
return false;
}
}
public void queryProductList(String funId, List<String> productIds) {
List<QueryProductDetailsParams.Product> productList = new ArrayList<>();
for (String productId : productIds) {
productList.add(
QueryProductDetailsParams.Product.newBuilder()
.setProductId(productId)
.setProductType(BillingClient.ProductType.INAPP)
.build());
}
QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder()
.setProductList(productList)
.build();
billingClient.queryProductDetailsAsync(
params,
(billingResult, productDetailsList) -> {
// Process the result
Map<String, ProductDetails> pMap = new HashMap<>();
for (ProductDetails details : productDetailsList) {
skuDetailsMap.put(details.getProductId(), details);
pMap.put(details.getProductId(), details);
}
final JSONArray dataArr = new JSONArray();
boolean hasErr = false;
for (Map.Entry<String, ProductDetails> entry : pMap.entrySet()) {
ProductDetails skuDetails = entry.getValue();
if (!parseProductDetails(dataArr, skuDetails)) {
hasErr = true;
break;
}
}
if (hasErr) {
JcSDK.nativeCb(funId, "parse product detail json error", null);
} else {
JcSDK.nativeCb(funId, null, dataArr.toString());
}
});
}
public void queryPurchase(String funId) {
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build(),
(billingResult, purchases) -> {
// Process the result
final JSONArray dataArr = new JSONArray();
boolean hasErr = false;
for (Purchase purchase : purchases) {
try {
handlePurchase(dataArr, purchase);
} catch (JSONException e) {
hasErr = true;
break;
}
}
if (hasErr) {
JcSDK.nativeCb(funId, "error parse purchase data", null);
} else {
JcSDK.nativeCb(funId, null, dataArr.toString());
}
});
}
public void buyOne(String funId, String productId, String orderId) {
if (mFunId != null && !mFunId.isEmpty()) {
JcSDK.nativeCb(funId, "another purchase is in progress", null);
return;
}
if (!skuDetailsMap.containsKey(productId)) {
JcSDK.nativeCb(funId, "product with : "+productId+ " not found", null);
return;
}
ProductDetails productDetails = skuDetailsMap.get(productId);
// Set the parameters for the offer that will be presented
// in the billing flow creating separate productDetailsParamsList variable
List<BillingFlowParams.ProductDetailsParams> productDetailsParamsList = Collections
.singletonList(BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.build());
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.setObfuscatedAccountId(orderId)
.setObfuscatedProfileId(orderId)
.build();
// Launch the billing flow
this.mFunId = funId;
MainActivity.app.runOnUiThread(() -> {
billingClient.launchBillingFlow((Activity) mContext, billingFlowParams);
});
}
}