Overview of OvoCheckout Mobile App - Complete Cross Platform Hybrid Payment Gateway | Android & iOS
OvoCheckout mobile app is the companion application for the OvoCheckout hybrid payment gateway platform. It is designed to help merchants and users manage payment activity on the go, offering a smooth mobile experience for accessing account information, tracking transactions, monitoring balances, and interacting with payment services directly from Android and iOS devices.
This documentation provides a clear overview of the entire system, summarizing the core processes for easy understanding. The platform includes an intuitive admin panel and landing page, designed for seamless use without any coding expertise. It also integrates multiple automated online payment gateways, simplifying global transactions effortlessly.
Thank you for choosing OvoCheckout. If you have any questions not covered in this documentation, please feel free to reach out to us via email. We're here to assist and will respond as promptly as possible.
All the prerequisites before starting with the OvoCheckout mobile app.
Before configuring the OvoCheckout mobile app, make sure your web platform or backend API is already installed and working correctly. You will also need to set up the app in Firebase Console, enable the required services, and download the configuration files for your mobile build.
Below are the requirements needed to set up and install a Flutter app.
The following installation requirements specify the essential setup for developing and running Flutter apps smoothly. Meeting these requirements helps ensure optimal performance and compatibility. Please confirm your environment meets these prerequisites before proceeding.
Application installation process
Setting up Flutter and Android Studio is straightforward and can be completed in a few simple steps. Our setup guide is designed to make the process seamless and efficient, ensuring a smooth start for your development environment.
flutter doctor to check if everything is set up
correctly. This command will display any missing
dependencies or configurations needed for development.
Important details about the application folder structure
After installation, your project folder will contain essential directories and files necessary for the proper functioning of the Flutter application.
assets folder contains all essential assets such as
images and fonts. This is the folder
to use if you need to add or update any images or custom fonts.
lib folder is the main directory for all Dart code
in the application. It is typically organized
into subfolders such as core for utility files and
dependencies, data for managing API
calls and local data sources, and view for UI
components and screens.
main.dart file is the entry point of the Flutter
application where the app's root
widget and initial setup are defined.
App name change overview
android/app/src/main/AndroidManifest.xml.
ios/Runner/Info.plist.
App icon change overview
android/app/src/main/res/.mipmap-hdpi/ic_launcher.png).AndroidManifest.xml file if necessary.
Assets.xcassets folder in the project navigator.
Customize the display text across the app by modifying string values.
The app's strings are managed in the file located at:
lib/core/utils/my_strings.dart.
my_strings.dart
in your preferred code editor.
Overview of how to change the base URL for your app's API requests.
lib\environment.dart).
static const String LIVE_API_URL = 'https://yourapi.com';
static const String TEST_API_URL = 'https://yourapi.com';).
Overview of how to change the app's primary and secondary color scheme.
lib\core\utils\my_color.dart).
Overview of how to change the package name for both Android and iOS in your Flutter app.
applicationId in the
android/app/build.gradle file:
Learn how to configure environment variables in Flutter using the `environment.dart` file for different environments like development, staging, and production.
The app's environment configuration is stored in the file located at:
lib/environment.dart.
environment.dart in your preferred code editor.
Learn how to change the logo of your Flutter app by updating the logo image in the assets directory.
Learn how to update Firebase settings for your Flutter app using the Firebase CLI tool and FlutterFire.
Still facing issues to add Firebase? Watch our step-by-step video guide to get it resolved quickly!
Learn how to generate a keystore and retrieve the SHA-1 and SHA-256 keys for your Flutter app.
android {
...
signingConfigs {
release {
keyAlias 'your_key_alias'
keyPassword 'your_key_password'
storeFile file('your_keystore_name.jks')
storePassword 'your_keystore_password'
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
shrinkResources false
}
}
}
Everything you need to build, sign, and release the iOS app: code signing, certificates, and provisioning profiles.
Building the iOS app requires a Mac computer with Xcode installed and an active Apple Developer Program membership (US $99 / year, billed by Apple). Apple requires every iOS app to be code-signed before it can run on a physical device or be submitted to the App Store. These accounts and fees are provided by Apple and are not included with OvoCheckout.
sudo gem install cocoapods) and the Flutter SDK configured for iOS.iOS code signing links your app to your Apple Developer identity using two pieces issued by Apple:
com.yourcompany.ovocheckout, registered in your
Apple Developer account and set in Xcode. It must match the package /
bundle id you configured for the app.
open ios/Runner.xcworkspace (open the .xcworkspace, not the .xcodeproj).Build the release binary with Flutter, then upload it through Xcode:
.ipa.Common signing errors: "No profiles for 'com.yourcompany.ovocheckout' were found" means the App ID or provisioning profile is missing or does not match the bundle identifier — recreate the profile for the exact bundle id. "Signing certificate is not valid" usually means the certificate expired or is not installed in your Keychain.
For the full, always-current process, follow Flutter's official guide: docs.flutter.dev/deployment/ios.
A practical guide to publishing a payment / financial app on the Apple App Store and Google Play, including the rules that most often cause rejection.
Please read first. Apps that handle payments, money transfers, or cryptocurrency are reviewed more strictly than ordinary apps. The notes below are general guidance to help you prepare your submission — they are not legal advice, and store policies change regularly. You are responsible for holding any licences or registrations required in the countries where you publish, and for complying with the current version of each store's policies. Always confirm against the official pages linked in this section before you submit.
Most payment-app rejections are caused by missing information rather than by the app itself. Have all of the following ready:
The rules below come from the App Store Review Guidelines. The sections most relevant to a payment app are Payments (3.1), Privacy (5.1) and Legal (5.0).
| Topic | What Apple expects |
|---|---|
| In-App Purchase vs. your own gateway | Digital content or services that are consumed inside the app (premium features, subscriptions to app content, in-app credits) must be sold through In-App Purchase. Payments for physical goods and real-world services consumed outside the app — the normal case for a payment gateway, invoice, or merchant checkout app — must not use In-App Purchase and may use your own payment processing. Make it obvious in the app and in your review notes which of the two your app does. |
| Person-to-person / merchant transfers | Apps that send money between people or settle merchant balances are allowed outside In-App Purchase, but the flow of funds must be clearly explained to the user before they confirm, including any fee you charge. |
| Cryptocurrency | Apple applies extra conditions to crypto: wallet apps must be submitted by a developer enrolled in the Apple Developer Program as an organisation (not an individual); on-device mining is not allowed; and exchange functionality is generally accepted only from the exchange itself or an established, licensed institution, and only in the regions where it is licensed. If your build enables crypto payments, expect to provide documentation. |
| Regulated / financial services | Apps providing banking, lending, trading, or money-transmission services must be submitted by the licensed institution itself, or you must be able to show written authorisation from it. |
| App Privacy details | Complete the App Privacy questionnaire in App Store Connect accurately — declare the contact data, financial/transaction data, identifiers, and usage data the app collects. Mismatches between this form and the app's real behaviour are a common rejection reason. |
| Account creation & deletion | If users can register in the app, the app must also offer a way to delete the account. Sign in with Apple may be required if you offer other third-party logins (such as Google Sign-In) as the only alternative to email sign-up. |
| App completeness | No placeholder text, broken links, or "coming soon" screens. Every gateway you leave enabled must actually work in the build you submit — disable the ones you have not configured. |
| Export compliance (encryption) |
The app uses HTTPS, so you must answer the encryption
questions at upload. Standard HTTPS usually qualifies for the
exemption — see
Apple's export-compliance guide
before setting ITSAppUsesNonExemptEncryption.
|
Useful Apple links: Review Guidelines, App Review overview, App Review support.
Google's rules live in the Developer Program Policy. For a payment app the important ones are Payments, Financial Services, Data safety, and Permissions.
| Topic | What Google expects |
|---|---|
| Payments policy | As on iOS, in-app digital goods must use Google Play Billing, while payments for physical goods and real-world services may use your own processor. See the Payments policy. |
| Financial Services policy | Apps offering financial products or services must complete the Financial features declaration in Play Console and, for regulated categories (lending, banking, money transmission, crypto exchanges and custodial wallets), upload licensing documentation and restrict distribution to the countries where you are licensed. See the Financial Services policy. |
| Data safety form | Every app must declare what it collects and shares in the Data safety section. Declare account data, financial/transaction data, and the Firebase messaging token, and state that data is encrypted in transit. |
| Account deletion | Provide in-app deletion plus a publicly reachable deletion request URL, and enter it in Play Console — see Account deletion requirements. |
| Permissions |
Only request permissions the app really uses. Sensitive
permissions such as SMS and Call Log require an approved
declaration and are usually rejected for payment apps that do
not need them — see the
Permissions policy.
Remove any permission you are not using from
AndroidManifest.xml before building.
|
| Target API level |
New apps and updates must target a recent Android API level.
Check the current requirement at
Target API level requirements
and set targetSdkVersion accordingly in
android/app/build.gradle.
|
| Metadata & store listing | Do not imply that your app is a bank or is endorsed by one, do not use another brand's logo, and make sure screenshots match the actual app. See the Store listing and promotion policy. |
| Developer account | Financial apps normally require a verified organisation account with verified identity and D-U-N-S details. Register at Play Console. |
AndroidManifest.xml and Info.plist.If you are rejected: both stores tell you which policy section failed. Reply in the review thread with a short, factual explanation, a screen recording of the flow the reviewer could not reach, and any licence documents they asked for. Most payment-app rejections are resolved by supplying a working demo account or the missing declaration rather than by changing code.
How to version your app, how to release an update to the stores, and how to take a new OvoCheckout release into an app you have already customised.
The app version is defined in pubspec.yaml at the root of the
Flutter project:
name: ovo_checkout
description: "Next-Generation Multi-Purpose Payment Gateway "
publish_to: "none"
version: 1.0.0+1
The value has two parts separated by a +:
1.0.0 — the version name. This is what
users see on the store listing. Flutter passes it to Android as
versionName and to iOS as
CFBundleShortVersionString.
+1 — the build number. Users never see
it. Flutter passes it to Android as versionCode and to
iOS as CFBundleVersion. Both stores use it to tell one
upload apart from another.
You do not edit the Android or iOS files to change the version. They
already read the values from pubspec.yaml —
android/app/build.gradle.kts contains:
versionCode = flutter.versionCode
versionName = flutter.versionName
and ios/Runner/Info.plist uses
$(FLUTTER_BUILD_NAME) and
$(FLUTTER_BUILD_NUMBER).
There is a second place to update.
lib/environment.dart holds a separate
appVersion string that the app displays in its own
interface:
static const appVersion = "1.0.0";
This one is not read from pubspec.yaml, so
it will not change on its own. Whenever you bump the version in
pubspec.yaml, update this string to match. Forgetting
it is the single most common versioning mistake — the app keeps
reporting an old version even though the store shows the new one.
A simple, widely used convention (semantic versioning) works well here.
Given a version MAJOR.MINOR.PATCH:
| Change you made | Bump | Example |
|---|---|---|
| Bug fixes, copy changes, small styling tweaks | Patch | 1.0.0+1 → 1.0.1+2 |
| New screens or features, backwards compatible | Minor | 1.0.1+2 → 1.1.0+3 |
| Redesign, or changes requiring a matching backend upgrade | Major | 1.1.0+3 → 2.0.0+4 |
| Re-uploading after a store rejection, no version-name change | Build number only | 1.0.0+1 → 1.0.0+2 |
The build number must always increase. Google Play
and App Store Connect both reject an upload whose build number is
equal to or lower than one you have already uploaded — even if that
earlier build was rejected, or you deleted it. Increment the number
after the + on every single upload, and never
reuse one.
version: in pubspec.yaml and
appVersion in lib/environment.dart.
lib/environment.dart, DEV_MODE must be
false and LIVE_API_URL must be your own
domain — not the demo URL that ships with the package.
flutter clean
flutter pub get
flutter build appbundle --release # Android
flutter build ipa --release # iOS (on macOS)
The app is a client for your OvoCheckout Laravel installation — it talks to
the server set in Environment.MAIN_API_URL. When a release
contains changes on both sides:
LIVE_API_URL at your upgraded installation and walk
through login, deposit and withdrawal before you upload.
The endpoints the app uses, and the response format it expects, are documented in the API Integration section of the main documentation. The Troubleshooting API Responses section above covers what to check when the app and server disagree.
When we publish a new version of OvoCheckout, you will have already customised your copy. Rather than starting over, keep track of which files are yours and carry them across.
Before you customise anything, put the untouched project under version control:
cd Files/Flutter
git init
git add .
git commit -m "OvoCheckout v1.0.0 - original files"
Then make your changes as normal commits. When a new release arrives, you
can copy the new files over the top and git diff will show
you exactly where your customisations and the new code overlap.
These are the files you are most likely to have changed, and that must be carried over or re-applied after any update:
| File / folder | What it holds |
|---|---|
lib/environment.dart |
App name, version string, API URL, default language and country. |
lib/firebase_options.dart |
Your Firebase configuration, generated by flutterfire configure. |
android/app/google-services.jsonios/Runner/GoogleService-Info.plist |
Your Firebase platform config files. |
android/app/build.gradle.kts |
Your applicationId and signing configuration. |
Your keystore .jks and key.properties |
Android upload signing. Keep these outside the project and backed up. |
assets/ |
Your logo, icons and onboarding images. |
| App name and string files | The display name and any wording you changed — see the Change App Name and Edit App Strings sections. |
After merging a new release, run flutter clean and
flutter pub get, then rebuild and test before you upload
anything.
Even if you change nothing in the app, the platforms move underneath it. Check the following a few times a year:
pubspec.yaml
(sdk: ">=3.10.0 <4.0.0"). Run
flutter upgrade to move to a newer stable Flutter.
flutter pub outdated lists what
has moved on; flutter pub upgrade applies updates allowed
by the existing constraints. Use
flutter pub upgrade --major-versions only when you are
ready to read changelogs and retest — major package upgrades can
introduce breaking changes.
compileSdk = 36 / targetSdk = 36 in
android/app/build.gradle.kts. Check the current
requirement at
Google Play target API level
requirements.
After any dependency or SDK change, do a full
flutter clean && flutter pub get and rebuild both
platforms — issues from these upgrades usually surface at build time
rather than at runtime.
Do not change the package name / bundle ID after
publishing. The Android
applicationId (com.ovosolution.ovocheckout
by default) and the iOS bundle identifier permanently identify your
app on the stores. Changing either creates a brand-new listing:
your existing users keep the old app, do not receive the update,
and your reviews and install count do not carry over. Decide on the
final identifier before your first upload — see the
Package Name section.
Do not lose your Android upload keystore. Every
update must be signed with the same key as the original upload. If
you lose the .jks file or its passwords you can no
longer update your own app, and recovery depends entirely on
whether you enrolled in Play App Signing. Back up the keystore and
key.properties somewhere safe and separate from the
project folder — see the
Keystore & SHA Key section.
The same applies to your Firebase project: keep the credentials in
lib/firebase_options.dart, google-services.json
and GoogleService-Info.plist backed up, and note that
changing the package name also invalidates the SHA fingerprints registered
in Firebase, which will break Google sign-in until you add the new ones.
External accounts, tools, and fees required to build and publish the mobile app.
Important: OvoCheckout does not include any third-party accounts, API keys, developer memberships, or paid subscriptions. Every service listed below belongs to an independent external company. You must create your own account with each service you choose to use, and you are solely responsible for reviewing and accepting that provider's current pricing, terms, and regional availability. All setup, membership, subscription, per-transaction, and usage-based fees are charged directly by the third-party provider — not by OvoCheckout or its author.
| Service | Used for | Required? | Typical costs (charged by the provider) |
|---|---|---|---|
| Apple Developer Program developer.apple.com/programs |
Signing, testing on real devices, and publishing the iOS app. | Only if you release on iOS. | Annual membership fee charged by Apple (US $99 / year at the time of writing). A Mac with Xcode is also required. |
| Google Play Console play.google.com/console |
Publishing and updating the Android app. | Only if you release on Google Play. | One-time registration fee charged by Google (US $25 at the time of writing). |
| Firebase (Google) firebase.google.com |
Push notifications (Cloud Messaging) and Google/Apple social sign-in. | Only if you use push notifications or social login. | Cloud Messaging and Authentication are free on the Spark plan for typical usage; very high volumes may need the pay-as-you-go Blaze plan. See Firebase pricing. |
| Payment gateways (NOWPayments for crypto; PayPal, Stripe and others for fiat) |
Processing the payments shown in the app. Configured on the server, not in the app. | Only for the payment types you accept. | Free account creation; per-transaction and/or percentage fees set individually by each gateway, plus any network or withdrawal fees. |
| Flutter SDK & Android Studio / Xcode | Building the app from source. | Required to build. | Free to download. Xcode requires macOS. |
| Your OvoCheckout server | The backend the app connects to (web hosting, domain, SSL). | Required. | Charged by your hosting provider and domain registrar. |
Prices shown here are indicative only and can change at any time. Always confirm the latest fees on each provider's official website before you commit. You only need the services you actually intend to use — for example, you can ship an Android-only build without an Apple Developer membership, or run the app without Firebase if you disable push notifications and social login.
How the OvoCheckout API replies, what every error code means, and how to handle each one in the Flutter app.
Every endpoint returns the same JSON shape, produced by the
apiResponse() helper in
core/app/Http/Helpers/helpers.php:
{
"remark": "login_success",
"status": "success",
"message": ["Login successful"],
"data": { "access_token": "...", "user": { } }
}
remark - a stable, machine-readable key. Branch your app
logic on this, never on the human-readable text.
status - the string "success" or
"error". Note this is a string, not a boolean.
message - a flat array of strings, not a
keyed object and not a single string. Join the entries or show the
first one.
data - present only when there is a payload. Treat it as
optional and always null-check it.
Most errors come back with HTTP 200.
apiResponse() defaults to status code 200, and the
controllers deliberately use that default for business errors.
So a failed login, a validation failure and a rejected withdrawal
all arrive as HTTP 200 with
"status": "error" in the body. An app that decides
success by checking statusCode == 200 will silently
treat every one of those failures as a success.
Check status first, then switch on remark, and
use the HTTP code only for transport-level problems:
final http.Response res = await http.post(uri, headers: headers, body: body);
// 1. Transport / server level first - the body may not even be JSON.
if (res.statusCode >= 500) {
throw ApiException('Server error (${res.statusCode}). Please try again later.');
}
late final Map<String, dynamic> json;
try {
json = jsonDecode(res.body) as Map<String, dynamic>;
} on FormatException {
// An HTML error page instead of JSON - almost always a server misconfiguration.
throw ApiException('Unexpected response from server.');
}
// 2. Then the application level - this is what actually decides success.
final String remark = json['remark'] as String? ?? '';
final bool isError = json['status'] != 'success';
final String text = (json['message'] as List?)?.join('\n') ?? 'Something went wrong.';
if (isError) {
switch (remark) {
case 'unauthorized': return _forceLogout(); // token expired
case 'unverified': return _goToVerification(); // email / SMS / 2FA
case 'profile_incomplete': return _goToProfileSetup();
case 'kyc_verification': return _goToKycForm();
case 'validation_error': return _showFieldErrors(text);
default: return _showError(text);
}
}
These come from the global handler in
core/app/Http/Exception/ExceptionHandler.php, so they can
reach any endpoint.
| HTTP | Remark | What it means in the app |
|---|---|---|
| 401 | unauthorized |
The Sanctum bearer token is missing, malformed, or was revoked (for example the user logged out elsewhere, or you deleted the account). Clear the stored token and send the user back to the login screen. Do not retry. |
| 403 | restricted |
The action is not permitted for this account. Show the message; retrying will not help. |
| 404 | not_found |
The route or record does not exist. In practice this usually means a typo in your endpoint path or a stale ID - check your base URL first. |
| Remark | Cause | How the app should respond |
|---|---|---|
validation_error |
One or more fields failed validation. Every failure is a separate entry in message. |
Show them against the form. This is the most frequent error by a wide margin. |
invalid_credential |
Wrong email/username or password at login. | Show the message on the login form; do not clear what the user typed. |
login_error |
The account exists but is banned or otherwise blocked from signing in. | Show the message and point the user at support. |
user_not_found |
No account matches the supplied email during password reset. | Show a neutral message - avoid confirming whether an address is registered. |
registration_disabled |
The admin has turned off new registrations. | Hide or disable the sign-up screen. |
invalid_code / code_not_match / wrong_code |
The emailed or texted verification code is wrong or has expired. | Let the user retype it and offer "resend code". |
already_verified |
That verification step is already done. | Skip straight to the next step instead of showing an error. |
try_after |
A code was requested again too soon. | Disable the resend button and show the countdown from message. |
token_not_exists |
The device token you tried to remove was not registered. | Harmless - ignore it silently. |
exception |
An unhandled server-side error. message carries the exception text. |
Show a generic failure message. Ask the site owner to check core/storage/logs/laravel.log. |
Three middlewares sit in front of the money-moving endpoints, so a
perfectly good token can still be turned away. These are
not bugs - each one is telling you to send the user
somewhere first. All three return HTTP 200 with
"status": "error".
| Remark | Raised by | Where to send the user |
|---|---|---|
profile_incomplete |
registration.complete |
The profile completion screen - post to api/user-data-submit. |
unverified |
check.status |
The verification screen. The response's data.user object tells you which step is outstanding: ev email, sv mobile, tv two-factor, status account enabled. |
kyc_verification |
kyc |
The KYC form - fetch it from api/kyc-form. Applies to withdrawals, invoices, wallets and payment history. |
jsonDecode throws a FormatException. This is
a server-side failure (usually a missing core/vendor
folder, a permissions problem, or a PHP fatal error) surfacing as an
error page. Always wrap decoding in a try/catch, as shown above.
GET
requests, and back off exponentially. Never blindly retry
payment-initiate or a withdrawal request - you risk
creating a duplicate transaction.
LIVE_API_URL / TEST_API_URL in
lib/environment.dart and the DEV_MODE flag -
a trailing slash or a stale host is the usual culprit.
Integrating a website or server rather than the mobile app? The
merchant-facing REST API, its client-id /
client-secret headers, IP whitelisting and IPN
webhooks are documented in the
API Integration section
of the main documentation.
How to get assistance
Thank you for purchasing our product! For any support or assistance, feel free to reach out to us via the provided email address. Our dedicated support team is available 24/7, ready to help with any questions, technical issues, or inquiries you may have. We are committed to providing prompt and reliable assistance to ensure a seamless experience with our product. Your satisfaction is our priority, and we are here to support you every step of the way.
Social Authentication Setup
Learn how to configure and integrate social authentication providers like Google.
Google Authentication