Flutter Production Checklist

This section highlights best practices and recommendations to help you achieve the best user experience with Dapi integration.

General Production Checklist

👍

ClientUserID configuration

ClientUserID is used to distinguish between different users on the same device. The value for ClientUserID needs to be set by you. We recommend setting clientUserID to your actual user ID that you use to distinguish between users. You should update the clientUserID once the user logs out and another user logs in.

Why? clientUserID is used to keep track of the bank connections in the cache.

👍

INVALID_CREDENTIALS or INVALID_CONNECTION

When receiving this error, the bank account should be connected again. Depending on your flow, you can do one or multiple of those steps:

  • Delete the connection connection.delete()
  • Prompt the user with Connect screen call Dapi.presentConnect() to reconnect the bank account. User selects the bank and logs in again.
  • Prompt the user with the same bank login screen Dapi.instance.presentConnect(bankID) to reconnect the bank account. User can log in again to the same bank that was connected before and skips the bank choosing stage.

Why? This error indicates that the user has updated their online banking credentials. This means that also the linked bank account details should be updated. If your application continues to use outdated details, it may result in blocking the user's banking account.

To reproduce this error on Sandbox to test your implementation you can do the following:

1- Login to your sandbox account
2- Open your app on the dashboard and navigate to the Sandbox tab. You'll see the Sandbox Users table.
3- Delete the account you're currently logged in to.
4- Go back to the Dapi SDK and make any API call and it will return this error

2692

Delete Sandbox Account

👍

Bank IDs

If your logic is relying on bankIDs, then note that sandbox and production bankIDs are different. For example, ADCB:

  • Sandbox: DAPIBANK_AE_ADCB
  • Production: ADCBAEAA

👍

Do you have internal timeouts?

Do you usually have a default timeout for the requests going out of your application or server? It is possible that resolving a request with the bank can take longer. Dapi has an internal timeout at

  • 240 seconds for connection.createTransfer() and connection.createWireTransfer()
  • 120 seconds for all other endpoints

Having a shorter timeout on your end can result in it occasional 504 errors.

Data API Specific Checklist

getTransactions

Only applicable if you are querying for transaction histories

👍

Transaction ranges

Each bank supports a different range of transactions.

You can retrieve the supported ranges from Metadata API. Refer to the transactionRange parameter.

Payment API Specific Checklist

👍

Special characters

Please double-check that you are only passing in alpha-numeric values in the beneficiary information. Including special characters in any of the fields will result in errors later on.

👍

BENEFICIARY_COOL_DOWN_PERIOD

Make sure you have handled the beneficiary cooldown period. Receiving this error means that beneficiary activation will take time from the bank side and the user must wait for the cooldown period to end before attempting the transfer again.

The exact time taken varies based on the user's bank. You can get the time take for the beneficiary to be active for any bank by using getAccountsMetaData API. You can for example use it to schedule a notification for the user to send money again when the beneficiary is activated.

👍

Do you need to reconcile the transfers?

If yes, make sure you are leveraging the remark field in connection.createTransfer(account, beneficiary, amount, ##remark##) or connection.createTransferToExistingBeneficiary(account, beneficiaryID, amount, ##remark##).

The remark field can hold any value set by you. It would be useful to use a value that uniquely identifies the transfer on your application side.

In order to be sure that the entire value can be used, we recommend keeping the remark shorter than 15 characters.

NB! No special characters are allowed in the remark field

👍

Transfer Confirmation Failed

TRANSFER_CONFRIMATION_FAILED is a special error that we recommend handling. The error means that at the very last step of the transfer the bank returns a general error message.

This can happen sometimes due to the bank's side internal errors.

In these cases, there is still a possibility that the transfer was successful and the money was debited from the account. We advise checking with the end-user when this error message is received.

👍

Common UI Issue In Flutter Framework

A common issue in the Flutter Framework is that Flutter Views may receive touches and events even if hidden behind another ViewComtroller. There are multiple workarounds to fix this issue. We recommend checking the issue on GitHub!

Here is one possible fix recommended by Dapi:
Change the rootViewController from the AppDelegate and override methods of AppDelegate assuming that the rootViewController is an object of type FlutterViewController, not a native UIViewController.

@interface AppDelegate : FlutterAppDelegate
@property (nonatomic, strong) FlutterViewController *flutterViewController;
@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    self.flutterViewController = [[FlutterViewController alloc] init];
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:self.flutterViewController];
    [self.window makeKeyAndVisible];
    
    [GeneratedPluginRegistrant registerWithRegistry:self];

return [super application:application didFinishLaunchingWithOptions:launchOptions];
}


- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
    if ([[UIApplication sharedApplication].keyWindow.rootViewController isKindOfClass:[FlutterViewController class]]) {
        return;
    }
}
- (NSObject<FlutterPluginRegistrar> *)registrarForPlugin:(NSString *)pluginKey {
    return [self.flutterViewController registrarForPlugin:pluginKey];
}

- (BOOL)hasPlugin:(NSString *)pluginKey {
    return [self.flutterViewController hasPlugin:pluginKey];
}

- (NSObject *)valuePublishedByPlugin:(NSString *)pluginKey {
    return [self.flutterViewController valuePublishedByPlugin:pluginKey];
}

@end

Production Access

Once you have completed the above checklist here are the 2 simple steps to move your application from Sandbox to Production.

1. AppKey Permissions

Contact the Dapi team to give your existing appKey the permission to make calls in our Production environment.

2. Change the environment variable to DapiEnvironment.PRODUCTION

await Dapi.start(
  "APP_KEY",
  "CLIENT_USER_ID",
  configurations: new DapiConfigurations(
  		environment: DapiEnvironment.PRODUCTION, //used to be DapiEnvironment.SANDBOX
  		,...
);

Congratulations, you are all set with your Dapi integration!