Basic concepts

One of the features of the MoneyHash Flutter SDK is providing a single state for each step in the payment flow. The state along with stateDetails guide the actions and methods required to proceed and complete the flow.

SDK States Table

StateActionIntent Type
MethodSelectionThe MethodSelection state contains an IntentMethods object with available payment methods. You can render these methods natively with custom styles. After the user selects a method, use moneyHash.proceedWithMethod to proceed.Payment, Payout
FormFieldsRender form fields received in stateDetails, including card fields, billing, and shipping fields. Submit them using moneyHash.submitForm. For more information on card fields, refer to Card Form Fields.Payment, Payout
UrlToRenderUse moneyHash.renderForm to let MoneyHash handle the current stage of the payment/payout flow. After completing the process, MoneyHash will call the completion handler with intent details or an error.Payment, Payout, CardToken
SavedCardCVVCollect CVV using the schema from stateDetails.cvvField, then use moneyHash.submitCardCVV to proceed with the payment.Payment
IntentFormUse moneyHash.renderForm to let MoneyHash handle the current stage of the payment/payout flow. After completing the process, MoneyHash will call the completion handler with intent details or an error.Payment, Payout
IntentProcessedDisplay a success confirmation UI with the intent details.Payment, Payout
TransactionFailedDisplay a failure UI with the intent details.Payment, Payout
TransactionWaitingUserActionDisplay a pending actions confirmation UI with the intent details and any externalActionMessage if provided in TransactionData.Payment, Payout
ExpiredDisplay an expired intent UI.Payment
ClosedDisplay a closed intent UI.Payment, Payout
CardIntentSuccessfulDisplay a success UI for the created card token.CardToken
CardIntentFailedDisplay a failure UI for the created card token.CardToken

States Handling

There are some intent states that you can use to customize your payment/payout experience.

When handling the FormFields state in MoneyHash, you will deal with two primary tasks:

  1. Handling Billing or Shipping Data
  2. Handling Card Form

1. Handling Billing or Shipping Data

  1. Rendering Input Fields:

    • Use InputField data to render form fields. Each field corresponds to an attribute like name, address, etc., and comes with specific properties to guide the rendering.
  2. Collecting User Input:

    • Capture input from these fields in the form of a map where the key is the field name and the value is the user-provided value.

Example of handling billing data:

Map<String, String> billingData = {
  "firstName": "John",
  "lastName": "Doe",
  "email": "[email protected]"
};

2. Handling Card Form

Handling card form input involves securely capturing card details from the user. This section will guide you through the process, which includes:


3. Submit Form

After collecting the necessary billing, shipping, and card data, the next step is to submit this data using the submitForm method provided by the MoneyHash SDK.

Ensure that you set the public API key when initializing the SDK. The submitForm method allows you to send the collected information to proceed with the payment or payout process. Make sure to pass the selectedMethod from the IntentDetails object to the API.

import 'package:moneyhash_payment/moneyhash_payment.dart';

final moneyHashSDK = MoneyHashSDKBuilder()
    .setPublicKey("your_public_key")  // Set your public API key here
    .build();

Future<void> submitPayment(IntentDetails intentDetails) async {
  try {
    // Get form fields from the intent details
    Map<String, String> billingData = {};
    Map<String, String> shippingData = {};

    if (intentDetails.intentState is FormFields) {
      FormFields formFields = intentDetails.intentState as FormFields;

      // Prepare billing data based on the input fields
      formFields.billingFields?.forEach((field) {
        billingData[field.name] = 'user_input_value'; // Replace with actual input values
      });

      // Prepare shipping data based on the input fields
      formFields.shippingFields?.forEach((field) {
        shippingData[field.name] = 'user_input_value'; // Replace with actual input values
      });
    }

    // Optionally collect card data if needed
    VaultData? cardData; // Assume card data is collected if needed

    // Submit the form data
    IntentDetails? submittedIntentDetails = await moneyHashSDK.submitForm(
      intentDetails.intent?.id ?? '',
      intentDetails.selectedMethod ?? '',
      billingData,   // Optional
      shippingData,  // Optional
      cardData,      // Optional
      true           // Set to true to save the card (optional)
    );

    // Handle success
    print('Form submitted successfully: $submittedIntentDetails');
  } catch (error) {
    // Handle error
    print('Error submitting form: $error');
  }
}

In this example:

  • The MoneyHashSDKBuilder is configured with a public API key using the setPublicKey method before building the SDK instance.
  • Billing and shipping data are extracted from FormFields in intentState.
  • The submitForm method sends the data, including the selectedMethod, along with optional card data, to complete the payment.

The UrlToRender state occurs when you need to render a URL as part of the payment or payout flow. The UrlToRender class provides two key properties:

  • url: The URL that needs to be rendered.
  • renderStrategy: A suggestion on how the URL should be rendered (iframe, popupIframe, or redirect).

In this state, you should use the moneyHash.renderForm() method to handle the payment/payout flow. This method automatically takes care of rendering the URL based on the intent and manages the interaction flow. After the flow is completed (either successfully or with an error), it returns the corresponding IntentDetails or throws an exception.

Steps to Handle the UrlToRender State

  1. Invoke the renderForm() Method:

    Call the renderForm() method with the necessary intentId and intentType parameters to proceed with rendering the form and handling the current stage of the payment/payout flow.

    // Example of handling UrlToRender state
    if (intentState is UrlToRender) {
      final intentDetails = await moneyHash.renderForm(
        intentId: intentId, // The unique identifier of the intent
        intentType: intentType, // The type of the intent, either 'payment' or 'payout'
        embedStyle: null, // Optional: Custom embed style if needed
      );
    
      if (intentDetails != null) {
        // Handle success
        print("Form rendered successfully: $intentDetails");
      } else {
        // Handle case where result is null
        print("Form rendering was cancelled or no result was returned.");
      }
    }
    
  2. Completion Handling:

    • On Processed: The renderForm() method returns an IntentDetails object that provides the final state and outcome of the process.
    • On Error: If an error occurs during the process, the method throws an exception. You should handle this exception appropriately to display an error message or take corrective action.
// Example of handling the UrlToRender state in your app
if (intentState is UrlToRender) {
  try {
    final intentDetails = await moneyHash.renderForm(
      intentId: "<intent_id>",
      intentType: intentType, // The intent type ('payment' or 'payout')
    );

    if (intentDetails != null) {
      // Success: Render successful confirmation UI
      print("Payment completed: $intentDetails");
    } else {
      // Form rendering was cancelled
      print("Form rendering cancelled.");
    }
  } on MHException catch (e) {
    // Handle error
    print("Error during form rendering: ${e.message}");
  }
}

In this example, MoneyHash handles the entire flow of rendering the URL and managing user interaction. After the form rendering is complete, it returns either IntentDetails if processed or throws an exception if something goes wrong.

💡

Tip: You can customize the form appearance by providing an embedStyle object, allowing for a more tailored user interface during the form rendering process.


The SavedCardCVV state occurs when a saved card requires the user to input their CVV to proceed with the payment. In this state, you will collect the CVV from the user and submit it using the moneyHash.submitCardCVV() method.

The SavedCardCVV class provides two key properties:

  • cvvField: An InputField object that describes the CVV field, including its label, validation requirements, and other metadata.
  • cardTokenData: Optional information about the saved card for which the CVV is being collected.

Steps to Handle the SavedCardCVV State

  1. Render the CVV Input Field:

    • Use the cvvField property from the SavedCardCVV state to render the input field for the CVV.
    • Ensure you apply the field validation based on the rules provided in the cvvField.
  2. Collect the CVV Value:

    After the user inputs their CVV, collect the value and validate it based on the constraints provided in cvvField.

  3. Submit the CVV Using submitCardCVV():

    Once you have the CVV from the user, use the submitCardCVV() method to submit the CVV and proceed with the payment.

    // Example of handling the SavedCardCVV state
    if (intentState is SavedCardCVV) {
      final cvv = "123"; // Example of the collected CVV from the user
    
      try {
        final intentDetails = await moneyHash.submitCardCVV(
          intentId: "<intent_id>",
          cvv: cvv,
        );
    
        if (intentDetails != null) {
          // Success: Handle successful payment
          print("Payment processed successfully: $intentDetails");
        } else {
          // Handle case where result is null
          print("Payment was cancelled or no result was returned.");
        }
      } on MHException catch (e) {
        // Handle error
        print("Error during CVV submission: ${e.message}");
      }
    }
    
  4. Completion Handling:

    • On Success: If the CVV is submitted successfully, the submitCardCVV() method returns an IntentDetails object, containing information about the current intent state.
    • On Error: If an error occurs during the CVV submission, handle the exception appropriately by displaying an error message or prompting the user to retry.

The IntentForm state occurs when MoneyHash needs to handle the current stage of the payment or payout flow via the embedded form. In this state, you will use the moneyHash.renderForm() method to render the form and handle the completion of the process through the provided completion handler.

Steps to Handle the IntentForm State

  1. Render the MoneyHash Form:

    Use the moneyHash.renderForm() method to render the form within your app, passing the required parameters such as intentId, intentType, and optionally, embedStyle to customize the form's appearance.

  2. Handle Completion or Error:

    The form will guide the user through the payment or payout process. After the user completes the form, MoneyHash will call the completion handler with the IntentDetails if the process is successful, or an error if it fails.

Example of Handling the IntentForm State

// Example of handling the IntentForm state
if (intentState is IntentForm) {
  try {
    final intentId = "<intent_id>";
    final intentType = IntentType.payment; // Example of intent type (payment or payout)

    // Render the MoneyHash form
    final intentDetails = await moneyHash.renderForm(
      intentId,
      intentType,
      null, // Optional: Pass EmbedStyle if you want to customize the form
    );

    if (intentDetails != null) {
      // Success: Handle the successful completion of the form
      print("Form completed successfully: $intentDetails");
      // You can now proceed to the next step in your flow, e.g., showing a confirmation screen
    } else {
      // Handle case where result is null, e.g., user cancelled the process
      print("The form was cancelled or no result was returned.");
    }
  } on MHException catch (e) {
    // Handle error during form rendering or submission
    print("Error rendering form: ${e.message}");
  }
}

Other available MoneyHash SDK methods

In addition to the essential steps and methods previously described, the MoneyHash Flutter SDK provides other methods to customize the user experience. These additional methods are presented and described next.

In the MoneyHash Flutter SDK, you can reset the selected payment method for a specific intent using the resetSelectedMethod() method. This allows your customers to go back and choose a different payment method if needed. It can be used in the following scenarios:

  • Offering a button to let the customer return and select a new payment method after they already selected one.
  • Allowing the customer to retry and choose a different payment method after a failed transaction.

Example of Resetting the Selected Method

try {
  final intentId = "<intent_id>";
  final intentType = IntentType.payment; // Example: IntentType.payment or IntentType.payout

  // Reset the selected method
  final intentResult = await moneyHash.resetSelectedMethod(intentId, intentType);

  // Handle the result after resetting
  final intent = intentResult.details?.intent;
  final transaction = intentResult.details?.transaction;
  final selectedMethod = intentResult.details?.selectedMethod;
  final methods = intentResult.methods?.paymentMethods;

  print("Intent: $intent");
  print("Transaction: $transaction");
  print("Selected Method: $selectedMethod");
  print("Available Methods: $methods");
  
  // You can now render available payment methods again for the user to select a different one.
} on MHException catch (e) {
  // Handle any error during resetting the selected method
  print("Error resetting selected method: ${e.message}");
}

By using this method, you can offer more flexibility to your users during the payment flow, letting them go back and change their payment method even after selection.


In the MoneyHash Flutter SDK, you can delete a customer's saved card using the deleteSavedCard() method. This is useful when managing a list of customer-saved cards, allowing them to remove a card they no longer wish to keep on file.

Example of Deleting a Saved Card

try {
  final cardTokenId = "<card_token_id>"; // The ID of the saved card to be deleted
  final intentSecret = "<intent_secret>"; // The intent secret for verification

  // Call the method to delete the saved card
  await moneyHash.deleteSavedCard(cardTokenId, intentSecret);

  print("Card deleted successfully.");
  
  // Handle any post-deletion logic, like refreshing the card list or showing a confirmation message
} on MHException catch (e) {
  // Handle any error during the card deletion process
  print("Error deleting saved card: ${e.message}");
}

By using this method, you can allow users to manage their saved cards within your app, providing the option to delete cards as needed.


In the MoneyHash Flutter SDK, you can update the discount for an intent using the updateDiscount() method. This method allows you to pass the intentId and a DiscountItem object to adjust the discount on an intent level.

Please note the following restrictions when updating a discount:

  • Discounts can't be updated for intents that have transactions.
  • Can't update discount when the intent has product items that have a discount.
  • Can't update discount when the intent has fees that have a discount.
  • Discount value must not be more than the intent amount.

Example of Updating a Discount

try {
  final intentId = "<intent_id>"; // The ID of the intent to update the discount
  final discount = DiscountItem(
    title: {
      Language.english: "Discount Title",
      Language.arabic: "عنوان الخصم",
      Language.french: "Titre de la remise",
    },
    type: DiscountType.amount, // Discount type: amount or percentage
    value: "10", // Discount value
  );

  // Call the method to update the discount
  final DiscountData? updatedDiscount = await moneyHash.updateDiscount(intentId, discount);

  if (updatedDiscount != null) {
    print("Discount updated successfully: ${updatedDiscount.amount}");
    // Handle any further logic, like displaying the updated discount details to the user
  } else {
    print("No discount update applied.");
  }
} on MHException catch (e) {
  // Handle any error during the discount update process
  print("Error updating discount: ${e.message}");
}

This method allows you to apply or update a discount on an intent, adhering to the defined conditions and limitations.


In the MoneyHash Flutter SDK, you can update the fees associated with an intent using the updateFees() method. This method requires the intentId and a list of FeeItem objects, allowing you to adjust the fees at the intent level.

Please note that fees cannot be updated for intents that already have transactions.

Example of Updating Fees

try {
  final intentId = "<intent_id>"; // The ID of the intent to update the fees
  final fees = [
    FeeItem(
      title: {
        Language.english: "Service Fee",
        Language.arabic: "رسوم الخدمة",
        Language.french: "Frais de service",
      },
      value: "10", // Fee value
    ),
    FeeItem(
      title: {
        Language.english: "Handling Fee",
        Language.arabic: "رسوم المناولة",
        Language.french: "Frais de manutention",
      },
      value: "15", // Fee value
      discount: DiscountItem(
        title: {
          Language.english: "Discount",
          Language.arabic: "خصم",
          Language.french: "Remise",
        },
        type: DiscountType.percentage, // Discount type: amount or percentage
        value: "5", // Discount value
      ),
    ),
  ];

  // Call the method to update the fees
  final FeesData? updatedFees = await moneyHash.updateFees(intentId, fees);

  if (updatedFees != null) {
    print("Fees updated successfully: ${updatedFees.amount}");
    // Handle any further logic, like displaying the updated fees details to the user
  } else {
    print("No fees update applied.");
  }
} on MHException catch (e) {
  // Handle any error during the fees update process
  print("Error updating fees: ${e.message}");
}

This method enables you to modify the fees associated with an intent while following the necessary restrictions regarding existing transactions.


In the MoneyHash Flutter SDK, you can set the locale for the SDK to handle localization for different languages. This can be done by calling the setLocale() method and passing the desired Language enum value.

Example of Setting Locale

try {
  // Set the SDK locale to Arabic
  moneyHash.setLocale(Language.arabic);
  print("Locale set to Arabic successfully");
} on MHException catch (e) {
  // Handle error if setting locale fails
  print("Error setting locale: ${e.message}");
}

You can choose from the following languages:

  • Language.arabic
  • Language.english
  • Language.french

To authenticate and make use of the MoneyHash SDK, you need to set your public API key by calling the setPublicKey() method. This should be done early in your application's initialization process.

Example of Setting Public Key

try {
  // Set the public API key
  moneyHash.setPublicKey("<your-public-api-key>");
  print("Public key set successfully");
} on MHException catch (e) {
  // Handle error if setting the public key fails
  print("Error setting public key: ${e.message}");
}

Make sure to replace "<your-public-api-key>" with your actual public API key.


You can adjust the SDK's log level using the setLogLevel() method. The LogLevel enum defines the possible levels of logging, allowing you to control the verbosity of the logs.

Example of Setting Log Level

try {
  // Set the log level to 'debug'
  moneyHash.setLogLevel(LogLevel.debug);
  print("Log level set to debug successfully");
} on MHException catch (e) {
  // Handle error if setting log level fails
  print("Error setting log level: ${e.message}");
}

You can set the log level to any of the following options:

  • LogLevel.verbose
  • LogLevel.debug
  • LogLevel.info
  • LogLevel.warn
  • LogLevel.error
  • LogLevel.assertion

This helps manage the SDK's logging behavior, which can be useful for debugging and monitoring purposes.


Customization

With MoneyHash's Flutter SDK, you can customize the appearance of the embedded form, including the submit button, input fields, and loader. To do so, you can pass an EmbedStyle object when calling the renderForm() method, allowing you to modify styles according to your brand’s needs.

Here’s how you can use the customization options with EmbedStyle for different components:

Customizing Submit Button

You can customize the submit button by providing the EmbedButtonStyle inside the EmbedStyle. This allows you to style the button's base, hover, and focus states.

Example:

EmbedButtonStyle buttonStyle = EmbedButtonStyle(
  base: EmbedButtonViewStyle(
    color: "#FFFFFF",
    background: "#000000",
    borderRadius: "5px",
    fontSize: "16px",
  ),
  hover: EmbedButtonViewStyle(
    background: "#333333",
  ),
  focus: EmbedButtonViewStyle(
    borderColor: "#FF0000",
  ),
);

EmbedStyle embedStyle = EmbedStyle(
  submitButton: buttonStyle,
);

Customizing Input Fields

The EmbedInputStyle allows you to customize the base, focus, and error states of the input fields in the form. This helps you to match the input fields with the overall design of your application.

Example:

EmbedInputStyle inputStyle = EmbedInputStyle(
  base: EmbedInputViewStyle(
    background: "#F5F5F5",
    borderColor: "#CCCCCC",
    padding: "10px",
  ),
  focus: EmbedInputViewStyle(
    borderColor: "#000000",
  ),
  error: EmbedInputViewStyle(
    borderColor: "#FF0000",
  ),
);

EmbedStyle embedStyle = EmbedStyle(
  input: inputStyle,
);

Customizing Loader

You can also customize the appearance of the loader that is shown during the form submission process.

Example:

EmbedLoaderStyle loaderStyle = EmbedLoaderStyle(
  backgroundColor: "#000000",
  color: "#FFFFFF",
);

EmbedStyle embedStyle = EmbedStyle(
  loader: loaderStyle,
);

Applying the Custom Styles

Once you’ve customized the styles, you can apply them when calling the renderForm() method.

Example:

Future<IntentDetails?> renderCustomForm(String intentId, IntentType intentType) async {
  try {
    EmbedStyle customStyle = EmbedStyle(
      submitButton: buttonStyle,
      input: inputStyle,
      loader: loaderStyle,
    );

    var result = await moneyHash.renderForm(intentId, intentType, customStyle);

    if (result != null) {
      // Handle successful form rendering
      print("Form rendered successfully");
    }
    return result;
  } on PlatformException catch (e) {
    print("Error rendering form: ${e.message}");
    return null;
  }
}

By customizing the EmbedStyle, you can ensure that the MoneyHash form integrates seamlessly with your app’s design and provides a consistent user experience.


Handling Errors in the SDK

In the MoneyHash Flutter SDK, errors are represented using the MHException class. This class provides detailed information about the error that occurred during a request or operation, including the error message, type, and additional details. This allows you to handle different error scenarios effectively and provide users with meaningful feedback.

MHException Structure

The MHException class contains the following key attributes:

  • message: A user-friendly error message describing the issue.
  • errors: A list of ErrorDetail objects that provide more granular details about specific fields or issues.
  • type: An MHExceptionType enum value that categorizes the type of error.

MHExceptionType Enum

The MHExceptionType enum categorizes the errors that can occur within the SDK. The possible types are:

  • network: Indicates a network-related issue, such as connectivity problems or API error.
  • unknown: An unexpected or unknown error occurred.
  • cardValidation: Validation failed for card details provided by the user.
  • cancelled: The operation was cancelled by the user or the system.
  • applePayTransactionFailed: The Apple Pay transaction could not be completed.
  • notCompatibleWithApplePay: The device is not compatible with Apple Pay.

Error Handling Example

When an error occurs during an operation, such as submitting payment details, you can catch the MHException and handle it accordingly.

Example:

try {
  // Some MoneyHash SDK operation
  var result = await moneyHash.submitCardCVV("<intentId>", "123");
  if (result != null) {
    // Handle success
    print("Payment submitted successfully.");
  }
} on MHException catch (e) {
  // Handle different error types
  switch (e.type) {
    case MHExceptionType.network:
      print("Network error: ${e.message}");
      break;
    case MHExceptionType.cardValidation:
      print("Card validation failed: ${e.message}");
      break;
    case MHExceptionType.cancelled:
      print("Operation cancelled: ${e.message}");
      break;
    case MHExceptionType.applePayTransactionFailed:
      print("Apple Pay transaction failed: ${e.message}");
      break;
    case MHExceptionType.notCompatibleWithApplePay:
      print("Device not compatible with Apple Pay: ${e.message}");
      break;
    default:
      print("Unknown error: ${e.message}");
  }

  // Additional error details
  if (e.errors.isNotEmpty) {
    for (var error in e.errors) {
      print("Error detail - Key: ${error.key}, Message: ${error.message}");
    }
  }
}

ErrorDetail Class

The ErrorDetail class provides additional context for specific errors. Each error has:

  • key: The specific field or component that caused the error.
  • message: A detailed message explaining the error related to that field or component.

Example of ErrorDetail:

ErrorDetail error = ErrorDetail(key: "cardNumber", message: "Invalid card number");
print("Error with field: ${error.key}, Message: ${error.message}");

By handling errors with the MHException class, you can ensure that your app provides clear and specific feedback to users, making the payment process more reliable and user-friendly.