Flutter + Paytm: Fixing “Namespace not specified”, missing paytm package, and AGP 8 build errors (Complete Guide)

Posted by

Limited Time Offer!

For Less Than the Cost of a Starbucks Coffee, Access All DevOpsSchool Videos on YouTube Unlimitedly.
Master DevOps, SRE, DevSecOps Skills!

Enroll Now

0) The symptoms you’ll see

  • Namespace not specified. Specify a namespace in the module's build file
  • Error: Couldn't resolve the package 'paytm' in 'package:paytm/paytm.dart'
  • FileSystemException ... org-dartlang-untranslatable-uri: package%3Apaytm%2Fpaytm.dart
  • “SDK processing. This version only understands SDK XML versions up to 3… version 4 was encountered” (a harmless warning once the main issues are resolved)

Root causes, in short:

  1. AGP 8+ requires namespace in every Android module (android { namespace "..." }).
  2. The deprecated paytm plugin is incompatible with AGP 8, and you had code importing it.
  3. Your app should use paytm_allinonesdk instead, with the correct method calls and callback URL.

1) What you should use in 2025

  • Preferred Paytm plugin for Flutter: paytm_allinonesdk
  • Do not use: paytm (older package; causes AGP issues and missing API errors)

Pubspec (good):

dependencies:
  flutter:
    sdk: flutter
  paytm_allinonesdk: ^1.2.6
  provider: ^6.1.2
  http: ^1.2.1
  # ...anything else you need

Remove this if present:

# ❌ remove
paytm: ^3.0.1

Run:

flutter clean
flutter pub get

2) Android Gradle Plugin 8+ requires namespace

Open android/app/build.gradle and make sure you have:

android {
    namespace "com.example.flutter_application_1" // <-- use your real package
    compileSdkVersion 34

    defaultConfig {
        applicationId "com.example.flutter_application_1"
        minSdkVersion 21
        targetSdkVersion 34
        // ...
    }
    // ...
}

If you saw the namespace error in a plugin module, it’s because that plugin is outdated (like paytm). Removing that plugin (and its imports) solves it. If you must keep an old plugin (not recommended), see Section 10 for a temporary Gradle workaround.


3) Replace legacy Paytm calls with All-in-One SDK

3.1 Imports (Dart)

Old (remove):

import 'package:paytm/paytm.dart';

New:

import 'package:paytm_allinonesdk/paytm_allinonesdk.dart';

3.2 ViewModel method (drop-in)

Below is a complete replacement for your makePayment(...) method that works with paytm_allinonesdk, handles emulator web flow, and navigates on success/failure.

Future<Map<String, dynamic>> makePayment({
  required double totalSum,
  required double gst,
  required double sumGst,
  required List<String> cartId,
  required String email,
  required List<CartDetail> cartDetail,
  required String currency,
  required List<int> proid,
  required List<String> influencername,
  required List<String> influenceremail,
  required List<int> influnceradminid,
  required BuildContext context,
}) async {
  isLoading = true;
  notifyListeners();

  try {
    final String orderId = generateOrderId();

    // Get txnToken from YOUR backend (never generate it on device)
    final String txnToken = await api.generateTransactionTokens(
      orderId: orderId,
      totalSum: totalSum,
      gst: gst,
      sumGst: sumGst,
      cartId: cartId,
      email: email,
      currency: currency,
      proid: proid,
      influencername: influencername,
      influenceremail: influenceremail,
      influnceradminid: influnceradminid,
    );

    const bool isStaging = true; // set to false in production
    final String callbackUrl = isStaging
        ? 'https://securegw-stage.paytm.in/theia/paytmCallback?ORDER_ID=$orderId'
        : 'https://securegw.paytm.in/theia/paytmCallback?ORDER_ID=$orderId';

    // Emulator often lacks Paytm app; force webview route:
    final bool restrictAppInvoke = true;

    final result = await AllInOneSdk.startTransaction(
      'COTOCU83816930595730', // <-- replace with your MID
      orderId,
      totalSum.toStringAsFixed(2),
      txnToken,
      callbackUrl,
      isStaging,
      restrictAppInvoke,
    );

    final Map<String, dynamic> res = Map<String, dynamic>.from(result as Map);
    final String status = (res['STATUS'] ?? res['resultStatus'] ?? '').toString();

    if (status == 'TXN_SUCCESS') {
      Navigator.push(
        context,
        MaterialPageRoute(builder: (_) => OrderSuccessScreen(orderId: orderId)),
      );
      return {
        'status': 'success',
        'orderId': orderId,
        'gateway': res,
      };
    } else {
      // If you have a Cancelled() screen, navigate there
      Navigator.push(
        context,
        MaterialPageRoute(builder: (_) => const Cancelled()),
      );
      return {
        'status': 'failure',
        'orderId': orderId,
        'gateway': res,
      };
    }
  } catch (e, st) {
    print('Paytm error: $e');
    print(st);
    return {'status': 'error', 'errorMessage': e.toString()};
  } finally {
    isLoading = false;
    notifyListeners();
  }
}

Your UI screen can keep its button that calls influencerViewModel.makePayment(...).
Remove the import 'package:paytm/paytm.dart'; from any widget files.


4) Backend: generating the Paytm transaction token

Never generate the token on the device. Your server should:

  1. Create an order in your system (with orderId).
  2. Call Paytm’s token/checksum API using your merchant key.
  3. Return the txnToken to the app.

Pseudo-example (Node/Express):

app.post('/paytm/token', async (req, res) => {
  const { orderId, amount, customerId } = req.body;

  // Build body as per Paytm docs (MID, ORDER_ID, TXN_AMOUNT, CUST_ID, CALLBACK_URL, etc.)
  // Sign with merchant key to create checksum/token.
  // Send to Paytm, get txnToken, return it to client:
  return res.json({ txnToken: "<server-generated-token>" });
});

Your Flutter code calls this endpoint via api.generateTransactionTokens(...).


5) Android app setup sanity checklist

  • Internet permission in android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET"/>
  • MainActivity only needs to be a FlutterFragmentActivity if you ever see an error like
    “Plugin requires a FragmentActivity”:
class MainActivity : FlutterFragmentActivity() // only if needed
  • Min/Target SDK: typical minSdkVersion 21, targetSdkVersion 34.
  • Proguard/R8: usually no special rules needed for All-in-One SDK, but if you obfuscate and hit class-not-found, temporarily disable minify to test.
  • Emulator: set restrictAppInvoke = true to force webview flow when Paytm app isn’t installed.

6) Common errors & fixes

A) “Namespace not specified…”

  • Add namespace in android/app/build.gradle (Section 2).
  • If it’s inside a plugin (like :paytm), update/remove the plugin. Using paytm_allinonesdk fixes it.

B) “Couldn’t resolve package ‘paytm’…”

  • You still have import 'package:paytm/paytm.dart'; somewhere. Remove it.
  • Ensure pubspec.yaml does not list paytm. Only paytm_allinonesdk.

C) “SDK processing… XML versions up to 3; version 4 encountered”

  • Command-line tools vs Android Studio mismatch. It’s a warning. Fix by updating SDK command-line tools later (sdkmanager --update). Not a blocker.

D) Callback mis-match / order not found

  • Ensure your callback URL matches environment:
    • Staging: https://securegw-stage.paytm.in/theia/paytmCallback?ORDER_ID=$orderId
    • Prod : https://securegw.paytm.in/theia/paytmCallback?ORDER_ID=$orderId
  • Make sure ORDER_ID is exactly the same on device, token generation, and callback.

E) Activity/FragmentActivity errors

  • If the plugin demands FragmentActivity, change MainActivity base class as shown above.

F) “Duplicate class …” after adding libraries

  • You likely pulled two overlapping dependencies. Remove the redundant one; avoid keeping both paytm and paytm_allinonesdk.

G) JAVA_HOME / Gradle version errors

  • Use a supported JDK (e.g., 17) for modern AGP.
  • If you must temporarily run older AGP to keep a legacy plugin:
    • classpath 'com.android.tools.build:gradle:7.4.2'
    • distributionUrl=.../gradle-7.6-all.zip
      But the recommended path is not downgrading—fix the plugin instead.

7) End-to-end test steps

  1. Remove paytm from pubspec.yaml. Keep paytm_allinonesdk.
    flutter clean && flutter pub get
  2. Add namespace in android/app/build.gradle.
  3. Ensure callback URL and MID are correct for staging/prod.
  4. Ensure server endpoint returns a valid txnToken.
  5. On emulator: restrictAppInvoke: true.
  6. Run: flutter run.
  7. Complete the flow → verify success landing screen and server logs.

8) Production switch checklist

  • Set isStaging = false
  • Update callbackUrl to production
  • Ensure your server uses production Paytm keys
  • Verify order/accounting handling on success/failure

9) Full minimal snippets to copy

Import:

import 'package:paytm_allinonesdk/paytm_allinonesdk.dart';

Helper (optional):

Future<Map<String, dynamic>> _startPaytmTransactionAIO({
  required String mid,
  required String orderId,
  required String amount,
  required String txnToken,
  required String callbackUrl,
  bool isStaging = false,
  bool restrictAppInvoke = false,
}) async {
  try {
    final result = await AllInOneSdk.startTransaction(
      mid, orderId, amount, txnToken, callbackUrl, isStaging, restrictAppInvoke,
    );
    return Map<String, dynamic>.from(result as Map);
  } catch (e) {
    return {'STATUS': 'TXN_FAILURE', 'ERROR_MESSAGE': e.toString()};
  }
}

Make payment (drop-in): use the big method from Section 3.2.

Android build.gradle (module: app):

android {
    namespace "com.yourcompany.yourapp"
    compileSdkVersion 34

    defaultConfig {
        applicationId "com.yourcompany.yourapp"
        minSdkVersion 21
        targetSdkVersion 34
    }
}

10) (Optional) Temporary workaround for legacy plugins (not recommended)

If you’re forced to keep a very old Android plugin that lacks namespace, you can inject a fallback in android/build.gradle (project-level) that sets namespace from the plugin’s AndroidManifest.xml:

subprojects { subproject ->
    afterEvaluate {
        def hasAndroid = subproject.plugins.hasPlugin('com.android.application') || subproject.plugins.hasPlugin('com.android.library')
        if (hasAndroid) {
            def androidExt = subproject.extensions.findByName("android")
            if (androidExt != null && androidExt.hasProperty("namespace") && androidExt.namespace == null) {
                def manifest = file("${subproject.projectDir}/src/main/AndroidManifest.xml")
                if (manifest.exists()) {
                    def txt = manifest.getText('UTF-8')
                    def m = (txt =~ /package="(.*?)"/)
                    if (m.find()) {
                        androidExt.namespace = m.group(1)
                        println "Set namespace for ${subproject.name} -> ${androidExt.namespace}"
                    }
                }
            }
        }
    }
}

But seriously—prefer removing the legacy plugin (like paytm) and using paytm_allinonesdk.


11) Quick FAQ

  • Do I need the Paytm app installed on device?
    No. Use restrictAppInvoke: true to force webview flow (handy on emulators).
  • Where do I put merchant key?
    On your server only (used to generate txnToken). Never ship merchant keys in your app.
  • Why am I still seeing the old errors?
    You likely still have the old import package:paytm/paytm.dart in some file. Global-search your project for paytm/paytm.dart and remove it.

12) Final sanity script (run these)

# 1) Ensure only All-in-One SDK is present
grep -R "package:paytm/paytm.dart" -n lib || echo "No legacy paytm import found ✅"

# 2) Clean & get
flutter clean
flutter pub get

# 3) Build & run
flutter run

Leave a Reply

Your email address will not be published. Required fields are marked *

0
Would love your thoughts, please comment.x
()
x