Commit 56a3b326 by Minhan Jeong

화웨이 로그인 시스템 추가중

parent b9a8ea41
......@@ -145,8 +145,10 @@ KeyAlias=rmvw2021
KeyStorePassword=3df1q2w3e
KeyPassword=3df1q2w3e
ApplicationDisplayName=Real Madrid Virtual World
bBuildForArmV7=False
bBuildForArm64=True
bBuildForArmV7=True
bBuildForArm64=False
MinSDKVersion=26
TargetSDKVersion=30
[/Script/GooglePADEditor.GooglePADRuntimeSettings]
bOnlyDistribution=False
......
......@@ -33,6 +33,11 @@
"Linux",
"Mac"
]
},
{
"Name": "AndroidPlayBilling",
"Enabled": true,
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/ad0040f7dbbc45cebdcbc4b2c5982904"
}
]
}
\ No newline at end of file
{
"FileVersion": 3,
"Version": 1,
"VersionName": "1.0",
"FriendlyName": "Android Play Billing",
"Description": "Android Play Billing plugin for Unreal Engine provides Google Play Billing methods for buying consumable, non-consumable products and subscriptions.",
"Category": "Android",
"CreatedBy": "Nikita Klimov",
"CreatedByURL": "https://github.com/kulichin",
"DocsURL": "https://github.com/kulichin/UnrealAndroidPlayBilling/wiki",
"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/ad0040f7dbbc45cebdcbc4b2c5982904",
"SupportURL": "https://github.com/kulichin/UnrealAndroidPlayBilling/issues",
"EngineVersion": "4.25.0",
"CanContainContent": false,
"IsBetaVersion": true,
"Installed": true,
"Modules": [
{
"Name": "AndroidPlayBilling",
"Type": "Runtime",
"LoadingPhase": "Default",
"WhitelistPlatforms": [
"Win64",
"Mac",
"Android"
]
}
]
}
\ No newline at end of file
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Allman
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 132
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: true
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
FixNamespaceComments: true
ForEachMacros:
- for
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '.*\.generated\.h'
Priority: 100
- Regex: '.*(PCH).*'
Priority: -1
- Regex: '".*"'
Priority: 1
- Regex: '^<.*\.(h)>'
Priority: 3
- Regex: '^<.*>'
Priority: 4
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ''
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 4
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 4
UseTab: Always
...
// Copyright (C) 2021. Nikita Klimov. All rights reserved.
package com.kulichin.androidplaybilling;
import android.app.Activity;
import androidx.annotation.NonNull;
import com.android.billingclient.api.AccountIdentifiers;
import com.android.billingclient.api.AcknowledgePurchaseParams;
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.ConsumeParams;
import com.android.billingclient.api.PriceChangeFlowParams;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchaseHistoryRecord;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
public class AndroidPlayBilling
{
public static native void NativeCompleteOperation(int ResponseCode, String ResponseMessage);
public static native void NativeCompleteLaunchBillingFlow(
int ResponseCode,
String ResponseMessage,
PurchaseRecord[] Array,
int ArraySize);
public static native void NativeCompleteQueryPurchaseHistory(
int ResponseCode,
String ResponseMessage,
PurchaseRecordFromHistory[] Array,
int ArraySize);
public static native void NativeCompleteQuerySkuDetails(
int ResponseCode,
String ResponseMessage,
SkuDetailsRecord[] Array,
int ArraySize);
public static native void NativeCompleteQueryPurchases(
int ResponseCode,
String ResponseMessage,
PurchaseRecord[] Array,
int ArraySize);
public static native void NativeInitialize(
Class SkuDetailsRecordClass,
Class PurchaseRecordFromHistoryClass,
Class PurchaseRecordClass
);
public static class SkuDetailsRecord
{
public String OriginalJSON;
public String ProductID;
public String Type;
public String Price;
public long PriceAmountMicros;
public String PriceCurrencyCode;
public String OriginalPrice;
public long OriginalPriceAmountMicros;
public String Title;
public String Description;
public String SubscriptionPeriod;
public String FreeTrialPeriod;
public String IntroductoryPrice;
public long IntroductoryPriceAmountMicros;
public String IntroductoryPricePeriod;
public int IntroductoryPriceCycles;
public String IconURL;
SkuDetailsRecord(
String OriginalJSON,
String ProductID,
String Type,
String Price,
long PriceAmountMicros,
String PriceCurrencyCode,
String OriginalPrice,
long OriginalPriceAmountMicros,
String Title,
String Description,
String SubscriptionPeriod,
String FreeTrialPeriod,
String IntroductoryPrice,
long IntroductoryPriceAmountMicros,
String IntroductoryPricePeriod,
int IntroductoryPriceCycles,
String IconURL
)
{
this.OriginalJSON = OriginalJSON;
this.ProductID = ProductID;
this.Type = Type;
this.Price = Price;
this.PriceAmountMicros = PriceAmountMicros;
this.PriceCurrencyCode = PriceCurrencyCode;
this.OriginalPrice = OriginalPrice;
this.OriginalPriceAmountMicros = OriginalPriceAmountMicros;
this.Title = Title;
this.Description = Description;
this.SubscriptionPeriod = SubscriptionPeriod;
this.FreeTrialPeriod = FreeTrialPeriod;
this.IntroductoryPrice = IntroductoryPrice;
this.IntroductoryPriceAmountMicros = IntroductoryPriceAmountMicros;
this.IntroductoryPricePeriod = IntroductoryPricePeriod;
this.IntroductoryPriceCycles = IntroductoryPriceCycles;
this.IconURL = IconURL;
}
}
public static class PurchaseRecordFromHistory
{
public String ProductID;
public long PurchaseTime;
public String PurchaseToken;
public String DeveloperPayload;
public String OriginalJSON;
public String Signature;
PurchaseRecordFromHistory(
String ProductID,
long PurchaseTime,
String PurchaseToken,
String DeveloperPayload,
String OriginalJSON,
String Signature
)
{
this.ProductID = ProductID;
this.PurchaseTime = PurchaseTime;
this.PurchaseToken = PurchaseToken;
this.DeveloperPayload = DeveloperPayload;
this.OriginalJSON = OriginalJSON;
this.Signature = Signature;
}
}
public static class PurchaseRecord
{
public String OrderID;
public String PackageName;
public String ProductID;
public long PurchaseTime;
public String PurchaseToken;
public int PurchaseState;
public String DeveloperPayload;
public boolean bAcknowledged;
public boolean bAutoRenewing;
public String OriginalJSON;
public String Signature;
public String ObfuscatedAccountID;
public String ObfuscatedProfileID;
PurchaseRecord(
String OrderID,
String PackageName,
String ProductID,
long PurchaseTime,
String PurchaseToken,
int PurchaseState,
String DeveloperPayload,
boolean bAcknowledged,
boolean bAutoRenewing,
String OriginalJSON,
String Signature,
String ObfuscatedAccountID,
String ObfuscatedProfileID)
{
this.OrderID = OrderID;
this.PackageName = PackageName;
this.ProductID = ProductID;
this.PurchaseTime = PurchaseTime;
this.PurchaseToken = PurchaseToken;
this.PurchaseState = PurchaseState;
this.DeveloperPayload = DeveloperPayload;
this.bAcknowledged = bAcknowledged;
this.bAutoRenewing = bAutoRenewing;
this.OriginalJSON = OriginalJSON;
this.Signature = Signature;
this.ObfuscatedAccountID = ObfuscatedAccountID;
this.ObfuscatedProfileID = ObfuscatedProfileID;
}
}
private final int PRODUCT_INAPP_ID = 0;
private final int PRODUCT_SUBS_ID = 1;
private final String[] ProductTypes =
{
"inapp",
"subs"
};
private final String[] FeatureTypes =
{
"inAppItemsOnVr",
"priceChangeConfirmation",
"subscriptions",
"subscriptionsOnVr",
"subscriptionsUpdate"
};
private final Activity GameActivity;
private final HashMap<String, PurchaseRecord> PurchaseRecords = new HashMap<>();
private final BillingClient AppBillingClient;
public AndroidPlayBilling(Activity GameActivity)
{
this.GameActivity = GameActivity;
final PurchasesUpdatedListener PurchasesListener = (Result, Purchases) ->
{
PurchaseRecord[] WrappedPurchaseRecords = null;
int WrappedPurchaseRecordsSize = 0;
if (Purchases != null)
{
WrappedPurchaseRecords = new PurchaseRecord[Purchases.size()];
WrappedPurchaseRecordsSize = Purchases.size();
for (int Idx = 0; Idx < Purchases.size(); Idx++)
{
// Wrap to our class
Purchase PurchaseRecord = Purchases.get(Idx);
PurchaseRecord WrappedPurchaseRecord = WrapToPurchaseRecord(PurchaseRecord);
// Put record to hash map
String ProductID = WrappedPurchaseRecord.ProductID;
PurchaseRecords.put(ProductID, WrappedPurchaseRecord);
// Append to 'WrappedPurchaseRecords'
WrappedPurchaseRecords[Idx] = WrappedPurchaseRecord;
// Handle purchase
HandlePurchaseInternal(PurchaseRecord);
}
}
// Complete operation
NativeCompleteLaunchBillingFlow(
Result.getResponseCode(),
Result.getDebugMessage(),
WrappedPurchaseRecords,
WrappedPurchaseRecordsSize);
};
// Initialize billing client
AppBillingClient = BillingClient.newBuilder(GameActivity)
.setListener(PurchasesListener)
.enablePendingPurchases()
.build();
AppBillingClient.startConnection(new BillingClientStateListener()
{
@Override
public void onBillingSetupFinished(@NonNull BillingResult Result)
{
QueryBillingDetailsInternal();
}
// TODO: try to restart google billing
@Override
public void onBillingServiceDisconnected()
{
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
});
// Call native initializator
NativeInitialize(
SkuDetailsRecord.class,
PurchaseRecordFromHistory.class,
PurchaseRecord.class);
}
public void OnDestroy()
{
AppBillingClient.endConnection();
}
public void LaunchBillingFlow(String ProductID, int ProductType)
{
// Query Sku details & start purchase
QuerySkuDetailsInternal(new String[] { ProductID }, ProductType, (QueryResult, SkuDetailsList) ->
{
SkuDetails Details = ConstructSkuDetailsInternal(ProductID, ProductType);
if (SkuDetailsList != null && SkuDetailsList.size() > 0)
{
Details = SkuDetailsList.get(0);
}
// Build BillingFlowParams
BillingFlowParams FlowParams = BillingFlowParams.newBuilder()
.setSkuDetails(Details)
.build();
BillingResult FlowResult = AppBillingClient.launchBillingFlow(GameActivity, FlowParams);
if (FlowResult.getResponseCode() != BillingClient.BillingResponseCode.OK)
{
// Complete operation
NativeCompleteLaunchBillingFlow(
FlowResult.getResponseCode(),
FlowResult.getDebugMessage(),
null,
0);
}
// else: wait for 'PurchasesListener' callback
});
}
public void LaunchPriceChangeConfirmationFlow(String ProductID, int ProductType)
{
// Query Sku details & start purchase
QuerySkuDetailsInternal(new String[] { ProductID }, ProductType, (QueryResult, SkuDetailsList) ->
{
SkuDetails Details = ConstructSkuDetailsInternal(ProductID, ProductType);
if (SkuDetailsList != null && SkuDetailsList.size() > 0)
{
Details = SkuDetailsList.get(0);
}
// Build PriceChangeParams
PriceChangeFlowParams PriceChangeParams = PriceChangeFlowParams.newBuilder()
.setSkuDetails(Details)
.build();
AppBillingClient.launchPriceChangeConfirmationFlow(
GameActivity,
PriceChangeParams,
FlowResult ->
// Complete operation
NativeCompleteOperation(
FlowResult.getResponseCode(),
FlowResult.getDebugMessage()));
});
}
public void QueryPurchases(int ProductType)
{
Purchase.PurchasesResult Result = QueryPurchasesInternal(ProductType);
List<Purchase> Purchases = Result.getPurchasesList();
if (Result.getResponseCode() == BillingClient.BillingResponseCode.OK &&
Purchases != null)
{
// Construct array for wrapped records
int PurchasesSize = Purchases.size();
PurchaseRecord[] Records = new PurchaseRecord[PurchasesSize];
for (int Idx = 0; Idx < PurchasesSize; Idx++)
{
Records[Idx] = WrapToPurchaseRecord(Purchases.get(Idx));
}
// Complete operation
NativeCompleteQueryPurchases(
Result.getResponseCode(),
Result.getBillingResult().getDebugMessage(),
Records,
Records.length);
}
else
{
// Complete operation
NativeCompleteQueryPurchases(
Result.getResponseCode(),
Result.getBillingResult().getDebugMessage(),
null,
0);
}
}
public void QuerySkuDetails(String[] ProductIDs, int ProductType)
{
// Query Sku details & start purchase
QuerySkuDetailsInternal(ProductIDs, ProductType, (Result, SkuDetailsList) ->
{
if (Result.getResponseCode() == BillingClient.BillingResponseCode.OK &&
SkuDetailsList != null)
{
final int SkuDetailsSize = SkuDetailsList.size();
SkuDetailsRecord[] DetailsRecords = new SkuDetailsRecord[SkuDetailsSize];
for (int Idx = 0; Idx < SkuDetailsSize; Idx++)
{
SkuDetails Details = SkuDetailsList.get(Idx);
DetailsRecords[Idx] = WrapToSkuDetailsRecord(Details);
}
// Complete operation
NativeCompleteQuerySkuDetails(
Result.getResponseCode(),
Result.getDebugMessage(),
DetailsRecords,
DetailsRecords.length);
}
else
{
// Complete operation
NativeCompleteQuerySkuDetails(
Result.getResponseCode(),
Result.getDebugMessage(),
null,
0);
}
});
}
public void QueryPurchaseHistory(int ProductType)
{
AppBillingClient.queryPurchaseHistoryAsync(ProductTypes[ProductType], (Result, Purchases) ->
{
if (Result.getResponseCode() == BillingClient.BillingResponseCode.OK &&
Purchases != null)
{
// Construct array for wrapped records
int PurchasesSize = Purchases.size();
PurchaseRecordFromHistory[] Records = new PurchaseRecordFromHistory[PurchasesSize];
for (int Idx = 0; Idx < PurchasesSize; Idx++)
{
Records[Idx] = WrapToPurchaseRecordFromHistory(Purchases.get(Idx));
}
// Complete operation
NativeCompleteQueryPurchaseHistory(
Result.getResponseCode(),
Result.getDebugMessage(),
Records,
Records.length);
}
else
{
// Complete operation
NativeCompleteQueryPurchaseHistory(
Result.getResponseCode(),
Result.getDebugMessage(),
null,
0);
}
});
}
public int IsFeatureSupported(int FeatureType)
{
BillingResult Result = AppBillingClient.isFeatureSupported(FeatureTypes[FeatureType]);
return Result.getResponseCode();
}
public boolean IsIAPInitialized()
{
if (AppBillingClient != null)
{
return AppBillingClient.isReady();
}
return false;
}
public boolean IsPurchased(String ProductID)
{
return PurchaseRecords.containsKey(ProductID);
}
// Internal
private Purchase.PurchasesResult QueryPurchasesInternal(int ProductType)
{
return AppBillingClient.queryPurchases(ProductTypes[ProductType]);
}
private void QueryBillingDetailsInternal()
{
// Query purchases on resume
Purchase.PurchasesResult SubscriptionsResult = QueryPurchasesInternal(PRODUCT_SUBS_ID);
Purchase.PurchasesResult ProductsResult = QueryPurchasesInternal(PRODUCT_INAPP_ID);
// Handle purchases
for (Purchase Subscription : SubscriptionsResult.getPurchasesList())
{
HandlePurchaseInternal(Subscription);
}
for (Purchase Product : ProductsResult.getPurchasesList())
{
HandlePurchaseInternal(Product);
}
}
private SkuDetails ConstructSkuDetailsInternal(String ProductID, int ProductType)
{
try
{
String SkuDetailsJSON = new JSONObject()
.put("productId", ProductID)
.put("type", ProductTypes[ProductType])
.toString();
return new SkuDetails(SkuDetailsJSON);
}
catch (JSONException ignored)
{
return null;
}
}
// Internal
private void QuerySkuDetailsInternal(
String[] ProductIDs,
int ProductType,
SkuDetailsResponseListener SkuDetailsListener)
{
// Build Sku details
List<String> SkuList = Arrays.asList(ProductIDs);
SkuDetailsParams SkuDetails = SkuDetailsParams.newBuilder()
.setType(ProductTypes[ProductType])
.setSkusList(SkuList)
.build();
// Query Sku details & start purchase
AppBillingClient.querySkuDetailsAsync(SkuDetails, SkuDetailsListener);
}
public void HandlePurchaseInternal(Purchase PurchaseRecord)
{
if (PurchaseRecord != null && PurchaseRecord.getDeveloperPayload().isEmpty())
{
// Try to consume item
ConsumeParams Params = ConsumeParams.newBuilder()
.setPurchaseToken(PurchaseRecord.getPurchaseToken())
.build();
AppBillingClient.consumeAsync(Params, (ConsumeResult, PurchaseToken) ->
{
// If item not consumed then acknowledge them
if (ConsumeResult.getResponseCode() != BillingClient.BillingResponseCode.OK)
{
AcknowledgePurchaseParams AcknowledgeParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(PurchaseRecord.getPurchaseToken())
.build();
AppBillingClient.acknowledgePurchase(AcknowledgeParams, AcknowledgeResult -> {});
}
});
}
}
// Wrappers
private PurchaseRecord WrapToPurchaseRecord(Purchase Record)
{
// Check validity of 'getAccountIdentifiers'
String ObfuscatedAccountID = "";
String ObfuscatedProfileID = "";
AccountIdentifiers Identifiers = Record.getAccountIdentifiers();
if (Identifiers != null)
{
ObfuscatedAccountID = Identifiers.getObfuscatedAccountId();
ObfuscatedProfileID = Identifiers.getObfuscatedProfileId();
}
return new PurchaseRecord
(
Record.getOrderId(),
Record.getPackageName(),
Record.getSkus().get(0),
Record.getPurchaseTime(),
Record.getPurchaseToken(),
Record.getPurchaseState(),
Record.getDeveloperPayload(),
Record.isAcknowledged(),
Record.isAutoRenewing(),
Record.getOriginalJson(),
Record.getSignature(),
ObfuscatedAccountID,
ObfuscatedProfileID
);
}
private PurchaseRecordFromHistory WrapToPurchaseRecordFromHistory(PurchaseHistoryRecord Record)
{
return new PurchaseRecordFromHistory
(
Record.getSkus().get(0),
Record.getPurchaseTime(),
Record.getPurchaseToken(),
Record.getDeveloperPayload(),
Record.getOriginalJson(),
Record.getSignature()
);
}
private SkuDetailsRecord WrapToSkuDetailsRecord(SkuDetails Record)
{
return new SkuDetailsRecord(
Record.getOriginalJson(),
Record.getSku(),
Record.getType(),
Record.getPrice(),
Record.getPriceAmountMicros(),
Record.getPriceCurrencyCode(),
Record.getOriginalPrice(),
Record.getOriginalPriceAmountMicros(),
Record.getTitle(),
Record.getDescription(),
Record.getSubscriptionPeriod(),
Record.getFreeTrialPeriod(),
Record.getIntroductoryPrice(),
Record.getIntroductoryPriceAmountMicros(),
Record.getIntroductoryPricePeriod(),
Record.getIntroductoryPriceCycles(),
Record.getIconUrl()
);
}
}
// Copyright (C) 2021. Nikita Klimov. All rights reserved.
using UnrealBuildTool;
public class AndroidPlayBilling : ModuleRules
{
public AndroidPlayBilling(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine" });
string PluginPath = Utils.MakePathRelativeTo(ModuleDirectory, Target.RelativeEnginePath);
if (Target.Platform == UnrealTargetPlatform.Android)
{
PrivateDependencyModuleNames.Add("Launch");
AdditionalPropertiesForReceipt.Add("AndroidPlugin", System.IO.Path.Combine(PluginPath, "AndroidPlayBilling_UPL_Android.xml"));
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Add dependencies -->
<androidManifestUpdates>
<addPermission android:name="android.permission.INTERNET" />
<addPermission android:name="com.android.vending.BILLING" />
</androidManifestUpdates>
<!-- Copy Library directory & google-services.json -->
<gradleCopies>
<copyDir
src="$S(PluginDir)/Android/"
dst="$S(BuildDir)/gradle/app/src/main/java/com/kulichin/androidplaybilling/" />
</gradleCopies>
<!-- Import dependencies -->
<AARImports>
<insertValue value="com.android.billingclient,billing,4.0.0" />
<insertNewline />
</AARImports>
<!--Enable AndroidX support-->
<gradleProperties>
<insert>
android.useAndroidX = true
android.enableJetifier = true
</insert>
</gradleProperties>
<!-- Replace supportlib imports with AndroidX in engine source files -->
<baseBuildGradleAdditions>
<insert>
allprojects
{
def mappings =
[
'android.arch.lifecycle.Lifecycle': 'androidx.lifecycle.Lifecycle',
'android.arch.lifecycle.LifecycleObserver': 'androidx.lifecycle.LifecycleObserver',
'android.arch.lifecycle.OnLifecycleEvent': 'androidx.lifecycle.OnLifecycleEvent',
'android.arch.lifecycle.ProcessLifecycleOwner': 'androidx.lifecycle.ProcessLifecycleOwner',
'android.arch.lifecycle': 'androidx.lifecycle',
'android.support.annotation': 'androidx.annotation',
'android.support.v13.app.FragmentCompat': 'androidx.legacy.app.FragmentCompat',
'android.support.v4.app.ActivityCompat': 'androidx.core.app.ActivityCompat',
'android.support.v4.app.NotificationCompat': 'androidx.core.app.NotificationCompat',
'android.support.v4.app.NotificationManagerCompat': 'androidx.core.app.NotificationManagerCompat',
'android.support.v4.content.ContextCompat': 'androidx.core.content.ContextCompat',
'android.support.v4.content.FileProvider': 'androidx.core.content.FileProvider',
'constProduct.getSku()': 'constProduct.getSkus().get(0)',
'ownedProduct.getSku()': 'ownedProduct.getSkus().get(0)',
'purchase.getSku()': 'purchase.getSkus().get(0)',
]
beforeEvaluate { project ->
project.rootProject.projectDir.traverse(type: groovy.io.FileType.FILES, nameFilter: ~/.*\.java$/) { f ->
mappings.each { entry ->
if (f.getText('UTF-8').contains(entry.key)) {
println "Updating ${entry.key} to ${entry.value} in file ${f}"
ant.replace(file: f, token: entry.key, value: entry.value)
}
}
}
}
}
</insert>
</baseBuildGradleAdditions>
<!-- Gradle additions -->
<buildGradleAdditions>
<insert>
android
{
compileOptions
{
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
}
</insert>
</buildGradleAdditions>
<!-- ProGuard additions -->
<proguardAdditions>
<insert>
-dontwarn com.kulichin.**
-keep class com.kulichin.** { *; }
-keep interface com.kulichin.** { *; }
-dontwarn com.android.billingclient.**
-keep class com.android.billingclient.** { *; }
-keep interface com.android.billingclient.** { *; }
</insert>
</proguardAdditions>
<!-- GameActivity.java additions -->
<gameActivityImportAdditions>
<insert>
import com.kulichin.androidplaybilling.AndroidPlayBilling;
</insert>
</gameActivityImportAdditions>
<!-- GameActivity.java additions -->
<gameActivityOnCreateAdditions>
<insert>
// Initialize billing class
<!-- PlayBilling = new AndroidPlayBilling(this);-->
</insert>
</gameActivityOnCreateAdditions>
<!-- GameActivity.java additions -->
<gameActivityOnDestroyAdditions>
<insert>
PlayBilling.OnDestroy();
</insert>
</gameActivityOnDestroyAdditions>
<!-- GameActivity.java additions -->
<gameActivityClassAdditions>
<insert>
private AndroidPlayBilling PlayBilling;
public void AndroidThunkJava_LaunchBillingFlow(String ProductID, int ProductType)
{
PlayBilling.LaunchBillingFlow(ProductID, ProductType);
}
public void AndroidThunkJava_LaunchPriceChangeConfirmationFlow(String ProductID, int ProductType)
{
PlayBilling.LaunchPriceChangeConfirmationFlow(ProductID, ProductType);
}
public void AndroidThunkJava_QueryPurchases(int ProductType)
{
PlayBilling.QueryPurchases(ProductType);
}
public void AndroidThunkJava_QuerySkuDetails(String[] ProductIDs, int ProductType)
{
PlayBilling.QuerySkuDetails(ProductIDs, ProductType);
}
public void AndroidThunkJava_QueryPurchaseHistory(int ProductType)
{
PlayBilling.QueryPurchaseHistory(ProductType);
}
public int AndroidThunkJava_IsFeatureSupported(int FeatureType)
{
return PlayBilling.IsFeatureSupported(FeatureType);
}
public boolean AndroidThunkJava_IsIAPInitialized()
{
return PlayBilling.IsIAPInitialized();
}
public boolean AndroidThunkJava_IsPurchased(String ProductID)
{
return PlayBilling.IsPurchased(ProductID);
}
</insert>
</gameActivityClassAdditions>
</root>
// Copyright (C) 2021. Nikita Klimov. All rights reserved.
#include "AndroidPlayBilling.h"
#include "AndroidPlayBillingSettings.h"
#include "Settings/Public/ISettingsModule.h"
#define LOCTEXT_NAMESPACE "FAndroidPlayBillingModule"
void FAndroidPlayBillingModule::StartupModule()
{
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
{
SettingsModule->RegisterSettings(
TEXT("Project"),
TEXT("Plugins"),
TEXT("Android Play Billing"),
LOCTEXT("Android Play Billing", "Android Play Billing"),
LOCTEXT("Android Play Billing", "Settings for Android Play Billing"),
GetMutableDefault<UAndroidPlayBillingSettings>());
}
}
void FAndroidPlayBillingModule::ShutdownModule()
{
if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings"))
{
SettingsModule->UnregisterSettings(
TEXT("Project"),
TEXT("Plugins"),
TEXT("AndroidPlayBilling"));
}
}
FAndroidPlayBillingModule* FAndroidPlayBillingModule::GetModule()
{
return FModuleManager::Get().GetModulePtr<FAndroidPlayBillingModule>("AndroidPlayBilling");
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FAndroidPlayBillingModule, AndroidPlayBilling)
// Copyright (C) 2021. Nikita Klimov. All rights reserved.
#include "AndroidPlayBillingSettings.h"
#if WITH_EDITOR
void UAndroidPlayBillingSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
SaveConfig(CPF_Config, *GetDefaultConfigFilename());
}
#endif
// Copyright (C) 2021. Nikita Klimov. All rights reserved.
#include "AndroidPlayBillingSubsystem.h"
#if PLATFORM_ANDROID
#include "AndroidPlayBilling.h"
#include "Android/AndroidJNI.h"
#include "Android/AndroidApplication.h"
// SkuDetails class fields
static jfieldID SkuDetailsRecord_OriginalJSON_FieldID;
static jfieldID SkuDetailsRecord_ProductID_FieldID;
static jfieldID SkuDetailsRecord_Type_FieldID;
static jfieldID SkuDetailsRecord_Price_FieldID;
static jfieldID SkuDetailsRecord_PriceAmountMicros_FieldID;
static jfieldID SkuDetailsRecord_PriceCurrencyCode_FieldID;
static jfieldID SkuDetailsRecord_OriginalPrice_FieldID;
static jfieldID SkuDetailsRecord_OriginalPriceAmountMicros_FieldID;
static jfieldID SkuDetailsRecord_Title_FieldID;
static jfieldID SkuDetailsRecord_Description_FieldID;
static jfieldID SkuDetailsRecord_SubscriptionPeriod_FieldID;
static jfieldID SkuDetailsRecord_FreeTrialPeriod_FieldID;
static jfieldID SkuDetailsRecord_IntroductoryPrice_FieldID;
static jfieldID SkuDetailsRecord_IntroductoryPriceAmountMicros_FieldID;
static jfieldID SkuDetailsRecord_IntroductoryPricePeriod_FieldID;
static jfieldID SkuDetailsRecord_IntroductoryPriceCycles_FieldID;
static jfieldID SkuDetailsRecord_IconURL_FieldID;
// PurchaseRecordFromHistory class fields
static jfieldID PurchaseRecordFromHistory_ProductID_FieldID;
static jfieldID PurchaseRecordFromHistory_PurchaseTime_FieldID;
static jfieldID PurchaseRecordFromHistory_PurchaseToken_FieldID;
static jfieldID PurchaseRecordFromHistory_DeveloperPayload_FieldID;
static jfieldID PurchaseRecordFromHistory_OriginalJSON_FieldID;
static jfieldID PurchaseRecordFromHistory_Signature_FieldID;
// PurchaseRecord class fields
static jfieldID PurchaseRecord_OrderID_FieldID;
static jfieldID PurchaseRecord_PackageName_FieldID;
static jfieldID PurchaseRecord_ProductID_FieldID;
static jfieldID PurchaseRecord_PurchaseTime_FieldID;
static jfieldID PurchaseRecord_PurchaseToken_FieldID;
static jfieldID PurchaseRecord_PurchaseState_FieldID;
static jfieldID PurchaseRecord_DeveloperPayload_FieldID;
static jfieldID PurchaseRecord_bAcknowledged_FieldID;
static jfieldID PurchaseRecord_bAutoRenewing_FieldID;
static jfieldID PurchaseRecord_OriginalJSON_FieldID;
static jfieldID PurchaseRecord_Signature_FieldID;
static jfieldID PurchaseRecord_ObfuscatedAccountID_FieldID;
static jfieldID PurchaseRecord_ObfuscatedProfileID_FieldID;
// Google Play Billing methods
static jmethodID LaunchBillingFlow_MethodID;
static jmethodID LaunchPriceChangeConfirmationFlow_MethodID;
static jmethodID QueryPurchases_MethodID;
static jmethodID QuerySkuDetails_MethodID;
static jmethodID QueryPurchaseHistory_MethodID;
static jmethodID IsFeatureSupported_MethodID;
static jmethodID IsIAPInitialized_MethodID;
static jmethodID IsPurchased_MethodID;
static inline FScopedJavaObject<jstring> GetScopedStringFromObject(
JNIEnv* Env,
jobject Object,
jfieldID FieldID)
{
if (FieldID == NULL)
{
return FScopedJavaObject<jstring>(Env, Env->NewStringUTF(""));
}
return NewScopedJavaObject(Env, (jstring) Env->GetObjectField(Object, FieldID));
}
static jmethodID FindMethod(
JNIEnv* Env,
const char* Name,
const char* Signature)
{
if (Env && Name && Signature)
{
return Env->GetMethodID(FJavaWrapper::GameActivityClassID, Name, Signature);
}
return nullptr;
}
static jfieldID FindField(
JNIEnv* Env,
jclass Class,
const char* Name,
const char* Type)
{
if (Env && Name && Type && Class)
{
return Env->GetFieldID(Class, Name, Type);
}
return nullptr;
}
static bool CallBooleanMethod(JNIEnv* Env, jmethodID Method, ...)
{
// make sure the function exists
jobject Object = FJavaWrapper::GameActivityThis;
if (Method == NULL || Object == NULL || Env == NULL)
{
return false;
}
va_list Args;
va_start(Args, Method);
return Env->CallBooleanMethodV(Object, Method, Args);
va_end(Args);
}
static bool CallIntMethod(JNIEnv* Env, jmethodID Method, ...)
{
// make sure the function exists
jobject Object = FJavaWrapper::GameActivityThis;
if (Method == NULL || Object == NULL || Env == NULL)
{
return false;
}
va_list Args;
va_start(Args, Method);
return Env->CallIntMethodV(Object, Method, Args);
va_end(Args);
}
static void CallVoidMethod(JNIEnv* Env, jmethodID Method, ...)
{
// make sure the function exists
jobject Object = FJavaWrapper::GameActivityThis;
if (Method == NULL || Object == NULL || Env == NULL)
{
return;
}
va_list Args;
va_start(Args, Method);
Env->CallVoidMethodV(Object, Method, Args);
va_end(Args);
}
static jobject CallObjectMethod(JNIEnv* Env, jmethodID Method)
{
// make sure the function exists
jobject Object = FJavaWrapper::GameActivityThis;
if (Method == NULL || Object == NULL || Env == NULL)
{
return NULL;
}
return Env->CallObjectMethod(Object, Method);
}
FPurchaseRecord GetPurchaseRecordStructFromJavaObject(JNIEnv* Env, jobject Object)
{
if (Object == NULL || Env == NULL)
{
return {};
}
// PurchaseRecord class fields
auto JOrderID = GetScopedStringFromObject(Env, Object, PurchaseRecord_OrderID_FieldID);
auto JPackageName = GetScopedStringFromObject(Env, Object, PurchaseRecord_PackageName_FieldID);
auto JProductID = GetScopedStringFromObject(Env, Object, PurchaseRecord_ProductID_FieldID);
int64 PurchaseTime = (int64)Env->GetLongField(Object, PurchaseRecord_PurchaseTime_FieldID);
auto JPurchaseToken = GetScopedStringFromObject(Env, Object, PurchaseRecord_PurchaseToken_FieldID);
EPurchaseState PurchaseState = (EPurchaseState)Env->GetIntField(Object, PurchaseRecord_PurchaseState_FieldID);
auto JDeveloperPayload = GetScopedStringFromObject(Env, Object, PurchaseRecord_DeveloperPayload_FieldID);
bool bAcknowledged = (bool)Env->GetBooleanField(Object, PurchaseRecord_bAcknowledged_FieldID);
bool bAutoRenewing = (bool)Env->GetBooleanField(Object, PurchaseRecord_bAutoRenewing_FieldID);
auto JOriginalJSON = GetScopedStringFromObject(Env, Object, PurchaseRecord_OriginalJSON_FieldID);
auto JSignature = GetScopedStringFromObject(Env, Object, PurchaseRecord_Signature_FieldID);
auto JObfuscatedAccountID = GetScopedStringFromObject(Env, Object, PurchaseRecord_ObfuscatedAccountID_FieldID);
auto JObfuscatedProfileID = GetScopedStringFromObject(Env, Object, PurchaseRecord_ObfuscatedProfileID_FieldID);
// Construct FPurchaseRecord struct
return
{
FJavaHelper::FStringFromParam(Env, *JOrderID),
FJavaHelper::FStringFromParam(Env, *JPackageName),
FJavaHelper::FStringFromParam(Env, *JProductID),
PurchaseTime,
FJavaHelper::FStringFromParam(Env, *JPurchaseToken),
PurchaseState,
FJavaHelper::FStringFromParam(Env, *JDeveloperPayload),
bAcknowledged,
bAutoRenewing,
FJavaHelper::FStringFromParam(Env, *JOriginalJSON),
FJavaHelper::FStringFromParam(Env, *JSignature),
FJavaHelper::FStringFromParam(Env, *JObfuscatedAccountID),
FJavaHelper::FStringFromParam(Env, *JObfuscatedProfileID)
};
}
FPurchaseRecordFromHistory GetPurchaseRecordFromHistoryStructFromJavaObject(JNIEnv* Env, jobject Object)
{
if (Object == NULL || Env == NULL)
{
return {};
}
// PurchaseRecordFromHistory class fields
auto JProductID = GetScopedStringFromObject(Env, Object, PurchaseRecordFromHistory_ProductID_FieldID);
int64 PurchaseTime = (int64)Env->GetLongField(Object, PurchaseRecordFromHistory_PurchaseTime_FieldID);
auto JPurchaseToken = GetScopedStringFromObject(Env, Object, PurchaseRecordFromHistory_PurchaseToken_FieldID);
auto JDeveloperPayload = GetScopedStringFromObject(Env, Object, PurchaseRecordFromHistory_DeveloperPayload_FieldID);
auto JOriginalJSON = GetScopedStringFromObject(Env, Object, PurchaseRecordFromHistory_OriginalJSON_FieldID);
auto JSignature = GetScopedStringFromObject(Env, Object, PurchaseRecordFromHistory_Signature_FieldID);
// Construct FPurchaseRecordFromHistory struct
return
{
FJavaHelper::FStringFromParam(Env, *JProductID),
PurchaseTime,
FJavaHelper::FStringFromParam(Env, *JPurchaseToken),
FJavaHelper::FStringFromParam(Env, *JDeveloperPayload),
FJavaHelper::FStringFromParam(Env, *JOriginalJSON),
FJavaHelper::FStringFromParam(Env, *JSignature),
};
}
FSkuDetailsRecord GetSkuDetailsRecordStructFromJavaObject(JNIEnv* Env, jobject Object)
{
if (Object == NULL || Env == NULL)
{
return {};
}
// SkuDetailsRecord class fields
auto JOriginalJSON = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_OriginalJSON_FieldID);
auto JProductID = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_ProductID_FieldID);
auto JType = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_Type_FieldID);
auto JPrice = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_Price_FieldID);
int64 PriceAmountMicros = (int64)Env->GetLongField(Object, SkuDetailsRecord_PriceAmountMicros_FieldID);
auto JPriceCurrencyCode = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_PriceCurrencyCode_FieldID);
auto JOriginalPrice = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_OriginalPrice_FieldID);
int64 OriginalPriceAmountMicros = (int64)Env->GetLongField(Object, SkuDetailsRecord_OriginalPriceAmountMicros_FieldID);
auto JTitle = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_Title_FieldID);
auto JDescription = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_Description_FieldID);
auto JSubscriptionPeriod = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_SubscriptionPeriod_FieldID);
auto JFreeTrialPeriod = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_FreeTrialPeriod_FieldID);
auto JIntroductoryPrice = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_IntroductoryPrice_FieldID);
int64 IntroductoryPriceAmountMicros = (int64)Env->GetLongField(Object, SkuDetailsRecord_IntroductoryPriceAmountMicros_FieldID);
auto JIntroductoryPricePeriod = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_IntroductoryPricePeriod_FieldID);
int IntroductoryPriceCycles = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_IntroductoryPriceCycles_FieldID);
auto JIconURL = GetScopedStringFromObject(Env, Object, SkuDetailsRecord_IconURL_FieldID);
// Construct FSkuDetailsRecord struct
return
{
FJavaHelper::FStringFromParam(Env, *JOriginalJSON),
FJavaHelper::FStringFromParam(Env, *JProductID),
FJavaHelper::FStringFromParam(Env, *JType),
FJavaHelper::FStringFromParam(Env, *JPrice),
PriceAmountMicros,
FJavaHelper::FStringFromParam(Env, *JPriceCurrencyCode),
FJavaHelper::FStringFromParam(Env, *JOriginalPrice),
OriginalPriceAmountMicros,
FJavaHelper::FStringFromParam(Env, *JTitle),
FJavaHelper::FStringFromParam(Env, *JDescription),
FJavaHelper::FStringFromParam(Env, *JSubscriptionPeriod),
FJavaHelper::FStringFromParam(Env, *JFreeTrialPeriod),
FJavaHelper::FStringFromParam(Env, *JIntroductoryPrice),
IntroductoryPriceAmountMicros,
FJavaHelper::FStringFromParam(Env, *JIntroductoryPricePeriod),
IntroductoryPriceCycles,
FJavaHelper::FStringFromParam(Env, *JIconURL),
};
}
#endif
void UAndroidPlayBillingSubsystem::LaunchBillingFlow(
FOnLaunchBillingFlowCompleted OnOperationResult,
const FString& ProductID,
const ESkuType ProductType)
{
#if PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
if (FAndroidPlayBillingModule* Module = FAndroidPlayBillingModule::GetModule())
{
// Bind callback
Module->AddOnLaunchBillingFlowCompleted_Handle(OnOperationResult);
auto JProductID = FJavaHelper::ToJavaString(Env, ProductID);
// Call Java method
CallVoidMethod(
Env,
LaunchBillingFlow_MethodID,
*JProductID,
(int)ProductType);
}
}
#endif
}
void UAndroidPlayBillingSubsystem::LaunchPriceChangeConfirmationFlow(
FOnOperationCompleted OnOperationResult,
const FString& ProductID,
const ESkuType ProductType)
{
#if PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
if (FAndroidPlayBillingModule* Module = FAndroidPlayBillingModule::GetModule())
{
// Bind callback
Module->AddOnOperationCompleted_Handle(OnOperationResult);
auto JProductID = FJavaHelper::ToJavaString(Env, ProductID);
// Call Java method
CallVoidMethod(
Env,
LaunchPriceChangeConfirmationFlow_MethodID,
*JProductID,
(int)ProductType);
}
}
#endif
}
void UAndroidPlayBillingSubsystem::QueryPurchases(
FOnQueryPurchasesCompleted OnOperationResult,
const ESkuType ProductType)
{
#if PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
if (FAndroidPlayBillingModule* Module = FAndroidPlayBillingModule::GetModule())
{
// Bind callback
Module->AddOnQueryPurchasesCompleted_Handle(OnOperationResult);
// Call Java method
CallVoidMethod(Env, QueryPurchases_MethodID, (int)ProductType);
}
}
#endif
}
void UAndroidPlayBillingSubsystem::QuerySkuDetails(
FOnQuerySkuDetailsCompleted OnOperationResult,
const TArray<FString>& ProductIDs,
const ESkuType ProductType)
{
#if PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
if (FAndroidPlayBillingModule* Module = FAndroidPlayBillingModule::GetModule())
{
// Bind callback
Module->AddOnQuerySkuDetailsCompleted_Handle(OnOperationResult);
// Populate some java types with the provided product information
auto JProductIDs = NewScopedJavaObject(
Env,
(jobjectArray)Env->NewObjectArray(ProductIDs.Num(),
FJavaWrapper::JavaStringClass,
NULL));
for (int Idx = 0; Idx < ProductIDs.Num(); Idx++)
{
auto JProductID = FJavaHelper::ToJavaString(Env, ProductIDs[Idx]);
Env->SetObjectArrayElement(*JProductIDs, Idx, *JProductID);
}
// Call Java method
CallVoidMethod(
Env,
QuerySkuDetails_MethodID,
*JProductIDs,
(int)ProductType);
}
}
#endif
}
void UAndroidPlayBillingSubsystem::QueryPurchaseHistory(
FOnQueryPurchaseHistoryCompleted OnOperationResult,
const ESkuType ProductType)
{
#if PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
if (FAndroidPlayBillingModule* Module = FAndroidPlayBillingModule::GetModule())
{
// Bind callback
Module->AddOnQueryPurchaseHistoryCompleted_Handle(OnOperationResult);
// Call Java method
CallVoidMethod(Env, QueryPurchaseHistory_MethodID, (int)ProductType);
}
}
#endif
}
EBillingResponseCode UAndroidPlayBillingSubsystem::IsFeatureSupported(const EFeatureType FeatureType)
{
#if PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
// Call Java method
return (EBillingResponseCode) CallIntMethod(Env, IsFeatureSupported_MethodID, (int) FeatureType);
}
#endif
return EBillingResponseCode::SERVICE_UNAVAILABLE;
}
bool UAndroidPlayBillingSubsystem::IsIAPInitialized()
{
#if PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
// Call Java method
return CallBooleanMethod(Env, IsIAPInitialized_MethodID);
}
#endif
return false;
}
bool UAndroidPlayBillingSubsystem::IsPurchased(const FString& ProductID)
{
#if PLATFORM_ANDROID
if (JNIEnv* Env = FAndroidApplication::GetJavaEnv())
{
auto JProductID = FJavaHelper::ToJavaString(Env, ProductID);
// Call Java method
return CallBooleanMethod(Env, IsIAPInitialized_MethodID, *JProductID);
}
#endif
return false;
}
#if PLATFORM_ANDROID
JNI_METHOD void Java_com_kulichin_androidplaybilling_AndroidPlayBilling_NativeInitialize(
JNIEnv* Env,
jobject Thiz,
jclass SkuDetailsRecordClass,
jclass PurchaseRecordFromHistoryClass,
jclass PurchaseRecordClass)
{
// SkuDetailsRecord class fields
SkuDetailsRecord_OriginalJSON_FieldID = FindField(Env, SkuDetailsRecordClass, "OriginalJSON", "Ljava/lang/String;");
SkuDetailsRecord_ProductID_FieldID = FindField(Env, SkuDetailsRecordClass, "ProductID", "Ljava/lang/String;");
SkuDetailsRecord_Type_FieldID = FindField(Env, SkuDetailsRecordClass, "Type", "Ljava/lang/String;");
SkuDetailsRecord_Price_FieldID = FindField(Env, SkuDetailsRecordClass, "Price", "Ljava/lang/String;");
SkuDetailsRecord_PriceAmountMicros_FieldID = FindField(Env, SkuDetailsRecordClass, "PriceAmountMicros", "J");
SkuDetailsRecord_PriceCurrencyCode_FieldID = FindField(Env, SkuDetailsRecordClass, "PriceCurrencyCode", "Ljava/lang/String;");
SkuDetailsRecord_OriginalPrice_FieldID = FindField(Env, SkuDetailsRecordClass, "OriginalPrice", "Ljava/lang/String;");
SkuDetailsRecord_OriginalPriceAmountMicros_FieldID = FindField(Env, SkuDetailsRecordClass, "OriginalPriceAmountMicros", "J");
SkuDetailsRecord_Title_FieldID = FindField(Env, SkuDetailsRecordClass, "Title", "Ljava/lang/String;");
SkuDetailsRecord_Description_FieldID = FindField(Env, SkuDetailsRecordClass, "Description", "Ljava/lang/String;");
SkuDetailsRecord_SubscriptionPeriod_FieldID = FindField(Env, SkuDetailsRecordClass, "SubscriptionPeriod", "Ljava/lang/String;");
SkuDetailsRecord_FreeTrialPeriod_FieldID = FindField(Env, SkuDetailsRecordClass, "FreeTrialPeriod", "Ljava/lang/String;");
SkuDetailsRecord_IntroductoryPrice_FieldID = FindField(Env, SkuDetailsRecordClass, "IntroductoryPrice", "Ljava/lang/String;");
SkuDetailsRecord_IntroductoryPriceAmountMicros_FieldID = FindField(Env, SkuDetailsRecordClass, "IntroductoryPriceAmountMicros","J");
SkuDetailsRecord_IntroductoryPricePeriod_FieldID = FindField(Env, SkuDetailsRecordClass, "IntroductoryPricePeriod", "Ljava/lang/String;");
SkuDetailsRecord_IntroductoryPriceCycles_FieldID = FindField(Env, SkuDetailsRecordClass, "IntroductoryPriceCycles", "I");
SkuDetailsRecord_IconURL_FieldID = FindField(Env, SkuDetailsRecordClass, "IconURL", "Ljava/lang/String;");
// PurchaseRecordFromHistory class fields
PurchaseRecordFromHistory_ProductID_FieldID = FindField(Env, PurchaseRecordFromHistoryClass, "ProductID", "Ljava/lang/String;");
PurchaseRecordFromHistory_PurchaseTime_FieldID = FindField(Env, PurchaseRecordFromHistoryClass, "PurchaseTime", "J");
PurchaseRecordFromHistory_PurchaseToken_FieldID = FindField(Env, PurchaseRecordFromHistoryClass, "PurchaseToken", "Ljava/lang/String;");
PurchaseRecordFromHistory_DeveloperPayload_FieldID = FindField(Env, PurchaseRecordFromHistoryClass, "DeveloperPayload","Ljava/lang/String;");
PurchaseRecordFromHistory_OriginalJSON_FieldID = FindField(Env, PurchaseRecordFromHistoryClass, "OriginalJSON", "Ljava/lang/String;");
PurchaseRecordFromHistory_Signature_FieldID = FindField(Env, PurchaseRecordFromHistoryClass, "Signature", "Ljava/lang/String;");
// PurchaseRecord class fields
PurchaseRecord_OrderID_FieldID = FindField(Env, PurchaseRecordClass, "OrderID", "Ljava/lang/String;");
PurchaseRecord_PackageName_FieldID = FindField(Env, PurchaseRecordClass, "PackageName", "Ljava/lang/String;");
PurchaseRecord_ProductID_FieldID = FindField(Env, PurchaseRecordClass, "ProductID", "Ljava/lang/String;");
PurchaseRecord_PurchaseTime_FieldID = FindField(Env, PurchaseRecordClass, "PurchaseTime", "J");
PurchaseRecord_PurchaseToken_FieldID = FindField(Env, PurchaseRecordClass, "PurchaseToken", "Ljava/lang/String;");
PurchaseRecord_PurchaseState_FieldID = FindField(Env, PurchaseRecordClass, "PurchaseState", "I");
PurchaseRecord_DeveloperPayload_FieldID = FindField(Env, PurchaseRecordClass, "DeveloperPayload", "Ljava/lang/String;");
PurchaseRecord_bAcknowledged_FieldID = FindField(Env, PurchaseRecordClass, "bAcknowledged", "Z");
PurchaseRecord_bAutoRenewing_FieldID = FindField(Env, PurchaseRecordClass, "bAutoRenewing", "Z");
PurchaseRecord_OriginalJSON_FieldID = FindField(Env, PurchaseRecordClass, "OriginalJSON", "Ljava/lang/String;");
PurchaseRecord_Signature_FieldID = FindField(Env, PurchaseRecordClass, "Signature", "Ljava/lang/String;");
PurchaseRecord_ObfuscatedAccountID_FieldID = FindField(Env, PurchaseRecordClass, "ObfuscatedAccountID","Ljava/lang/String;");
PurchaseRecord_ObfuscatedProfileID_FieldID = FindField(Env, PurchaseRecordClass, "ObfuscatedProfileID","Ljava/lang/String;");
// Find Google Play Billing methods
LaunchBillingFlow_MethodID = FindMethod(Env, "AndroidThunkJava_LaunchBillingFlow", "(Ljava/lang/String;I)V");
LaunchPriceChangeConfirmationFlow_MethodID = FindMethod(Env, "AndroidThunkJava_LaunchPriceChangeConfirmationFlow", "(Ljava/lang/String;I)V");
QueryPurchases_MethodID = FindMethod(Env, "AndroidThunkJava_QueryPurchases", "(I)V");
QuerySkuDetails_MethodID = FindMethod(Env, "AndroidThunkJava_QuerySkuDetails", "([Ljava/lang/String;I)V");
QueryPurchaseHistory_MethodID = FindMethod(Env, "AndroidThunkJava_QueryPurchaseHistory", "(I)V");
IsFeatureSupported_MethodID = FindMethod(Env, "AndroidThunkJava_IsFeatureSupported", "(I)I");
IsIAPInitialized_MethodID = FindMethod(Env, "AndroidThunkJava_IsIAPInitialized", "()Z");
IsPurchased_MethodID = FindMethod(Env, "AndroidThunkJava_IsPurchased", "(Ljava/lang/String;)Z");
}
JNI_METHOD void Java_com_kulichin_androidplaybilling_AndroidPlayBilling_NativeCompleteOperation(
JNIEnv* Env,
jobject Thiz,
jint ResponseCode,
jstring ResponseMessage)
{
if (FAndroidPlayBillingModule* Module = FAndroidPlayBillingModule::GetModule())
{
Module->TriggerOnOperationCompleted
(
(EBillingResponseCode)ResponseCode,
FJavaHelper::FStringFromParam(Env, ResponseMessage)
);
}
}
JNI_METHOD void Java_com_kulichin_androidplaybilling_AndroidPlayBilling_NativeCompleteLaunchBillingFlow(
JNIEnv* Env,
jobject Thiz,
jint ResponseCode,
jstring ResponseMessage,
jobjectArray Array,
jint ArraySize)
{
if (FAndroidPlayBillingModule* Module = FAndroidPlayBillingModule::GetModule())
{
// Convert java record
TArray<FPurchaseRecord> PurchaseRecords = {};
for (int Idx = 0; Idx < ArraySize; Idx++)
{
// Get element from java array
auto JPurchaseRecord = NewScopedJavaObject(Env, Env->GetObjectArrayElement(Array, Idx));
// Construct struct & add to array
PurchaseRecords.Add(GetPurchaseRecordStructFromJavaObject(Env, *JPurchaseRecord));
}
Module->TriggerOnLaunchBillingFlowCompleted
(
(EBillingResponseCode)ResponseCode,
FJavaHelper::FStringFromParam(Env, ResponseMessage),
PurchaseRecords
);
}
}
JNI_METHOD void Java_com_kulichin_androidplaybilling_AndroidPlayBilling_NativeCompleteQueryPurchaseHistory(
JNIEnv* Env,
jobject Thiz,
jint ResponseCode,
jstring ResponseMessage,
jobjectArray Array,
jint ArraySize)
{
if (FAndroidPlayBillingModule* Module = FAndroidPlayBillingModule::GetModule())
{
// Convert java record
TArray<FPurchaseRecordFromHistory> PurchaseRecordsFromHistory = {};
for (int Idx = 0; Idx < ArraySize; Idx++)
{
// Get element from java array
auto JRecordFromHistory = NewScopedJavaObject(Env, Env->GetObjectArrayElement(Array, Idx));
// Construct struct & add to array
PurchaseRecordsFromHistory.Add(GetPurchaseRecordFromHistoryStructFromJavaObject(Env, *JRecordFromHistory));
}
Module->TriggerOnQueryPurchaseHistoryCompleted
(
(EBillingResponseCode)ResponseCode,
FJavaHelper::FStringFromParam(Env, ResponseMessage),
PurchaseRecordsFromHistory
);
}
}
JNI_METHOD void Java_com_kulichin_androidplaybilling_AndroidPlayBilling_NativeCompleteQuerySkuDetails(
JNIEnv* Env,
jobject Thiz,
jint ResponseCode,
jstring ResponseMessage,
jobjectArray Array,
jint ArraySize)
{
if (FAndroidPlayBillingModule* Module = FAndroidPlayBillingModule::GetModule())
{
// Convert java record
TArray<FSkuDetailsRecord> SkuDetailsRecords = {};
for (int Idx = 0; Idx < ArraySize; Idx++)
{
// Get element from java array
auto JSkuDetailsRecord = NewScopedJavaObject(Env, Env->GetObjectArrayElement(Array, Idx));
// Construct struct & add to array
SkuDetailsRecords.Add(GetSkuDetailsRecordStructFromJavaObject(Env, *JSkuDetailsRecord));
}
Module->TriggerOnQuerySkuDetailsCompleted
(
(EBillingResponseCode)ResponseCode,
FJavaHelper::FStringFromParam(Env, ResponseMessage),
SkuDetailsRecords
);
}
}
JNI_METHOD void Java_com_kulichin_androidplaybilling_AndroidPlayBilling_NativeCompleteQueryPurchases(
JNIEnv* Env,
jobject Thiz,
jint ResponseCode,
jstring ResponseMessage,
jobjectArray Array,
jint ArraySize)
{
if (FAndroidPlayBillingModule* Module = FAndroidPlayBillingModule::GetModule())
{
// Convert java record
TArray<FPurchaseRecord> PurchaseRecords = {};
for (int Idx = 0; Idx < ArraySize; Idx++)
{
// Get element from java array
auto JPurchaseRecord = NewScopedJavaObject(Env, Env->GetObjectArrayElement(Array, Idx));
// Construct struct & add to array
PurchaseRecords.Add(GetPurchaseRecordStructFromJavaObject(Env, *JPurchaseRecord));
}
Module->TriggerOnQueryPurchasesCompleted
(
(EBillingResponseCode)ResponseCode,
FJavaHelper::FStringFromParam(Env, ResponseMessage),
PurchaseRecords
);
}
}
#endif
// Copyright (C) 2021. Nikita Klimov. All rights reserved.
#pragma once
#include "AndroidPlayBillingSubsystem.h"
#include "Engine.h"
#define INITIALIZE_DELEGATE_HANDLER_Base(DelegateClass, DelegateName) \
public: \
DelegateClass DelegateName; \
void Add##DelegateName##_Handle(const DelegateClass& Delegate) \
{ \
ClearAll##DelegateName(); \
DelegateName = Delegate; \
} \
void ClearAll##DelegateName() \
{ \
if(DelegateName.IsBound()) \
{ \
DelegateName.Clear(); \
} \
}
#define INITIALIZE_DELEGATE_HANDLER_TwoParams( \
DelegateClass, DelegateName, \
FirstParamType, FirstParamName, SecondParamType, SecondParamName) \
INITIALIZE_DELEGATE_HANDLER_Base(DelegateClass, DelegateName) \
void Trigger##DelegateName(FirstParamType FirstParamName, SecondParamType SecondParamName) \
{ \
if (DelegateName.IsBound()) \
{ \
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady( \
FSimpleDelegateGraphTask::FDelegate::CreateLambda([FirstParamName, SecondParamName, this]() \
{ \
DelegateName.Execute(FirstParamName, SecondParamName); \
ClearAll##DelegateName(); \
}), \
TStatId(), nullptr, ENamedThreads::GameThread); \
} \
}
#define INITIALIZE_DELEGATE_HANDLER_ThreeParams( \
DelegateClass, DelegateName, \
FirstParamType, FirstParamName, SecondParamType, SecondParamName, ThirdParamType, ThirdParamName) \
INITIALIZE_DELEGATE_HANDLER_Base(DelegateClass, DelegateName) \
void Trigger##DelegateName(FirstParamType FirstParamName, SecondParamType SecondParamName, ThirdParamType ThirdParamName) \
{ \
if (DelegateName.IsBound()) \
{ \
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady( \
FSimpleDelegateGraphTask::FDelegate::CreateLambda([FirstParamName, SecondParamName, ThirdParamName, this]() \
{ \
DelegateName.Execute(FirstParamName, SecondParamName, ThirdParamName); \
ClearAll##DelegateName(); \
}), \
TStatId(), nullptr, ENamedThreads::GameThread); \
} \
}
class FAndroidPlayBillingModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
static FAndroidPlayBillingModule* GetModule();
#if PLATFORM_ANDROID
INITIALIZE_DELEGATE_HANDLER_TwoParams(
FOnOperationCompleted, OnOperationCompleted,
const EBillingResponseCode, ResponseCode,
const FString&, ResponseMessage)
INITIALIZE_DELEGATE_HANDLER_ThreeParams(
FOnLaunchBillingFlowCompleted, OnLaunchBillingFlowCompleted,
const EBillingResponseCode, ResponseCode,
const FString&, ResponseMessage,
const TArray<FPurchaseRecord>&, UpdatedPurchaseRecords);
INITIALIZE_DELEGATE_HANDLER_ThreeParams(
FOnQueryPurchaseHistoryCompleted, OnQueryPurchaseHistoryCompleted,
const EBillingResponseCode, ResponseCode,
const FString&, ResponseMessage,
const TArray<FPurchaseRecordFromHistory>&, PurchaseRecordsFromHistory);
INITIALIZE_DELEGATE_HANDLER_ThreeParams(
FOnQuerySkuDetailsCompleted, OnQuerySkuDetailsCompleted,
const EBillingResponseCode, ResponseCode,
const FString&, ResponseMessage,
const TArray<FSkuDetailsRecord>&, SkuDetailsRecords);
INITIALIZE_DELEGATE_HANDLER_ThreeParams(
FOnQueryPurchasesCompleted, OnQueryPurchasesCompleted,
const EBillingResponseCode, ResponseCode,
const FString&, ResponseMessage,
const TArray<FPurchaseRecord>&, PurchaseRecords);
#endif
};
// Copyright (C) 2021. Nikita Klimov. All rights reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "AndroidPlayBillingSettings.generated.h"
UCLASS(transient, config = Engine)
class UAndroidPlayBillingSettings : public UObject
{
GENERATED_BODY()
UPROPERTY(Config, EditAnywhere, Category = "Android Play Billing")
FString BillingLicenseKey;
#if WITH_EDITOR
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
};
// Copyright (C) 2021. Nikita Klimov. All rights reserved.
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "AndroidPlayBillingSubsystem.generated.h"
UENUM(BlueprintType)
enum EBillingResponseCode
{
/**
* Billing API version is not supported for the type requested.
*/
BILLING_UNAVAILABLE = 3 UMETA(DisplayName = "BillingUnavailable"),
/**
* Invalid arguments provided to the API.
*/
DEVELOPER_ERROR = 5 UMETA(DisplayName = "DeveloperError"),
/**
* Fatal error during the API action.
*/
ERROR = 6 UMETA(DisplayName = "Error"),
/**
* Requested feature is not supported by Play Store on the
* current device.
*/
FEATURE_NOT_SUPPORTED = -2 UMETA(DisplayName = "FeatureNotSupported"),
/**
* Failure to purchase since item is already owned.
*/
ITEM_ALREADY_OWNED = 7 UMETA(DisplayName = "ItemAlreadyOwned"),
/**
* Failure to consume since item is not owned.
*/
ITEM_NOT_OWNED = 8 UMETA(DisplayName = "ItemNotOwned"),
/**
* Requested product is not available for purchase.
*/
ITEM_UNAVAILABLE = 4 UMETA(DisplayName = "ItemUnavailable"),
/**
* Success.
*/
OK = 0 UMETA(DisplayName = "OK"),
/**
* Play Store service is not connected now - potentially
* transient state.
*/
SERVICE_DISCONNECTED = -1 UMETA(DisplayName = "ServiceDisconnected"),
/**
* The request has reached the maximum timeout before
* Google Play responds.
*/
SERVICE_TIMEOUT = -3 UMETA(DisplayName = "ServiceTimeout"),
/**
* Network connection is down.
*/
SERVICE_UNAVAILABLE = 2 UMETA(DisplayName = "ServiceUnavailable"),
/**
* User pressed back or canceled a dialog.
*/
USER_CANCELED = 1 UMETA(DisplayName = "UserCanceled")
};
UENUM(BlueprintType)
enum EFeatureType
{
/**
* Purchase/query for in-app items on VR.
*/
IN_APP_ITEMS_ON_VR = 0 UMETA(DisplayName = "InAppPurchaseOnVR"),
/**
* Launch a price change confirmation flow.
*/
PRICE_CHANGE_CONFIRMATION = 1 UMETA(DisplayName = "PriceChangeConfirmation"),
/**
* Purchase/query for subscriptions.
*/
SUBSCRIPTIONS = 2 UMETA(DisplayName = "Subcriptions"),
/**
* Purchase/query for subscriptions on VR.
*/
SUBSCRIPTIONS_ON_VR = 3 UMETA(DisplayName = "SubscriptionsOnVR"),
/**
* Subscriptions update/replace.
*/
SUBSCRIPTIONS_UPDATE = 4 UMETA(DisplayName = "SubscriptionsUpdate")
};
UENUM(BlueprintType)
enum EPurchaseState
{
PENDING = 2 UMETA(DisplayName = "Pending"),
PURCHASED = 1 UMETA(DisplayName = "Purchased"),
UNSPECIFIED_STATE = 0 UMETA(DisplayName = "UnspecifiedState")
};
UENUM(BlueprintType)
enum ESkuType
{
/**
* A type of SKU for Android apps in-app products.
*/
INAPP = 0 UMETA(DisplayName = "InAppProduct"),
/**
* A type of SKU for Android apps subscriptions.
*/
SUBS = 1 UMETA(DisplayName = "InAppSubscription")
};
USTRUCT(BlueprintType)
struct FSkuDetailsRecord
{
GENERATED_BODY()
/**
* Returns a String in JSON format that contains SKU details.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString OriginalJSON;
/**
* Returns the product Id.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString ProductID;
/**
* Returns the of the product.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString Type;
/**
* Returns formatted price of the item, including its currency sign.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString Price;
/**
* Returns price in micro-units, where 1,000,000 micro-units equal
* one unit of the currency.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
int64 PriceAmountMicros;
/**
* Returns ISO 4217 currency code for price and original price.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString PriceCurrencyCode;
/**
* Returns formatted original price of the item, including its
* currency sign.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString OriginalPrice;
/**
* Returns the original price in micro-units, where 1,000,000
* micro-units equal one unit of the currency.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
int64 OriginalPriceAmountMicros;
/**
* Returns the title of the product being sold.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString Title;
/**
* Returns the description of the product.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString Description;
/**
* Subscription period, specified in ISO 8601 format.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString SubscriptionPeriod;
/**
* Trial period configured in Google Play Console, specified in
* ISO 8601 format.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString FreeTrialPeriod;
/**
* Formatted introductory price of a subscription, including its
* currency sign, such as €3.99.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString IntroductoryPrice;
/**
* Introductory price in micro-units.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
int64 IntroductoryPriceAmountMicros;
/**
* The billing period of the introductory price, specified in
* ISO 8601 format.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString IntroductoryPricePeriod;
/**
* The number of subscription billing periods for which the user will
* be given the introductory price, such as 3.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
int IntroductoryPriceCycles;
/**
* Returns the icon of the product if present.
*/
UPROPERTY(BlueprintReadOnly, Category = "SkuDetailsRecord")
FString IconURL;
};
USTRUCT(BlueprintType)
struct FPurchaseRecordFromHistory
{
GENERATED_BODY()
/**
* Returns the product Id.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecordFromHistory")
FString ProductID;
/**
* Returns the time the product was purchased, in milliseconds since
* the epoch (Jan 1, 1970).
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecordFromHistory")
int64 PurchaseTime;
/**
* Returns a token that uniquely identifies a purchase for a given
* item and user pair.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecordFromHistory")
FString PurchaseToken;
/**
* Returns the payload specified when the purchase was acknowledged or
* consumed.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecordFromHistory")
FString DeveloperPayload;
/**
* Returns a String in JSON format that contains details about the
* purchase order.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecordFromHistory")
FString OriginalJSON;
/**
* Returns String containing the signature of the purchase data
* that was signed with the private key of the developer.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecordFromHistory")
FString Signature;
};
USTRUCT(BlueprintType)
struct FPurchaseRecord
{
GENERATED_BODY()
/**
* Returns a unique order identifier for the transaction.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
FString OrderID;
/**
* Returns the application package from which the purchase originated.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
FString PackageName;
/**
* Returns the product Id.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
FString ProductID;
/**
* Returns the time the product was purchased, in milliseconds since
* the epoch (Jan 1, 1970).
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
int64 PurchaseTime;
/**
* Returns a token that uniquely identifies a purchase for a given
* item and user pair.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
FString PurchaseToken;
/**
* Returns PurchaseState indicating the state of the purchase.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
TEnumAsByte<EPurchaseState> PurchaseState;
/**
* Returns the payload specified when the purchase was acknowledged or
* consumed.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
FString DeveloperPayload;
/**
* Indicates whether the purchase has been acknowledged.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
bool bAcknowledged;
/**
* Indicates whether the subscription renews automatically.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
bool bAutoRenewing;
/**
* Returns a String in JSON format that contains details about the
* purchase order.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
FString OriginalJSON;
/**
* Returns String containing the signature of the purchase data that
* was signed with the private key of the developer.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
FString Signature;
/**
* The obfuscated account id.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
FString ObfuscatedAccountID;
/**
* The obfuscated profile id.
*/
UPROPERTY(BlueprintReadOnly, Category = "PurchaseRecord")
FString ObfuscatedProfileID;
};
/**
* Operation delegate
*/
DECLARE_DYNAMIC_DELEGATE_TwoParams(
FOnOperationCompleted,
EBillingResponseCode, ResponseCode,
const FString&, ResponseMessage);
/**
* Launch Billing Flow delegate
*/
DECLARE_DYNAMIC_DELEGATE_ThreeParams(
FOnLaunchBillingFlowCompleted,
EBillingResponseCode, ResponseCode,
const FString&, ResponseMessage,
const TArray<FPurchaseRecord>&, UpdatedPurchaseRecords);
/**
* Query Purchase History delegate
*/
DECLARE_DYNAMIC_DELEGATE_ThreeParams(
FOnQueryPurchaseHistoryCompleted,
EBillingResponseCode, ResponseCode,
const FString&, ResponseMessage,
const TArray<FPurchaseRecordFromHistory>&, PurchaseRecordsFromHistory);
/**
* Query Sku Details delegate
*/
DECLARE_DYNAMIC_DELEGATE_ThreeParams(
FOnQuerySkuDetailsCompleted,
EBillingResponseCode, ResponseCode,
const FString&, ResponseMessage,
const TArray<FSkuDetailsRecord>&, SkuDetailsRecords);
/**
* Query Purchase delegate
*/
DECLARE_DYNAMIC_DELEGATE_ThreeParams(
FOnQueryPurchasesCompleted,
EBillingResponseCode, ResponseCode,
const FString&, ResponseMessage,
const TArray<FPurchaseRecord>&, PurchaseRecords);
// TODO: add examples
UCLASS()
class UAndroidPlayBillingSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
/**
* Initiates the billing flow for an in-app purchase or subscription.
*
* @param OnOperationResult callback is called when operation has been completed.
* @param ProductID specifies the ID of the item being purchased.
* @param ProductType specifies the Type of the item being purchased.
*/
UFUNCTION(BlueprintCallable, Category = "AndroidPlayBilling")
static void LaunchBillingFlow(
FOnLaunchBillingFlowCompleted OnOperationResult,
const FString& ProductID,
const ESkuType ProductType);
/**
* Initiates a flow to confirm the change of price for an item
* subscribed by the user.
*
* @param OnOperationResult callback is called when operation has been completed.
* @param ProductID specifies the ID of the item that has the pending price change.
* @param ProductType specifies the Type of the item that has the pending price change.
*/
UFUNCTION(BlueprintCallable, Category = "AndroidPlayBilling")
static void LaunchPriceChangeConfirmationFlow(
FOnOperationCompleted OnOperationResult,
const FString& ProductID,
const ESkuType ProductType);
/**
* Returns purchases details for currently owned items bought
* within your app.
*
* @param OnOperationResult callback is called when operation has been completed.
* @param ProductType the type of the product.
*/
UFUNCTION(BlueprintCallable, Category = "AndroidPlayBilling")
static void QueryPurchases(
FOnQueryPurchasesCompleted OnOperationResult,
const ESkuType ProductType);
/**
* Performs a network query to get SKU details and return the
* result asynchronously.
*
* @param OnOperationResult callback is called when operation has been completed.
* @param ProductIDs specifies the ProductIDs that are queried for as
* published in the Google Developer console.
*
* @param ProductType specifies the Type of the product to query.
*/
UFUNCTION(BlueprintCallable, Category = "AndroidPlayBilling")
static void QuerySkuDetails(
FOnQuerySkuDetailsCompleted OnOperationResult,
const TArray<FString>& ProductIDs,
const ESkuType ProductType);
/**
* Returns the most recent purchase made by the user for each SKU,
* even if that purchase is expired, canceled, or consumed.
*
* @param OnOperationResult callback is called when operation has been completed.
* @param ProductType the type of the product.
*/
UFUNCTION(BlueprintCallable, Category = "AndroidPlayBilling")
static void QueryPurchaseHistory(
FOnQueryPurchaseHistoryCompleted OnOperationResult,
const ESkuType ProductType);
/**
* Checks if the specified feature or capability is supported
* by the Play Store.
*
* @param FeatureType one of the EFeatureType constants.
*/
UFUNCTION(BlueprintPure, Category = "AndroidPlayBilling")
static EBillingResponseCode IsFeatureSupported(const EFeatureType FeatureType);
/**
* Checks if the client is currently connected to the service, so
* that requests to other methods will succeed.
*/
UFUNCTION(BlueprintPure, Category = "AndroidPlayBilling")
static bool IsIAPInitialized();
/**
* Checks if user owning the product.
*
* @param ProductID specifies the ID of the item.
*/
UFUNCTION(BlueprintPure, Category = "AndroidPlayBilling")
static bool IsPurchased(const FString& ProductID);
};
......@@ -89,6 +89,15 @@
<buildGradleAdditions>
<insert>
android
{
compileOptions
{
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
}
apply plugin: 'com.android.application'
dependencies
......@@ -139,6 +148,7 @@
import com.Plugins.HuaweiSDK.activity.ConsumptionActivity;
import com.Plugins.HuaweiSDK.activity.EntryActivity;
import com.Plugins.HuaweiSDK.activity.AccountActivity;
</insert>
</gameActivityImportAdditions>
......@@ -147,10 +157,18 @@
</insert>
</gameActivityPostImportAdditions>
<gameActivityOnCreateAdditions>
<insert>
mAccountActivity = new AccountActivity(this);
</insert>
</gameActivityOnCreateAdditions>
<gameActivityClassAdditions>
<insert>
private ConsumptionActivity mConsumptionActivity;
private EntryActivity mEntryActivity;
private AccountActivity mAccountActivity;
public void AndroidThunkJava_CreateHuaweiIap()
{
mConsumptionActivity = new ConsumptionActivity(this);
......@@ -170,18 +188,19 @@
{
mEntryActivity.Payment(productId);
}
</insert>
</gameActivityClassAdditions>
<gameActivityOnCreateAdditions>
<insert>
public void AndroidThunkJava_SignIn()
{
mAccountActivity = new AccountActivity(this);
mAccountActivity.signIn();
}
</insert>
</gameActivityOnCreateAdditions>
</gameActivityClassAdditions>
<gameActivityOnActivityResultAdditions>
<insert>
mEntryActivity.onActivityResult(requestCode, resultCode, data);
mAccountActivity.onActivityResult(requestCode, resultCode, data);
</insert>
</gameActivityOnActivityResultAdditions>
......
package com.Plugins.HuaweiSDK.activity;
import android.app.Activity;
import android.content.Intent;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.util.Log;
import com.huawei.hmf.tasks.Task;
import com.huawei.hms.common.ApiException;
import com.huawei.hms.support.account.AccountAuthManager;
import com.huawei.hms.support.account.request.AccountAuthParams;
import com.huawei.hms.support.account.request.AccountAuthParamsHelper;
import com.huawei.hms.support.account.result.AuthAccount;
import com.huawei.hms.support.account.service.AccountAuthService;
public class AccountActivity extends activity {
public class AccountActivity extends Activity {
private String TAG = "HuaweiAccountActivity";
private AccountAuthService mAuthManager;
......@@ -20,13 +26,15 @@ public class AccountActivity extends activity {
mActivity = activity;
}
private void signIn() {
public void signIn() {
Log.i(TAG, "AccountActivity.signIn");
mAuthParam = new AccountAuthParamsHelper(AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
.setIdToken()
.setAccessToken()
.createParams();
mAuthManager = AccountAuthManager.getService(mActivity, mAuthParam);
startActivityForResult(mAuthManager.getSignInIntent(), Constant.REQUEST_SIGN_IN_LOGIN);
startActivityForResult(mAuthManager.getSignInIntent(), 1002);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
......@@ -45,10 +53,10 @@ public class AccountActivity extends activity {
} else {
Log.i(TAG, "signIn failed: " + ((ApiException) authAccountTask.getException()).getStatusCode());
nativeSignInResult(false, 0);
nativeSignInResult(false, "0");
}
}
if (requestCode == Constant.REQUEST_SIGN_IN_LOGIN_CODE) {
if (requestCode == 1003) {
//login success
Task<AuthAccount> authAccountTask = AccountAuthManager.parseAuthResultFromIntent(data);
if (authAccountTask.isSuccessful()) {
......
......@@ -2,7 +2,7 @@ package com.Plugins.HuaweiSDK.activity;
import android.app.Activity;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
......
......@@ -38,8 +38,13 @@ static void ReqSignIn()
static jmethodID SignInMethod = FJavaWrapper::FindMethod(Env, FJavaWrapper::GameActivityClassID, "AndroidThunkJava_SignIn", "()V", false);
if (SignInMethod)
{
UE_LOG(LogTemp, Log, TEXT("Find AndroidThunkJava_SignIn"));
FJavaWrapper::CallVoidMethod(Env, FJavaWrapper::GameActivityThis, SignInMethod);
}
else
{
UE_LOG(LogTemp, Log, TEXT("!Find AndroidThunkJava_SignIn"));
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment