Skip to content

Working with payments in SDK🔗

To integrate payments you need to complete a number of simple steps:

Step 1. Import the payment module🔗

  • Add MRGSBank (MRGSBilling) module

    Unity:

    Adding to the project (general instruction)

    Step 1. Add Sources

    To add MRGS to a project via the Unity Package Manager (available from Unity 2018+) simply add a scopedRegistries section to the Packages/manifest.json file by adding the following entry:

    {
        "dependencies": {
            ...
        },
        "scopedRegistries": [
                {
                    "name": "MRGS",
                    "url": "https://mrgs-nexus.my.games/repository/mrgs-uninty-plugins/",
                    "scopes": [
                        "games.my.mrgs"
                    ]
                }
        ]
    }
    

    Alternatively, you can click Edit -> Project Settings -> Package Manager -> '+' in scoped registry section, and fill in the fields according to the data above.

    Step 2. Add dependency

    • Click Window -> Package Manager -> select 'Packages: MyRegistries' from dropdown list, select package MRGSBank from the list, then click "Install"
    • Import the module: using MRGS;
    • Download the latest version of the library. Unzip the archive.
    • (For unitypackage integration) In Unity, click Assets -> Import Package -> Custom Package, and select the games.my.mrgs.bank.unitypackage package from the downloaded archive.
    • (For tgz integration) In Unity, click Window -> Package Manager -> '+' -> Add package from tarball, and select the games.my.mrgs.bank-<version> package. tgz from the downloaded archive.
    • Import the module: using MRGS;

    iOS:

    Adding to the project (general instruction)

    Step 1: Add dependencies

    Via Package collection

    • In Xcode select File > Add Packages
    • Select "+" > "Add Swift Package Collection"
    • Insert URL: https://mrgs-nexus.my.games/repository/ios-sdks/MRGSPackageCollection.json
    • Select module MRGSBank from "MRGS Package Collection".
    • Or you can select "MRGS" package from "MRGS Package Collection" (contains all mrgs modules as products) and then select "MRGS/Bank" product only.

    Individual packages

    • In Xcode select File > Add Packages
    • In the search bar at the top right, paste the URL: https://mrgs-gitea.my.games/mrgs/mrgsbank-ios-sdk.git
    • Add a module to your project
    • Or you can paste the url https://mrgs-gitea.my.games/mrgs/ios-sdks.git to include the "MRGS" package which contains all mrgs modules as products and then select only the product" MRGS/Bank".

    Step 2: Add support for ObjectiveC categories

    • Set the -ObjC flag in the "Other linker Flags" field in the project settings.
    • Import the module in code: @import MRGServiceKit; or @import MRGSBank; or #import <MRGSBank/MRGSBank.h>

    Step 1. Add Sources

    In your podfile, add sources to the top of the file:

    source 'https://github.com/CocoaPods/Specs.git' # For main repo
    source 'https://mrgs-gitea.my.games/mrgs/cocoapods-specs.git'  # For MRGS repo
    

    Step 2: Add dependencies

    Add the latest version of MRGSBank to target:

    To add via subspecs:

    target 'MyProject' do
        pod 'MRGS', '~> 5.0.0', :subspecs => ['Bank']
    end
    

    To add via individual modules:

    target 'MyProject' do
        pod 'MRGSBank', '~> 5.0.0'
    end
    

    To add all mrgs modules:

    target 'MyProject' do
        pod 'MRGS/AllKits', '~> 5.0.0'
    end
    

    Step 3: Install dependencies

    • Run pod install (or pod install --repo-update if necessary)
    • Import the module in code: @import MRGServiceKit; or @import MRGSBank; or #import <MRGSBank/MRGSBank.h>

    Step 1: Add dependencies

    Add the dependency to your Cartfile:

    binary "https://mrgs-nexus.my.games/repository/ios-sdks/MRGSBank/MRGSBank.json" ~> 5.0.0
    

    Step 2: Install dependencies

    • Run carthage update --use-xcframeworks
    • Add downloaded frameworks to your project (make sure "do not embed" option is enabled)
    • Set the -ObjC flag in the "Other linker Flags" field in the project settings.
    • Import the module in code: @import MRGServiceKit; or @import MRGSBank; or #import <MRGSBank/MRGSBank.h>
    • Download the latest version of the library. Unzip the archive.
    • Add MRGSBank.xcframework from the downloaded archive to your project (Drag the libraries to the "Linked frameworks and Libraries" section) (Also contains MRGSBank.framework for compatibility - fat framework)

    • Set the -ObjC flag in the "Other linker Flags" field in the project settings.

    • Import the module in code: @import MRGSBank; or #import <MRGSBank/MRGSBank.h>
    • Also, you can add the MRGServiceKit.h and module.modulemap files from the archive to your project, or specify the path to them in the project settings in the Build Settings -> Header search paths section. Now, instead of importing each of our frameworks separately, you can only import one header file: @import MRGServiceKit;

    Android:

    Add a dependency in build.gradle file:

    dependencies {
        def mrgsVersion = "6.x.x"
        implementation "games.my.mrgs:billing:$mrgsVersion"
    }
    

    Copy the MRGSBilling.aa file into the libs directory of your project. Add the necessary dependencies into the build.gradle file.

    dependencies {
        ...
        implementation(name: 'MRGSBilling', ext:'aar')
        implementation 'androidx.appcompat:appcompat:1.6.1'
        implementation 'com.android.billingclient:billing:6.0.1'
        ...
    }
    

Step 2. Preliminary setup🔗

Step 2.1. Parameter setup🔗

Upon starting the MRGS SDK it is necessary to set the type of the store for payments and enable payment processing completely through MRGS:

using MRGS;

public class MasterController : MonoBehaviour
{
    void Awake()
    {
        var serviceParams = new MRGServiceParams(APP_ID, APP_SECRET);
        // Setting MRGS params
        // ...

        // Enable payment processing on iOS
        serviceParams.IOSExtraOptions.DisablePaymentsCheck = false;
        serviceParams.IOSExtraOptions.AutomaticPaymentTracking = false;

        // Choosing a store to work on Android
        // Available MRGSPlatform: Amazon, Android, Huawei, Samsung, and FacebookCloud
        serviceParams.AndroidExtraOptions.Platform = MRGSPlatformAndroid.Android;

        // Configuring External SDKs and initializing MRGS
        // ...
    }
}
@import MRGService;

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    MRGServiceParams *mrgsParams = [[MRGServiceParams alloc] initWithAppId: <MRGS_APP_ID> secret: <CLIENT_SECRET>];
    //Setting MRGS parameters
    // ...

    //Enabling the payment processing
    mrgsParams.disablePaymentsCheck = false;
    mrgsParams.automaticPaymentTracking = false;

    //Setting External SDKs and initializing MRGS
    // ...
}
import games.my.mrgs.MRGSPlatform;
import games.my.mrgs.MRGService;
import games.my.mrgs.MRGServiceParams;

public class YourApplicationClass extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        // Available MRGSPlatform: AMAZON, ANDROID, HUAWEI, SAMSUNG and FACEBOOK_CLOUD
        final MRGServiceParams params = MRGServiceParams.init(<MRGS_APP_ID>, <CLIENT_SECRET>, MRGSPlatform.ANDROID);
        MRGService.service(context, params);
    }
}

Do not forget to enable the payment processing for iOS

Pay attention to the DisablePaymentsCheck and AutomaticPaymentTracking flags in the code above for iOS and Unity. If you want to use the MRGSBilling modules for making payments, those flags should be false.

Make sure to set the application store

For the Android platform it is necessary to mention the store, which you want to use for processing payments, for example "google". If the type of the store is not mentioned, an exception will happen upon the attempt to make a payment.

Support for third-party stores (Samsung, Amazon, Huawei/AppTouch) on Android

To support third-party stores, you must specify the desired store when starting the SDK (point above), and also add the required SDKs to the project. For Huawei/AppTouch, you need to add to the gradle com.huawei.hms: iap: <version>, we supply the SDK for Samsung and Amazon in the archive in the root in the Extra folder (in Unity they additionally placed in the MRGSBank/Extra module, but it is possible to add them to the project using the menu Window/MRGS/<SDK>)

Step 2.2. User game identifier setup🔗

For the correct work of the payments you need to set the user game ID. You can read about setting the user game ID in the "Connecting the SDK" section Unity, iOS, Android

Step 2.3. Check the necessary conditions🔗

Make sure that you set the environment correctly on the MRGS website, namely:

  • Make sure that you have added the necessary payment validation keys on the MRGS website.

  • Make sure that you have added all SKU and prices, which you are going to work with, in the product section of your application (cart icon) on the MRGS website.

Why it is necessary to fill the table with products

To convert the currencies in payments MRGS uses the API of the Central Bank of the Russian Federation. If the payment currency is missing on the the Central Bank's website, MRGS will take the price in USD from the SKU table. Important This information is used only when processing the payments in the server. The MRGS SDK on the client does not receive this SKU list and always loads product information only from the application store.

If the received payment is not in our admin panel and we could not convert the received currency into dollars via api, then 0 will get to MRGS and the terrabank. MyTracker.MyUA, 1Link collects payments separately from MRGS, through MyTracker.

Step 3. Installing the delegate and checking the availability🔗

Before working with the bank you should check its availability for a user (payments may be forbidden for the account) and set the delegate for receiving notifications about loading and payments:

// Availability check
MRGSBank.Instance.IsBankAvailable();
// Or an asynchronous version
MRGSBank.Instance.IsBankAvailable(callback);

// Setting a delegate
// Conforms to IMRGSBankDelegate protocol
MRGSBank.Instance.Delegate = this;
//Checking the availability
[[MRGSBank sharedInstance] isBankAvailable];

//Installing the delegate
[MRGSBank sharedInstance].delegate = self; //where self - is an object, realizing the MRGSBankDelegate protocol
import games.my.mrgs.billing.MRGSBilling;
import games.my.mrgs.billing.MRGSBillingDelegate;

// Sync checking
MRGSBilling.getInstance().isBillingAvailable(context);
// Async checking
MRGSBilling.getInstance().isBillingAvailable(context, callback);

// Setting MRGSBillingDelegate
MRGSBilling.getInstance().setDelegate(this);

Recommendation

For Android, we recommend using the async IsBankAvailable(callback)/isBillingAvailable(context, callback) method.

Step 4. Receiving the information about available products🔗

Firstly, it is necessary to load the list of all the available products (which were added) from the servers of the store. In order to do that, you need:

  1. Create a request indicating the required products and their types.
  2. Send the request.

Where does MRGS get the information about products

The information about products is loaded from the Google Play/AppStore application stores, not from the MRGS server. If one of the products is not loaded, it may be installed incorrectly in Google Play/AppStore.

For the request, a special class MRGSBankProductsRequest is used, which stores a list of products and their types (MRGSBankProductType, can be consumable, non-consumable, subscription). Allows to add both a specific product with its type and an array of product identifiers of a specific type. To create a product request, you need to call:

// Request creation
MRGSBankProductsRequest productsRequest = new MRGSBankProductsRequest();
// Filling request with products
List<string> consumables = new List<string> { "games.my.mrgs.framework.consumable", "..." };
// Adding an array of products of the same type at once
productsRequest.Add(productsIdentifiers: consumables, type: MRGSBankProductType.Consumable);
// Adding a specific product
productsRequest.Add(productIdentifier: "games.my.mrgs.framework.fake_product_id", type: MRGSBankProductType.Consumable);
// Request creation
MRGSBankProductsRequest* request = [[MRGSBankProductsRequest alloc] init];
// Filling request with products
NSArray* consumables = @[ @"games.my.mrgs.framework.consumable", @"..." ];
// Adding an array of products of the same type at once
[request addProductsIdentifiers:consumables withType:kMRGSBankProductTypeConsumable];
// Adding a specific product
[request addProductIdentifier:@"games.my.mrgs.framework.some_product" withType:kMRGSBankProductTypeConsumable];
import java.util.Arrays;
import java.util.List;

import games.my.mrgs.billing.MRGSBillingEntities.MRGSBankProductsRequest;
import games.my.mrgs.billing.MRGSBillingProduct;

// Request Products
final List<String> consumable = Arrays.asList(
        "games.my.mrgs.purchase1",
        "games.my.mrgs.purchase2",
        "android.test.purchased");
final List<String> nonConsumable = Arrays.asList("games.my.mrgs.noncons");
final List<String> subscriptions = Arrays.asList(
        "games.my.mrgs.subs1",
        "games.my.mrgs.subs2",
        "games.my.mrgs.subs3");

final MRGSBankProductsRequest productRequest = new MRGSBankProductsRequest();
productRequest.add(consumable, MRGSBillingProduct.CONS);
productRequest.add(nonConsumable, MRGSBillingProduct.NONCONS);
productRequest.add(subscriptions, MRGSBillingProduct.SUBS);

After preparing the array of the products for the request, use the product loading method:

// Where productsRequest is the generated request.
MRGSBank.Instance.RequestProductsInfo(productsRequest);
[[MRGSBank sharedInstance] requestProductsInfo:request]; //where request - created request.
import games.my.mrgs.billing.MRGSBilling;

MRGSBilling.getInstance().requestProductsInfo(request); //where request - created request.

Creating the subscriptions for Android

Subscriptions can be created only in the Google Play application store. They are not supported for the Amazon application store and Samsung Galaxy Store.

List of products loading example:

var productsRequest = new MRGSBankProductsRequest();

var consumables = new List<string> { "games.my.mrgs.framework.consumable", "..." };
productsRequest.Add(consumables, MRGSBankProductType.Consumable);
// Adding a product individually, not in an array
productsRequest.Add("games.my.mrgs.framework.some_product_id", MRGSBankProductType.Consumable);

var nonConsumables = new List<string> { "games.my.mrgs.framework.non_consumable", "..." };
productsRequest.Add(nonConsumables, MRGSBankProductType.NonConsumable);

var subscriptions = new List<string> { "games.my.mrgs.framework.subscription", "..."};
productsRequest.Add(subscriptions, MRGSBankProductType.Subscription);

MRGSBank.Instance.RequestProductsInfo(productsRequest);
MRGSBankProductsRequest* request = [[MRGSBankProductsRequest alloc] init];

NSArray* consumables = @[ @"games.my.mrgs.framework.consumable", @"..." ];
[request addProductsIdentifiers:consumables withType:kMRGSBankProductTypeConsumable];
// Adding a product individually, not in an array         
[request addProductIdentifier:@"games.my.mrgs.framework.some_product" withType:kMRGSBankProductTypeConsumable];

NSArray* nonConsumables = @[ @"games.my.mrgs.framework.non_consumable", @"..." ];
[request addProductsIdentifiers:nonConsumables withType:kMRGSBankProductTypeNonConsumable];

NSArray* subscriptions = @[ @"games.my.mrgs.framework.subscription", @"..."];
[request addProductsIdentifiers:subscriptions withType:kMRGSBankProductTypeSubscription];

[[MRGSBank sharedInstance] requestProductsInfo:request];
import java.util.Arrays;
import java.util.List;

import games.my.mrgs.billing.MRGSBilling;
import games.my.mrgs.billing.MRGSBillingEntities.MRGSBankProductsRequest;
import games.my.mrgs.billing.MRGSBillingProduct;

final List<String> consumable = Arrays.asList(
        "games.my.mrgs.purchase1",
        "games.my.mrgs.purchase2",
        "android.test.purchased");
final List<String> nonConsumable = Arrays.asList("games.my.mrgs.noncons");
final List<String> subscriptions = Arrays.asList(
        "games.my.mrgs.subs1",
        "games.my.mrgs.subs2",
        "games.my.mrgs.subs3");

final MRGSBankProductsRequest productRequest = new MRGSBankProductsRequest();
productRequest.add(consumable, MRGSBillingProduct.CONS);
productRequest.add(nonConsumable, MRGSBillingProduct.NONCONS);
productRequest.add(subscriptions, MRGSBillingProduct.SUBS);

MRGSBilling.getInstance().requestProductsInfo(productRequest);

After a response from the store server, one of the MRGSBankDelegate delegate methods will be called, indicating the successful or unsuccessful loading of products, and the LoadedProducts and LoadedProductsIdentifiers fields in our bank object will also be filled:

// Successful product load
public void OnReceiveProductsResponse(MRGSBankProductsResponse productsResponse)
{
    Debug.Log("OnReceiveProductsResponse: " + productsResponse);
    foreach(MRGSBankProduct validProduct in productsResponse.ValidProducts)
    {
        if (validProduct.Android.Rewarded)
        {
            Debug.Log("This is rewarded product");
        }
        if (validProduct.iOS.Discounts.Count > 0)
        {
            Debug.Log("Discounts are available for subscription");
        }
        Debug.Log("Product: " + validProduct);
    }
    foreach (string invalidProductId in productsResponse.InvalidProductsIds)
    {
        Debug.Log("Invalid product identifier: " + invalidProductId);
    }
}

// Unsuccessful product load
public void OnReceiveProductsError(MRGSBankProductsResponse productsResponse)
{
    Debug.Log("OnReceiveProductsError: " + productsResponse);
    Debug.Log("Received error: " + productsResponse.RequestError);
}
// Successful loading of products
- (void)didReceiveProductsResponse:(MRGSBankProductsResponse * _Nonnull)response {
    NSLog(@"didReceiveProductsResponse: %@", response);

    for (MRGSBankProduct *validProduct in response.validProducts) {
        NSLog(@"Valid product: %@", validProduct);
        NSLog(@"Valid product price : %@", validProduct.priceInformation.localizedPrice);
        NSLog(@"Valid product discouts : %@", validProduct.skProduct.discounts);
    }

    for (NSString *invalidProductId in response.invalidProductsIds) {
        NSLog(@"Invalid product: %@", invalidProductId);
    }
}

// Unsuccessful loading of products
- (void)didReceiveProductsError:(MRGSBankProductsResponse * _Nonnull)response {
    NSLog(@"didReceiveProductsError: %@", response);
    NSLog(@"received error: %@", response.requestError);
}
// Successful request
@Override
public void onReceiveProductsResponse(MRGSBillingEntities.MRGSBankProductsResponse productsResponse) {
    Log.v("onReceiveProductsResponse", "valid products" + productsResponse.getValidItems());
    Log.v("onReceiveProductsResponse", "invalid products" + productsResponse.getInvalidItems());
}

@Override
// Failed request
public void onReceiveProductsError(MRGSBillingEntities.MRGSBankProductsResponse productsResponse) {
    Log.v("onReceiveProductsError", "Error loading products" + productsResponse.getError());
}
What if the list of the products is empty? (Android)

If the delegate received an empty list, it may indicate several problems.

  1. Check that the user is logged in the application store (he can enter it, download a game/application).
  2. Check that the OS (Android) has only one Google account. There were cases, when the system had the second account with invalid (expired) password, and the list of the products could not be loaded (even though the active account had no problems).
  3. Check that the account has a payment card linked to it.
  4. Check that the application (Android) is signed with the same live certificate, with which the application is uploaded to the Google Play console.
  5. If you use the developer build (iOS), check that you use the testing Sandbox account as a user (it is adjusted in the AppStore Connect).
What if the list of the products is empty? (iOS)

If an empty list is returned to the delegate, this could be an indication of several problems at once. Check the following points:

  • You did not complete all the financial requirements (see the "Contracts, Tax, and Banking Information" section of this document).
  • You did not use an explicit App ID.
  • You did not use the Provisioning Profile associated with your explicit App ID.
  • You did not use the correct product identifier in your code. See Technical Q&A, QA1329, 'In App Purchase Product Identifiers' for more information about product identifiers.
  • You did not clear your In App Purchase products for sale in iTunes Connect.
  • You might have modified your products, but these changes are not yet available to all the App Store servers.
  • If you or App Review rejected your most recent binary in iTunes Connect.
  • Try to remove the Existing application from the Device and Install the fresh build from Xcode.
  • Have you enabled In-App Purchases for your App ID?
  • Does your project’s .plist Bundle ID match your App ID?
  • Have you generated and installed a new provisioning profile for the new App ID?
  • Have you configured your project to code sign using this new provisioning profile?
  • Are you using the full product ID when making a SKProductRequest?
  • Have you waited several hours since adding your product to iTunes Connect?
  • Are your bank details active on iTunes Connect?
  • Is your device jailbroken? If so, you need to revert the jailbreak for IAP to work.
  • If you are using a developer build, check that you are using a valid Sandbox tester account (configurable in AppStore Connect)

Product download speed on iOS

If the project immediately requests a large number of products (200+) in one request, then the request execution time can be quite large (Apple breaks a large request into several small ones. For example, if you request a hundred products, there are about 10 requests to the servers in small batches so it takes so long). By dividing the products into two requests: "priority" 10-15 items and "all the rest", you can significantly speed up the readiness of the store.

After receiving the list of the available products, you can make payments.

Step 5. Processing the payment🔗

As soon as the user has chosen a product for purchasing, you need to call only one function for process the payment and validating it. MRGS supports the following functions:

// Start buying a product with the specified ID
// Optionally, you can add payload to the payment, which will come as a result to the client and server
public void PurchaseProduct(string productIdentifier, string developerPayload = null);

// Start buying a product with a request object
// Allows you to specify additional parameters for making a payment, such as quantity, discount ID, payload, and more.
public void PurchaseProduct(MRGSBankPurchaseRequest purchaseRequest);
// Start buying a product with the specified ID
- (void)purchaseProduct:(NSString* _Nonnull)productIdentifier;

// Start buying a product with the specified ID
// Optionally, you can add payload to the payment, which will come as a result to the client and to the server
- (void)purchaseProduct:(NSString* _Nonnull)productIdentifier withDeveloperPayload:(NSString* _Nullable)payload;

// Start buying a product with a request object
// Allows you to specify additional parameters for making a payment, such as quantity, discount ID, payload, and more.
- (void)purchaseProductWithRequest:(MRGSBankPurchaseRequest* _Nonnull)purchaseRequest;
//Payment
public void buyItem(String sku);

//Payment, allows adding Payload to the transaction
public void buyItem(String sku, String developerPayload);

// Start buying a product with a request object
// Allows you to specify additional parameters for making a payment, such payload
public void buyItem(MRGSBillingEntities.MRGSBankPurchaseRequest purchaseRequest);

// Where `sku` - is the identifier of the product, `type` - product type, `developerPayload` - is the line, which will be sent to the MRGS server.

Example:

public void OnPurchaseProduct(){
    // Regular purchase of a product with the addition of a payload
    MRGSBank.Instance.PurchaseProduct("games.my.mrgs.framework.consumable", "{\"item\": 123,\"store\": 546}");

    // Purchase using a special payment request object with additional parameters
    var purchaseRequest = new MRGSBankPurchaseRequest("games.my.mrgs.framework.subscription", "{\"item\": 123,\"store\": 546}");
    purchaseRequest.iOS.DiscountIdentifier = "MRGSPromo_5";
    purchaseRequest.iOS.SimulatesAskToBuyInSandbox = true;

    MRGSBank.Instance.PurchaseProduct(purchaseRequest);
}
- (IBAction)buyButtonPressed:(UIButton *)sender {
    // Regular purchase of a product with the addition of a payload
    [[MRGSBank sharedInstance] purchaseProduct:@"games.my.mrgs.framework.consumable" withDeveloperPayload:@"{\"item\": 123,\"store\": 456}"];

    // Purchase using a special payment request object with additional parameters
    MRGSBankPurchaseRequest* request = [MRGSBankPurchaseRequest requestWithProductIdentifier:@"games.my.mrgs.framework.subscription" developerPayload:@"{\"item\": 123,\"store\": 546}"];
    request.discountIdentifier = @"MRGSPromo_5";
    request.simulatesAskToBuyInSandbox = true;
    [[MRGSBank sharedInstance] purchaseProductWithRequest:request];
}
import games.my.mrgs.billing.MRGSBilling;
import games.my.mrgs.billing.MRGSBillingEntities.MRGSBankPurchaseRequest;

public void onPurchaseProduct(){
    final String sku = "games.my.mrgs.purchase1";
    final String devPayload = "{\"item\": 123,\"store\": 456}";

    // Common purchase request.
    MRGSBilling.getInstance().buyItem(sku, devPayload);

    // Create a request
    final MRGSBankPurchaseRequest request = new MRGSBankPurchaseRequest(sku);
    // Create a request with developer payload.
    final MRGSBankPurchaseRequest request = new MRGSBankPurchaseRequest(sku, devPayload);

    MRGSBilling.getInstance().buyItem(request);
}

The user will see the dialogue of initiating the product purchasing. If the user agrees, and the application store takes the payment for the product, the MRGS client receives the order data and send is to the server for validation. The validation process is presented in the description of the structure work section. This approach ensures the maximum protection from hacking and faking. Simultaneously with sending with the payment data to the server, the MRGS client sends the payment data to the MyTracker SDK, which transfers data to the MyTracker server for additional validation.

After validation, we call the methods of the delegate MRGSBankDelegate, depending on the result of the check, or the payment (the user could click the cancel button, or the validation server might not be available), with a special object of the class MRGSBankPurchaseResult, which describes the result of the payment:

// Protocol method, called in case of successful completion of the payment.
// purchase - Detailed information about the payment, including transaction description, selected discounts, purchased product, payload, and more.
void OnReceiveSuccessfulPurchase(MRGSBankPurchaseResult purchase);

// Protocol method, called in case of unsuccessful payment completion
// purchase - Detailed information about the payment, including the description of the transaction, the selected discounts, the purchased product, the payload, the error that occurred, and more.
void OnReceiveFailedPurchase(MRGSBankPurchaseResult purchase);

// Protocol method, called in case of a hung payment. This can happen if, when making a purchase, a child needs to ask the parent's permission to purchase (iOS),
// either the user needs to be given time to put money on the card (Android), or the Internet was lost during validation, and the validation will be tried again later.
// Such events do not require additional actions from the developers, they only serve to notify the user.
// purchase - Detailed information about the payment, including the description of the transaction, selected discounts, product, payload, error if it occurred (validation, for example), and more.
void OnReceivePendingPurchase(MRGSBankPurchaseResult purchase);

// Protocol method, called if the user cancels the payment.
// purchase - Detailed information about the payment, including the description of the transaction, selected discounts, product, payload, error if it occurred, and more.
void OnReceiveCancelledPurchase(MRGSBankPurchaseResult purchase);

// Protocol method, called when the process of restoring previously purchased non-consumable purchases and active subscriptions is complete.
void OnTransactionsRestoreCompleted();
// Protocol method, called in case of successful completion of the payment.
// purchase - Detailed information about the payment, including transaction description, selected discounts, purchased product, payload, and more.
- (void)didReceiveSuccessfulPurchase:(MRGSBankPurchaseResult* _Nonnull)purchase;

// Protocol method, called in case of unsuccessful payment completion
// purchase - Detailed information about the payment, including the description of the transaction, the selected discounts, the purchased product, the payload, the error that occurred, and more.
- (void)didReceiveFailedPurchase:(MRGSBankPurchaseResult* _Nonnull)purchase;

// Protocol method, called in case of a hung payment. This can happen if, when making a purchase, a child needs to ask the parent for permission to purchase,
// or during validation, the Internet is lost, and the next validation attempt will be later.
// Such events do not require additional actions from the developers, they only serve to notify the user.
// purchase - Detailed information about the payment, including the description of the transaction, selected discounts, product, payload, error if it occurred (validation, for example) and more.
- (void)didReceivePendingPurchase:(MRGSBankPurchaseResult* _Nonnull)purchase;

// Protocol method, called if the user cancels the payment.
// purchase - Detailed information about the payment, including the description of the transaction, selected discounts, product, payload, error if it occurred, and more.
- (void)didReceiveCancelledPurchase:(MRGSBankPurchaseResult* _Nonnull)purchase;

// Protocol method, called when the process of restoring previously purchased non-consumable purchases and active subscriptions is complete.
- (void)didCompleteTransactionsRestore;
// Interface method, called in case of successful completion of the payment.
// purchase - Detailed information about the payment, including transaction description, selected discounts, purchased product, payload, and more.
void onReceiveSuccessfulPurchase(MRGSBillingEntities.MRGSBankPurchaseResult purchase);
// Interface method, called in case of unsuccessful payment completion
// purchase - Detailed information about the payment, including the description of the transaction, the selected discounts, the purchased product, the payload, the error that occurred, and more.
void onReceiveFailedPurchase(MRGSBillingEntities.MRGSBankPurchaseResult purchase);
// Interface method, called in case of deferred payment. This can happen if an invoice is issued upon purchase and the user has to pay for it in the payment terminal.
void onReceivePendingPurchase(MRGSBillingEntities.MRGSBankPurchaseResult purchase);
// Interface method, called if the user cancels the payment.
// purchase - Detailed information about the payment, including the description of the transaction, selected discounts, product, payload, error if it occurred, and more.
void onReceiveCancelledPurchase(MRGSBillingEntities.MRGSBankPurchaseResult purchase);
// Interface method, called when the process of restoring previously purchased non-consumable purchases and active subscriptions is complete.
void onTransactionsRestoreCompleted();
What is onReceivePendingPurchase on Android

This method can be called only on Android with the Google Play application store. In some countries, where credit and debit cards are not widely spread, the user can make a payment, receive a Google receipt, which will further by paid in a special booth. In case of such payment, Google will return the Purchase is pending status, i.e. the purchase is waiting for being paid. After calling the purchaseIsPending method, application may inform the player, that the game currency will be received after completing the payment. As soon as the user pays the check, product is received. Read the Restoring purchases section, where we describe how the game client learns about receiving the payment.

What is onReceivePendingPurchase on iOS

This method can be called if the child has parental control enabled and needs to receive confirmation from the parent to purchase the product. After sending the request to the parent, this method will be called. After the parent agrees or rejects the request, the transaction will be returned to the Sucessfull or Failed delegate.

Important

If the payment went through, but the MRGS SDK was unable to deliver the data to the server (the Internet was lost, our server is unavailable, etc.), then the MRGS SDK will call the onReceivePendingPurchase method indicating an error with a specific code, but as soon as it is possible to receive a response from the server, we will call the delegate method again, according to the server's response. We advise you in such a situation to show the user a window explaining the situation.

Error codes

In case an error occurs, it will be in the purchase.error field. First, you can check where the error is from (Android / Apple / MRGS), for this you can compare purchase.error.domain with domain constants (see the class code for more details). If this is an error from MRGS, you can understand what it means, for this we give below a list of error codes with a description:

// Unknown error
Unknown = 0,
// Internal unclassifiable error
InnerError,
// The error indicates that purchases are not available for the user
// UserID is not set or checking the availability of payments returns false
PurchasesAreUnavailableForUser,
// Invalid product ID passed (not loaded before using the `requestProductsInfo` method)
InvalidProductIdentifier,
// An error when purchasing a subscription with a discount on iOS indicates that the MRGS server is unavailable and that such a purchase is impossible
DiscountSignatureRequestFailed,
// During validation, it was received that the transaction is not real, most likely it is fraud
ValidationFailed_PurchaseIsInvalid,

// Error coming when the delegate method `ReceivePendingPurchase` is called
// Indicates that the MRGS server is not available, and the validation will be performed later (on restart)
// We recommend showing the user a message about this
ValidationIsPending_ServerUnreachable,

Peculiarities of having a transactionID on Android and iOS

It is worth noting that on Android, transactionId is issued only when money is debited from the user, so if there is a billing error (no internet, google could not process the transaction, etc.), the system does not issue transactionId, and the Transaction object will be empty in the payment error. It can be filled in only if the purchase was successful in the system, but did not pass validation with us (fake check, etc.), then FailedPurchase will come, and it will contain the Transaction object, because the system issued it, it's just not valid. In iOS, transactionId is issued at the start of the payment, so for any result of the transaction, the Transaction object will exist.

DeveloperPaylod has a size limit of 255 bytes

Step 6. Informing about a purchase delivery🔗

There are two types of closing payments - in the platform store (product consumption), and in the MRGS system. After successful validation of the payment on the client, the payment in the store is closed (thus, subsequent restoration of purchases, for example, will no longer return the payment data). Then, the project needs to close the payment in the MRGS system. This is done so that the project is guaranteed to receive information about the purchase.

  • If the application has a server, then our system will go to the url of your server specified in the settings with information about the payment, and if your server responds successfully to this message, the payment is considered closed. Otherwise, our server will continue to make requests to the specified url with some frequency.
  • If the application is only a client application and the reward is issued locally, in this case you must use the method below. All unclosed payments will be sent to MRGSServerDataDelegate, that is, if the reward was not issued on the previous launch, the payment will not be lost (an important clarification - in the application settings there is an option “auto-close payments” - in this case, all unclosed payments that were given to MRGSServerDataDelegate will be considered private, but we do not recommend this approach).
// Setting transaction data
public abstract void NotifyMRGSAboutConsume(string sku, string transactionId);
//Transaction is being sent
- (void)notifyMRGSAboutConsume:(SKPaymentTransaction*)transaction;

//Transaction data is being sent
- (void)notifyMRGSAboutConsume:(NSString*)transactionId andProductIdentifier:(NSString*)productIdentifier;
import games.my.mrgs.billing.MRGSBilling;

MRGSBilling.getInstance().notifyMRGSAboutConsume(String sku, String transactionId);

Step 7. Restoring purchases🔗

7.1 Restoring purchases on Android🔗

Google recommends calling the restorePurchase method upon every launch of the application. There are several reasons for that:

  1. If the payment has not been correctly processed and cancelled (connection problem, application froze, etc.), after calling the restorePurchase method, the MRGS SDK will receive this payment and send it to the MRGS server to complete the validation process and the purchase will be correctly finished, and the game will receive data on the purchased product.
  2. In Google Play you can activate the promotional code on the store page to receive a specific product. The game learns about this purchase only after calling the restorePurchase method.
  3. If the game has not yet been released, the developer can set up the product in the Google Play console, which will be credited to the player for pre-installation immediately after installing the game. The game learns about crediting this product after calling the restorePurchase method.
  4. If the user has subscribed or bought a non-consumable product, the only way to find out about the availability of goods after reinstalling the game or when changing the game account is to call the restorePurchase method.

Automatic restoring

If you want the restorePurchase method to be called automatically immediately after loading the product list, you need to call the autoRestorePurchase method before products request, for example right after sdk initialization. This is the recommended approach.

7.2 Restoring purchases on iOS🔗

On iOS, Apple recommends adding a special Restore purchases" button, and calling the restorePurchase method only when pressing it. Calling this method is necessary only in one case.

  • If the user has subscribed or bought a non-consumable product, the only way to find out about the availability of the product after reinstalling the game or when changing the game account is to call the restorePurchase method.

Important

You should not call this method randomly on iOS, without the intention of the user, since every time when this method is called, the user will have to enter his AppleID and password. According to the guidelines, you only need to make a button.

public void RestorePurchases()
[[MRGSBank sharedInstance] restorePurchase];
import games.my.mrgs.billing.MRGSBilling;

MRGSBilling.getInstance().restoreTransaction();

Congratulations, you have implemented the work with payments into your application!

What should be done, if we want only to send the statistics to MRGS?🔗

Then you do need to use the current module, You can read more amout payment tracking in the "SDK intergation" section -> "Step 4 - Add payment tracking".

Example🔗

MRGSBank.Instance.Delegate = this;

public void OnGetProductsInfoClick()
{
    if (MRGSBank.Instance.IsBankAvailable())
    {
        var productsRequest = new MRGSBankProductsRequest();

        var consumables = new List<string> { "games.my.mrgs.framework.consumable", "..." };
        productsRequest.Add(consumables, MRGSBankProductType.Consumable);

        productsRequest.Add("games.my.mrgs.framework.non_consumable", MRGSBankProductType.NonConsumable);

        productsRequest.Add("games.my.mrgs.framework.subscription", MRGSBankProductType.Subscription);

        MRGSBank.Instance.RequestProductsInfo(productsRequest);

    } else Debug.Log("Billing is not available!!!");
}

public void OnPurchaseProductClick()
{
    if (MRGSBank.Instance.IsBankAvailable())
    {
        MRGSBank.Instance.PurchaseProduct("games.my.mrgs.framework.consumable", "{\"item\": 123,\"store\": 546}");

        // Purchase using a special payment request object with additional parameters
        // var purchaseRequest = new MRGSBankPurchaseRequest("games.my.mrgs.framework.subscription", "{\"item\": 123,\"store\": 546}");
        // purchaseRequest.iOS.DiscountIdentifier = "MRGSPromo_5";
        // purchaseRequest.iOS.SimulatesAskToBuyInSandbox = true;
        // MRGSBank.Instance.PurchaseProduct(purchaseRequest);
    }
    else Debug.Log("Billing is not available!!!");
}

public void OnRestorePurchaseClick(){
    MRGSBank.Instance.RestorePurchases();
}

// IMRGSBankDelegate
public void OnReceiveProductsResponse(MRGSBankProductsResponse productsResponse)
{
    Debug.Log("OnReceiveProductsResponse: " + productsResponse);
    foreach(MRGSBankProduct validProduct in productsResponse.ValidProducts)
    {
        Debug.Log("Product: " + validProduct);
    }
    foreach (string invalidProductId in productsResponse.InvalidProductsIds)
    {
        Debug.Log("Invalid product identifier: " + invalidProductId);
    }
}

public void OnReceiveProductsError(MRGSBankProductsResponse productsResponse)
{
    Debug.Log("OnReceiveProductsError: " + productsResponse);
    Debug.Log("Received error: " + productsResponse.RequestError);
}

public void OnReceiveSuccessfulPurchase(MRGSBankPurchaseResult purchase)
{
    Debug.Log("OnReceiveSuccessfulPurchase: " + purchase);
}

public void OnReceiveFailedPurchase(MRGSBankPurchaseResult purchase)
{
    Debug.Log("OnReceiveFailedPurchase: " + purchase);
}

public void OnReceivePendingPurchase(MRGSBankPurchaseResult purchase)
{
    Debug.Log("OnReceivePendingPurchase: " + purchase);
}

public void OnReceiveCancelledPurchase(MRGSBankPurchaseResult purchase)
{
    Debug.Log("OnReceiveCancelledPurchase: " + purchase);
}

public void OnTransactionsRestoreCompleted()
{
    Debug.Log("OnTransactionsRestoreCompleted");
}
[MRGSBank sharedInstance].delegate = self;

-(void)getProductsInfo{
    if (![[MRGSBank sharedInstance] isBankAvailable]) return;

    MRGSBankProductsRequest* request = [[MRGSBankProductsRequest alloc] init];

    NSArray* consumables = @[ @"games.my.mrgs.framework.consumable", @"..." ];
    [request addProductsIdentifiers:consumables withType:kMRGSBankProductTypeConsumable];

    [request addProductIdentifier:@"games.my.mrgs.framework.non_consumable" withType:kMRGSBankProductTypeNonConsumable];

    [request addProductIdentifier:@"games.my.mrgs.framework.subscription" withType:kMRGSBankProductTypeSubscription];

    [[MRGSBank sharedInstance] requestProductsInfo:request];
}

- (IBAction)buyButtonPressed:(UIButton *)sender {
    if ([[MRGSBank sharedInstance] isBankAvailable]){
        [[MRGSBank sharedInstance] purchaseProduct:@"games.my.mrgs.framework.consumable" withDeveloperPayload:@"{\"item\": 123,\"store\": 456}"];

        // Purchase using a special payment request object with additional parameters
        //MRGSBankPurchaseRequest* request = [MRGSBankPurchaseRequest requestWithProductIdentifier:@"games.my.mrgs.framework.subscription" developerPayload:@"{\"item\": 123,\"store\": 546}"];
        //request.discountIdentifier = @"MRGSPromo_5";
        //request.simulatesAskToBuyInSandbox = true;
        //[[MRGSBank sharedInstance] purchaseProductWithRequest:request];
    }else{NSLog(@"Bank is unavailable");}
}

- (IBAction)restoreButtonPressed:(UIButton *)sender {
    [[MRGSBank sharedInstance] restorePurchase];
}

// MRGSBankDelegate
- (void)didReceiveProductsResponse:(MRGSBankProductsResponse * _Nonnull)response {
    NSLog(@"didReceiveProductsResponse: %@", response);

    for (MRGSBankProduct *validProduct in response.validProducts) {
        NSLog(@"Valid product: %@", validProduct);
        NSLog(@"Valid product price : %@", validProduct.priceInformation.localizedPrice);
    }

    for (NSString *invalidProductId in response.invalidProductsIds) {
        NSLog(@"Invalid product: %@", invalidProductId);
    }
}
- (void)didReceiveProductsError:(MRGSBankProductsResponse * _Nonnull)response {
    NSLog(@"didReceiveProductsError: %@", response);
    NSLog(@"received error: %@", response.requestError);
}

- (void)didReceiveSuccessfulPurchase:(MRGSBankPurchaseResult * _Nonnull)purchase {
    NSLog(@"didReceiveSuccessfulPurchase: %@", purchase);
}
- (void)didReceiveFailedPurchase:(MRGSBankPurchaseResult * _Nonnull)purchase {
    NSLog(@"didReceiveFailedPurchase: %@", purchase);
}
- (void)didReceiveCancelledPurchase:(MRGSBankPurchaseResult * _Nonnull)purchase {
    NSLog(@"didReceiveCancelledPurchase: %@", purchase);
}
- (void)didReceivePendingPurchase:(MRGSBankPurchaseResult * _Nonnull)purchase {
    NSLog(@"didReceivePendingPurchase: %@", purchase);
}

- (void)didCompleteTransactionsRestore {
    NSLog(@"didCompleteTransactionsRestore");
}
import android.widget.Toast;

import androidx.annotation.NonNull;

import java.util.Arrays;

import games.my.mrgs.billing.MRGSBilling;
import games.my.mrgs.billing.MRGSBillingDelegate;
import games.my.mrgs.billing.MRGSBillingEntities.MRGSBankProductsRequest;
import games.my.mrgs.billing.MRGSBillingEntities.MRGSBankProductsResponse;
import games.my.mrgs.billing.MRGSBillingEntities.MRGSBankPurchaseRequest;
import games.my.mrgs.billing.MRGSBillingEntities.MRGSBankPurchaseResult;
import games.my.mrgs.billing.MRGSBillingEntities.MRGSBankTransaction;
import games.my.mrgs.billing.MRGSBillingProduct;

private void loadProducts() {
    MRGSBilling.getInstance().setDelegate(this);
    if (MRGSBilling.getInstance().isBillingAvailable(this)) {
        final MRGSBankProductsRequest request = new MRGSBankProductsRequest();
        request.add(Arrays.asList("games.my.mrgs.purchase1", "games.my.mrgs.purchase2"), MRGSBillingProduct.CONS);

        MRGSBilling.getInstance().requestProductsInfo(request);
    } else {
        Toast.makeText(this, "Billing is not supported", Toast.LENGTH_LONG).show();
    }
}

private void purchaseItem(int position) {
    if (position >= 0 && position < productList.size()) {
        final MRGSBillingProduct i = productList.getValidItems().get(position);
        final MRGSBankPurchaseRequest request = new MRGSBankPurchaseRequest(i.getSku(), "Test Payload");
        MRGSBilling.getInstance().buyItem(request);
    }
}

@Override
public void onReceiveProductsResponse(@NonNull MRGSBankProductsResponse response) {
    productList = response.getValidItems();
}

@Override
public void onReceiveProductsError(@NonNull MRGSBankProductsResponse response) {
    showMessage("Error loading products: " + response.getError());
}

@Override
public void onReceiveSuccessfulPurchase(@NonNull MRGSBankPurchaseResult result) {
    final MRGSBillingProduct item = result.getPurchaseItem();
    if (item.getType().equals(MRGSBillingProduct.SUBS)) {
        purchasedProducts.add(item);
    }
    showMessage(String.format("%s %s", item.getSku(), "purchased"));
    final MRGSBankTransaction transaction = result.getTransaction();
    MRGSBilling.getInstance().notifyMRGSAboutConsume(item.getSku(), transaction.getTransactionIdentifier());
}

@Override
public void onReceiveFailedPurchase(@NonNull MRGSBankPurchaseResult result) {
    String sku = "";
    MRGSBillingProduct item = result.getPurchaseItem();
    if (item != null)
        sku = item.getSku();
    String error = "Error purchasing item "  + sku + result.getError();
    showMessage(error);
    MRGSCrashReports.sendHandleException(error, "PurchaseFail");
}

@Override
public void onReceivePendingPurchase(@NonNull MRGSBankPurchaseResult result) {
    Toast.makeText(this, "Item is pending purchase: " + result.getProductIdentifier(), Toast.LENGTH_LONG).show();
}

@Override
public void onReceiveCancelledPurchase(@NonNull MRGSBankPurchaseResult purchase) {
    showMessage("User canceled purchase: " + purchase.getPurchaseItem());
}

@Override
public void onTransactionsRestoreCompleted() {
    showMessage("Restore Complete");
}

Additional features🔗

MRGS provides a large number of additional functions when working with payments. For more details, see the "additional features" section:


Last update: 2025-01-21
Created: 2020-02-17