noon_payments 1.2.0 copy "noon_payments: ^1.2.0" to clipboard
noon_payments: ^1.2.0 copied to clipboard

A Flutter plugin for integrating Noon Payments SDK on Android and iOS.

example/lib/main.dart

import 'dart:convert';
import 'dart:developer';

import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:noon_payments/noon_payments.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _paymentResult = "Waiting for action...";
  bool _isLoading = false;

  /// Whether Apple Pay can be used here. Checked once on startup so the web
  /// button is gated WITHOUT awaiting inside the tap (Safari requires the
  /// Apple Pay session to be created synchronously in the user gesture).
  bool _applePayAvailable = false;

  /// Replace these with real data from your backend.
  ///
  /// 🚧 IMPORTANT: Your server-side INITIATE API must have the correct returnUrl:
  /// Android: https://localhost/noonappsdkresponse
  /// iOS: https://noonpayments.com/sdk/response
  final String testOrderId =
      '123456789012'; // The order Id that is received in the INITIATE API response
  final String testAuthHeader =
      "Key YOUR_AUTHORIZED_KEY"; // The authorization header for your business

  /// Your backend base URL (web Apple Pay routes the two Noon calls through it).
  static const String backendBaseUrl = 'https://your-server.com';

  @override
  void initState() {
    super.initState();
    NoonPayments.isApplePayAvailable().then((available) {
      if (mounted) setState(() => _applePayAvailable = available);
    });
  }

  /// 🚀 Standard Payment Flow (English)
  Future<void> _startStandardPayment() async {
    setState(() {
      _isLoading = true;
      _paymentResult = 'Launching...';
    });

    try {
      final result = await NoonPayments.initiatePayment(
        orderId: testOrderId,
        authHeader: testAuthHeader,
        environment: NoonEnvironment.sandbox,
        language: NoonPaymentLanguage.english,
      );

      _handleResult(result);
    } catch (e) {
      _handleException(e);
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  /// 🎨 Custom Styled Payment Flow with Logo
  Future<void> _startCustomStyledPayment() async {
    setState(() {
      _isLoading = true;
      _paymentResult = 'Launching Styled...';
    });

    try {
      // 1. Optional: Load your company logo from assets if you want it on the payment
      final ByteData rawLogo = await rootBundle.load('assets/icons/icon.png');
      final Uint8List logoBytes = rawLogo.buffer.asUint8List();

      final customStyle = NoonPaymentStyle(
        logoBytes: logoBytes,
        backgroundColor: "#F8F9FA",
        paymentOptionHeadingText: "Secure Checkout",
        paymentOptionHeadingForeground: "#2196F3",
        iosPaymentOptionHeadingFontSize: 20.0,
        paymentOptionBackground: "#FFFFFF",
        paymentOptionForeground: "#333333",
        iosPaymentOptionFontSize: 16.0,
        payNowButtonBackground: "#4CAF50",
        payNowButtonForeground: "#FFFFFF",
        payNowButtonText: "Secure Pay Now",
        iosPayNowButtonRadius: 8.0,
        payableAreaBackground: "#FFF9C4",
        payableAmountText: "Amount Due",
        payableAmountForeground: "#D32F2F",
        footerText: "Verified by Noon Payments",
        footerForeground: "#9E9E9E",
        iosYesButtonBackground: "#4CAF50",
        iosNoButtonBackground: "#F44336",
      );

      final result = await NoonPayments.initiatePayment(
        orderId: testOrderId,
        authHeader: testAuthHeader,
        // environment: NoonEnvironment.sandbox,
        environment: NoonEnvironment(
          "https://api-test.sa.noonpayments.com/payment/v1/order",
        ),
        language: NoonPaymentLanguage.english,
        style: customStyle,
      );

      _handleResult(result);
    } catch (e) {
      _handleException(e);
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  /// 🍏 Apple Pay — Direct Integration (iOS only)
  ///
  /// Presents the *native* Apple Pay sheet (not Noon's drop-in sheet) and
  /// submits the collected token to Noon's INITIATE API from the device.
  ///
  /// This is **iOS only**. On Flutter Web `payWithApplePay` returns a
  /// `USE_SERVER_SIDE` failure (the browser can't call Noon directly — CORS);
  /// use [_startApplePayWeb] there instead.
  Future<void> _startApplePayDirect() async {
    setState(() {
      _isLoading = true;
      _paymentResult = 'Checking Apple Pay...';
    });

    try {
      if (!await NoonPayments.isApplePayAvailable()) {
        setState(
          () =>
              _paymentResult = '🍏 Apple Pay is not available on this device.',
        );
        return;
      }

      final result = await NoonPayments.payWithApplePay(
        config: const NoonApplePayConfig(
          merchantIdentifier: 'merchant.com.yourcompany.app',
          countryCode: 'AE',
          currencyCode: 'AED',
          summaryItems: [
            NoonApplePaySummaryItem(label: 'Your Business', amount: '10'),
          ],
          supportedNetworks: [
            ApplePayNetwork.visa,
            ApplePayNetwork.masterCard,
            ApplePayNetwork.mada,
          ],
        ),
        order: const NoonOrder(
          amount: '10',
          currency: 'AED',
          name: 'Test Order',
          category: 'pay',
          reference: 'NPORDTEST0001',
        ),
        authHeader: testAuthHeader,
        environment: NoonEnvironment.sandbox,
        paymentAction: 'AUTHORIZE,SALE',
      );

      _handleResult(result);
    } catch (e) {
      _handleException(e);
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  /// 🌐 Apple Pay on the Web (Flutter Web, server-side flow)
  ///
  /// Presents the browser's Apple Pay (sheet in Safari, cross-device QR in
  /// Chrome/Edge) and routes the two Noon calls through YOUR backend via the
  /// callbacks — your auth key never reaches the browser.
  ///
  /// 🚧 Safari rule: this MUST be invoked directly from the button tap with
  /// **no `await` before** `payWithApplePayServerSide` (the Apple Pay session
  /// has to be created inside the user gesture). That's why availability is
  /// checked in [initState], not here.
  Future<void> _startApplePayWeb() async {
    setState(() {
      _isLoading = true;
      _paymentResult = 'Starting Apple Pay (web)...';
    });

    // A reference your backend uses to tie the two calls to one order.
    const reference = 'NPORDTEST0001';

    try {
      // ⚠️ First awaited call — nothing is awaited before it in this gesture.
      final result = await NoonPayments.payWithApplePayServerSide(
        enableLogs: true, // prints "🍏 NoonApplePayWeb: ..." to the browser console
        config: const NoonApplePayConfig(
          merchantIdentifier: 'merchant.com.yourcompany.app',
          countryCode: 'AE',
          currencyCode: 'AED',
          summaryItems: [
            NoonApplePaySummaryItem(label: 'Your Business', amount: '10'),
          ],
          supportedNetworks: [
            ApplePayNetwork.visa,
            ApplePayNetwork.masterCard,
            ApplePayNetwork.mada,
          ],
        ),

        // 1) Merchant validation: POST Apple's validationUrl to YOUR backend,
        //    which calls Noon INITIATE and returns
        //    `result.paymentData.data.validationData` (a JSON string).
        onValidateMerchant: (validationUrl) async {
          final res = await http.post(
            Uri.parse('$backendBaseUrl/apple-pay/validate'),
            headers: {'Content-Type': 'application/json'},
            body: jsonEncode({
              'validationUrl': validationUrl,
              'reference': reference,
            }),
          );
          final body = jsonDecode(res.body) as Map<String, dynamic>;
          return body['validationData'] as String;
        },

        // 2) Payment authorized: POST the token to YOUR backend, which calls
        //    Noon PROCESS_AUTHENTICATION and returns the outcome.
        onPaymentAuthorized: (paymentInfo) async {
          final res = await http.post(
            Uri.parse('$backendBaseUrl/apple-pay/authorize'),
            headers: {'Content-Type': 'application/json'},
            body: jsonEncode({
              'paymentInfo': paymentInfo,
              'reference': reference,
            }),
          );
          final body = jsonDecode(res.body) as Map<String, dynamic>;
          return body['status'] == 'paid'
              ? NoonPaymentResult.parse(res.body)
              : NoonPaymentResult.failed(
                  errorMessage: body['message']?.toString() ?? 'Declined',
                );
        },
      );

      _handleResult(result);
    } catch (e) {
      _handleException(e);
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  void _handleResult(NoonPaymentResult result) {
    log("NoonPayment Result: $result");
    setState(() {
      if (result.isSuccess) {
        _paymentResult = '✅ Payment Successful!\nResult: ${result.rawResponse}';
      } else if (result.isCancelled) {
        _paymentResult = '🚫 Payment was cancelled.';
      } else {
        _paymentResult =
            '❌ Payment Failed.\nCode: ${result.errorCode}\nMessage: ${result.errorMessage}';
      }
    });
  }

  void _handleException(dynamic e) {
    log("Plugin Error: $e");
    setState(() {
      _paymentResult = '💀 Unexpected Plugin Error:\n$e';
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.amber),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Noon Payments Example'),
          centerTitle: true,
        ),
        body: Padding(
          padding: const EdgeInsets.all(24.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Card(
                elevation: 0,
                shape: RoundedRectangleBorder(
                  side: BorderSide(color: Colors.black12),
                  borderRadius: BorderRadius.all(Radius.circular(12)),
                ),
                child: Padding(
                  padding: EdgeInsets.all(16.0),
                  child: Column(
                    children: [
                      ListTile(
                        leading: Icon(Icons.shopping_bag_outlined),
                        title: Text('Order #$testOrderId'),
                        subtitle: Text('Total Amount: 100.00 AED'),
                      ),
                      Divider(height: 1),
                      Padding(
                        padding: EdgeInsets.all(16.0),
                        child: Text(
                          'Scan the item or proceed to secure payment using Noon Payments SDK below.',
                          textAlign: TextAlign.center,
                          style: TextStyle(color: Colors.black54, fontSize: 13),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
              const Spacer(),
              Text(
                _paymentResult,
                textAlign: TextAlign.center,
                style: const TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 16,
                ),
              ),
              const SizedBox(height: 32),
              if (_isLoading)
                const Center(child: CircularProgressIndicator())
              else ...[
                ElevatedButton.icon(
                  onPressed: _startStandardPayment,
                  icon: const Icon(Icons.payment),
                  label: const Text('Standard Payment'),
                ),
                const SizedBox(height: 12),
                OutlinedButton.icon(
                  onPressed: _startCustomStyledPayment,
                  icon: const Icon(Icons.brush),
                  label: const Text('Custom Styled Payment'),
                ),
                const SizedBox(height: 12),
                // Apple Pay — direct (iOS) vs server-side (web).
                if (!kIsWeb)
                  OutlinedButton.icon(
                    onPressed: _startApplePayDirect,
                    icon: const Icon(Icons.apple),
                    label: const Text('Apple Pay (iOS Direct)'),
                  )
                else if (_applePayAvailable)
                  OutlinedButton.icon(
                    // Called directly from the tap — no await before it, so the
                    // Apple Pay session is created inside the user gesture.
                    onPressed: _startApplePayWeb,
                    icon: const Icon(Icons.apple),
                    label: const Text('Apple Pay (Web)'),
                  ),
              ],
              const Spacer(),
            ],
          ),
        ),
      ),
    );
  }
}
6
likes
160
points
252
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for integrating Noon Payments SDK on Android and iOS.

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, flutter_web_plugins, http, plugin_platform_interface

More

Packages that depend on noon_payments

Packages that implement noon_payments