Skip to main content

Hosted Checkout in Flutter

The malipopay_checkout package opens your hosted checkout full screen inside your Flutter app (card and mobile money) and returns how it ended.

Card numbers go straight from the payer to the bank's hosted fields. They never pass through your app or your servers, so your app stays out of PCI scope. The bank's 3-D Secure challenge opens inside the same screen and comes back to the checkout when it is done.

Before you start​

  1. In the Malipopay dashboard open Settings > Checkout, create a checkout and copy its id. See Create a checkout.

  2. If the checkout only accepts signed links, sign them on your server. See Signed links.

  3. Android: your app needs the internet permission in android/app/src/main/AndroidManifest.xml:

    <uses-permission android:name="android.permission.INTERNET"/>

Install​

dependencies:
malipopay_checkout: ^0.1.0

Open the checkout​

import 'package:malipopay_checkout/malipopay_checkout.dart';

final result = await MalipopayCheckout.open(
context,
integration: 'YOUR_CHECKOUT_ID',
amount: 25000,
order: 'ORDER-42',
);

switch (result.status) {
case CheckoutStatus.successful:
// Confirm on your server before you deliver (see below).
break;
case CheckoutStatus.cancelled:
// The payer closed the checkout or went back.
break;
default:
// pending, partial or failed: check on your server.
break;
}

result.reference is the Malipopay payment reference and result.receiptUrl the receipt when it was paid.

Always confirm on your server​

The result is what the payer's phone saw. Before you deliver, confirm the payment on your server with the payment.confirmed webhook or GET /api/v1/payment/verify/{reference}. The webhook carries your order as customerReference.

One order, one payment​

Pass your own order id as order. Opening the checkout again for the same order continues the same payment instead of charging twice. Opening it for the same order with a different amount is refused.

Options​

ArgumentWhat it does
integrationYour checkout id (required).
amountWhat to charge, in TZS. Leave it out to let the payer enter it, if your checkout allows that. A checkout with a fixed amount ignores it.
orderYour order id.
descriptionA line shown under the amount.
payerCheckoutPayer(name:, phone:, email:), so the page can skip asking.
language'en' or 'sw'. The payer can switch on the page.
signedSignedLink(sig:, exp:) from your server.
returnUrlOnly used on a signed link, and only if it is on one of your checkout's website addresses.
environmentCheckoutEnvironment.production (default) or CheckoutEnvironment.uat.
baseUrlOverride the checkout host (for example a proxy).
titleThe app bar title. Payment by default.

To place the checkout inside your own layout instead of a full-screen route, use MalipopayCheckoutView(uri: checkoutUri(...), onResult: ...).

When Only accept signed links is on for your checkout, the amount and order cannot be changed on the phone. Your server computes the signature exactly as described in Signed links:

HMAC-SHA256(signingSecret, "{checkoutId}|{amount}|{currency}|{order}|{returnUrl}|{exp}")

Empty values stay empty between the bars, and exp is a Unix time in seconds. Send sig and exp to the app and pass them as signed: SignedLink(sig: sig, exp: exp).

Never put the signing secret in the app

Anyone can read a secret out of an app bundle. Sign on your server and send only sig and exp to the app.

How it works​

The package opens https://app.malipopay.co.tz/c/{checkoutId}/embed (or the UAT address) in a WebView and listens on a JavaScript channel. It believes the page's messages only while the WebView is on the checkout's own address, so no other page, such as the bank's 3-D Secure page, can end the checkout.

Testing​

Use environment: CheckoutEnvironment.uat with a checkout created on the UAT dashboard (demo.malipopay.co.tz). Card payments there use the test cards in Testing Card Payments, and no money moves.