feat: Flexion 先验签后消费支付流程 + playerId 绑定
实现 Flexion 渠道支付验签重构:支付成功后不立即消费,将 purchaseJson/signature/purchaseToken 回调至 Unity,由服务端 SHA1withRSA 验签并校验 developerPayload.playerId 后再调用 consume。 同步更新 SDKManager 接口、UnityBridgeFunc 桥接、TYSdkModel 字段及 ExtraPluginCfgs。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -54,12 +54,13 @@
|
||||
追加 ${applicationId}(Flexion 包名)作为后缀后,与 GP 版 authority 不同,
|
||||
两个渠道包可同时安装在同一设备上,不会触发 INSTALL_FAILED_CONFLICTING_PROVIDER。
|
||||
参考:Flexion 官方文档《Unique package name - follow-on requirements》
|
||||
-->
|
||||
|
||||
<provider
|
||||
android:name="com.facebook.FacebookContentProvider"
|
||||
android:authorities="com.facebook.app.FacebookContentProvider162066986963808.${applicationId}"
|
||||
android:exported="true"
|
||||
tools:replace="android:authorities" />
|
||||
-->
|
||||
</application>
|
||||
<queries>
|
||||
<provider android:authorities="com.facebook.katana.provider.PlatformProvider" />
|
||||
|
||||
@@ -5,7 +5,7 @@ public class ChannelHelpers {
|
||||
public static final String CHANNEL = "Flexion";
|
||||
|
||||
static void register() {
|
||||
SDKManager.registerHelper(CHANNEL, new FlexionSDKHelper());
|
||||
SDKManager.registerHelper(new FlexionSDKHelper());
|
||||
}
|
||||
|
||||
|
||||
@@ -14,4 +14,7 @@ public class ChannelHelpers {
|
||||
public static String normalizeSkuList(String msg) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
|
||||
//public SDKExtBuilder;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
public class FlexionSDKHelper implements SDKManager.ISDKHelper {
|
||||
@@ -33,12 +34,18 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
|
||||
private static boolean billingConnected = false;
|
||||
|
||||
private static final PurchasesUpdateListener purchasesListener = (billingResult, purchase) -> {
|
||||
|
||||
|
||||
Log.i(TAG, "[PAY-FLOW] PurchasesUpdateListener: code=" + billingResult.getResponseCode()
|
||||
+ " hasPurchase=" + (purchase != null));
|
||||
if (billingResult.getResponseCode() == BillingResults.ResultCode.PURCHASE_SUCCESS_CODE) {
|
||||
if (purchase != null) handlePurchase(purchase);
|
||||
} else if (billingResult.getResponseCode() == BillingResults.ResultCode.PURCHASE_USER_CANCELLED_CODE) {
|
||||
sendPayCallback(1, "user_cancelled", null, null);
|
||||
Log.i(TAG, "[PAY-FLOW] user cancelled");
|
||||
sendPayCallback(1, "user_cancelled", null, null, null);
|
||||
} else {
|
||||
sendPayCallback(1, "purchase_failed_" + billingResult.getResponseCode(), null, null);
|
||||
Log.e(TAG, "[PAY-FLOW] purchase failed: code=" + billingResult.getResponseCode());
|
||||
sendPayCallback(1, "purchase_failed_" + billingResult.getResponseCode(), null, null, null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -83,19 +90,36 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
|
||||
@Override
|
||||
public void pay(Activity activity, String productId, String productName,
|
||||
String productCount, String prodorderId, String appInfo) {
|
||||
Log.i(TAG, "pay: " + productId);
|
||||
Log.i(TAG, "[PAY-FLOW] pay called: productId=" + productId + " billingReady=" + billingConnected);
|
||||
if (billingService == null || !billingConnected) {
|
||||
sendPayCallback(1, "billing_not_ready", null, null);
|
||||
Log.e(TAG, "[PAY-FLOW] billing not ready");
|
||||
sendPayCallback(1, "billing_not_ready", null, null, null);
|
||||
return;
|
||||
}
|
||||
activity.runOnUiThread(() ->
|
||||
billingService.launchBillingFlow(activity,
|
||||
new BillingFlowParams(productId, "inapp", "")));
|
||||
String developerPayload = "";
|
||||
try {
|
||||
byte[] decoded = Base64.getDecoder().decode(appInfo);
|
||||
JSONObject json = new JSONObject(new String(decoded, "UTF-8"));
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("playerId", json.optString("playerId", ""));
|
||||
payload.put("customData", json.optString("customData", ""));
|
||||
developerPayload = payload.toString();
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Failed to extract playerId/customData from appInfo: " + e.getMessage());
|
||||
}
|
||||
String finalPayload = developerPayload;
|
||||
activity.runOnUiThread(() -> {
|
||||
Log.i(TAG, "[PAY-FLOW] launching billing flow for: " + productId + " payload=" + finalPayload);
|
||||
billingService.launchBillingFlow(activity,
|
||||
new BillingFlowParams(productId, "inapp", finalPayload));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryProducts() {
|
||||
Log.i(TAG, "queryProducts called, billingConnected=" + billingConnected);
|
||||
if (billingService == null || !billingConnected) {
|
||||
Log.w(TAG, "queryProducts: billing not ready");
|
||||
sendExtensionError("billing_not_ready");
|
||||
return;
|
||||
}
|
||||
@@ -108,6 +132,7 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
|
||||
SDKAPI.getIns().thirdExtend(jsonObject.toString(), new SDKCallBack.Extend() {
|
||||
@Override
|
||||
public void callback(int code, String msg) {
|
||||
Log.i(TAG, "TuYoo thirdExtend callback: code=" + code + " msg=" + msg);
|
||||
if (code == SDKCallBack.Extend.EXTEND_CODE_SUCCESS && msg != null) {
|
||||
try {
|
||||
JSONArray products = new JSONArray(msg);
|
||||
@@ -117,6 +142,7 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
|
||||
String pid = p.optString("productId", "");
|
||||
if (!pid.isEmpty()) productIds.add(pid);
|
||||
}
|
||||
Log.i(TAG, "queryProductDetails with " + productIds.size() + " items: " + productIds);
|
||||
queryProductDetails(productIds);
|
||||
} catch (JSONException e) {
|
||||
sendExtensionError("parse_error: " + e.getMessage());
|
||||
@@ -131,10 +157,12 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
|
||||
// ---- Billing Infrastructure ----
|
||||
|
||||
private static void initBillingService(Activity activity) {
|
||||
Log.i(TAG, "[PAY-FLOW] initBillingService");
|
||||
billingService = FLX.createBillingService(activity, purchasesListener);
|
||||
billingService.startConnection(activity, new ConnectionStateListener() {
|
||||
@Override
|
||||
public void onBillingSetupFinished(BillingResult result) {
|
||||
Log.i(TAG, "[PAY-FLOW] onBillingSetupFinished: code=" + result.getResponseCode());
|
||||
if (result.getResponseCode() == BillingResults.ResultCode.CONNECTION_SUCCESS_CODE) {
|
||||
billingConnected = true;
|
||||
queryPendingPurchases();
|
||||
@@ -142,20 +170,22 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
|
||||
}
|
||||
@Override
|
||||
public void onBillingServiceDisconnected() {
|
||||
Log.w(TAG, "[PAY-FLOW] billing service disconnected");
|
||||
billingConnected = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void handlePurchase(Purchase purchase) {
|
||||
Log.i(TAG, "[PAY-FLOW] handlePurchase: state=" + purchase.getPurchaseState()
|
||||
+ " token=" + purchase.getToken());
|
||||
if (purchase.getPurchaseState() == 0) {
|
||||
sendPayCallback(0, "success", purchase.getPurchaseJson(), purchase.getSignature());
|
||||
billingService.consumeAsync(
|
||||
new ConsumeParams(purchase.getToken()),
|
||||
r -> Log.i(TAG, "Consume " +
|
||||
(r.getResponseCode() == BillingResults.ResultCode.CONSUME_SUCCESS_CODE ? "ok" : "fail")));
|
||||
Log.i(TAG, "[PAY-FLOW] purchaseJson=" + purchase.getPurchaseJson());
|
||||
Log.i(TAG, "[PAY-FLOW] signature=" + purchase.getSignature());
|
||||
sendPayCallback(0, "success", purchase.getPurchaseJson(), purchase.getSignature(), purchase.getToken());
|
||||
} else if (purchase.getPurchaseState() == 2) {
|
||||
sendPayCallback(2, "pending", null, null);
|
||||
Log.i(TAG, "[PAY-FLOW] purchase state=2 (pending)");
|
||||
sendPayCallback(2, "pending", null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,13 +237,19 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
|
||||
|
||||
// ---- Callbacks to Unity ----
|
||||
|
||||
private static void sendPayCallback(int code, String errStr, String purchaseJson, String signature) {
|
||||
private static void sendPayCallback(int code, String errStr,
|
||||
String purchaseJson, String signature, String purchaseToken) {
|
||||
Log.i(TAG, "[PAY-FLOW] sendPayCallback: code=" + code + " errStr=" + errStr
|
||||
+ " hasPurchaseJson=" + (purchaseJson != null) + " hasSignature=" + (signature != null)
|
||||
+ " hasToken=" + (purchaseToken != null));
|
||||
try {
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("code", code);
|
||||
result.put("errStr", errStr != null ? errStr : "");
|
||||
if (purchaseJson != null) result.put("purchaseJson", purchaseJson);
|
||||
if (signature != null) result.put("signature", signature);
|
||||
if (purchaseToken != null) result.put("purchaseToken", purchaseToken);
|
||||
Log.i(TAG, "[PAY-FLOW] UnitySendMessage callback: " + result.toString());
|
||||
UnityPlayer.UnitySendMessage(ConfigManager.UNITY_FACADE_NAME,
|
||||
ConfigManager.PAY_CALLBACK_FUNCTION_NAME, result.toString());
|
||||
} catch (JSONException e) {
|
||||
@@ -222,6 +258,18 @@ public class FlexionSDKHelper implements SDKManager.ISDKHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static void consumePurchase(String token) {
|
||||
if (billingService == null || token == null || token.isEmpty()) {
|
||||
Log.w(TAG, "[PAY-FLOW] consumePurchase: billingService or token is null");
|
||||
return;
|
||||
}
|
||||
Log.i(TAG, "[PAY-FLOW] consumePurchase: token=" + token);
|
||||
billingService.consumeAsync(
|
||||
new ConsumeParams(token),
|
||||
r -> Log.i(TAG, "[PAY-FLOW] consume result: " +
|
||||
(r.getResponseCode() == BillingResults.ResultCode.CONSUME_SUCCESS_CODE ? "ok" : "fail")));
|
||||
}
|
||||
|
||||
private static void sendExtensionError(String errStr) {
|
||||
try {
|
||||
JSONObject result = new JSONObject();
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user