Mobile SDK Core

Това е по-скоро библиотека, отколкото SDK. Тя се използва за шифроване на данните на картата в seToken, който може безопасно да се предаде и съхрани в която и да е система (например на сървъра на продавача). Това е добро решение за продавачи, които искат максимална гъвкавост или не искат да се занимават с данните на картата на своите сървъри.

Начини на плащане, поддържани от този SDK:

Начин на плащане Поддръжка
Банкова карта Да
Съхранени потребителски данни/карта Да
Apple Pay Не се изисква
Google Pay Не се изисква

Процес на плащане SDK Core

Web View за 3DS

На приведената по-долу диаграма е показан процесът на плащане SDK Core с пренасочване 3DS чрез Web View.

sequenceDiagram participant MA as Мобилно приложение participant MS as Мобилен сървър participant SDK as SDK participant PG as Платежен шлюз participant 3DS as 3DSS/ACS/DS MA ->> MS: 1. клиент създава поръчка MS ->> PG: 2. регистрация на поръчка чрез API PG -->> MS: 3. уникален номер на поръчка (mdOrder) MA ->> MA: 4. клиент въвежда данни MA -->> SDK: 5. генериране на seToken MA ->> MS: 6. изпращане на seToken на сървъра MS ->> PG: 7. извикване на платежно API alt Плащането е завършено PG -->> MS: 8. отговор със статус на плащането (Преход към 16) else изисква се 3DS2 PG -->> MS: 9. отговор с пренасочване към 3DS MS ->> MA: 10. отваряне на Web View за 3DS MA ->> ACS: 11. клиент въвежда парола ACS -->> PG: 12. пренасочване към платежния шлюз PG ->> PG: 13. плащане PG -->> MA: 14. пренасочване към returnUrl MA ->> MA: 15. затваряне на Web View end opt Callback-известия са настроени PG -->> MS: 16. callback-известие end MS ->> PG: 17. проверка на статуса на плащането MS ->> MA: 18. показване на резултата от плащането на клиента
  1. Клиентът създава поръчка
  2. Мобилният сървър регистрира тази поръчка в платежния шлюз чрез register.do. Използвайте параметъра returnUrl като маркер за затваряне на Web View след пренасочването от ACS на Стъпка 14.
  3. Мобилният сървър получава в отговора уникален номер на поръчка mdOrder.
  4. Клиентът попълва платежните данни в мобилното приложение.
  5. Мобилното приложение извиква SDK за създаване на seToken (secure token). (Android: sdkCore.generateWithCard; iOS: CKCToken.generateWithCard).

    Публичният ключ, който се изисква в съответния метод, трябва да се взема от online ресурса https://uat.dskbank.bg/payment/se/keys.do. Ако на този линк са достъпни няколко ключа, следва да се използва първият ключ. (Имайте предвид, че за тестовата и работната среда се използват различни ключове.)

  6. Мобилното приложение изпраща seToken на мобилния сървър.

  7. Мобилният сървър използва този seToken за извършване на плащане чрез paymentorder.do.

    • Използвайте seToken вместо pan, cvc и дата на изтичане.
    • Не забравяйте да посочите името на притежателя на картата в полето TEXT. Ако не събирате името на притежателя на картата, просто изпратете стойността CARDHOLDER.
  8. Мобилният сървър получава отговор без пренасочване към ACS. Това означава, че плащането е завършено и трябва да преминем към Стъпка 16.

  9. Мобилният сървър получава отговор с пренасочване към ACS.

  10. Мобилното приложение отваря Web View с данните за пренасочване към ACS.

  11. Клиентът въвежда своята еднократна парола във формата на ACS.

  12. ACS пренасочва клиента към платежния шлюз.

  13. Платежният шлюз осъществява плащането.

  14. Платежният шлюз пренасочва клиента към returnUrl, който може да се използва като маркер за затваряне на Web View.

  15. Мобилното приложение затваря Web View.

  16. Платежният шлюз изпраща известие за обратна връзка на сървъра на продавача, ако е настроено за продавача.

  17. Мобилният сървър проверява окончателния статус на плащането чрез getOrderStatusExtended.do.

  18. Мобилното приложение показва резултата от плащането на клиента.

IOS

iOS-интеграция

Интеграция SDKCore.framework

Можете да интегрирате SDKCore.framework, като го добавите ръчно.

SDKCore.framework

Фигура 1. Добавяне на файл SDKCore.framework

Фигура 2. Промяна на свойствата на SDKCore.framework

След това импортирайте фреймуорка във файла ViewController.swift .
//ViewController.swift
...
import SDKCore
...

Работа с API V1

Външни зависимости

За генериране на токен е необходимо да се зададе открит ключ.

let publicKey: String =
      "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoIITqh9xlGx4tWA+aucb0V0YuFC9aXzJb0epdioSkq3qzNdRSZIxe/dHqcbMN2SyhzvN6MRVl3xyjGAV+lwk8poD4BRW3VwPUkT8xG/P/YLzi5N8lY6ILlfw6WCtRPK5bKGGnERcX5dqL60LhOPRDSYT5NHbbp/J2eFWyLigdU9Sq7jvz9ixOLh6xD7pgNgHtnOJ3Cw0Gqy03r3+m3+CBZwrzcp7ZFs41bit7/t1nIqgx78BCTPugap88Gs+8ZjdfDvuDM+/3EwwK0UVTj0SQOv0E5KcEHENL9QQg3ujmEi+zAavulPqXH5907q21lwQeemzkTJH4o2RCCVeYO+YrQIDAQAB-----END PUBLIC KEY-----"

Метод за генериране на токен

let sdkCore = SdkCore()

let cardParams = CardParams(
     pan: "4111111111111111",
     cvc: "123",
     expiryMMYY: "12/28",
     cardholder: "TEST CARDHOLDER",
     mdOrder: "mdOrder",
     pubKey: publicKey
)

let cardParamsConfig = SDKCoreConfig(
    paymentMethodParams: .cardParams(params: cardParams)
)
let tokenResult = sdkCore.generateWithConfig(config: cardParamsConfig)

let bindignParams = BindingParams(
    pubKey: publicKey,
    bindingId: "das",
    cvc: "123",
    mdOrder: "mdOrder"
)
let bindingParamsConfig = SDKCoreConfig(
    paymentMethodParams: .bindingParams(params: bindignParams)
)
let tokenResult = sdkCore.generateWithConfig(config: bindingParamsConfig)

Модели

CardParams

Име на свойство Тип данни Стойност по подразбиране Незадължително Описание
mdOrder String - Не Номер на поръчката
pan String - Не Номер на картата
cvc String - Не Таен код на картата
expiryMMYY String - Не Срок на валидност на картата
cardHolder String - Да Име и фамилия на притежателя на картата
pubKey String - Не Публичен ключ

BindingParams

Име на свойство Тип данни Стойност по подразбиране Незадължително Описание
mdOrder String - Не Номер на поръчката
bindingId String - Не Номер на запазените платежни данни за карта
cvc String - Да Таен код на картата
pubKey String - Не Публичен ключ

Грешки при валидация на полетата

ParamField Грешка Описание
UNKNOWN - Неизвестна грешка
PAN required Посочено е празно поле
invalid Некоректна стойност
invalid-format Използват се недопустими символи. Достъпни са само цифри.
CVC required Посочено е празно поле
invalid Некоректна стойност
EXPIRY required Посочено е празно поле
invalid Некоректна стойност
invalid-format Форматът не съответства на шаблона MM/YY
CARDHOLDER required Посочено е празно поле
invalid Некоректна стойност
invalid-format Използват се недопустими символи. Достъпни са само букви и интервали.
BINDING_ID required Посочено е празно поле
invalid Некоректна стойност
MD_ORDER required Посочено е празно поле
invalid Некоректна стойност
PUB_KEY required Посочено е празно поле

Android

Android-интеграция

Свързване към Gradle проект чрез добавяне на .aar файлове на библиотеката

Необходимо е да добавите файла на библиотеката sdk_core-release.aar в папката libs, а след това да посочите зависимостта на добавената библиотека.

build.gradle.kts

allprojects {
    repositories {
        // ...
        flatDir {
            dirs("libs")
        }
    }
}

dependencies {
    // dependency is mandatory to add
    implementation(group = "", name = "sdk_core-release", ext = "aar")
}

build.gradle

allprojects {
    repositories {
        // ...
        flatDir {
            dirs 'libs'
        }
    }
}

dependencies {
    // dependency is mandatory to add
    implementation(group: '', name: 'sdk_core-release', ext: 'aar')
}

Конфигурация за Android

Логиране

Вътрешните процеси се логират с таг SDK-Core. Можете също така да логирате собствените си процеси.

Логирането е достъпно чрез обекта Logger.

...
    Logger.addLogInterface(object : LogInterface {
        override fun log(classMethod: Class<Any>, tag: String, message: String, exception: Exception?) {
                Log.i(tag, "$classMethod: $message", exception)
            }
        })
...

По подразбиране се използва тагът SDK-Core. Можете да зададете свой собствен, ако желаете.

...
     Logger.log(this.javaClass, "MyTag", "My process...", null)
...

Пример Kotlin_core (без графичен интерфейс)

Пример за формиране на криптограма

import net.payrdr.mobile.payment.sdk.core.SDKCore
import net.payrdr.mobile.payment.sdk.core.TokenResult
import net.payrdr.mobile.payment.sdk.core.model.BindingParams
import net.payrdr.mobile.payment.sdk.core.model.CardParams
import net.payrdr.mobile.payment.sdk.core.validation.BaseValidator
import net.payrdr.mobile.payment.sdk.core.validation.CardCodeValidator
import net.payrdr.mobile.payment.sdk.core.validation.CardExpiryValidator
import net.payrdr.mobile.payment.sdk.core.validation.CardHolderValidator
import net.payrdr.mobile.payment.sdk.core.validation.CardNumberValidator
import net.payrdr.mobile.payment.sdk.core.validation.OrderNumberValidator

class MainActivity : AppCompatActivity() {
    // initialization of validators for card information entry fields
    private val cardNumberValidator by lazy { CardNumberValidator(this) }
    private val cardExpiryValidator by lazy { CardExpiryValidator(this) }
    private val cardCodeValidator by lazy { CardCodeValidator(this) }
    private val cardHolderValidator by lazy { CardHolderValidator(this) }
    private val orderNumberValidator by lazy { OrderNumberValidator(this) }
    private val sdkCore by lazy { SDKCore(context = this) }
    override fun onCreate(savedInstanceState: Bundle?) {
        // инсталиране на валидатори на полетата за въвеждане на информация за карта
        cardNumberInput.setupValidator(cardNumberValidator)
        cardExpiryInput.setupValidator(cardExpiryValidator)
        cardCodeInput.setupValidator(cardCodeValidator)
        cardHolderInput.setupValidator(cardHolderValidator)
        mdOrderInput.setupValidator(orderNumberValidator)

        // създаване на обект и инициализация на полета за нова карта
        val params = NewPaymentMethodCardParams(
            pan = cardNumberInput.text.toString(),
            cvc = cardCodeInput.text.toString(),
            expiryMMYY = cardExpiryInput.text.toString(),
            cardHolder = cardHolderInput.text.toString(),
            pubKey = pubKeyInput.text.toString()
        )
        // извикване на метод за получаване на криптограма за нова карта
        sdkCore.generateWithConfig(SDKCoreConfig(params))

        // Създаване на обект и инициализация на полета за свързана карта
        val params = NewPaymentMethodStoredCardParams(
            storedPaymentId = "storedPaymentMethodId",
            cvc = "123",
            pubKey = pubKeyInput.text.toString()
        )
        // извикване на метод за получаване на криптограма за свързаната карта
        sdkCore.generateWithConfig(SDKCoreConfig(params))
    }
}

Модели

NewPaymentMethodCardParams

Име на свойството Тип данни Стойност по подразбиране Незадължително Описание
pan String - Не Номер на карта
cvc String - Не Таен код на карта
expiryMMYY String - Не Срок на валидност на карта
cardHolder String - Не Име и фамилия на притежателя на карта
pubKey String - Не Публичен ключ

NewPaymentMethodStoredCardParams

Име на свойството Тип данни Стойност по подразбиране Незадължително Описание
storedPaymentId String - Не Номер на съхранени платежни данни за карта
cvc String - Не Таен код на карта
pubKey String - Не Публичен ключ

TokenResult

Име на свойството Тип данни Стойност по подразбиране Незадължително Описание
token String - Не Token като низ
errors Map - Не Грешка при генериране на token

Грешки при валидиране на полета

ParamField Грешка Описание
PAN required Посочено е празно поле
invalid Некоректна стойност
invalid-format Използват се недопустими символи. Достъпни са само цифри.
CVC required Посочено е празно поле
invalid Некоректна стойност
EXPIRY required Посочено е празно поле
invalid Некоректна стойност
invalid-format Форматът не съответства на шаблона MM/YY.
CARDHOLDER required Посочено е празно поле
invalid Некоректна стойност
invalid-format Използват се недопустими символи. Достъпни са само букви и интервали.
PUB_KEY required Посочено е празно поле
STORED_PAYMENT_ID requrired Посочено е празно поле
invalid Некоректна стойност

Работа с API V1

Външни зависимости

За генериране на token е необходимо да се зададе публичен ключ.

val publicKey: String =
    "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoIITqh9xlGx4tWA+aucb0V0YuFC9aXzJb0epdioSkq3qzNdRSZIxe/dHqcbMN2SyhzvN6MRVl3xyjGAV+lwk8poD4BRW3VwPUkT8xG/P/YLzi5N8lY6ILlfw6WCtRPK5bKGGnERcX5dqL60LhOPRDSYT5NHbbp/J2eFWyLigdU9Sq7jvz9ixOLh6xD7pgNgHtnOJ3Cw0Gqy03r3+m3+CBZwrzcp7ZFs41bit7/t1nIqgx78BCTPugap88Gs+8ZjdfDvuDM+/3EwwK0UVTj0SQOv0E5KcEHENL9QQg3ujmEi+zAavulPqXH5907q21lwQeemzkTJH4o2RCCVeYO+YrQIDAQAB-----END PUBLIC KEY-----"

Метод за генериране на token

// TokenResult with CardParams
val cardParams: CardParams = CardParams(
    mdOrder = "mdOrder",
    pan = "4111111111111111",
    cvc = "123",
    expiryMMYY = "12/28",
    cardHolder = "TEST CARDHOLDER",
    pubKey = "publicKey"
)
val tokenResult = sdkCore.generationWithConfig(paymentCardParams = cardParams)

// TokenResult with BindingParams
val bindingParams: BindingParams = BindingParams(
    mdOrder = "mdOrder",
    bindingID = "das",
    cvc = "123",
    pubKey = "publicKey"
)

val tokenResult = sdkCore.generationWithConfig(paymentCardParams = bindingParams)

Кросплатформени фреймуърци

Mobile SDK Core може да се използва не само в нативни приложения: извикванията на SDK се прехвърлят в нативния код чрез мост, който декларирате от страната на приложението.

React Native

За реализацията на интеграция на Mobile SDK Core в React Native приложение е необходимо да настроите мост — обвивка на Kotlin/Java или Swift/Objective-C, която ще приема извиквания от JavaScript и ще ги предава в нативния код на SDK.

Мостовете в React Native позволяват на вашия JS код да извиква методи на нативни библиотеки толкова просто, колкото обикновените асинхронни функции: вие декларирате нативен модул с методи, които връщат резултат чрез Promise, React Native автоматично свързва тези методи с JavaScript, и след това работите с тях в приложението без дълбоки познания за платформените API.

IOS

  1. Интегрирайте SDKCore във вашия проект в съответствие с раздел iOS-интеграция.

  2. Проверете, че за фреймуорка SDKCore.xcframework в колоната Embed е избрано Embed & Sign.

  3. Създайте Swift модул RadarSdkBridge.swift със следното съдържание:

    import Foundation
    import React
    import SDKCore
    
    @objc(RadarSdk)
    class RadarSdkBridge: NSObject {
        private let sdkCore = SdkCore()
    
        @objc
        func generateNewCardToken(
            _ pan: String,
            cvc: String,
            expiryMMYY: String,
            cardHolder: String,
            mdOrder: String,
            pubKey: String,
            resolver resolve: RCTPromiseResolveBlock,
            rejecter reject: RCTPromiseRejectBlock
        ) {
            do {
                let params = CardParams(
                    pan: pan,
                    cvc: cvc,
                    expiryMMYY: expiryMMYY,
                    cardholder: cardHolder,
                    mdOrder: mdOrder,
                    pubKey: pubKey
                )
                let config = SDKCoreConfig(paymentMethodParams: .cardParams(params: params))
                let result = try sdkCore.generateWithConfig(config: config)
                resolve(result.token)
            } catch {
                reject("TOKEN_ERROR", error.localizedDescription, error)
            }
        }
    
        @objc
        func generateStoredCardToken(
            _ storedPaymentId: String,
            cvc: String,
            pubKey: String,
            mdOrder: String,
            resolver resolve: RCTPromiseResolveBlock,
            rejecter reject: RCTPromiseRejectBlock
        ) {
            do {
                let params = BindingParams(
                    pubKey: pubKey,
                    bindingId: storedPaymentId,
                    cvc: cvc,
                    mdOrder: mdOrder
                )
                let config = SDKCoreConfig(paymentMethodParams: .bindingParams(params: params))
                let result = try sdkCore.generateWithConfig(config: config)
                resolve(result.token)
            } catch {
                reject("TOKEN_ERROR", error.localizedDescription, error)
            }
        }
    
        @objc
        static func requiresMainQueueSetup() -> Bool {
            return false
        }
    }
  4. Реализирайте Objective-C мост в файла RadarSdkBridge.m:

    #import <React/RCTBridgeModule.h>
    
    @interface RCT_EXTERN_MODULE(RadarSdk, NSObject)
    
    RCT_EXTERN_METHOD(generateNewCardToken:
      (NSString *)pan
      cvc:(NSString *)cvc
      expiryMMYY:(NSString *)expiryMMYY
      cardHolder:(NSString *)cardHolder
      mdOrder:(NSString *)mdOrder
      pubKey:(NSString *)pubKey
      resolver:(RCTPromiseResolveBlock)resolve
      rejecter:(RCTPromiseRejectBlock)reject
    )
    
    RCT_EXTERN_METHOD(generateStoredCardToken:
      (NSString *)storedPaymentId
      cvc:(NSString *)cvc
      pubKey:(NSString *)pubKey
      mdOrder:(NSString *)mdOrder
      resolver:(RCTPromiseResolveBlock)resolve
      rejecter:(RCTPromiseRejectBlock)reject
    )
    
    @end

Android

  1. Изтеглете файловете sdk_core-release.aar и sdk_logs-release.aar и ги копирайте в папката android/app/libs/ според раздел Android-интеграция.

  2. Добавете плоския репозиторий и зависимостите във файла android/app/build.gradle:

    android {
        // … останалата конфигурация на вашия проект …
    }
    
    repositories {
        flatDir {
            dirs 'libs'
        }
    }
    
    dependencies {
        // … други зависимости …
        implementation(name: 'sdk_core-release', ext: 'aar')
        implementation(name: 'sdk_logs-release', ext: 'aar')
    }

Реализация на Native Module за Kotlin

  1. Създайте файла RadarSdkModule.kt в директорията android/app/src/main/java/.../radar/:

    package com.mobilesdkdemo.radar
    
    import com.facebook.react.bridge.Promise
    import com.facebook.react.bridge.ReactApplicationContext
    import com.facebook.react.bridge.ReactContextBaseJavaModule
    import com.facebook.react.bridge.ReactMethod
    import net.payrdr.mobile.payment.sdk.core.SDKCore
    import net.payrdr.mobile.payment.sdk.core.model.SDKCoreConfig
    import net.payrdr.mobile.payment.sdk.core.model.CardParams
    import net.payrdr.mobile.payment.sdk.core.model.BindingParams
    
    class RadarSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
        override fun getName(): String = "RadarSdk"
    
        private val sdkCore by lazy { SDKCore(context = reactApplicationContext) }
    
        @ReactMethod
        fun generateNewCardToken(
            pan: String,
            cvc: String,
            expiryMMYY: String,
            cardHolder: String,
            mdOrder: String,
            pubKey: String,
            promise: Promise
        ) {
            try {
                val params = CardParams(
                    pan = pan,
                    cvc = cvc,
                    expiryMMYY = expiryMMYY,
                    cardHolder = cardHolder,
                    mdOrder = mdOrder,
                    pubKey = pubKey
                )
                val config = SDKCoreConfig(paymentCardParams = params)
                val result = sdkCore.generateWithConfig(config)
                promise.resolve(result.token)
            } catch (e: Exception) {
                promise.reject("TOKEN_ERROR", e)
            }
        }
    
        @ReactMethod
        fun generateStoredCardToken(
            storedPaymentId: String,
            cvc: String,
            pubKey: String,
            mdOrder: String,
            promise: Promise
        ) {
            try {
                val params = BindingParams(
                    bindingID = storedPaymentId,
                    mdOrder = mdOrder,
                    cvc = cvc,
                    pubKey = pubKey
                )
                val config = SDKCoreConfig(paymentCardParams = params)
                val result = sdkCore.generateWithConfig(config)
                promise.resolve(result.token)
            } catch (e: Exception) {
                promise.reject("TOKEN_ERROR", e)
            }
        }
    }
  2. Създайте пакета RadarSdkPackage.kt в същата директория:

    package com.mobilesdkdemo.radar
    
    import com.facebook.react.ReactPackage
    import com.facebook.react.bridge.NativeModule
    import com.facebook.react.bridge.ReactApplicationContext
    import com.facebook.react.uimanager.ViewManager
    
    class RadarSdkPackage : ReactPackage {
        override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
            listOf(RadarSdkModule(reactContext))
    
        override fun createViewManagers(
            reactContext: ReactApplicationContext
        ): List<ViewManager<*, *>> = emptyList()
    }
  3. Регистрирайте създадения пакет в MainApplication.kt:

    package com.mobilesdkdemo
    
    import android.app.Application
    import com.facebook.react.PackageList
    import com.facebook.react.ReactApplication
    import com.facebook.react.ReactNativeHost
    import com.facebook.react.ReactHost
    import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
    import com.facebook.react.defaults.DefaultReactNativeHost
    import com.facebook.soloader.SoLoader
    import com.mobilesdkdemo.radar.RadarSdkPackage
    
    class MainApplication : Application(), ReactApplication {
    
        override val reactNativeHost: ReactNativeHost =
            object : DefaultReactNativeHost(this) {
                override fun getPackages(): List<ReactPackage> =
                    PackageList(this).packages.apply {
                        add(RadarSdkPackage()) // Регистрираме пакета
                    }
    
                override fun getJSMainModuleName(): String = "index"
                override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
                override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
                override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
            }
    
        override val reactHost: ReactHost
            get() = getReactNativeHost(applicationContext, reactNativeHost)
    
        override fun onCreate() {
            super.onCreate()
            SoLoader.init(this, false)
            if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
                load()
            }
        }
    }

Използване

Използвайте методите от JavaScript/TypeScript по следния начин:

import { NativeModules } from 'react-native';
const { RadarSdk } = NativeModules;

// Генериране на токен за нова карта
const token = await RadarSdk.generateNewCardToken(
  '4111111111111111', // PAN
  '123',              // CVC
  '12/25',            // Expiry MM/YY
  'CARDHOLDER NAME',  // Име на притежателя на картата
  'mdOrder',          // Номер, получен при регистрация на поръчката
  '-----BEGIN PUBLIC KEY-----...' // Публичен ключ от /payment/se/keys.do
);

// Генериране на токен за свързана карта
const bindToken = await RadarSdk.generateStoredCardToken(
  'bindingId',        // ID на запазената карта
  '123',              // CVC
  '-----BEGIN PUBLIC KEY-----…', // Публичен ключ от /payment/se/keys.do
  'mdOrder'           // Номер, получен при регистрация на поръчката
);

Демо приложение

Пример за интеграция на Mobile Sdk Core в приложение на React Native може да се види на GitHub.

Flutter

За реализиране на интеграцията на Mobile SDK Core в Flutter приложение е необходимо да настроите platform channel — канал, чрез който Dart кодът извиква native методи на SDK на Android и iOS.

Platform channel свързва Dart и native кода по име на канала: вие декларирате MethodChannel с едно и също име на двете страни, извиквате метод от Dart и получавате резултата чрез Future. Името на канала е единственото, което свързва страните, затова при несъвпадение извикването ще завърши с изключение MissingPluginException в runtime, а не с грешка при компилация.

Аргументите се предават като Map<String, dynamic> и се разбират на native страната по имена на ключовете. Чрез канала преминават само стойности, разбираеми от стандартния кодек — примитиви, списъци и речници; обектите на SDK се създават на native страната и не се предават чрез канала.

iOS

  1. Интегрирайте SDKCore във вашия проект в съответствие с раздел iOS-интеграция.

  2. Проверете, че за framework SDKCore.xcframework в колоната Embed е избрано Embed & Sign. Framework-ът вътре в xcframework е динамична библиотека: ако само се свърже, но не се вгради, проектът ще се компилира, а приложението ще се срине при стартиране.

  3. Създайте файл RadarSdkBridge.swift в target Runner:

    import Flutter
    import Foundation
    import SDKCore
    
    final class RadarSdkBridge: NSObject {
        static let channelName = "radar_sdk"
    
        private let channel: FlutterMethodChannel
        private let sdkCore = SdkCore()
    
        init(messenger: FlutterBinaryMessenger) {
            channel = FlutterMethodChannel(name: RadarSdkBridge.channelName, binaryMessenger: messenger)
            super.init()
            channel.setMethodCallHandler { [weak self] call, result in
                self?.handle(call, result: result)
            }
        }
    
        private func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
            switch call.method {
            case "generateNewCardToken":
                generateNewCardToken(call, result: result)
            case "generateStoredCardToken":
                generateStoredCardToken(call, result: result)
            default:
                result(FlutterMethodNotImplemented)
            }
        }
    
        private func generateNewCardToken(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
            guard
                let args = call.arguments as? [String: Any],
                let pan = args["pan"] as? String,
                let cvc = args["cvc"] as? String,
                let expiryMMYY = args["expiryMMYY"] as? String,
                let cardHolder = args["cardHolder"] as? String,
                let mdOrder = args["mdOrder"] as? String,
                let pubKey = args["pubKey"] as? String
            else {
                result(FlutterError(code: "TOKEN_ERROR", message: "Invalid arguments", details: nil))
                return
            }
    
            do {
                let params = CardParams(
                    pan: pan,
                    cvc: cvc,
                    expiryMMYY: expiryMMYY,
                    cardholder: cardHolder,
                    mdOrder: mdOrder,
                    pubKey: pubKey
                )
                let config = SDKCoreConfig(paymentMethodParams: .cardParams(params: params))
                let tokenResult = try sdkCore.generateWithConfig(config: config)
                result(tokenResult.token)
            } catch {
                result(FlutterError(code: "TOKEN_ERROR", message: error.localizedDescription, details: nil))
            }
        }
    
        private func generateStoredCardToken(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
            guard
                let args = call.arguments as? [String: Any],
                let storedPaymentId = args["storedPaymentId"] as? String,
                let cvc = args["cvc"] as? String,
                let pubKey = args["pubKey"] as? String,
                let mdOrder = args["mdOrder"] as? String
            else {
                result(FlutterError(code: "TOKEN_ERROR", message: "Invalid arguments", details: nil))
                return
            }
    
            do {
                let params = BindingParams(
                    pubKey: pubKey,
                    bindingId: storedPaymentId,
                    cvc: cvc,
                    mdOrder: mdOrder
                )
                let config = SDKCoreConfig(paymentMethodParams: .bindingParams(params: params))
                let tokenResult = try sdkCore.generateWithConfig(config: config)
                result(tokenResult.token)
            } catch {
                result(FlutterError(code: "TOKEN_ERROR", message: error.localizedDescription, details: nil))
            }
        }
    }
  4. Регистрирайте моста в AppDelegate.swift:

    import Flutter
    import UIKit
    
    @main
    @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
        // Мостът трябва да се пази в свойство: обект, създаден в локална
        // променлива, се освобождава и мълчаливо престава да отговаря на извиквания от Dart.
        private var radarSdkBridge: RadarSdkBridge?
    
        func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
            GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
    
            if let messenger = engineBridge.pluginRegistry.registrar(forPlugin: "RadarSdkBridge")?.messenger() {
                radarSdkBridge = RadarSdkBridge(messenger: messenger)
            }
        }
    }

Android

  1. Свалете файловете sdk_core-release.aar и sdk_logs-release.aar и ги копирайте в папката android/app/libs/ според раздел Android-интеграция.

  2. Добавете зависимостите в файла android/app/build.gradle.kts:

    android {
        // … останалата конфигурация на вашия проект …
    }
    
    dependencies {
        // … други зависимости …
        implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
    }
  3. Създайте файл RadarSdkBridge.kt в директорията android/app/src/main/kotlin/.../:

    package com.mobilesdkdemo
    
    import android.content.Context
    import io.flutter.plugin.common.BinaryMessenger
    import io.flutter.plugin.common.MethodCall
    import io.flutter.plugin.common.MethodChannel
    
    import net.payrdr.mobile.payment.sdk.core.SDKCore
    import net.payrdr.mobile.payment.sdk.core.model.BindingParams
    import net.payrdr.mobile.payment.sdk.core.model.CardParams
    import net.payrdr.mobile.payment.sdk.core.model.SDKCoreConfig
    
    private const val CHANNEL_NAME = "radar_sdk"
    
    class RadarSdkBridge(private val context: Context) : MethodChannel.MethodCallHandler {
    
        private val sdkCore by lazy { SDKCore(context = context) }
    
        fun register(messenger: BinaryMessenger) {
            MethodChannel(messenger, CHANNEL_NAME).setMethodCallHandler(this)
        }
    
        override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
            when (call.method) {
                "generateNewCardToken" -> generateNewCardToken(call, result)
                "generateStoredCardToken" -> generateStoredCardToken(call, result)
                else -> result.notImplemented()
            }
        }
    
        private fun generateNewCardToken(call: MethodCall, result: MethodChannel.Result) {
            try {
                val params = CardParams(
                    pan = call.argument<String>("pan")!!,
                    cvc = call.argument<String>("cvc")!!,
                    expiryMMYY = call.argument<String>("expiryMMYY")!!,
                    cardHolder = call.argument<String>("cardHolder")!!,
                    mdOrder = call.argument<String>("mdOrder")!!,
                    pubKey = call.argument<String>("pubKey")!!
                )
                val config = SDKCoreConfig(paymentCardParams = params)
                val token = sdkCore.generateWithConfig(config).token
    
                result.success(token)
            } catch (e: Exception) {
                result.error("TOKEN_ERROR", e.message, null)
            }
        }
    
        private fun generateStoredCardToken(call: MethodCall, result: MethodChannel.Result) {
            try {
                val params = BindingParams(
                    bindingID = call.argument<String>("storedPaymentId")!!,
                    mdOrder = call.argument<String>("mdOrder")!!,
                    cvc = call.argument<String>("cvc")!!,
                    pubKey = call.argument<String>("pubKey")!!
                )
                val config = SDKCoreConfig(paymentCardParams = params)
                val token = sdkCore.generateWithConfig(config).token
    
                result.success(token)
            } catch (e: Exception) {
                result.error("TOKEN_ERROR", e.message, null)
            }
        }
    }
  4. Регистрирайте моста в MainActivity.kt:

    package com.mobilesdkdemo
    
    import io.flutter.embedding.android.FlutterActivity
    import io.flutter.embedding.engine.FlutterEngine
    
    class MainActivity : FlutterActivity() {
        override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
            super.configureFlutterEngine(flutterEngine)
            RadarSdkBridge(applicationContext).register(flutterEngine.dartExecutor.binaryMessenger)
        }
    }

Използване

  1. Създайте обвивка над канала в lib/radar_sdk.dart. Името на канала и имената на ключовете в аргументите трябва да съвпадат с нативната страна:

    import 'package:flutter/services.dart';
    
    class RadarSdk {
        const RadarSdk._();
    
        static const MethodChannel _channel = MethodChannel('radar_sdk');
    
        static Future<String?> generateNewCardToken({
            required String pan,
            required String cvc,
            required String expiryMMYY,
            required String cardHolder,
            required String mdOrder,
            required String pubKey,
        }) {
            return _channel.invokeMethod<String>('generateNewCardToken', {
                'pan': pan,
                'cvc': cvc,
                'expiryMMYY': expiryMMYY,
                'cardHolder': cardHolder,
                'mdOrder': mdOrder,
                'pubKey': pubKey,
            });
        }
    
        static Future<String?> generateStoredCardToken({
            required String storedPaymentId,
            required String cvc,
            required String pubKey,
            required String mdOrder,
        }) {
            return _channel.invokeMethod<String>('generateStoredCardToken', {
                'storedPaymentId': storedPaymentId,
                'cvc': cvc,
                'pubKey': pubKey,
                'mdOrder': mdOrder,
            });
        }
    }
  2. Използвайте методите от Dart по следния начин:

    // Генериране на токен за нова карта
    final token = await RadarSdk.generateNewCardToken(
      pan: '4111111111111111',  // PAN
      cvc: '123',               // CVC
      expiryMMYY: '12/25',      // Срок на валидност MM/YY
      cardHolder: 'CARDHOLDER NAME', // Име на притежателя на картата
      mdOrder: 'mdOrder',       // Номер, получен при регистрация на поръчката
      pubKey: '-----BEGIN PUBLIC KEY-----...', // Публичен ключ от /payment/se/keys.do
    );
    
    // Генериране на токен за свързана карта
    final bindToken = await RadarSdk.generateStoredCardToken(
      storedPaymentId: 'bindingId', // ID на съхранената карта
      cvc: '123',                   // CVC
      pubKey: '-----BEGIN PUBLIC KEY-----…', // Публичен ключ от /payment/se/keys.do
      mdOrder: 'mdOrder',           // Номер, получен при регистрация на поръчката
    );

Грешките от нативната страна идват в Dart като PlatformException: при неуспешно генериране на токен мостът отговаря с код TOKEN_ERROR, а в полето message се предава текстът на грешката от SDK.

try {
  final token = await RadarSdk.generateNewCardToken(/* … */);
} on PlatformException catch (e) {
  // e.code == 'TOKEN_ERROR', e.message — текст на грешката от SDK
}

Демо приложение

Пример за интеграция на Mobile SDK Core в приложение на Flutter може да се види в GitHub.

Mobile SDK Source

iOS

iOS изходен код в GitHub

iOS издания в GitHub

Изисквания: iOS 10.0 или по-висока версия

Android

Android изходен код в GitHub

Android издания в GitHub

Изисквания: Android 5.0 или по-висока версия

:
eCommerce SDK