Перейти к содержанию

Работа с платежами в SDK🔗

Для интеграции платежей необходимо выполнить несколько простых шагов

Шаг 1. Импортируйте модуль платежей🔗

  • Добавьте модуль MRGSBank (MRGSBilling)

    Unity:

    Добавление в проект (общая инструкция)

    Шаг 1. Добавьте источники

    Для того, чтобы добавить MRGS в проект через Unity Package Manager (доступно с Unity 2018+) просто добавьте в файл Packages/manifest.json раздел scopedRegistries, добавив в него следующую запись:

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

    Альтернативно, можно нажать Edit -> Project Settings -> Package Manager -> '+' in scoped registry section, и заполнить поля в соответствии с данными выше.

    Шаг 2. Добавьте зависимость

    • Нажмите Window -> Package Manager -> select 'Packages: MyRegistries' from dropdown list, выберите пакет MRGSBank из списка, затем нажмите "Install"
    • Импортируйте модуль: using MRGS;
    • Загрузите последнюю версию библиотеки. Распакуйте архив.
    • (Для интеграции unitypackage) В Unity нажмите Assets -> Import Package -> Custom Package, и выберите пакет games.my.mrgs.bank.unitypackage из скачанного архива.
    • (Для интеграции tgz) В Unity нажмите Window -> Package Manager -> '+' -> Add package from tarball, и выберите пакет games.my.mrgs.bank-<version>.tgz из скачанного архива.
    • Импортируйте модуль: using MRGS;

    iOS:

    Добавление в проект (общая инструкция)

    Шаг 1. Добавьте зависимости

    Через Package collection

    • В Xcode выберите File > Add Packages
    • Выберите «+» > "Add Swift Package Collection"
    • Вставьте URL: https://mrgs-nexus.my.games/repository/ios-sdks/MRGSPackageCollection.json
    • Выберите модуль MRGSBank из "MRGS Package Collection".
    • Или вы можете выбрать пакет «MRGS» из "MRGS Package Collection" (содержит все модули mrgs в качестве продуктов), а затем выбрать только продукт "MRGS/Bank".

    Отдельными пакетами

    • В Xcode выберите File > Add Packages
    • В строке поиска в правом верхнем углу вставьте URL: https://mrgs-gitea.my.games/mrgs/mrgsbank-ios-sdk.git
    • Добавьте модуль в свой проект
    • Или вы можете вставить URL https://mrgs-gitea.my.games/mrgs/ios-sdks.git, чтобы подключить пакет "MRGS", который содержит все модули mrgs в качестве продуктов, а затем выбрать только продукт "MRGS/Bank".

    Шаг 2. Добавьте поддержку категорий ObjectiveC

    • В настройках проекта установите флаг -ObjC в поле "Other linker Flags".
    • Импортируйте модуль в коде: @import MRGServiceKit; или @import MRGSBank; или #import <MRGSBank/MRGSBank.h>

    Шаг 1. Добавьте источники

    В вашем podfile добавьте источники в начало файла:

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

    Шаг 2. Добавьте зависимости

    В target добавьте последнюю версию MRGSBank:

    Чтобы добавить через subspecs:

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

    Для добавления через отдельные модули:

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

    Чтобы добавить все модули mrgs:

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

    Шаг 3. Установите зависимости

    • Выполните pod install (или pod install --repo-update если необходимо)
    • Импортируйте модуль в коде: @import MRGServiceKit; или @import MRGSBank; или #import <MRGSBank/MRGSBank.h>

    Шаг 1. Добавьте зависимости

    Добавьте зависимость в ваш Cartfile:

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

    Шаг 2. Установите зависимости

    • Выполните carthage update --use-xcframeworks
    • Добавьте загруженные фреймворки в свой проект (убедитесь, что опция "do not embed" включена)
    • В настройках проекта установите флаг -ObjC в поле "Other linker Flags".
    • Импортируйте модуль в коде: @import MRGServiceKit; или @import MRGSBank; или #import <MRGSBank/MRGSBank.h>
    • Загрузите последнюю версию библиотеки. Распакуйте архив.
    • Добавьте MRGSBank.xcframework из скачанного архива в ваш проект (Перетащите библиотеки в раздел "Linked frameworks and Libraries") (для совместимости в архиве также находится MRGSBank.framework - fat framework старого вида)

    • В настройках проекта установите флаг -ObjC в поле "Other linker Flags".

    • Импортируйте модуль в коде: @import MRGSBank; или #import <MRGSBank/MRGSBank.h>
    • Также, вы можете добавить из архива файлы MRGServiceKit.h и module.modulemap в свой проект, либо в настройках проекта укажите путь до них в разделе Build Settings -> Header search paths. Теперь вместо импорта каждого из наших фреймворков по отдельности, вы можете импортировать только один заголовочный файл: @import MRGServiceKit;

    Android:

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

    Скопируйте файл MRGSBilling.aar в директорию libs вашего проекта. Добавьте необходимые зависимости в файл build.gradle

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

Шаг 2. Предварительная настройка🔗

Шаг 2.1. Настройка параметров🔗

При старте SDK MRGS необходимо указать типа магазина для платежей, а так же включить проведение платежей полностью через 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>];
    //Настройка параметров MRGS
    // ...

    //Включение обработки платежей
    mrgsParams.disablePaymentsCheck = false;
    mrgsParams.automaticPaymentTracking = false;

    //Настройка Внешних SDK и инициализация 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);
    }
}

Не забудьте включить обработку платежей для iOS

Обратите внимание на флаги DisablePaymentsCheck и AutomaticPaymentTracking в коде выше для iOS и Unity. Если вы хотите использовать модуль MRGSBilling для совершения платежей, они должны быть выставлены в false

Обязательно укажите магазин приложений

Для платформы Android крайне важно указать магазин, который вы хотите использовать для совершения платежей, например "google". Если тип магазина не будет указан, произойдет исключение при попытке совершить платеж

Поддержка сторонних магазинов (Samsung, Amazon, Huawei/AppTouch) на Android

Для поддержки сторонних магазинов необходимо указать желаемый магазин при старте SDK (пункт выше), а также добавить необходимые SDK в проект. Для Huawei/AppTouch необходимо добавить в gradle com.huawei.hms:iap:6.4.0.301, SDK для Samsung и Amazon мы поставляем в архиве в корне в папке Extra (в Unity они дополнительно лежат в модуле MRGSBank/Extra, но есть возможность добавить их в проект с помощью меню Window/MRGS/<SDK>)

Шаг 2.2. Установка игрового идентификатора пользователя🔗

Для корректной работы платежей необходимо установить игровой идентификатор пользователя. Про установку идентификатора пользователя читайте в разделе "Подключение SDK" Unity, iOS, Android

Шаг 2.3. Проверка необходимых условий🔗

Убедитесь, что вы правильно настроили окружение на сайте MRGS, а именно:

  • Убедитесь, что на сайте MRGS добавлены необходимые ключи для валидации платежей.
  • Убедитесь, что в разделе товаров вашего приложения (иконка с корзиной) на сайте MRGS добавлены все SKU и цены, с которыми вы собираетесь работать.
Зачем заполнять таблицу с товарами

Для конвертации валют внутри платежей MRGS использует API сайта ЦБ РФ. Если, указанной в платеже, валюты нет на сайте ЦБ РФ, то MRGS возьмет цену в USD из таблицы SKU. Важно Данная информация используется только при обработке платежей на сервере. SDK MRGS на клиенте не получает этот список SKU и всегда загружает информацию по продуктам только из магазина приложений.

В случае, если пришедшего платежа нет в нашей админке и мы не смогли сконвертировать пришедшую валюту в доллары через api, то тогда в MRGS и Террабанк попадет 0. MyTracker.MyUA, 1Link собирает платежи отдельно от MRGS, через MyTracker.

Шаг 3. Установка делегата и проверка доступности🔗

Перед работой с банком желательно проверить его доступность для пользователя (покупки могут быть запрещены для аккаунта), а также необходимо установить делегат для получения уведомлений о загрузке и покупках:

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

// Setting a delegate
// Conforms to IMRGSBankDelegate protocol
MRGSBank.Instance.Delegate = this;
//Проверка доступности
[[MRGSBank sharedInstance] isBankAvailable];

//Установка делегата
[MRGSBank sharedInstance].delegate = self; //где self - объект реализующий протокол MRGSBankDelegate
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);

Рекомендация

Для Android рекомендуется использовать асинхронный метод IsBankAvailable(callback)/ isBillingAvailable(context, callback).

Шаг 4. Получение информации о доступных продуктах🔗

В первую очередь необходимо загрузить список доступных продуктов (те, которые были заведены) с серверов магазина. Для этого необходимо:

  1. Создать запрос с указанием необходимых продуктов и их типов.
  2. Отправить запрос.

Откуда MRGS SDK загружает информацию о продуктах

Информация о продуктах загружается из магазинов Google Play/AppStore, а не с сервера MRGS. Если какой-то из продуктов не загружается, возможно он некорректно настроен в Google Play/AppStore.

Для запроса используется специальный класс MRGSBankProductsRequest, который хранит в себе список продуктов и их типов (MRGSBankProductType, может быть consumable, non-consumable, subscription). Позволяет добавить как конкретный продукт с его типом, так и массив идентификаторов продуктов определенного типа. Для создания запроса продуктов необходимо вызвать:

// Создание запроса
var productsRequest = new MRGSBankProductsRequest();
// Наполнение запроса продуктами
var consumables = new List<string> { "games.my.mrgs.framework.consumable", "..." };
// Добавление массива продуктов одного типа сразу
productsRequest.Add(productsIdentifiers: consumables, type: MRGSBankProductType.Consumable);
// Добавление конкретного продукта
productsRequest.Add(productIdentifier: "games.my.mrgs.framework.fake_product_id", type: MRGSBankProductType.Consumable);
// Создание запроса
MRGSBankProductsRequest* request = [[MRGSBankProductsRequest alloc] init];
// Наполнение запроса продуктами
NSArray* consumables = @[ @"games.my.mrgs.framework.consumable", @"..." ];
// Добавление массива продуктов одного типа сразу
[request addProductsIdentifiers:consumables withType:kMRGSBankProductTypeConsumable];
// Добавление конкретного продукта
[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);

После подготовки запроса, воспользуйтесь методом загрузки продуктов:

// Where productsRequest is the generated request.
MRGSBank.Instance.RequestProductsInfo(productsRequest);
[[MRGSBank sharedInstance] requestProductsInfo:request]; //где request - созданный запрос.
import games.my.mrgs.billing.MRGSBilling;

MRGSBilling.getInstance().requestProductsInfo(request); //где request - созданный запрос.

Оформление подписок на Android

Оформить подписку можно только при использовании магазина приложений Google Play. Подписки не поддерживаются для магазинов Amazon и Samsung Galaxy Store

Пример загрузки списка продуктов:

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];
// Добавление продукта по отдельности, а не в массиве          
[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);

После ответа от сервера магазина будет вызван один из методов делегата MRGSBankDelegate, говорящий об успешной или неуспешной загрузке продуктов, а также будут заполнены поля LoadedProducts и LoadedProductsIdentifiers в объекте нашего банка:

// 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);
}
// Успешная загрузка продуктов
- (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);
    }
}

// Неуспешная загрузка продуктов
- (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());
}
Что если список продуктов пуст? (Android)

Если в делегат вернулся пустой список, это может быть показателем сразу нескольких проблем.

  1. Проверьте что пользователь авторизован в магазине приложений (туда можно зайти, скачать игру/приложение)
  2. Проверьте что системе (Android) только один Google аккаунт. Бывали случаи, когда в системе был заведен второй аккаунт с неправильным (истекшим) паролем и список продуктов не загружался (хотя с активным аккаунтом все было в порядке)
  3. Проверьте, что к аккаунту привязана банковская карта оплаты
  4. Проверьте, что приложение (Android) подписано боевым сертификатом, таким же, с каким приложение загружено в консоль Google Play
Что если список продуктов пуст? (iOS)

Если в делегат вернулся пустой список, это может быть показателем сразу нескольких проблем. Проверьте следующие пункты:

  • 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)

Скорость загрузки продуктов на iOS

В случае, если проект сразу запрашивает большое количество товаров (200+) в одном запросе, то время выполнения запроса может быть довольно большим (Apple разбивает большой запрос на несколько маленьких. Например, если запросить сотню продуктов, происходит примерно 10 запросов на сервера небольшими партиями, поэтому так долго). Разделив продукты на два запроса: "приоритетные" 10-15 штук и "все остальные", можно значительно ускорить готовность магазина.

После получения списка доступных продуктов, Вы можете показать совершать платежи.

Шаг 5. Проведение платежа🔗

Как только пользователь выбрал продукт для покупки, Вам необходимо вызвать всего лишь одну функцию для проведения оплаты и валидации. MRGS поддерживает следующие функции:

// 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);
// Начало покупки продукта с указанным идентификатором
- (void)purchaseProduct:(NSString* _Nonnull)productIdentifier;

// Начало покупки продукта с указанным идентификатором
// Опционально можно добавить payload к платежу, который придет в результате на клиент и на сервер
- (void)purchaseProduct:(NSString* _Nonnull)productIdentifier withDeveloperPayload:(NSString* _Nullable)payload;

// Начало покупки продукта с объектом запроса
// Позволяет указать дополнительные параметры для совершения платежа, такие как количество, идентификатор скидки, payload и другое.
- (void)purchaseProductWithRequest:(MRGSBankPurchaseRequest* _Nonnull)purchaseRequest;
//Покупка
public void buyItem(String sku);

//Покупка, позволяет добавить Payload к платежу
public void buyItem(String sku, String developerPayload);

//Начало покупки продукта с объектом запроса
//Позволяет указать дополнительные параметры для совершения платежа, такие как developerPayload.
public void buyItem(MRGSBillingEntities.MRGSBankPurchaseRequest purchaseRequest);

// Где `sku` - идентификатор продукта, `developerPayload` - Строка, которая будет отправлена на сервер MRGS.

Пример:

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 {
    // Обычная покупка продукта с добавлением пейлоада
    [[MRGSBank sharedInstance] purchaseProduct:@"games.my.mrgs.framework.consumable" withDeveloperPayload:@"{\"item\": 123,\"store\": 456}"];

    // Покупка с использованием специального объекта запроса платежа с дополнительными параметрами
    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);
}

Пользователь увидит диалог начала покупки товара. Если пользователь соглашается и магазин приложений взимает оплату за товар, MRGS клиент получает данные по платежу и отправляет их на сервер для валидации. Процесс валидации приведен в разделе описания структуры работы. Такой подход гарантирует максимальную защиту от взломов и подделок. Одновременно с отправкой данных по платежу на сервер, MRGS клиент передает данные о платеже в SDK MyTracker, который в свою очередь отправляет данные на сервер MyTracker где они также проходят процесс валидации.

После проведения валидации, мы вызываем методы делегата MRGSBankDelegate, в зависимости от результата проверки, или проведения платежа (пользователь мог нажать кнопку отмены, или сервер валидации может быть недоступен), со специальным объектом класса MRGSBankPurchaseResult, который описывает результат платежа:

// Метод протокола, вызывается в случае успешного завершения платежа.
// purchase - Подробная информация о платеже, включая описание транзакции, выбранные скидки, купленный продукт, payload и другое.
void OnReceiveSuccessfulPurchase(MRGSBankPurchaseResult purchase);

// Метод протокола, вызывается в случае неуспешного завершения платежа
// purchase - Подробная информация о платеже, включая описание транзакции, выбранные скидки, купленный продукт, payload, ошибку, которая произошла и другое.
void OnReceiveFailedPurchase(MRGSBankPurchaseResult purchase);

// Метод протокола, вызывается в случае зависшего платежа. Такое может произойти если при покупке ребенку нужно спросить разрешение на покупку у родителя (iOS),
// или пользователю нужно дать время положить деньги на карту (Android), или при валидации пропал интернет, и повторная попытка валидации будет позже.
// Такие события не требуют дополнительных действий со стороны разработчиков, они служат только для оповещения пользователя.
// purchase - Подробная информация о платеже, включая описание транзакции, выбранные скидки, продукт, payload, ошибку, если она произошла (валидации, например)  и другое.
void OnReceivePendingPurchase(MRGSBankPurchaseResult purchase);

// Метод протокола, вызывается в случае отмены пользователем платежа.
// purchase - Подробная информация о платеже, включая описание транзакции, выбранные скидки, продукт, payload, ошибку, если она произошла и другое.
void OnReceiveCancelledPurchase(MRGSBankPurchaseResult purchase);

// Метод протокола, вызывается, когда процесс восстановления ранее купленных непотребляемых покупок и активных подписок закончен.
void OnTransactionsRestoreCompleted();
// Метод протокола, вызывается в случае успешного завершения платежа.
// purchase - Подробная информация о платеже, включая описание транзакции, выбранные скидки, купленный продукт, payload и другое.
- (void)didReceiveSuccessfulPurchase:(MRGSBankPurchaseResult* _Nonnull)purchase;

// Метод протокола, вызывается в случае неуспешного завершения платежа
// purchase - Подробная информация о платеже, включая описание транзакции, выбранные скидки, купленный продукт, payload, ошибку, которая произошла и другое.
- (void)didReceiveFailedPurchase:(MRGSBankPurchaseResult* _Nonnull)purchase;

// Метод протокола, вызывается в случае зависшего платежа. Такое может произойти если при покупке ребенку нужно спросить разрешение на покупку у родителя,
// или при валидации пропал интернет, и повторная попытка валидации будет позже.
// Такие события не требуют дополнительных действий со стороны разработчиков, они служат только для оповещения пользователя.
// purchase - Подробная информация о платеже, включая описание транзакции, выбранные скидки, продукт, payload, ошибку, если она произошла (валидации, например)  и другое.
- (void)didReceivePendingPurchase:(MRGSBankPurchaseResult* _Nonnull)purchase;

// Метод протокола, вызывается в случае отмены пользователем платежа.
// purchase - Подробная информация о платеже, включая описание транзакции, выбранные скидки, продукт, payload, ошибку, если она произошла и другое.
- (void)didReceiveCancelledPurchase:(MRGSBankPurchaseResult* _Nonnull)purchase;

// Метод протокола, вызывается, когда процесс восстановления ранее купленных непотребляемых покупок и активных подписок закончен.
- (void)didCompleteTransactionsRestore;
// Метод интерфейса, вызывается в случае успешного завершения платежа.
// purchase - Подробная информация о платеже, включая описание транзакции, выбранные скидки, купленный продукт, payload и другое.
void onReceiveSuccessfulPurchase(MRGSBillingEntities.MRGSBankPurchaseResult purchase);
// Метод интерфейса, вызывается в случае неуспешного завершения платежа
// purchase - Подробная информация о платеже, включая описание транзакции, выбранные скидки, купленный продукт, payload, ошибку, которая произошла и другое.
void onReceiveFailedPurchase(MRGSBillingEntities.MRGSBankPurchaseResult purchase);
// Метод интерфейса, вызывается в случае отложенного платежа. Такое может произойти если при покупке выставляется счет и пользователь должен оплатить его в терминале оплаты
void onReceivePendingPurchase(MRGSBillingEntities.MRGSBankPurchaseResult purchase);
// Метод интерфейса, вызывается в случае отмены пользователем платежа.
void onReceiveCancelledPurchase(MRGSBillingEntities.MRGSBankPurchaseResult purchase);
// Метод интерфейса, вызывается, когда процесс восстановления ранее купленных непотребляемых покупок и активных подписок закончен.
void onTransactionsRestoreCompleted();
Что такое onReceivePendingPurchase на Android

Такой метод может быть вызван только на Android с магазином приложений Google Play. В некоторых странах, где мало распространены дебетовые и кредитные карты, пользователь может совершить покупку, получить от Google чек, который в дальнейшем может быть оплачен в специальном киоске. В случае такой системы оплаты, Google вернет статус Purchase is pending, т.е. платеж ожидает завершения оплаты. После вызова метода purchaseIsPending приложение может сообщить игроку, что игровая валюта будет зачислена после завершения оплаты. Как только пользователь оплатит выставленный счет, товар будет зачислен. Прочтите раздел Восстановление покупок, где объясняется как клиент игры узнает о начислении купленного продукта.

Что такое onReceivePendingPurchase на iOS

Такой метод может быть вызван в случае, если у ребенка включен родительский контроль, и для покупки продукта ему нужно получить подтверждение от родителя. После отправки запроса родителю и будет вызван данный метод. После того, как родитель согласится или отклонит запрос, в делегат Successful или Failed снова придет транзакция.

Важно

Если оплата прошла, но MRGS SDK не смог доставить данные на сервер (пропал интернет, наш сервер недоступен, и т.д.), то MRGS SDK вызовет метод onReceivePendingPurchase с указанием ошибки с конкретным кодом, но как только удастся получить ответ от сервера, мы снова вызовем метод делегата, в соответствии с ответом сервера. Мы советуем Вам в такой ситуации показать пользователю окно с объяснением ситуации.

Коды ошибок

В случае, если произошла ошибка, она будет находиться в поле purchase.error. Для начала можно проверить, откуда ошибка (Android/Apple/MRGS), для этого можно сравнить purchase.error.domain с константами доменов (подробнее смотрите в коде класса). В случае, если это ошибка от MRGS, можно понять, что она значит, для этого приводим ниже список кодов ошибок с описанием:

// Неизвестная ошибка
Unknown = 0,
// Внутренняя не классифицируемая ошибка
InnerError,
// Ошибка говорит о том, что покупки недоступны для пользователя
// Не установлен UserID или проверка доступности совершения платежей возвращает false
PurchasesAreUnavailableForUser,
// Передан неверный идентификатор продукта (не загружен до этого с помощью метода `requestProductsInfo`)
InvalidProductIdentifier,
// Ошибка при покупке подписки со скидкой на iOS, говорит о недоступности сервера MRGS и невозможности такой покупки
DiscountSignatureRequestFailed,
// При валидации было получено, что транзакция не настоящая, скорее всего это фрод
ValidationFailed_PurchaseIsInvalid,

// Ошибка, приходящая, когда вызывается метод делегата `ReceivePendingPurchase`
// Говорит о том, что сервер MRGS недоступен, и валидация будет произведена позже (при перезапуске)
// Рекомендуем показать пользователю сообщение об этом
ValidationIsPending_ServerUnreachable,

Особенности наличия transactionID на Android и iOS

Стоит отметить, что на Android transactionId выдается только при списании денег с пользователя, поэтому при ошибке биллинга(нет интернета, google не смог обработать транзакцию, и тд) система не выдает transactionId, и в ошибке платежа объект Transaction будет пустой. Он может быть заполнен только в случае, если покупка прошла успешно в системе, но не прошла валидацию у нас(поддельный чек и др), тогда придет FailedPurchase, и в нем будет объект Transaction, т.к. система его выдала, просто она невалидна. В iOS transactionId выдается при старте платежа, поэтому при любом результате транзакции объект Transaction будет существовать.

DeveloperPayload имеет ограничения на размер в 255 байт

Шаг 6. Информирование о выдаче платежа🔗

Существует два вида закрытия платежей - в магазине платформы (потребление товара), и в системе MRGS. После успешной валидации платежа на клиенте, платеж в магазине закрывается (таким образом, последующее восстановление покупок, например, уже не вернет данных платеж). Затем, проекту нужно закрыть платеж в системе MRGS. Это сделано для того, чтобы проект гарантированной получил информацию о покупке.

  • Если приложение имеет сервер, то наша система будет ходить на указанный в настройках url вашего сервера с информацией о платеже, и в случае, если ваш сервер отвечает успешно на данное сообщение - платеж считается закрытым. Иначе, наш сервер будет дальше делать запросы на указанный url с некоторой периодичностью.
  • Если приложение только клиентское и награда выдается локально - в этом случае необходимо воспользоваться методом ниже. Все незакрытые платежи будут приходить в MRGSServerDataDelegate, то есть если награда не была выдана на предыдущем запуске - платеж не будет потерян (важное уточнение - в настройках приложения есть опция «автоматическое закрытие платежей» - в таком случае все незакрытые платежи, которые были отданы в MRGSServerDataDelegate будут считаться закрытыми, но мы не рекомендуем данный подход).
// Setting transaction data
public abstract void NotifyMRGSAboutConsume(string sku, string transactionId);
//Передается транзакция
- (void)notifyMRGSAboutConsume:(SKPaymentTransaction*)transaction;

//Передаются данные транзакции
- (void)notifyMRGSAboutConsume:(NSString*)transactionId andProductIdentifier:(NSString*)productIdentifier;
import games.my.mrgs.billing.MRGSBilling;

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

Шаг 7. Восстановление покупок🔗

7.1 Восстановление покупок на Android🔗

Google рекомендует вызывать метод restorePurchase при каждом "запуске" приложения. На это есть ряд причин:

  1. Если платеж не был корректно обработан и закрыт (проблема со связью, повисло приложение и т.д.), после вызова метода restorePurchase MRGS SDK получит этот платеж и отправит его на сервер MRGS для завершения процесса валидации и процесс покупки корректно завершиться, а игра получит данные о купленном товаре
  2. В Google Play можно активировать промокод на странице магазина, для получения определенного товара. Игра узнает об этой покупке только после вызова метода restorePurchase
  3. Если игра еще не вышла, разработчик может настроить в консоли Google Play товар, который будет зачислен игроку за предустановку сразу после установки игры. Игра узнает о зачислении этого товара после вызова метода restorePurchase
  4. Если пользователь оформил подписку или купил непотребляемый товар, единственный способ узнать о доступности товара после переустановки игры или при смене игрового аккаунта - это вызвать метод restorePurchase

Автоматическое восстановление

Если вы хотите, чтоб метод restorePurchase вызывался автоматически сразу после загрузки списка продуктов, необходимо вызвать метод autoRestorePurchase до загрузки продуктов, например, сразу после инициализации sdk. Это рекомендуемый подход.

7.2 Восстановление покупок на iOS🔗

На iOS Apple рекомендует в гайдлайнах добавить специальную кнопку "Восстановление покупок" и только при нажатии на нее вызывать метод restorePurchase. Вызов данного метода необходим только в одном случае

  • Если пользователь оформил подписку или купил непотребляемый товар, единственный способ узнать о доступности товара после переустановки игры или при смене игрового аккаунта - это вызвать метод restorePurchase

Важно

Не стоит вызывать данный метод произвольно на iOS, без намеренного желания пользователя, так как каждый раз, когда вызывается данный метод, пользователю придется вводить AppleID и пароль. Согласно гайдлайнам, необходимо сделать только кнопку.

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

MRGSBilling.getInstance().restoreTransaction();

Поздравляем, Вы внедрили работу с платежами в Ваше приложение!

Что делать, если мы хотим отправлять только статистику в MRGS?🔗

Тогда Вам не нужно использовать текущий модуль. Подробнее про трекинг платежей читайте в разделе "Подключение SDK" -> "Шаг 4 - Добавьте трекинг платежей"

Пример🔗

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}"];

        // Покупка с использованием специального объекта запроса платежа с дополнительными параметрами
        //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, "Товар ожидает завершения покупки: " + 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");
}

Дополнительные возможности🔗

MRGS предоставляет большое количество дополнительных функций при работе с платежами. Подробнее смотрите в разделе "дополнительные возможности":


Последнее обновление: 2025-01-21
Дата создания: 2020-01-20