flutter_paystack 1.0.2+1

  • Readme
  • Changelog
  • Example
  • Installing
  • 91

πŸ’³ Paystack Plugin for Flutter #

pub package

A Flutter plugin for making payments via Paystack Payment Gateway. Fully supports Android and iOS.

πŸš€ Installation #

To use this plugin, add flutter_paystack as a dependency in your pubspec.yaml file.

Then initialize the plugin preferably in the initState of your widget.

import 'package:flutter_paystack/flutter_paystack.dart';

class _PaymentPageState extends State<PaymentPage> {
  var publicKey = '[YOUR_PAYSTACK_PUBLIC_KEY]';

  @override
  void initState() {
    PaystackPlugin.initialize(
            publicKey: publicKey);
  }
}

No other configuration requiredβ€”the plugin works out of the box.

πŸ’² Making Payments #

There are two ways of making payment with the plugin.

  1. Checkout: This is the easy way; as the plugin handles all the processes involved in making a payment (except transaction initialization and verification which should be done from your backend).
  2. Charge Card: This is a longer approach; you handle all callbacks and UI states.

You initialize a charge object with an amount, email & accessCode or reference. Pass an accessCode only when you have initialized the transaction from your backend. Otherwise, pass a reference.

 Charge charge = Charge()
       ..amount = 10000
       ..reference = _getReference()
        // or ..accessCode = _getAccessCodeFrmInitialization()
       ..email = 'customer@email.com';
     CheckoutResponse response = await PaystackPlugin.checkout(
       context context,
       method: CheckoutMethod.card, // Defaults to CheckoutMethod.selectable
       charge: charge,
     );

Please, note that an accessCode is required if the method is CheckoutMethod.bank or CheckoutMethod.selectable.

PaystackPlugin.checkout() returns the state and details of the payment in an instance of CheckoutResponse .

It is recommended that when PaystackPlugin.checkout() returns, the payment should be verified on your backend.

2. ⭐ Charge Card

You can choose to initialize the payment locally or via your backend.

1.a. This starts by making a HTTP POST request to paystack on your backend.

1.b If everything goes well, the initialization request returns a response with an access_code. You can then create a Charge object with the access code and card details. The charge is in turn passed to the PaystackPlugin.chargeCard() function for payment:

  PaymentCard _getCardFromUI() {
    // Using just the must-required parameters.
    return PaymentCard(
      number: cardNumber,
      cvc: cvv,
      expiryMonth: expiryMonth,
      expiryYear: expiryYear,
    );

    // Using Cascade notation (similar to Java's builder pattern)
//    return PaymentCard(
//        number: cardNumber,
//        cvc: cvv,
//        expiryMonth: expiryMonth,
//        expiryYear: expiryYear)
//      ..name = 'Segun Chukwuma Adamu'
//      ..country = 'Nigeria'
//      ..addressLine1 = 'Ikeja, Lagos'
//      ..addressPostalCode = '100001';

    // Using optional parameters
//    return PaymentCard(
//        number: cardNumber,
//        cvc: cvv,
//        expiryMonth: expiryMonth,
//        expiryYear: expiryYear,
//        name: 'Ismail Adebola Emeka',
//        addressCountry: 'Nigeria',
//        addressLine1: '90, Nnebisi Road, Asaba, Deleta State');
  }

  _chargeCard(String accessCode) {
    var charge = Charge()
      ..accessCode = accessCode
      ..card = _getCardFromUI();

    PaystackPlugin.chargeCard(context,
        charge: charge,
        beforeValidate: (transaction) => handleBeforeValidate(transaction),
        onSuccess: (transaction) => handleOnSuccess(transaction),
        onError: (error, transaction) => handleOnError(error, transaction));
  }

  handleBeforeValidate(Transaction transaction) {
    // This is called only before requesting OTP
    // Save reference so you may send to server if error occurs with OTP
  }

  handleOnError(Object e, Transaction transaction) {
    // If an access code has expired, simply ask your server for a new one
    // and restart the charge instead of displaying error
  }


  handleOnSuccess(Transaction transaction) {
    // This is called only after transaction is successful
  }

2. Initialize Locally #

Just send the payment details to PaystackPlugin.chargeCard

      // Set transaction params directly in app (note that these params
      // are only used if an access_code is not set. In debug mode,
      // setting them after setting an access code would throw an error
      Charge charge = Charge();
      charge.card = _getCardFromUI();
      charge
        ..amount = 2000
        ..email = 'user@email.com'
        ..reference = _getReference()
        ..putCustomField('Charged From', 'Flutter PLUGIN');
      _chargeCard();

πŸ”§ πŸ”© Validating Card Details

You are expected but not required to build the UI for your users to enter their payment details. For easier validation, wrap the TextFormFields inside a Form widget. Please check this article on validating forms on Flutter if this is new to you.

NOTE: You don't have to pass a card object to Charge. The plugin will call-up a UI for the user to input their card.

You can validate the fields with these methods:

card.validNumber #

This method helps to perform a check if the card number is valid.

card.validCVC #

Method that checks if the card security code is valid.

card.validExpiryDate #

Method checks if the expiry date (combination of year and month) is valid.

card.isValid #

Method to check if the card is valid. Always do this check, before charging the card.

card.getType #

This method returns an estimate of the string representation of the card type(issuer).

βœ”οΈ Verifying Transactions #

This is quite easy. Just send a HTTP GET request to https://api.paystack.co/transaction/verify/$[TRANSACTION_REFERENCE]. Please, check the official documentaion on verifying transactions.

🚁 Testing your implementation #

Paystack provides tons of payment cards for testing.

▢️ Running Example project #

For help getting started with Flutter, view the online documentation.

An example project has been provided in this plugin. Clone this repo and navigate to the example folder. Open it with a supported IDE or execute flutter run from that folder in terminal.

:pencil: Contributing, 😞 Issues and πŸ› Bug Reports

The project is open to public contribution. Please feel very free to contribute. Experienced an issue or want to report a bug? Please, report it here. Remember to be as descriptive as possible.

πŸ† Credits #

Thanks to the authors of Paystack iOS and Android SDKS. I leveraged on their work to bring this plugin to fruition.

1.0.2+1 #

  • Corrected typo

1.0.2 #

  • Made plugin theme customizable
  • Switched deprecated UIWebView for WKWebView for iOS
  • Added a customizable company logo
  • Displayed "Secured by Paystack" at bottom of payment prompt

1.0.1+1 #

  • Bumped dependencies versions

1.0.1 #

  • Migrated to AndroidX
  • Bumped up dependencies to the latest versions
  • Improved month input formatter

1.0.0 #

  • Reintroduced and improved bank payment
  • Minor bug fixes

0.10.0 (Breaking change) #

  • Security Improvement: Removed usage of the secret key in checkout
  • Removed support for bank payment (requires secret key)
  • Transaction initialization and verification is no longer being handled by the checkout function (requires secret key)
  • Handled Gateway timeout error
  • Returning last for digits instead full card number after payment

0.9.3 #

  • Fixed failure of web OTP on iOS devices
  • Automatically closes soft keyboard when text-field entries are submitted
  • Changed date picker on iOS to CupertinoDatePicker

0.9.2 #

  • Bank account payment: fixed issue where the reference value passed to checkout is different from what is returned after transaction.
  • Increased width of checkout dialog.
  • Added flag to enable fullscreen checkout dialog.
  • Felt like doing some reorganising so I refactored some .dart files.

0.9.1+2 #

  • Fixed build failure because of difference in type of passed and expected value of encrypt function.

0.9.1+1 #

  • Updated to the latest gradle and kotlin dependencies.

0.9.1 #

  • Bumped version of dependencies.

0.9.0 #

  • Added checkout form and supported bank account payment.

0.5.2 #

  • Support for Flutter v0.5.1.

0.5.1 #

  • Exposed Paystack Exception
  • Properly formatted .dart files
  • Removed deprecated APIs

0.5.0 #

  • Initial beta release.

example/README.md

flutter_paystack_example #

Demonstrates how to use the flutter_paystack plugin.

Getting Started #

For help getting started with Flutter, view our online documentation.

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  flutter_paystack: ^1.0.2+1

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:flutter_paystack/flutter_paystack.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
85
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
91
Learn more about scoring.

We analyzed this package on Dec 9, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.6.1
  • pana: 0.13.1+4
  • Flutter: 1.12.13+hotfix.2

Maintenance issues and suggestions

Make sure dartdoc successfully runs on your package's source files. (-10 points)

Running dartdoc failed with the following output: DetailedApiRequestError(status: 504, message: )

#0      _validateResponse (package:_discoveryapis_commons/src/clients.dart:852:9)
<asynchronous suspension>
#1      ApiRequester.request (package:_discoveryapis_commons/src/clients.dart:74:22)
<asynchronous suspension>
#2      ObjectsResourceApi.insert (package:googleapis/storage/v1.dart:3067:32)
#3      _MediaUploadStreamSink._startNormalUpload (package:gcloud/src/storage_impl.dart:599:10)
#4      new _MediaUploadStreamSink (package:gcloud/src/storage_impl.dart:514:9)
#5      _BucketImpl.write (package:gcloud/src/storage_impl.dart:179:16)
#6      uploadWithRetry.<anonymous closure> (package:pub_dev/shared/storage.dart:49:27)
<asynchronous suspension>
#7      retryAsync (package:pub_dev/shared/utils.dart:326:24)
<asynchronous suspension>
#8      uploadWithRetry (package:pub_dev/shared/storage.dart:47:9)
<asynchronous suspension>
#9      DartdocBackend.uploadDir.upload (package:pub_dev/dartdoc/backend.dart:131:13)
<asynchronous suspension>
#10     DartdocBackend.uploadDir.<anonymous closure> (package:pub_dev/dartdoc/backend.dart:143:64)
#11     Pool.withResource (package:pool/pool.dart:127:28)
<asynchronous suspension>
#12     DartdocBackend.uploadDir (package:pub_dev/dartdoc/backend.dart:143:39)
<asynchronous suspension>
#13     DartdocJobProcessor.process (package:pub_dev/dartdoc/dartdoc_runner.dart:225:30)
<asynchronous suspension>
#14     JobProcessor.run (package:pub_dev/job/job.dart:55:28)
<asynchronous suspension>
#15     JobMaintenance.run (package:pub_dev/job/job.dart:95:18)
#16     _workerMain.<anonymous closure> (package:pub_dev/service/entrypoint/dartdoc.dart:99:26)
#17     _asyncThenWrapperHelper.<anonymous closure> (dart:async-patch/async_patch.dart:71:64)
#18     StackZoneSpecification._registerUnaryCallback.<anonymous closure>.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:129:26)
#19     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#20     StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:129:14)
#21     _rootRunUnary (dart:async/zone.dart:1132:38)
#22     _CustomZone.runUnary (dart:async/zone.dart:1029:19)
#23     _FutureListener.handleValue (dart:async/future_impl.dart:137:18)
#24     Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:678:45)
#25     Future._propagateToListeners (dart:async/future_impl.dart:707:32)
#26     Future._completeWithValue (dart:async/future_impl.dart:522:5)
#27     _AsyncAwaitCompleter.complete (dart:async-patch/async_patch.dart:30:15)
#28     _completeOnAsyncReturn (dart:async-patch/async_patch.dart:288:13)
#29     DartdocJobProcessor.generateDocsForSdk (package:pub_dev/dartdoc/dartdoc_runner.dart)
#30     _asyncThenWrapperHelper.<anonymous closure> (dart:async-patch/async_patch.dart:71:64)
#31     StackZoneSpecification._registerUnaryCallback.<anonymous closure>.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:129:26)
#32     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#33     StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:129:14)
#34     _rootRunUnary (dart:async/zone.dart:1132:38)
#35     _CustomZone.runUnary (dart:async/zone.dart:1029:19)
#36     _FutureListener.handleValue (dart:async/future_impl.dart:137:18)
#37     Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:678:45)
#38     Future._propagateToListeners (dart:async/future_impl.dart:707:32)
#39     Future._completeWithValue (dart:async/future_impl.dart:522:5)
#40     _AsyncAwaitCompleter.complete (dart:async-patch/async_patch.dart:30:15)
#41     _completeOnAsyncReturn (dart:async-patch/async_patch.dart:288:13)
#42     VersionedJsonStorage.hasCurrentData (package:pub_dev/shared/storage.dart)
#43     _asyncThenWrapperHelper.<anonymous closure> (dart:async-patch/async_patch.dart:71:64)
#44     StackZoneSpecification._registerUnaryCallback.<anonymous closure>.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:129:26)
#45     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#46     StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:129:14)
#47     _rootRunUnary (dart:async/zone.dart:1132:38)
#48     _CustomZone.runUnary (dart:async/zone.dart:1029:19)
#49     _FutureListener.handleValue (dart:async/future_impl.dart:137:18)
#50     Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:678:45)
#51     Future._propagateToListeners (dart:async/future_impl.dart:707:32)
#52     Future._completeWithValue (dart:async/future_impl.dart:522:5)
#53     _AsyncAwaitCompleter.complete (dart:async-patch/async_patch.dart:30:15)
#54     _completeOnAsyncReturn (dart:async-patch/async_patch.dart:288:13)
#55     ApiRequester.request (package:_discoveryapis_commons/src/clients.dart)
#56     _asyncThenWrapperHelper.<anonymous closure> (dart:async-patch/async_patch.dart:71:64)
#57     StackZoneSpecification._registerUnaryCallback.<anonymous closure>.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:129:26)
#58     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#59     StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:129:14)
#60     _rootRunUnary (dart:async/zone.dart:1132:38)
#61     _CustomZone.runUnary (dart:async/zone.dart:1029:19)
#62     _FutureListener.handleValue (dart:async/future_impl.dart:137:18)
#63     Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:678:45)
#64     Future._propagateToListeners (dart:async/future_impl.dart:707:32)
#65     Future._complete (dart:async/future_impl.dart:512:7)
#66     Stream.join.<anonymous closure> (dart:async/stream.dart:840:18)
#67     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#68     StackZoneSpecification._registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:119:48)
#69     _rootRun (dart:async/zone.dart:1120:38)
#70     _CustomZone.run (dart:async/zone.dart:1021:19)
#71     _CustomZone.runGuarded (dart:async/zone.dart:923:7)
#72     _BufferingStreamSubscription._sendDone.sendDone (dart:async/stream_impl.dart:389:13)
#73     _BufferingStreamSubscription._sendDone (dart:async/stream_impl.dart:399:15)
#74     _BufferingStreamSubscription._close (dart:async/stream_impl.dart:283:7)
#75     _SinkTransformerStreamSubscription._close (dart:async/stream_transformers.dart:96:11)
#76     _EventSinkWrapper.close (dart:async/stream_transformers.dart:23:11)
#77     _StringAdapterSink.close (dart:convert/string_conversion.dart:249:11)
#78     _Utf8ConversionSink.close (dart:convert/string_conversion.dart:300:20)
#79     _ConverterStreamEventSink.close (dart:convert/chunked_conversion.dart:80:18)
#80     _SinkTransformerStreamSubscription._handleDone (dart:async/stream_transformers.dart:141:24)
#81     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#82     StackZoneSpecification._registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:119:48)
#83     _rootRun (dart:async/zone.dart:1120:38)
#84     _CustomZone.run (dart:async/zone.dart:1021:19)
#85     _CustomZone.runGuarded (dart:async/zone.dart:923:7)
#86     _BufferingStreamSubscription._sendDone.sendDone (dart:async/stream_impl.dart:389:13)
#87     _BufferingStreamSubscription._sendDone (dart:async/stream_impl.dart:399:15)
#88     _BufferingStreamSubscription._close (dart:async/stream_impl.dart:283:7)
#89     _ForwardingStream._handleDone (dart:async/stream_pipe.dart:106:10)
#90     _ForwardingStreamSubscription._handleDone (dart:async/stream_pipe.dart:172:13)
#91     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#92     StackZoneSpecification._registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:119:48)
#93     _rootRun (dart:async/zone.dart:1120:38)
#94     _CustomZone.run (dart:async/zone.dart:1021:19)
#95     _CustomZone.runGuarded (dart:async/zone.dart:923:7)
#96     _BufferingStreamSubscription._sendDone.sendDone (dart:async/stream_impl.dart:389:13)
#97     _BufferingStreamSubscription._sendDone (dart:async/stream_impl.dart:399:15)
#98     _BufferingStreamSubscription._close (dart:async/stream_impl.dart:283:7)
#99     _ForwardingStream._handleDone (dart:async/stream_pipe.dart:106:10)
#100    _ForwardingStreamSubscription._handleDone (dart:async/stream_pipe.dart:172:13)
#101    StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#102    StackZoneSpecification._registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:119:48)
#103    _rootRun (dart:async/zone.dart:1120:38)
#104    _CustomZone.run (dart:async/zone.dart:1021:19)
#105    _CustomZone.runGuarded (dart:async/zone.dart:923:7)
#106    _BufferingStreamSubscription._sendDone.sendDone (dart:async/stream_impl.dart:389:13)
#107    _BufferingStreamSubscription._sendDone (dart:async/stream_impl.dart:399:15)
#108    _BufferingStreamSubscription._close (dart:async/stream_impl.dart:283:7)
#109    _SyncStreamControllerDispatch._sendDone (dart:async/stream_controller.dart:772:19)
#110    _StreamController._closeUnchecked (dart:async/stream_controller.dart:629:7)
#111    _StreamController.close (dart:async/stream_controller.dart:622:5)
#112    _HttpParser._closeIncoming (dart:_http/http_parser.dart:1037:23)
#113    _HttpParser._doParse (dart:_http/http_parser.dart:775:15)
#114    _HttpParser._parse (dart:_http/http_parser.dart:318:7)
#115    _HttpParser._onData (dart:_http/http_parser.dart:810:5)
#116    StackZoneSpecification._registerUnaryCallback.<anonymous closure>.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:129:26)
#117    StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#118    StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:129:14)
#119    _rootRunUnary (dart:async/zone.dart:1132:38)
#120    _CustomZone.runUnary (dart:async/zone.dart:1029:19)
#121    _CustomZone.runUnaryGuarded (dart:async/zone.dart:931:7)
#122    _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:336:11)
#123    _BufferingStreamSubscription._add (dart:async/stream_impl.dart:263:7)
#124    _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:764:19)
#125    _StreamController._add (dart:async/stream_controller.dart:640:7)
#126    _StreamController.add (dart:async/stream_controller.dart:586:5)
#127    _Socket._onData (dart:io-patch/socket_patch.dart:1829:41)
#128    StackZoneSpecification._registerUnaryCallback.<anonymous closure>.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:129:26)
#129    StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#130    StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:129:14)
#131    _rootRunUnary (dart:async/zone.dart:1132:38)
#132    _CustomZone.runUnary (dart:async/zone.dart:1029:19)
#133    _CustomZone.runUnaryGuarded (dart:async/zone.dart:931:7)
#134    _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:336:11)
#135    _BufferingStreamSubscription._add (dart:async/stream_impl.dart:263:7)
#136    _SyncStreamControllerDispatch._sendData (dart:async/stream_controller.dart:764:19)
#137    _StreamController._add (dart:async/stream_controller.dart:640:7)
#138    _StreamController.add (dart:async/stream_controller.dart:586:5)
#139    _RawSecureSocket._sendReadEvent (dart:io/secure_socket.dart:1016:19)
#140    StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#141    StackZoneSpecification._registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:119:48)
#142    _rootRun (dart:async/zone.dart:1120:38)
#143    _CustomZone.run (dart:async/zone.dart:1021:19)
#144    _CustomZone.runGuarded (dart:async/zone.dart:923:7)
#145    _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:963:23)
#146    StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#147    StackZoneSpecification._registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:119:48)
#148    _rootRun (dart:async/zone.dart:1124:13)
#149    _CustomZone.run (dart:async/zone.dart:1021:19)
#150    _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:947:23)
#151    Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:21:15)
#152    _Timer._runTimers (dart:isolate-patch/timer_impl.dart:382:19)
#153    _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:416:5)
#154    _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:172:12)

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.2.2 <3.0.0
async ^2.3.0 2.4.0
flutter 0.0.0
http ^0.12.0+2 0.12.0+2
intl ^0.16.0 0.16.0
meta ^1.1.7 1.1.8
Transitive dependencies
charcode 1.1.2
collection 1.14.11 1.14.12
http_parser 3.1.3
path 1.6.4
pedantic 1.9.0
sky_engine 0.0.99
source_span 1.5.5
string_scanner 1.0.5
term_glyph 1.1.0
typed_data 1.1.6
vector_math 2.0.8