mSDK
En esta sección se detallan las principales consideraciones y archivos
principales para la implementación en lenguajes Flutter, iOS (Swift) y Android,
como parte de esta documentación recibirá aplicaciones básicas de ejemplo en
estos lenguajes.
Es importante recalcar que la selección del lenguaje, SDK, arquitectura, flujos
a implementar dependerán de las necesidades, flujos, experiencia, prácticas y
políticas de cada empresa.
Implementación en Flutter
Version de Flutter: 2.10.1
Tipo de proyecto: 2.10.1
Dependencias (pubspec.yaml):
- get_ip: 0.4.0 : Usada para la obtención de la ip del dispositivo,
puede usarse otra a conveniencia
- https: ^0.12.2 : Requerida para el consumo del apiServer
Archivos: En este DEMO encontrará clases dart como
HomeScreen.dart,
IntroScreen.dart, TokenizeScreen.dart, entre otros, usados para realizar
la interfaz de este demo, sin embargo, los archivos que debe revisar
para su implementación son:
- Utils/DataWeb.dart: Esta es la clase principal de la integración,
creará y consumirá el canal creado entre Flutter y el intérprete
Android/iOS, disponibiliza las funciones:
- getCheckOutId: Recibe un objeto de tipo DWCheckOutReq y
consume el apiServer expuesto para generación de checkoutId,
retornará un objeto de tipo DWCheckOutRsp.
- ProcessPayment: Recibe el checkoutId y el tipo de pago,
consume el canal con la plataforma correspondiente y finalmente
obtiene el estado de la transacción mediante el apiServer
respondiendo un objeto de tipo DWStatusRsp, tambien es
responsable de la parametrización del widget, los parámetros
expuestos son:
- Config_Ambiente: Ambiente al cual se conectará el widget
(TEST, LIVE)
- Config_Brands: Marcas de tarjeta que soportará el widget,
separados por coma (,), por ejemplo:
VISA,MASTER,AMEX,DINERS,DISCOVER
- Config_MostrarTotal: Flag que indica si el plugin
mostrará el valor total de la transacción (true/false)
- Config_SkipCVV: Flag que indica si las tarjetas
tokenizadas requerirán o no del ingreso de CVV.
- Config_Tokenize: Indica si se debe mostrar la opción de
tokenización de tarjeta (0: Nunca, 1 Mostrar, 2. Siempre
Tokenizar - no recomendado)
- Pago_Diferidos: Instancia los meses diferidos a mostrar,
separados con coma (,), por ejemplo: 3,6,9
- Estilo_Tema: Indica el tema a aplicar al plugin
(Dark/Light)
- Estilo_Idioma: Indica el idioma a aplicar al plugin
(es_ES, en_US)
- Config_TipoPayment: Indica el tipo de crédito
seleccionado
Estas configuraciones se deberán crear en un mapa de strings
para su posterior envío a la plataforma, ejemplo:
Map payParams = new HashMap();
payParams['Config_Ambiente'] = "TEST";
payParams['Config_Brands'] = "VISA,MASTER,AMEX,DINERS,DISCOVER";
payParams['Config_MostrarTotal'] = "true";
payParams['Config_SkipCVV'] = "true";
payParams['Config_Tokenize'] = "1";
payParams['Pago_Diferidos'] = "3,6,9";
payParams['Estilo_Tema'] = "Dark";
payParams['Estilo_Idioma'] = "es_ES";
payParams['Config_TipoPayment'] = "0";
- TokenCheckOutId: Consume el apiServer expuesto para
generación de checkoutId para token, retornará un objeto de tipo
DWCheckOutRsp.
- TokenProcessPayment: Procesa un cobro basado en un token
previamente generado
- TokenDeleteCard: Procesa la eliminación de un token
previamente generado
Podrá cambiar el nombre/funcionalidad/características de estas
funciones conforme considere necesario.
Utils/DataWebObj.dart: Contiene los objetos tipo clase a usar
como
objetos de Requerimiento y Respuesta, como: DWCheckOutReq,
DWCheckOutRsp, DWStatusReq, DWTokenCheckOutReq, DWTokenCardRsp,
DWDeleteCard, DWStatusRsp, DWStatusFlutter.
Cabe recalcar que estos objetos podrán ser modificados, cambiando los
nombres de sus elementos, o agregando campos para cumplir con las
necesidades del negocio.
Utils/ColorsIOS.dart: Contiene los objetos modificables de diseño
(colores) del plugin en iOS (para Android es necesario editar el archivo
xml de colores), es muy importante que se respeten los nombres de las
variables pues son identificadores.
Un ejemplo de consumo de estos componentes desde la aplicación en
flutter (al presionar Pagar) se puede ver en la clase HomeScreen.dart,
donde se invoca la función openPaymentGateway (La función de
tokenización openPaymentGatewayTokenize funciona de similar forma), que
ejecuta principalmente lo siguiente:
Future openPaymentGateway() async {
DWCheckOutReq req = new DWCheckOutReq();
req.ClienteDocId = "";
req.ClientePNombre = myControllerName.text;
req.ClientePApellido = myControllerApellido.text;
req.ClienteEmail = myControllerEmail.text;
req.ClienteIP = await GetIp.ipAddress;
req.EnvioDireccion = myControllerEnvio.text;
req.Valor_Base0 = TotalBase0;
req.Valor_BaseImp = TotalBaseImp;
req.Valor_IVA = TotalIVA;
req.Valor_Total = Total;
if (tpayment == 0) {
req.Credito_Tipo = 0;
req.Credito_Meses = 0;
} else {
req.Credito_Tipo = tpayment;
}
DWCheckOutRsp rsp = await DataWeb.getCheckOutId(req);
DWStatusRsp status = await DataWeb.ProcessPayment(rsp.checkoutId, tpayment);
String MensajePopUp = "idTrx: " + (status.ResultId ? ? "") +
"\n\nResultCode: " + (status.ResultCode ? ? "") +
"\n\nResultDescription: " + (status.ResultDescription ? ? "") +
"\n\nDetailExtDesc: " + (status.DetailExtDesc ? ? "") +
"\n\nDetailAuth:" + (status.DetailAuth ? ? "--");
}
Donde se arma el objeto DWCheckOutReq para generación del checkoutId y
posterior consumo de ProcessPayment.
Configuración de Android:
Consideración: en caso de usarel callback asíncrono de OTT, se
debe
añadir la siguiente configuración en el archivo manifest esta
configuración debe estar en la actividad principal del proyecto:
<intent-filter>
<data android:scheme="dataweb"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
Cambiar el dataweb por el nombre de su proyecto, que fue colocado
en setShopperResultUrl.
En algunos casos los plugin de terceros devuelven un activity
result,
estos activity result serán ignorados al menos que se especifique lo
contrario, en caso de utilizarlos agregar la siguiente línea al inicio
del método onActivityResult del archivo MainActivity:
super.onActivityResult(requestCode, resultCode, data);
Ejemplo:
Remplazar los parámetros de acuerdo con su implementación.
Nota: Hacer el respectivo clean y get del proyecto.
Importar módulos: en las versiones actuales de Fultter o Android
se
debe agregar en la carpeta principal del proyecto Android app/libs los
archivos oppwa.mobile.aar, ipworks3ds_sdk.aar, oppwa.mobile.threeds.aar.
Agregar dependencias: En el build.gradle (Module App), agregar
las siguientes dependencias:
implementation fileTree(dir: "libs", include: ["*.aar"])
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.browser:browser:1.3.0"
implementation "com.google.android.material:material:1.4.0"
implementation "com.google.android.gms:play-services-base:17.6.0"
- Versión de compilación: De acuerdo a la versión de
flutter
utilizada en el build.gradle (Module App), agregar como versión
de compilación en el caso de flutter versión 10 es la versión de
compilación 31.
- Actualizar la versión de Kotlin: En el archivo gradle
principal actualizar la versión de kotlin si se utiliza la
versión 10 de flutter:
buildscript {
ext.kotlin_version = '1.6.0'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.10'
}
}
Agregar las siguientes clases en archivo proguard-rules.pro:
-keep class com.oppwa.mobile.connect.threeds.OppThreeDSService { *; }
-keep class com.nsoftware.ipworks3ds.sdk.ThreeDS2Service { *; }
Agregar tools namespace en root de AndroidManifest.xml:
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
package="com.df.dataweb_flutter">
Agregar exported en la actividad principal del proyecto de
AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
Declarar servicio de conexión en AndroidManifest.xml: Dentro del
nodo application agregar:
<service
android:name="com.oppwa.mobile.connect.service.ConnectService"
android:exported="false"/>
Implementar bridge: Reemplazar el archivo MainActivity.java
encontrado
en Android/app/src/main/kotlin/com.* con el provisto en esta
documentación y que podrá encontrar en el demo. Considerar que, si
cambia el valor de la variable CHANNEL, deberá también cambiarlo en
flutter, dentro de la función ProcessPayment encontrará la lógica que
presenta el formulario de CheckOut para lo cual se requiere:
- Inicializar el objeto CheckoutSettings:
CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands,
Connect.ProviderMode.TEST);
checkoutSettings.setShopperResultUrl("companyname://result");
Iniciar actividad
Intent intent = checkoutSettings.createCheckoutActivityIntent(this);
startActivityForResult(intent, CheckoutActivity.REQUEST_CODE_CHECKOUT);
Cambiar colores: Dentro del archivo de estilos (res/values/styles
y colors) encontrará un tema default, donde podrá cambiar colores en
caso de así requerirlo.
Configuración de iOS:
pod install: Ejecutar un pod install en directorio raiz
Importar sdk: Arrastrar los archivos OPPWAMobile.framework,
OPPWAMobile-Resources.bundle, ipworks3ds_sdk.xcframework y
OPPWAMobile_ThreeDS.xcframework a la carpeta Frameworks (Asegúrese de
tener marcada la opción de copiar elementos de ser necesario)
Activar configuraciones de compilación: Asegurarse que las
siguientes opciones se encuentren activadas:
- Enable Modules (C and Objective-C)
- Link Frameworks Automatically
Para SWIFT:
- Cargar Archivos base: Copiar archivos Language.swift,
Constant.swift y ColorsCheckout.swift a carpeta Runner del
proyecto.
- Importar header OPPWA: Importar en archivo
Runner-Bridging-Header.h, quedando:
#import "GeneratedPluginRegistrant.h"
#import <OPPWAMobile/OPPWAMobile.h>
@import OPPWAMobile_ThreeDS
@import ipworks3ds_sdk
Implementar bridge: Reemplazar el archivo
AppDelegate.swift en
carpeta Runner del proyecto. Considerar que, si cambia el valor
de la variable CHANNEL, deberá también cambiarlo en flutter,
dentro de la función recivePayment encontrará la lógica que
presenta el formulario de CheckOut para lo cual se requiere:
- Inicializar el objeto CheckoutSettings:
let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.paymentBrands = ["VISA", "DIRECTDEBIT_SEPA"]
checkoutSettings.shopperResultURL = "com.companyname.appname.payments://result"
Iniciar actividad
let checkoutProvider = OPPCheckoutProvider(paymentProvider: provider, checkoutID: checkoutID!, settings: checkoutSettings)
checkoutProvider?.presentCheckout(forSubmittingTransactionCompletionHandler: { (transaction, error) in
guard let transaction = transaction else {
// Handle invalid transaction, check error
return
}
if transaction.type == .synchronous {
// If a transaction is synchronous, just request the payment status
// You can use transaction.resourcePath or just checkout ID to do it
} else if transaction.type == .asynchronous {
// The SDK opens transaction.redirectUrl in a browser
// See 'Asynchronous Payments' guide for more details
} else {
// Executed in case of failure of the transaction for any reason
}
}, cancelHandler: {
// Executed if the shopper closes the payment page prematurely
})
Implementación en Android
Tipo de proyecto: Android Application
Archivos: En este DEMO encontrará clases java como usados para
realizar la
interfaz de este demo, sin embargo, los archivos que debe revisar para su
implementación son:
CheckoutSettings checkoutSettings = new CheckoutSettings(checkoutId, paymentBrands, Connect.ProviderMode.TEST);
checkoutSettings.setShopperResultUrl("companyname://result");
Iniciar actividad
Intent intent = checkoutSettings.createCheckoutActivityIntent(this);
startActivityForResult(intent, CheckoutActivity.REQUEST_CODE_CHECKOUT);
Configuración:
Importar módulos: Desde Android Studio agregar en la carpeta libs
los
archivos oppwa.mobile.aar, ipworks3ds_sdk.aar, oppwa.mobile.threeds.aar
Agregar dependencias: En el build.gradle (Module App), agregar
las siguientes dependencias.
implementation fileTree(dir: "libs", include: ["*.aar"])
implementation "androidx.appcompat:appcompat:1.3.1"
implementation "androidx.browser:browser:1.3.0"
implementation "com.google.android.material:material:1.4.0"
implementation "com.google.android.gms:play-services-base:17.6.0"
implementation 'com.android.support:multidex:2.0.1'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.pixplicity.easyprefs:library:1.9.0'
implementation 'com.vinaygaba:creditcardview:1.0.4'
implementation("androidx.recyclerview:recyclerview:1.2.1")
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
Agregar las siguientes clases en archivo proguard-rules.pro:
-keep class com.oppwa.mobile.connect.threeds.OppThreeDSService { *; }
-keep class com.nsoftware.ipworks3ds.sdk.ThreeDS2Service { *; }
Declarar servicio de conexión en AndroidManifest.xml: Dentro del
nodo application agregar:
Agregar las siguientes clases en archivo proguard-rules.pro:
<service
android:name="com.oppwa.mobile.connect.service.ConnectService"
android:exported="false"/>
Cambiar colores: Dentro del archivo de estilos (res/values/styles
y
colors) encontrará un tema default, donde podrá cambiar colores en caso
de así requerirlo
Pagos asíncronos (OTT)
Modificar el archivo AndroidManifest.xml
- En la actividad principal de la aplicación, agregar el
launchMode como singleTask y agregar un
intent-filter como se
especifica a continuación
<activity
android:name="YOUR_ACTIVITY"
android:launchMode="singleTask">
<intent-filter>
<data android:scheme="companyname"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
NOTA: Es importante que el launchMode tenga
asignado el valor de singleTask.
Agregar URL de pago
Tan pronto como se procesa el pago asíncrono, se abre la URL del
resultado del pago. Para aquello se debe configurarlo en la clase
CheckoutSettings.
Ejemplo de URL: companyname://result.
El esquema debe ser el mismo que has registrado en el paso anterior. El
resto de la URL puede ser cualquier cadena válida (pero no vacía).
val checkoutSettings = CheckoutSettings(checkoutId, paymentBrands, providerMode)
checkoutSettings.setShopperResultUrl("companyname://result")
Realizar este paso antes de enviar la solicitud de pago, ya que al
hacerlo antes provocara un error de anulación.
Redireccionamiento (Opcional)
Si se desea realizar un redireccionamiento de destino debe anular el
método onNewIntent, este mismo escenario puede ayudar a manejar
transacciones abortadas.
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.scheme == "companyname") {
/* request payment status */
}
}
Implementación en iOS
Tipo de proyecto: iOS Application
Archivos: En este DEMO encontrará clases swift usados para realizar
la
interfaz de este demo, sin embargo, los archivos que debe revisar para su
implementación son:
- Controller/HomeViewController: Esta es la clase principal de la
integración, consumirá el mSDK y presentará el formulario de pago con
las
variables configuradas, el ejemplo de consumo lo podrá encontrar en la
función displayForm donde se obtienen las variables y se inicializa y
presenta el mSDK, podrá encontrar las siguientes variables:
- Config_Ambiente: Ambiente al cual se conectará el widget
(TEST, LIVE)
- Config_Brands: Marcas de tarjeta que soportará el widget,
separados por coma (,), por ejemplo:
VISA,MASTER,AMEX,DINERS,DISCOVER
- Config_MostrarTotal: Flag que indica si el plugin
mostrará el
valor
total de la transacción (true/false)
- Config_SkipCVV: Flag que indica si las tarjetas
tokenizadas
requerirán o no del ingreso de CVV.
- Config_Tokenize: Indica si se debe mostrar la opción de
tokenización de tarjeta (0: Nunca, 1 Mostrar, 2. Siempre
Tokenizar –
no recomendado)
- Pago_Diferidos: Instancia los meses diferidos a mostrar,
separados con coma (,), por ejemplo: 3,6,9
- Estilo_Tema: Indica el tema a aplicar al plugin
(Dark/Light)
- Estilo_Idioma: Indica el idioma a aplicar al plugin
(es_ES,
en_US)
- Config_TipoPayment: Indica el tipo de crédito
seleccionado
Para presentar el formulario de CheckOut se requiere:
- Swift
- Inicializar el objeto CheckoutSettings:
let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.paymentBrands = ["VISA", "DIRECTDEBIT_SEPA"]
checkoutSettings.shopperResultURL = "com.companyname.appname.payments://result"
- Iniciar actividad
let checkoutProvider = OPPCheckoutProvider(paymentProvider: provider, checkoutID: checkoutID!, settings: checkoutSettings)
checkoutProvider?.presentCheckout(forSubmittingTransactionCompletionHandler: { (transaction, error) in
guard let transaction = transaction else {
// Handle invalid transaction, check error
return
}
if transaction.type == .synchronous {
// If a transaction is synchronous, just request the payment status
// You can use transaction.resourcePath or just checkout ID to do it
} else if transaction.type == .asynchronous {
// The SDK opens transaction.redirectUrl in a browser
} else {
// Executed in case of failure of the transaction for any reason
}
}, cancelHandler: {
// Executed if the shopper closes the payment page prematurely
})
Objective-C
- Inicializar el objeto CheckoutSettings:
OPPCheckoutSettings *checkoutSettings = [[OPPCheckoutSettings alloc] init];
checkoutSettings.paymentBrands = @[@"VISA", @"DIRECTDEBIT_SEPA"];
checkoutSettings.shopperResultURL = @"com.companyname.appname.payments://result";
Iniciar actividad
OPPCheckoutProvider *checkoutProvider = [OPPCheckoutProvider checkoutProviderWithPaymentProvider:provider
checkoutID:checkoutID settings:checkoutSettings];
[checkoutProvider presentCheckoutForSubmittingTransactionCompletionHandler:^(OPPTransaction * _Nullable transaction, NSError * _Nullable error) {
if (error) {
// Executed in case of failure of the transaction for any reason
} else if (transaction.type == OPPTransactionTypeSynchronous) {
// Send request to your server to obtain the status of the synchronous transaction
// You can use transaction.resourcePath or just checkout id to do it
} else {
// The SDK opens transaction.redirectUrl in a browser
}
} cancelHandler:^{
// Executed if the shopper closes the payment page prematurely
}];
Configuración:
Importar sdk: Arrastrar los archivos OPPWAMobile.framework,
OPPWAMobile-Resources.bundle, ipworks3ds_sdk.xcframework y
OPPWAMobile_ThreeDS.xcframework a la carpeta Frameworks (Asegúrese de
tener marcada la opción de copiar elementos de ser necesario)
- En la opción Build Phases sección Embed Framework:
Activar el
code sign de los tres sdk copiados en el paso anterior.
Activar configuraciones de compilación: Asegurarse que las
siguientes opciones se encuentren activadas:
- Enable Modules (C and Objective-C)
- Link Frameworks Automatically
Cargar Archivos base: Copiar archivos Language.swift,
Constant.swift y ColorsCheckout.swift a carpeta Runner del proyecto.
Importar header OPPWA: Importar en archivo *-Bridging-Header.h,
quedando:
#include <OPPWAMobile/OPPWAMobile.h>
@import OPPWAMobile_ThreeDS;
@import ipworks3ds_sdk;
Cambiar colores: Se podrán cambiar los colores de la actividad
desde el objeto OPPCheckoutSettings. Encontrará los posibles valores en
la sección de anexos.
let settings = OPPCheckoutSettings()
settings.theme.style = .light
settings.theme.confirmationButtonColor = UIColor.green
Pagos asíncronos (OTT)
Realizar estos pasos en el xcode.
- Registrar un nuevo custom scheme, para ello se debe dirigir a:
- App Target
- Info
- URL Types
El nombre deberá iniciar con el Bundle ID (identificador de la
aplicación), pudiendo quedar por ejemplo
“com.companyname.appname.payments” donde
“com.companyname.appname” es el
Bundle Id de la aplicación, suponiendo que el bundle id de su aplicación
es com.df.data ahora quedaría com.df.data.dataweb, donde
dataweb es el
nombre que se le asigna a la aplicación este mismo debe ser configurado
en el método shopperResultURL.
- Agregar el scheme en el whitelist del archivo Info.plist (Modificar con
URL definido previamente):
- Key: LSApplicationQueriesSchemes
- Value: com.companyname.appname.payments
<key>LSApplicationQueriesSchemes</key>
<array>
<string>com.companyname.appname.payments</string>
</array>
Agregar URL de pago
Tan pronto como se procesa el pago asíncrono, se abre la URL del resultado
del pago. Para aquello se debe configurarlo en la clase
CheckoutSettings.
Ejemplo de URL: com.companyname.appname.payments://result.
El esquema debe ser el mismo que has registrado en el paso anterior. El
resto de la URL puede ser cualquier cadena válida (pero no vacía).
let checkoutSettings = OPPCheckoutSettings()
checkoutSettings.shopperResultURL = "com.companyname.appname.payments://result"
Realizar este paso antes de enviar la solicitud de pago, ya que al hacerlo
antes provocara un error de anulación.
Manejar Solicitudes de URL
Manejar la devolución de llamada específica en AppDelegate o SceneDelegate
según el objetivo de implementación de su aplicació. Asegúrese de que el
esquema de URL sea idéntico al registrado en el paso anterior
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
if url.scheme?.caseInsensitiveCompare("com.companyname.appname.payments") == .orderedSame {
checkoutProvider.dismissCheckout(animated: true) {
// request payment status
}
return true
}
return false
}
Si se utiliza las ultimas versiones de iOS de 13 para se debe agregar el
esquema en la clase scene:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set) {
guard let url = URLContexts.first?.url else {
return
}
if (url.scheme?.caseInsensitiveCompare("com.companyname.appname.payments") == .orderedSame) {
checkoutProvider.dismissCheckout(animated: true) {
// request payment status
}
}
}
Nota Importante: usted es el responsable de cerrar el pago, llamando
al
método dismissCheckoutAnimated de OPPCheckoutProvider. Si no tiene acceso a
la instancia de OPPCheckoutProvider desde el delegado de la aplicación,
puede usar notificaciones de difusión para manejar el resultado en el
controlador de vista, por ejemplo, usar un observable como el
NotificationCenter.
Ejemplo:
- Definir el observable solo cuando el pago sea asíncrono:
if transaction.type == .asynchronous {
NotificationCenter.default.addObserver(self, selector: #selector(self.didReceiveAsynchronousPaymentCallback), name: Notification.Name(rawValue: "AsyncPaymentCompletedNotificationKey"), object: nil)
}
Llamar al método didReceiveAsynchronousPaymentCallback en la solicitudes
de url.
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool {
if url.scheme?.caseInsensitiveCompare("com.companyname.appname.payments ") == .orderedSame
{
didReceiveAsynchronousPaymentCallback(result: self.chResult!)
return true
}
return false
}
@objc func didReceiveAsynchronousPaymentCallback(result: @escaping FlutterResult) {
NotificationCenter.default.removeObserver(self, name: Notification.Name(rawValue: "AsyncPaymentCompletedNotificationKey"), object: nil)
self.checkoutProvider?.dismissCheckout(animated: true) {
DispatchQueue.main.async {
// request payment status
}
}
}