Client SDKs: Embedded Components

Embedded Components are similar to Hosted Payment Pages, with the difference being that the customer can supply the required information beforehand and the client SDK can control that flow natively without the need for redirecting to the PSP’s hosted page. This integration streamlines the user experience by keeping everything within the familiar environment of your app, creating a more cohesive transaction process.

Embedded Components alternative payment method flow

We have implemented the Native APM flow into our SDKs to make it easier for you to integrate Native alternative payment methods. If you wish to learn more about what happens in the background and the backend calls the ProcessOut SDK makes, please refer to the Server to Server: Embedded Components guide.

Below is a sequence diagram that shows the flow for a Native APM payment. The following sections explain the steps in more detail.

Sequence diagram showing the flow for an APM

Sequence diagram showing the flow for an APM

Setting up client SDKs

To set up the client SDKs you only need your project ID. Find examples of how to configure the SDKs below.

...

<!-- Load ProcessOut.js --> 
<script src="https://js.processout.com/processout.js"></script>

<!-- Set up ProcessOut.js with a valid `projectId` -->
<script>
	var client = new ProcessOut.ProcessOut(projectId);

...
import ProcessOut
import ProcessOutUI

let configuration = ProcessOutConfiguration.production(
    projectId: "test-proj_gAO1Uu0ysZJvDuUpOGPkUBeE3pGalk3x"
)
ProcessOut.configure(configuration: configuration)
PricessOutUI.configure() // optional

...
import com.processout.sdk.api.ProcessOutApi

...

ProcessOutApi.configure(
    ProcessOutApi.Configuration(
        context = this,
        projectId = "your_project_id"
    )
)

...

List the available Native APMs providers

You can have several Native APMs configured for use with ProcessOut at the same time (go to Dashboard › Payment providers to see the available native APMs or configure new ones).

However, this does not mean that all of them will be available for a particular purchase. For example, some native APMs might not operate in every country. The ProcessOut API lets you find the set of native APMs that are available for an invoice so that you can list them for your customer to choose from.

The code sample below shows how to retrieve a list of available native APMs for your invoice.

client.fetchGatewayConfigurations({
  invoiceID: "iv_tIWEiBcrXIFHzJeXcZzqyp8EpY0xwmuT",
  filter:    "native-alternative-payment-methods"
}, processoutAPMsReady, function(err) {
  console.log("Woops, could not fetch APMs: "+err);
});
let request = POAllGatewayConfigurationsRequest(
  filter: .nativeAlternativePaymentMethods
)
let response = await ProcessOut.shared.gatewayConfigurations.all(request: request)
// Coroutine function.
val request = POAllGatewayConfigurationsRequest(
    filter = POAllGatewayConfigurationsRequest.Filter.NATIVE_ALTERNATIVE_PAYMENT_METHODS,
    withDisabled = false
)
val result = ProcessOutApi.instance.gatewayConfigurations.fetch(request)
when (result) {
    is ProcessOutResult.Success -> TODO()
    is ProcessOutResult.Failure -> TODO()
}
// Callback function.
val request = POAllGatewayConfigurationsRequest(
    filter = POAllGatewayConfigurationsRequest.Filter.NATIVE_ALTERNATIVE_PAYMENT_METHODS,
    withDisabled = false
)
ProcessOutApi.instance.gatewayConfigurations.fetch(
    request = request,
    callback = object : ProcessOutCallback<POAllGatewayConfigurations> {
        override fun onSuccess(result: POAllGatewayConfigurations) {
            TODO()
        }
        override fun onFailure(e: Exception) {
            TODO()
        }
    }
)

Build your UI components and launch the Embedded Components payment sheet

Once you have obtained your available native APM configurations you can construct your own UI components that will supply the gateway_configuration_id and the invoice_id. These can be passed to the client SDKs so that they can create and launch the native APM payment sheet for you.

<script>

// Use `setupNativeApm` function which accepts `gatewayConfigurationId`
// and `invoiceId` to create the Native APM instance.
var nativeApm = client.setupNativeApm({
  gatewayConfigurationId,
  invoiceId
});
  
...

</script>

...

<!-- Create a container div element on your page with a specific id
     attribute. -->
<div id="napm-container"></div>

<!-- Mount the widget on the Web page using the `mount` function from the
     Native APM instance. It accepts a selector of for the container `div`
     element. -->
<script>
	nativeApm.mount('#napm-container');
</script>
import ProcessOutUI

let configuration = PONativeAlternativePaymentConfiguration(
    invoiceId: "invoice_id",
    gatewayConfigurationId: "gateway_configuration_id"
)
let view = PONativeAlternativePaymentView(configuration: configuration) { result in
    // TODO: Handle result
}

// Alternatively in UIKit environment you can use PONativeAlternativePaymentViewController.
// It is also created via init where the only difference is additional optional style
// argument
// 1) It is required to initialize the launcher in the onCreate()
//    method of an Activity or Fragment.

private lateinit var launcher: PONativeAlternativePaymentMethodLauncher

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    launcher = PONativeAlternativePaymentMethodLauncher.create(from = this) { result ->
        when (result) {
            PONativeAlternativePaymentMethodResult.Success -> TODO()
            is PONativeAlternativePaymentMethodResult.Failure -> TODO()
        }
    }
}

// 2) Launch the activity.

launcher.launch(
    PONativeAlternativePaymentMethodConfiguration(
        gatewayConfigurationId = "gway_conf_",
        invoiceId = "iv_"
    )
)

Once the customer is presented with the payment sheet they will be able to input the required information. The SDK handles the rendering of the UI components required to capture the customers' input and handles the communication with ProcessOut.

If any customer action is required (e.g. confirming the payment through their banking app), the client SDK will present a customizable page to the customer. It will then wait for the action to be completed before sending the response back to your app and handling the closing of the payment sheet.

Customization of the payment sheet

Mobile SDKs

We allow certain elements of the UI to be customizable on the payment sheet. You can customize the following:

  • Title style and text
  • Input elements style
  • Buttons’ style and text/icons
  • The background style
  • Message styles (success/failure/information).
  • Payment confirmation

The following code snippet gives you an example on how to do that for some elements. You can reference our mobile SDK docs for more information.

let payButtonStyle = POButtonStyle(
    normal: .init(
        title: .init(color: .black, typography: .init(font: .system(size: 14))),
        border: .clear(),
        shadow: .clear,
        backgroundColor: .green
    ),
    highlighted: .init(
        title: .init(color: .black, typography: .init(font: .system(size: 14))),
        border: .clear(),
        shadow: .clear,
        backgroundColor: .yellow
    ),
    disabled: .init(
        title: .init(color: .gray, typography: .init(font: .system(size: 14))),
        border: .clear(),
        shadow: .clear,
        backgroundColor: .darkGray
    ),
    progressStyle: .system(.medium)
)
let style = PONativeAlternativePaymentStyle(
    actionsContainer: .init(primary: payButtonStyle, secondary: .secondary, axis: .horizontal)
)

// Apply style to view
PONativeAlternativePaymentView(...)
    .nativeAlternativePaymentStyle(style)

// In UIKit pass style directly to controller's init
let viewController = PONativeAlternativePaymentViewController(
    style: uiConfiguration, ...
)

// Rather than changing styling you could change displayed data, for example
// to change screen’s title and action button text you could do:
let uiConfiguration = PONativeAlternativePaymentConfiguration(
    ...,
    title: "Pay here",
    submitButton: .init(title: "Submit", icon: Image(systemName: "centsign")),
    cancelButton: .init(disabledFor: 10, confirmation: .init())
)
PONativeAlternativePaymentView(configuration: uiConfiguration, ...)
val payButtonStyle = POButtonStyle(
    normal = POButtonStateStyle(
        text = POTextStyle(Color.WHITE, POTypography.actionDefaultMedium),
        border = POBorderStyle(radiusDp = 16, widthDp = 0, color = Color.TRANSPARENT),
        backgroundColor = Color.parseColor("#ffff8800")
    ),
    disabled = POButtonStateStyle(
        text = POTextStyle(Color.GRAY, POTypography.actionDefaultMedium),
        border = POBorderStyle(radiusDp = 16, widthDp = 0, color = Color.TRANSPARENT),
        backgroundColor = Color.LTGRAY
    ),
    highlightedBackgroundColor = Color.parseColor("#ffffbb33"),
    progressIndicatorColor = Color.WHITE
)
launcher.launch(
    PONativeAlternativePaymentMethodConfiguration(
        gatewayConfigurationId = uiModel.gatewayConfigurationId,
        invoiceId = uiModel.invoiceId,
        style = PONativeAlternativePaymentMethodConfiguration.Style(
            submitButton = payButtonStyle
        ),
        uiConfiguration = PONativeAlternativePaymentMethodConfiguration.UiConfiguration(
            title = "Payment details",
            submitButtonText = "Submit",
            successMessage = "Payment confirmed.\nThank you!"
        ),
        options = PONativeAlternativePaymentMethodConfiguration.Options(
            isBottomSheetCancelable = true
        )
    )
)

Web SDK

Our widget comes with default styling, but you have the option to customize its appearance in two ways:

Inline styles

The Native APM instance offers a convenient setTheme function, allowing you to style the widget using inline styles:

  nativeApm.setTheme({
    buttons: {
      default: {
        backgroundColor: 'green'
      }
    }
  });

This function accepts a theme object with the following schema:

{
  wrapper: { // styles widget wrapper
    display: 'flex',
    justifyContent: 'center',
    flexDirection: 'column',
    alignItems: 'center',
    width: '100%',
    fontSize: '1rem',
  },
  spinner: { // styles loading spinner
    width: '4rem',
    height: '4rem',
    border: '5px solid #f2f2f2',
    borderBottomColor: '#7e57c2',
    borderRadius: '50%',
    display: 'inline-block',
    boxSizing: 'border-box',
    animation: 'rotation 1s linear infinite',
  },
  logo: { // styles merchant logo
    width: '10rem',
    height: 'auto',
    marginBottom: '1rem',
  },
  buttons: { 
    default: { // styles buttons
      borderWidth: '0px',
      color: '#fff',
      backgroundColor: '#7e57c2',
      cursor: 'pointer',
      padding: '0.8rem 1.5rem',
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      position: 'relative',
      marginTop: '0.8rem',
      fontSize: '1rem',
      borderRadius: '5px',
    },
    spinner: { // styles loading spinner in the buttons
      position: 'absolute',
      width: '1rem',
      height: '1rem',
      border: '3px solid #f2f2f2',
      borderBottomColor: '#7e57c2',
      borderRadius: '50%',
      display: 'inline-block',
      boxSizing: 'border-box',
      animation: 'rotation 1s linear infinite',
    },
  },
  form: {
    inputs: {
      text: { // styles text inputs
        padding: '0 0.8rem',
        height: '3rem',
        border: '1px solid #d7d7d7',
        borderRadius: '5px',
        width: '100%',
        fontSize: '1rem',
      },
      select: { // styles select inputs
        height: '3rem',
        width: '100%',
        border: '1px solid #d7d7d7',
        borderRadius: '5px',
        fontSize: '1rem',
      },
      numeric: { // styles numeric inputs
        display: 'flex',
        flexDirection: 'row',
        gap: '0.8rem',
        character: { // styles single digit of numeric input
          width: '3rem',
          height: '3rem',
          fontSize: '1.5rem',
          border: '1px solid #d7d7d7',
          borderRadius: '5px',
          textAlign: 'center',
        },
      },
    },
    labels: { // styles input labels
      display: 'block',
      marginBottom: '10px',
      requiredStar: { // styles red required start of the label
        color: '#e74c3c',
        marginLeft: '0.1rem',
      },
    },
    errors: { // styles input error messages
      display: 'block',
      color: '#e74c3c',
      fontSize: '0.9rem',
      minHeight: '1rem',
      marginTop: '0.3rem',
      width: '100%',
    },
  },
  message: { // styles widget messages
    fontSize: '1.1rem',
  },
  actionImage: { // styles action images like pending view illustration, success icon
    marginTop: '1.3rem',
    width: '15rem',
    height: 'auto',
  },
};

While it is possible to override any of these properties, we strongly recommend refraining from modifying styles that govern the fundamental layout of the element.

CSS classes

You have the ability to customize the appearance of the elements on your page by overriding styles through CSS classes:

#napm-container .native-apm-button {
  background-color: 'green',
}

Each element is associated with its unique CSS class. Here's the list of CSS classes available for customization:

CSS classElement
.native-apm-widget-wrapperWrapper of the Native APM widget
.native-apm-buttonButtons, for example the Submit button
.native-apm-spinnerLoading spinner
.native-apm-numeric-inputNumeric inputs
.native-apm-numeric-input-characterSingle digit of numeric inputs
.native-apm-input-errorInput error messages
.native-apm-input-labelInput labels
.native-apm-payment-provider-logoPayment provider logo
.native-apm-messageCustomer messages
.native-apm-action-imageAction image, for example the success icon

Data prefilling

To enhance the user experience while using the Native APM widget, you have the option to prefill input fields with user data. This can be achieved by utilizing the prefillData function, which accepts an object containing user data:

  nativeApm.prefillData({
    email: '[email protected]',
    phone_number: '+48123123123'
  })
// 1. Define delegate
final class Delegate: PONativeAlternativePaymentDelegate {

    func nativeAlternativePayment(
        defaultValuesFor parameters: [PONativeAlternativePaymentMethodParameter]
    ) async -> [String: String] {
        [:]
    }
}

// 2. Pass a delegate when creating a view
let delegate = Delegate()
let view = PONativeAlternativePaymentView(delegate: delegate, ...)
// https://github.com/processout/processout-android/blob/master/sdk/documentation/NativeAlternativePaymentMethods.md#provide-default-values-for-payment-sheet-input-fields
viewModelScope.launch {
    with(ProcessOut.instance.dispatchers.nativeAlternativePaymentMethod) {
        // Subscribe for request to provide default values.
        defaultValuesRequest.collect { request ->
            // Default values should be provided as Map<String, String>
            // where key is PONativeAlternativePaymentMethodParameter.key
            // and value is a custom default value.
            val defaultValues = mutableMapOf<String, String>()

            // Populate default values map based on request parameters.
            // It's not mandatory to provide defaults for all parameters.
            request.parameters.find {
                it.type() == ParameterType.PHONE
            }?.also {
                defaultValues[it.key] = "+111122223333"
            }

            // Provide response which must be constructed from request with default values payload.
            // Note that once you've subscribed to 'defaultValuesRequest'
            // it's required to send response back, otherwise the payment flow will not proceed.
            // If there is no default values to provide it's still required
            // to call this method with 'emptyMap()'.
            provideDefaultValues(request.toResponse(defaultValues))
        }
    }
}

The supported data keys are the following:

Data keyExpected value
emailValid email address
phone_numberPhone number, including the international prefix and without spaces

Events

The Native APM widget dispatches custom events at each step in the Native APM flow to facilitate seamless integration with your page.

Web SDK

You can easily monitor these events by utilizing the Event Listener Browser API:

window.addEventListener('processout_native_apm_payment_success', () => {
    // Handle payment success.
  })

Here is the list of events which the widget emits:

Event nameDescription
processout_native_apm_loadingThe widget is loading the gateway configuration
processout_native_apm_readyThe widget is ready to accept customer input
processout_native_apm_payment_initThe widget initiates the payment
processout_native_apm_payment_additional_inputAdditional customer input is required
processout_native_apm_payment_successThe payment is successful
processout_native_apm_payment_errorAn error occurred during the payment
processout_native_apm_gateway_configuration_errorAn error occurred while retrieving the gateway configuration

Mobile SDKs

On iOS you need to pass a delegate conforming to PONativeAlternativePaymentDelegate protocol in order to observe events.

During the module lifecycle SDK calls nativeAlternativePaymentMethodDidEmitEvent(_:) method and passes event object that you can inspect

Your app can subscribe to events using the following methods:

final class Delegate: PONativeAlternativePaymentDelegate {

    func nativeAlternativePayment(didEmitEvent event: PONativeAlternativePaymentMethodEvent) {
        // todo: handle event
    }
}

let delegate = Delegate()
let view = PONativeAlternativePaymentView(delegate: delegate, ...)
viewModelScope.launch {
    ProcessOut.instance.dispatchers.nativeAlternativePaymentMethod
        .events.collect { event ->
            when (event) {
                PONativeAlternativePaymentMethodEvent.WillStart -> TODO()
                PONativeAlternativePaymentMethodEvent.DidStart -> TODO()
                PONativeAlternativePaymentMethodEvent.ParametersChanged -> TODO()
                PONativeAlternativePaymentMethodEvent.WillSubmitParameters -> TODO()
                is PONativeAlternativePaymentMethodEvent.DidSubmitParameters -> TODO()
                is PONativeAlternativePaymentMethodEvent.DidFailToSubmitParameters -> TODO()
                is PONativeAlternativePaymentMethodEvent.WillWaitForCaptureConfirmation -> TODO()
                PONativeAlternativePaymentMethodEvent.DidCompletePayment -> TODO()
                is PONativeAlternativePaymentMethodEvent.DidFail -> TODO()
            }
        }
}

Capturing the payment

The hosted page will attempt to automatically capture the payment and wait for a success or failure status for 30 seconds after the customer has entered the required information.

If you prefer to capture the payment manually or after 30 seconds, the status will remain unconfirmed. You can call the capture endpoint, with the gateway_configuration_id as the source parameter in the capture request.

More information can be seen here: Server to Server - Capture.


What’s Next