Flutter Connect

The PortOne Flutter SDK offers merchants a seamless way to integrate the PortOne Payment Gateway into their Flutter applications, enabling them to accept payments securely and efficiently. This SDK serves as a bridge between a merchant's app and the PortOne Payment Gateway, providing a comprehensive set of tools and features tailored specifically for handling payment transactions.

The PortOne Flutter SDK empowers merchants to unlock the full potential of their Flutter applications by seamlessly integrating a reliable and secure payment gateway, enhancing user experience, and driving business growth through efficient payment processing capabilities


Sample App

Check the sample app to integrate on GitHub


Prerequisites

  • Create an account on PortOne:
    Before proceeding with the integration, ensure you have created an account on PortOne to access their services and functionalities.
  • Enable Payment Channels and Methods:
    Customize and enable the specific payment channels and methods that align with your business requirements and preferences.
  • Access API Keys
    Login to the portone portal where you can access the API Keys (client key and secret key) for integrations under Settings -> API tab.
  • Flutter application for the integration:
    You will need an Flutter application in which you intend to integrate the PortOne Flutter SDK for payment processing capabilities.

Integration

Steps to integrate your Flutter application with PortOne Flutter SDK.

  1. Configure the Flutter SDK with the Flutter App
  2. Set the Intent Filters in the manifests
  3. Add a listener to listen the payment status
  4. Setup to Obtain JWT Token from the Server:
  5. Generate a Signature Hash for Payload

1. Configure the Flutter SDK with the Flutter App

  1. Retrieve the Flutter package distributed by the PortOne team and ensure it is placed at the same directory level as your Flutter application within the folder structure.

  2. To integrate the necessary dependencies in your Flutter project, you can update the pubspec.yaml file with the following configuration:

    dependencies:
      flutter:
        sdk: flutter
      portone_flutter_package:
        path: /Users/flutter_app_sdk/flutter_sdk
      app_links: ^6.1.1
    
    ParametersDescription
    portone_flutter_packageThis is the package where the portone flutter sdk lies and it also has one param name path which requires the path of the sdk.
    In above code the given path is dummy please put the path where your portone sdk lies.
    app_links: ^6.1.1The app_links package plays a crucial role in your application by enabling the reception of intents from deep links or other applications. Its functionality is essential for capturing payment status updates seamlessly within your Flutter app.

2. Enable deep links

  1. For Android:

    1. Change the project structure to Android from Project or open the android module as project in IDE

    2. To add an Intent Filter to the activity in your AndroidManifest.xml file so that users are navigated to a specific activity (default being Checkout Activity) after payment completion

      <activity android:name=".CheckoutActivity">
          <intent-filter>
              <action android:name="android.intent.action.VIEW" />
              <category android:name="android.intent.category.DEFAULT" />
              <category android:name="android.intent.category.BROWSABLE" />
              <data
                  android:scheme="portone"
                  android:host="checkout" />
          </intent-filter>
      </activity>
      
      

      In this setup:

      • The <intent-filter> block defines the conditions under which the activity should be launched.
      • The <data> tag specifies the scheme and host required in the incoming Intent for it to be directed to this activity after payment completion.
      • The <activity> tag specifies the activity to which the Intent Filter applies.

    Update the activity name (e.g., .CheckoutActivity) as per your actual activity name and place this Intent Filter configuration within the corresponding <activity> tag in the AndroidManifest.xml file to handle post-payment navigation effectively.

    By configuring the scheme as "portone" and host as "checkout" within the <data> tag of the Intent Filter, your Android application will be able to intercept the redirection URL with the format "portone://checkout" and navigate the user to the specified activity (e.g., CheckoutActivity) after payment completion. Adjust the activity name in the configuration according to your actual activity name for proper redirection handling.

  2. For iOS:

    1. To open your application, add the url schemes to the app, Go to ProjectSettings -> info

    2. Add url schemes and identifier inside the URL types.

      You can see the scheme and identifier details in info.plist as below:

      <key>CFBundleURLTypes</key>
      <array>
      		<dict>
      			<key>CFBundleTypeRole</key>
      			<string>Editor</string>
      			<key>CFBundleURLName</key>
      			<string>checkout</string>
      			<key>CFBundleURLSchemes</key>
      			<array>
      				<string>portone</string>
      			</array>
      		</dict>
      	</array>
      
      
    3. To open the other applications, should include the url schemes in info.plist

      <key>LSApplicationQueriesSchemes</key>
      	<array>
      		<string>itms-appss</string>
      		<string>zalopay</string>
      		<string>line</string>
      		<string>ascendmoney</string>
      	</array>
      
      

      LSApplicationQueriesSchemes - Specifies the URL schemes you want the app to be able to use with the canOpenURL: method of the UIApplication class.


3. Add a listener to listen the payment status by the callback to initState()

  1. Receiving Payment Status:

    a. Set up app-to-app communication to receive a deep link via intent post-checkout.

    b. In the root stateful widget, implement initState() and dispose() methods.

    c. Declare variables:

    late AppLinks _appLinks;
    StreamSubscription<Uri>? _linkSubscription;
    
    
  2. Initialization in initState():

    a. Initialize initState() method to handle payment status reception:

    @override
      void initState() {
        super.initState();
        initDeepLinks();
      }
      
      Future<void> initDeepLinks() async {
        _appLinks = AppLinks();
        _linkSubscription = _appLinks.uriLinkStream.listen((uri) {
          print('onAppLink: $uri');
        });
      }
    
  3. Dispose Method Handling:

    a. To cancel the subscription and avoid memory leaks, implement dispose():

      @override
      void dispose() {
         _linkSubscription?.cancel();
        super.dispose();
      }
    
  4. Adding Payment Status Listener:

    a. Implement the following method in the checkout activity to capture checkout status post-completion:

    portone.setPaymentStatusListener(
            callback: (Map<String, dynamic> paymentStatus) {
          print('PortOne_PaymentStatus-> $paymentStatus');
        });
    
    

    By integrating this method, you can effectively capture and process the payment status updates within the checkout activity using the PortOne SDK in your Flutter application.


4. Have a setup to get JWT token from the server

The PortOne SDK requires a JWT token for authentication. You need to implement a server-side process to generate this token and retrieve it in your app.

Steps:

  1. Implement server logic to generate a JWT token.
  2. In your app, fetch the token to process the checkout.

The process for generating a JWT token can be found in detail here

5. Generate Signture Hash

To generate a signature hash, create it on the server side using a secret key that will be included in the payload. This ensures secure processing of transactions.

Note: Generating a signature hash is optional if you have whitelisted your app while initializing the checkout. Detailed instructions on this are provided in the checkout setup section.

Steps:

  1. Implement server logic to generate a signature hash.
  2. In your app, fetch the signature hash to process the checkout.

The process for generating a Signature Hash can be found in detail here


Checkout: Flutter Connect

Flutter SDK Variant V2 empowers merchants with advanced business logic functionalities, focusing solely on core processing capabilities without the inclusion of pre-built user interface components. This design choice offers flexibility to merchants, allowing them the freedom to craft and customize their own unique checkout interface tailored to their brand aesthetics and user experience preferences.

The Flutter SDK V2 provides a detailed breakdown of key functionalities essential for merchants to enhance their payment processing capabilities efficiently. Each point represents a specific feature or action that can be easily referenced for seamless integration and utilization within merchant applications.

Overview

  1. Initialize the SDK
  2. Fetch Enabled Payment Methods and Channels
  3. Checkout
  4. Checkout Using a New Card
  5. Checkout Using Saved Credit/Debit Cards
    1. Retrieve Saved Cards Using Phone Number
      1. Fetch OTP for Authentication
      2. Fetch Saved Cards Using Phone Number and OTP
    2. Retrieve Saved Cards Using Auth Token
  6. Merchant-Centric Card Vault
    1. Create a Customer
    2. Save a Card for Customer
    3. Get Customer Information
    4. Fetch a List of Cards for a Customer
    5. Delete Card for a Customer
  7. PreAuth and Capture
  8. Failover Routing

Methods and Applications

1. Initialize the SDK

To begin using the Flutter Connect SDK, create an instance of the PortOne SDK in the activity where the payment checkout process will take place. Initialize the SDK by specifying the environment—either sandbox for testing or live for production.

String environment = "sandbox";            // For Sandbox
String environment = "live";               // For Live

late PortOneImpl portone;
portone = PortOneImpl(context, environment,"com.flutter.example");

Parameters:

  • context: The activity context where the SDK is being initialized.
  • environment: Defines the environment in which the SDK operates.
    • "sandbox" for testing
    • "live" for production
  • applicationId: (Optional) The unique ID of your application.

Note: The applicationId parameter is required if the merchant wants to whitelist the app. When an app is whitelisted, the signature hash generation becomes optional, as the app is pre-authorized to securely process payments without it.


2. Fetch Enabled Payment Methods and Channels

This method retrieves the available payment channels and methods that can be used for the checkout process.

 portone.getPaymentMethods(requests.portoneKey, requests.currency, null);

For receiving the response, the following listener needs to be added.

 portone.setPaymentMethodsListener(
        callback: (PaymentMethodResponse response) {
      final json = jsonEncode(response);
      print('Response-> $response--> $json');
    });

Parameter list for Merchant Details

portoneKey
string · required

The merchant's unique identifier.


subMerchantKey
string

The sub-merchant's identifier.


currency
string · required

The currency for the transaction.


3. Checkout

Checkout with All Payment Methods

The Checkout with All Payment Methods allows you to process payments using various methods, including:

  • Wallets
  • Saved Cards
  • Installments
  • Bank Transfers
  • Direct Bank Transfers

This method supports multiple payment channels and methods, including customer details (billing/shipping), order details, and redirection URLs for handling success or failure of transactions.

CheckoutRequest checkoutRequest = CheckoutRequest(
        amount: 50010,
        billingDetails: BillingDetails(
            billingAddress: BillingAddress(
                city: "VND",
                countryCode: "VN",
                line1: "302,Green Street",
                line2: "Town Hall",
                locale: "en",
                postalCode: "400202",
                state: "Taxes"),
            billingEmail: "[email protected]",
            billingName: "Test mark",
            billingPhone: mobileNo),
        shippingDetails: ShippingDetails(
            shippingAddress: ShippingAddress(
                city: "VND",
                countryCode: "VN",
                line1: "302,Green Street",
                line2: "Town Hall",
                locale: "en",
                postalCode: "400202",
                state: "Taxes"),
            shippingEmail: "[email protected]",
            shippingName: "Test mark",
            shippingPhone: mobileNo),
        currency: currency,
        failureUrl: "https://www.bing.com",
        portOneKey: portoneKey,
        merchantOrderId: orderId,
        orderDetails: [
          OrderDetails(
              id: "product_001",
              name: "Hand Gloves",
              image:
              "https://cdn.moglix.com/p/0DcA3eidlukGq-xxlarge.jpg",
              price: 19010,
              quantity: 1)
        ],
        merchantDetails: MerchantDetails(
            name: "Curo Sports",
            backUrl: "https://demo.chaipay.io/checkout.html",
            logo:
            "https://img.freepik.com/premium-vector/logo-sports-company-that-is-running_695276-2946.jpg",
            promoCode: null,
            promoDiscount: 10000.00,
            shippingCharges: 10000.00),
        signatureHash: signatureHash,
        successUrl: "https://www.google.com",
        source: "mobile",
        redirectUrl: "portonechai://checkout",
        environment: environment);
        
portone.checkout(checkoutRequest, null);

Parameter list for CheckoutEmbedDto.CheckoutUsingEmbedRequest

portone_key
string · required

The unique PortOne key for the merchant.


merchant_details
object
The JSON object for merchant details
name
string · The name of the merchant
logo
string · The logo of the merchant
back_url
string · The URL of the merchant site
promo_code
string · The promo code enabled on the order by the merchant
promo_discount
int · The promo code discount amount on the order by the merchant
shipping_charges
double · The shipping charges set by the merchant

merchant_order_id
string · required

The unique merchant order reference generated by the merchant.


signature_hash
string · required

The signature hash of transaction details.


amount
double · required

The amount of the transaction.


currency
string · required

The currency of the transaction.


country_code
string · required

The country code of the transaction.


billing_details
object
The JSON object for billing details
billing_name
string · The billing first and middle name
billing_surname
string · The billing last name
billing_email
string · The billing email address
billing_phone
string · The billing phone number
billing_address
object
The JSON object containing full address
city
string · City name
country_code
string · 2-digit country code
country_name
string · Full country name
locale
string · Region locale
line_1
string · Line 1 of the address
line_2
string · Line 2 of the address
postal_code
string · Postal code of the area
state
string · State/province of the country

shipping_details
object
The JSON object for shipping details
shipping_name
string · The shipping first and middle name
shipping_surname
string · The shipping last name
shipping_email
string · The shipping email address
shipping_phone
string · The shipping phone number
shipping_address
object
The JSON object containing full address
city
string · City name
country_code
string · 2-digit country code
country_name
string · Full country name
locale
string · Region locale
line_1
string · Line 1 of the address
line_2
string · Line 2 of the address
postal_code
string · Postal code of the area
state
string · State/province of the country

order_details
array of objects
The JSON array for order details
id
string · The unique identifier of the order item
price
double · The price of the product
name
string · The name of the product
quantity
integer · The quantity of the product
image
string · The URL of the product image

success_url
string · required

The URL of the success page hosted by the merchant.


failure_url
string · required

The URL of the failure page hosted by the merchant.


expiry_hours
int · required

The expiry time in hours for the checkout session.


redirect_url
string · required

The URL for redirection after the transaction.


environment
string · required

The environment for the transaction, either sandbox or live.


Checkout Using a New Credit Card

The Checkout Using a New Credit Card method is specifically designed for transactions where the customer enters new credit or debit card details.

CheckoutRequest checkoutRequest = CheckoutRequest();
        
 ChanexTokenRequest newCard = ChanexTokenRequest(
        cardNumber: "4242424242424242",
        cardType: "Visa",
        cardholderName: "NGUYEN VAN A",
        serviceCode: "123",
        expirationYear: "2025",
        expirationMonth: "05",
        saveCard: true);       
        
portone.checkoutUsingNewCard(
                              requests.getTokenizationRequest(),
                              newCard,
                              "JWT Token",null);

Parameter list for Payment Processing

paymentDetails
object · required
The JSON object for main checkout request details
amount· required
double · The total transaction amount.
currency· required
string · The currency for the transaction (e.g., USD, EUR).
description
string · Description of the transaction.

newCard
object · required
The JSON object for ChanexTokenRequest details
cardNumber· required
string · The card number for the transaction.
expiryMonth· required
string · The expiration month of the card.
expiryYear· required
string · The expiration year of the card.
cvv· required
string · The CVV/CVC code for the card.

subMerchantKey
string

Optional sub-merchant key used by master merchants.


token
string · required

The authentication token for secure processing.


4. Making a Payment Using Saved Cards

To make a payment using saved cards for the first time, this process retrieves cards linked to the user’s phone number. OTP authentication is required, making it a two-step process for initial access. After the initial retrieval, future access can use an authentication token for convenience.


4.1 Retrieving Saved Cards for the First Time

This method retrieves saved cards by first sending an OTP to the registered phone number, followed by using the OTP for authentication to access the saved cards.


Step 1: Send OTP to Registered Phone Number

 portone.getOTP(phoneNo);

Parameter list for OTP Generation

phoneNo
string · required

User’s phone number to receive the OTP.


Step 2: Retrieve Saved Cards Using OTP

 portone.getSavedCards(null, portoneKey,phoneNo,otp);

Listener :

portone.setSavedCardsListener(
        callback: (CreditCardDetailsResponse response) {
      final json = jsonEncode(response);
      print('Response-> $response--> $json');
    });

Parameter list for Retrieving Saved Cards

token
string

Authentication token, not required for first-time retrieval.


portoneKey
string · required

Merchant’s unique identifier.


phoneNo
string · required

User’s phone number for retrieving saved cards.


otp
string · required

OTP sent to the phone for verification.


4.2 Retrieving Saved Cards for Subsequent Access

After the initial retrieval, a token is returned in the response. This token can be used instead of an OTP for future card retrievals, allowing a more streamlined authentication process.

 portone.getSavedCards(token, portoneKey,phoneNo,null);

Listener:

portone.setSavedCardsListener(
        callback: (CreditCardDetailsResponse response) {
      final json = jsonEncode(response);
      print('Response-> $response--> $json');
    });

Parameter list for Subsequent Retrieval of Saved Cards

token
string · required

Authentication token obtained after initial retrieval.


portoneKey
string · required

Merchant’s unique identifier.


phoneNo
string · required

User’s phone number for retrieving saved cards.


otp
string

Not required for subsequent retrievals; set to null.


Merchant-Centric Card Vault

The Merchant-Centric Card Vault operates by enrolling merchants initially, followed by adding customers specific to each merchant. Cards are then saved based on individual customers, ensuring a personalized and secure card storage system. Several methods are available to facilitate various operations within this card vault setup.

1. Add Customer

This method is used to add a customer to the Merchant-Centric Card Vault.

portone.addCustomer("JWTToken",
                     portoneKey,
                     AddCustomerRequest(
                              name: "",
                              customerRef: "",
                              emailAddress: "",
                              phoneNumber: phoneNo));

Parameter list for Customer Registration

token
string · required

Authentication token for secure processing.


portoneKey
string · required

Merchant’s unique identifier.


subMerchantKey
string

Optional sub-merchant key used by master merchants.


name
string · required

The customer’s full name.


phoneNo
string · required

The customer’s phone number.


email
string · required

The customer’s email address.


customerRef
string · required

The unique reference identifier for the customer.


2. Save a Card for a Customer

This method is used to save a specific card for a particular customer.

 portone.addCardForCustomer(
                          customerUUID,
                          "JWTToken",
                          portoneKey,
                          NewCard(),
                          null);

Parameter list for Customer Registration

token
string · required

Authentication token for secure processing.


portoneKey
string · required

Merchant’s unique identifier.


subMerchantKey
string

Optional sub-merchant key used by master merchants.


name
string · required

The customer’s full name.


phoneNo
string · required

The customer’s phone number.


email
string · required

The customer’s email address.


customerRef
string · required

The unique reference identifier for the customer.


NewCard

Parameter list for Card Registration

cardNumber
string · required

The card number to be registered.


cardType
string · required

The type of the card (e.g., VISA, MASTERCARD).


cardholderName
string · required

The name of the cardholder.


serviceCode
string · required

The CVV/CVC code of the card.


expiryYear
string · required

The expiration year of the card.


expiryMonth
string · required

The expiration month of the card.


environment
string · required

The environment for the transaction, either sandbox or live.


portoneKey
string · required

Merchant’s unique identifier.


3. Retrieve Stored Cards for a Customer

This method retrieves the list of cards stored for a specific customer.

 portone.listCardsForCustomer(customerUUID,
                          "JWTToken", portoneKey);

Parameter list for Customer UUID Verification

customerUUID
string · required

The unique UUID of the customer.


token
string · required

Authentication token for secure processing.


portoneKey
string · required

Merchant’s unique identifier.


subMerchantKey
string

Optional sub-merchant key used by master merchants.


4. Delete a Card for a Customer

This method deletes a specific card for an individual customer.

portone.deleteCardForCustomer(
                          customerUUID,
                          "JWTToken",
                          portoneKey,
                          DeleteCardRequest(
                              token: "735eaf72a0a14965aced3e1f9a339b0b"));

Parameter list for Card Token Verification

customerUUID
string · required

The unique UUID of the customer.


token
string · required

Authentication token for secure processing.


portoneKey
string · required

Merchant’s unique identifier.


subMerchantKey
string

Optional sub-merchant key used by master merchants.


cardToken
string · required

The unique token of the card to be verified.


5. Get Customer Information

This method retrieves information about a specific customer.

portone.getCustomer("JWTToken", portoneKey, 
customerUUID);

Parameter list for Customer Identification

token
string · required

Authentication token for secure processing.


portOneKey
string · required

Merchant’s unique identifier.


customerUUID
string · required

Unique identifier for the customer.


Pre-authorization and Payment Capture

During Pre-authorization, the transaction is initially approved, allowing funds to be reserved. At a later time or within specified days, the payment can be finalized using the Capture API.

To designate a transaction for pre-authorization, configure the transactionType parameter in the payload as shown:

transactionType = "PREAUTH"

CheckoutRequest checkoutRequest = CheckoutRequest();
checkoutRequest.transactionType = "PREAUTH"

💡

Note: Pre-authorization is applicable only for credit card payments and is designed to provide a smooth workflow.


Capturing a Pre-authorized Transaction

Once a transaction has been pre-authorized, the following method is used to capture (finalize) the transaction:

portone.captureTransaction("2SDCUiBEv34oqeIdEDv1pftGeeY", "JWTToken", portoneKey);

Parameter list for Order Reference Validation

orderReference
string · required

The unique reference for the order.


portoneKey
string · required

Merchant’s unique identifier.


token
string · required

Authentication token for secure processing.


Failover Routing

Failover routing is designed to ensure seamless credit card payment processing. This feature allows the configuration of primary and secondary payment channels through the admin portal. If a payment attempt using the primary channel fails, the system automatically redirects the user to the secondary payment channel.

Fetching Routes

Use the following method to retrieve routes created in the admin portal:

portone.getRoutesList(portoneKey,"JWTToken");

Configuring Failover Routing

This method provides the routeRef that should be included in the payload as outlined below:

  1. Enable routing by setting isRoutingEnabled to true.
  2. Specify the Routing Param type as "failover".
  3. Include the Routing Ref, which is configured in the merchant portal.

CheckoutRequest checkoutRequest = CheckoutRequest();
checkoutRequest.isRoutingEnabled = true     
checkoutRequest.routingParams = RoutingParams(type = "failover", routeRef)

Possible Error Scenarios:

INVALID_UNAUTHORIZED_JWT_TOKEN_ERROR

  1. Ensure that the PortOne Key and Secret Key belong to the same account.
  2. Confirm that the Secret Key has not been altered.
  3. Verify that the Bearer keyword precedes the generated token with a space. Example: Bearer $jwtToken.
  4. Check if the token expiration time is after the current time.

INVALID_UNAUTHORIZED_TRANSACTION_SIGNATURE_ERROR

  1. Validate if all parameters align with the payload/request.
  2. Ensure that the PortOne key matches with the payload and the account.

INVALID_UNAUTHORIZED_TRANSACTION_IAMPORTKEY_ERROR

  1. Confirm that the PortOne key matches with the payload and the account.

INVALID_PAYMENT_CHANNEL

  1. Validate that the payment channels and methods included in the payload are enabled in the PortOne portal.

INVALID_ENVIRONMENT

  1. Verify that an environment (sandbox or live) has been specified.

Summation of order value, tax, duties, shipping, and discount should equal the total amount

  1. If items are provided, ensure that the values match the total amount calculation formula: sum(items price * items quantity) + shipping charge - discount = amount.
  2. Mandatory parameters in the payload:
    • price
    • promo_discount (0 accepted)
    • shipping_charges (0 accepted)