Nakroteck

Important

New Nakroteck billing portal is live

We have moved client billing accounts to the new Nakroteck billing portal. Use the same email address on your Nakroteck account to sign in. If you have not set your password yet, choose Forgot password on the login page. If you already reset your password and can log in, no further action is needed.
Flutter mobile guide

Build the Nakroteck mobile app on the v1 API

Use this flow for a Flutter app: token login, stateless client-account selection, invoice-first ordering, native payment handoff, push device registration, and support tickets with attachments.

Recommended app flow

1. Load config

Call GET /api/v1/mobile/config before login to get feature flags, Firebase app config, support departments, active gateways, limits, and update policy.

2. Login

Call POST /api/v1/auth/login and store the Sanctum bearer token in secure storage, not shared preferences.

3. Choose client

Use the selected client ID in the URL and send X-Client-Id for extra mismatch protection. Active and inactive clients can access the API.

4. Register device

After notification permission, call POST /clients/{client}/devices with the FCM/APNS token and app metadata.

5. Order invoice-first

Create orders through /clients/{client}/orders. The API returns an invoice and payment URL. Provisioning waits for paid invoice lifecycle.

6. Pay safely

Use Stripe Payment Sheet with the returned client secret, or open Paystack authorization_url in a secure webview. Poll payment-status and payment-timeline after callback.

Dart client skeleton

Dio setup with bearer token and client context

final dio = Dio(BaseOptions(
  baseUrl: 'https://nakroteck.net/api/v1',
  headers: {
    'Accept': 'application/json',
    if (token != null) 'Authorization': 'Bearer $token',
    if (clientId != null) 'X-Client-Id': clientId.toString(),
  },
));

Future<Map<String, dynamic>> loadMobileConfig() async {
  final response = await dio.get('/mobile/config');
  return response.data as Map<String, dynamic>;
}

Future<void> initFirebaseFromApi() async {
  final config = await loadMobileConfig();
  final firebase = config['firebase'] as Map<String, dynamic>;

  if (firebase['configured'] == true) {
    await Firebase.initializeApp(
      options: FirebaseOptions(
        apiKey: firebase['api_key'],
        appId: firebase['app_id'],
        messagingSenderId: firebase['messaging_sender_id'],
        projectId: firebase['project_id'],
        authDomain: firebase['auth_domain'],
        storageBucket: firebase['storage_bucket'],
        measurementId: firebase['measurement_id'],
      ),
    );
  }
}

Future<Map<String, dynamic>> login(String email, String password) async {
  final response = await dio.post('/auth/login', data: {
    'email': email,
    'password': password,
    'device_name': 'Nakroteck Flutter',
  });
  return response.data as Map<String, dynamic>;
}
Mobile payments

Stripe Payment Sheet and Paystack webview

final response = await dio.post(
  '/clients/$clientId/invoices/$invoiceId/payments/start',
  data: {'gateway': 'stripe'},
  options: Options(headers: {'Idempotency-Key': paymentAttemptId}),
);

final mobile = response.data['payment']['mobile'];
// Stripe: mobile['payment_intent_client_secret'] is passed to flutter_stripe Payment Sheet.
// Paystack: open mobile['authorization_url'] in a secure webview, then poll mobile['status_url'].
Payment confirmation remains gateway-driven. The app should not mark invoices paid locally. Poll payment-status and rely on Nakroteck webhook reconciliation.
Payment timeline

Show useful payment state in the app

final status = await dio.get('/clients/$clientId/invoices/$invoiceId/payment-status');
final timeline = await dio.get('/clients/$clientId/invoices/$invoiceId/payment-timeline');

// Use payment-status for the main invoice state.
// Use payment-timeline for retry UI, gateway name, failed attempts, decline code, and support/debug screens.
Pending
Keep polling or show “waiting for gateway confirmation”.
Failed
Show safe decline message and “try another payment method”.
Paid
Refresh invoices, orders, services, domains, and tickets.
Push and support

Device registration and ticket uploads

Firebase public app configuration is returned from /mobile/config.firebase. Server-side push delivery still requires Nakroteck admin FCM service-account JSON before push notifications can be sent.
await dio.post('/clients/$clientId/devices', data: {
  'device_id': installationId,
  'device_name': deviceName,
  'platform': Platform.isIOS ? 'ios' : 'android',
  'push_provider': Platform.isIOS ? 'apns' : 'fcm',
  'push_token': pushToken,
  'app_version': packageInfo.version,
  'build_number': packageInfo.buildNumber,
  'timezone': await FlutterNativeTimezone.getLocalTimezone(),
  'notifications_enabled': true,
});

final form = FormData.fromMap({
  'department_id': 1,
  'subject': 'Need help with my hosting',
  'message': 'Screenshot attached.',
  'priority': 'medium',
  'attachments[]': await MultipartFile.fromFile(file.path),
});
await dio.post('/clients/$clientId/tickets', data: form);

Implementation rules for the Flutter app

  • Store API tokens with flutter_secure_storage or platform keychain storage.
  • Do not cache payment success as truth. Always ask the API for the invoice status and use payment timeline for retries.
  • Use a unique Idempotency-Key for every order/payment-start attempt and reuse it only for safe retries.
  • Never send raw hosting passwords, card data, or gateway secrets to the API.
  • Use /mobile/config to control feature flags and force-update behavior without app-store releases.
  • Register push devices only after permission is granted. If the user disables notifications, update the device with notifications_enabled=false.