Implementing your own payment validation webview

In this guide, once the payment has been created on the API, we will validate it without using Powens' Webview.

This guide assumes you are authorized to implement your own webview. If you don't know whether you are or not, ask your commercial contact about it.

This guide assumes that you have the following elements:

  • A Powens domain with Pay enabled.

  • A payment, regardless of its type, with the "created" status.

A payment validation webview must be isolated, with a defined role and behaviour whatever its design integration is. This role is described here, through specific steps and conditions the webview must traverse to validate the current operation on a payment.

Get the payment's current state

At startup, the webview must get the current state of the payment:

  • If the payment's state is created, then the webview can do the steps to validate the payment.

  • If the payment's state is validating and the current action is not confirm, then the webview can continue to handle interactive steps.

  • Otherwise, the payment is unsuitable for validation by the webview, and the end user should be redirected to the final redirect URL with a suitable state.

Note that this request is also required to get other information regarding the payment, e.g. to determine the constraints applied to obtain eligible connectors.

The Pay API does not support changing webviews during a single payment validation flow. If the payment is already in the validating state, this guide assumes that the initiation payment validation was done by the same webview, and either the end user has reloaded the page (e.g. resumed the flow due to a client-side crash), or came back from a redirect flow with the bank.

Validate the payment

This section is required if the payment is currently in the created state.

Validating the payment is about placing it in a state where we have to answer interactive steps. In order to do this, we must prepare a few properties before validating the payment.

Select a connector for the payment

If the connector isn't set yet on the payment, i.e. if connector_uuid is set to null on the current status of the payment, you need to pick a connector for the payment. In order to find available connectors for making payments, you need to make the following request:

GET /connectors
{
  "connectors": [
    {
      "id": 1234,
      "name": "Arkéa Banking Services",
      ...
      "payment_settings": {
        "available_validate_mechanisms": ["webauth"],
        ...
      }
    },
    ...
  ]
}

Connectors supporting the Pay product, identified by having "pay" in its products list, will define a payment_settings object, which present Pay constraints and features for this specific connectors. You need to filter them on the client side, depending on the payment:

  • The connector must have "pay" in its products list.

  • The payment's number of instructions must be less or equal to maximum_number_of_instructions from the connector's payment_settings.

  • For all instructions of the payment:

    • The instruction's execution_date_type value must be present in the execution_date_types within the connector's payment_settings.

    • The instruction's scheme_name from beneficiary value must be present in the connector's beneficiary_types attribute in payment_settings.

    • If the instruction has an execution_date_type set to periodic, the frequency is present in execution_frequencies from the connector's payment_settings.

Beyond these standard filters, you can also apply yours depending on your configuration:

  • If you want to avoid having to ask the end user for their IBAN, you can avoid connectors with the providing_payer_account set to mandatory.

  • If you want to avoid payments ending in the accepted state, you can ensure that every instruction has an execution date type that isn't included in the connector's partial_status_tracking list.

If you want to get the top connectors for Pay in terms of number of payments in the last month, in case you want to display them on top, you can request such connectors with the following request:

GET /connectors?top=10&top_key=payments

Note that this request may return less than the number of connectors specified in the top parameter, for example it may return 7 connectors if all payments created in the last month have been affected only 7 connectors out of all possible connectors.

Select a validation mechanism

The connector you've chosen provides a given set of validation mechanisms, in the available_validate_mechanisms attribute of the payment_settings at connector level. It is recommended to check which validation mechanisms you want to use at this stage.

If you're not a white label, you must either restrict the connector list to connectors bearing the webauth (or any mechanism starting with webauth_) validation mechanism, or set the connector to the payment and redirect to Powens' webview, as you are not allowed to handle non-webauth validation mechanisms in your webview. Note that the procedure stops here if you redirect to Powens' webview at this stage. You are not to validate terms for the payment; Powens' webview will take care of this.

Validate terms for the payment

If you are a white label, this section is optional.

First, the terms of service must be accepted by the end user. In order to do so, you must do the following call:

GET /payments/{paymentId}/terms

If the to_sign attribute is both present and set to true, then the terms of service are to be accepted by the end user.

{
  "to_sign": "true",
  ...
}

If the terms of service have not been accepted yet, you can get the content to display to the end user by making the following request:

GET /terms
{
  "terms": {
    "id": {idTerms},
    ...
  },
  "content": "Conditions générales d'utilisation de Budgea Bank & Pay & Bill...",
  "language": "fr"
}

In order to signify the acceptance of the terms of service for the payment by the end user, you can make the following request:

POST /payments/{paymentId}/terms
{
  "id_terms": {idTerms}
}
{
  "success": "ok"
}

Prepare the expected fields

In order to check if fields are to be sent to the connector, you need to get the connector fields by making the following request:

GET /connectors/{connectorUUID}?expand=payment_fields
{
  "id": 123,
  ...
  "payment_fields": [
    {
      "name": "website",
      "label": "Cas d'usage",
      "regex": null,
      "type": "list",
      "required": false,
      "auth_mechanisms": ["webauth"],
      "values": [
        {
          "label": "Particuliers",
          "value": "par",
        },
        ...
      ]
    },
    ...
  ],
  ...
}

The website field should be requested from the end user if the validation mechanism chosen beforehand is present in the auth_mechanisms array in the field definition.

Regardless of whether you are a white label or not, you MUST use front encryption on all fields before transmitting them. See End-to-end encryption for more information.

Prepare the payer account

If the connector's UUID was already provided in the payment by the webview's client, you need to request the connector's information here by calling GET /connectors/{connectorUUID}.

If providing_payer_account in the connector's payment_settings is set as mandatory, then the payer's identification must be requested from them.

Send the validation request

From the previous sections, you now have obtained the following information for validating the payment from the end user:

  • The connector UUID (if it wasn't already set).

  • The validate mechanism.

  • The field values, passed through end-to-end encryption (if at least one is set).

  • The payer account identification (if it is required).

  • The callback URL for redirect actions (as webview_url).

You must prepare a webview_url, in order to redirect the end user back to your webview to continue the validation process for the payment after redirect actions. This URL can contain query parameters which will be included in the callback.

In case of multiple redirect steps, the end user will always be redirected to the same webview URL as provided in the validation request. Please ensure that you don't have nonces in said URL.

You can now validate the payment, in order to pass it from the created state to validating:

POST /payments/{paymentId}
{
  "connector_uuid": "{connectorUUID}",
  "validate_mechanism": "webauth",
  "fields": {
    "website": "eyJh..."
  },
  "payer": {
    "scheme_name": "iban",
    "identification": "FR76...",
    "label": "M. Jean DUPONT"
  },
  "webview_url": "https://example.org/payment-validation-webview?redirect_url=...&state=123&id_payment=456",
  "validated": true
}
{
  "id": {paymentId},
  "state": "validating",
  ...
}

The payment is now initiated, and interactive steps need to be taken for the payment to be fully validated.

Handle interactive steps

Either after successfully validating a payment in the created state or resuming validation on a payment in the validating state, interactive steps need to be taken towards continuing the operation.

The current validation step is represented by the action property of the payment.

"redirect" action (redirect flow)

This means that a redirect flow is to be accomplished by the end user. In this case, you must redirect the end user to the URL present in the validate_uri attribute of the payment.

The URL MUST NOT be used in an iframe, only in full redirect, as it might be app-to-app compatible on mobile devices.

At the end of the redirect flow, the end user will be redirected back to the webview through the URL you've defined in the webview_url attribute of the validation request.

"decoupled" action (validation on other channel)

This means that a validation has been requested through another channel, such as a notification in a mobile application, or a link sent by e-mail or SMS. In this case, you must inform the end user that the validation is taking place, then request a check on the decoupled validation status with a 10 second delay, using the following request:

POST /payments/123
{
  "resume": true
}

The 10 second timer should start after the response is received. For efficiency, some connectors may internalize part of the polling, which means the request defined above could take more than 10 seconds.

"additionalInformationNeeded" action (temporary values required)

This means that an additional information is required from the end user, such as one-time passwords or the identifier of the account to select for the payment. In this case, you must get the list of fields to ask the end user.

For example, the payment will resemble this:

{
    "id": 123,
    "state": "validating",
    "action": "additionalInformationNeeded",
    ...,
    "fields": [
      {
        "name": "account_id",
        "type": "list",
        "label": "Please choose your payment account",
        ...
      }
    ],
    ...
}

And, in order to submit the values picked by the end user back to the API, a request such as this one must be made:

POST /payments/123
{
  "fields": {
     "account_id": "FR76..."
  }
}

"psuValidation" action (payment request summary suggested)

This means that presenting a summary of the payment to the end user and letting them choose whether or not they want to pursue the validation flow is recommended at this point. This is mostly used when no redirect flow is available to do this, but a webview may emulate end user consent on such actions.

In order to communicate that the end user consents to pursuing the validation flow, the following request should be made:

POST /payments/123
{
  "psu_validate": true
}

If however, the end user chooses not to pursue the validation flow, the following request should be made:

POST /payments/123
{
  "psu_validate": false
}

"confirm" action (client confirmation required)

This means that payment validation confirmation is expected from the client. This must be considered as a "pending" payment state by the webview, which should redirect the end user to the client redirect URL and state.

Validate your implementation with the test connector use cases

Different use cases are provided with the test connector to trigger specific validation mechanisms during the validation process. This will allow you to verify that you have correctly implemented the different interactive steps.

List all available use cases

GET /connectors/338178e6-3d01-564f-9a7b-52ca442459bf?expand=payment_fields
{
  "id": 59,
  "name": "Connecteur de test",
  ...
  "payment_fields": [
    ...
    {
      "name": "use_case_pay",
      "label": "Use case",
      "regex": null,
      "type": "list",
      "required": false,
      "auth_mechanisms": [
        "webauth"
      ],
      "connector_sources": [
        "openapi"
      ],
      "values": [
        {
          "label": "Legacy (custom behaviour)",
          "value": "legacy"
        },
        {
          "label": "Webauth redirection",
          "value": "redirect"
        },
        {
          "label": "Decoupled with successful validation",
          "value": "decoupled"
        },
        {
          "label": "Decoupled with cancelled validation",
          "value": "decoupled_cancelled"
        },
        {
          "label": "Decoupled with expired validation",
          "value": "decoupled_expired"
        },
        {
          "label": "One time password (SMS)",
          "value": "otp_sms"
        },
        {
          "label": "OTP + Decoupled",
          "value": "otp_plus_decoupled"
        },
        {
          "label": "Payment account choice",
          "value": "account_choice"
        },
        {
          "label": "Webauth redirection without explicit confirmation",
          "value": "redirect_no_confirmation"
        }
      ]
    }
  ]

The field openapiwebsite can be ignored when initiating payments.

How to initiate a payment with a use case

In order to choose a use case for your payment on the test connector, you need to provide it as part of the expected fields.

Here's an example of how to initiate a payment with the redirect use case.

POST /payments/{payment_id}
{
    "connector_uuid": "338178e6-3d01-564f-9a7b-52ca442459bf",
    "validation_mechanism": "webauth",
    "fields": {
        "use_case_pay": "redirect"
    },
    "validated": true,
    "webview_url": "{{webview_url}}"
}

From there, the payment will follow a specific sequence of required actions from your part in order to be validated.

In the following sections we will see the specifities of each available use case.

"redirect" : Validating a payment with a single redirection

Upon validation, the payment enters the validating state with a redirect action. The redirection URI is provided in the validate_uri field.

GET /payments/{payment_id}
{
    "id": {payment_id},
    "state": "validating",
    "action": "redirect",
    "validate_uri": "https://biapi.pro/2.0/webauth/callback?state=...&authFactorValue=AUTHENTICATION_VALIDATED",
    ...
}

You need to redirect the end user to the provided address, which will allow the payment to be validated and change its state to done. After which your end user would be redirected to webview_url.

"decoupled": Validating a payment with a decoupled authorization

This use case will trigger a decoupled validation and ends with a payment in the state done.

Upon validation, the payment enters the validating state with a decoupled action. The error_description field contains an example description you could get from a real connector. You don't really have to validate the operation anywhere here, this is just an example for you to display in your validation webview.

GET /payments/{payment_id}
{
    "id": {payment_id},
    "state": "validating",
    "action": "decoupled",
    "error_description": "Please confirm the transaction in the phone app.",
    ...
}

From here you can poll the payment until its state changes to done. The validation takes approximately 10 seconds to occur.

POST /payments/{payment_id}
{
    "resume": true
}
{
    "id": {{payment_id}},
    "state": "done",
    "action": null,
    ...
}

"decoupled_cancelled": Validating a payment with a cancelled decoupled authorization

This use case will trigger a decoupled validation and simulate a cancellation by the PSU in the decoupled channel.

Upon validation, the payment enters the validating state with a decoupled action.

GET /payments/{payment_id}
{
    "id": {payment_id},
    "state": "validating",
    "action": "decoupled",
    "error_description": "Please confirm the transaction in the phone app.",
    ...
}

After approximately 10 seconds, the payment state will change to rejected, with the error_code CancelledByUser.

POST /payments/{payment_id}
{
    "resume": true
}
{
    "id": {{payment_id}},
    "state": "done",
    "action": null,
    "error_code": "CancelledByUser",
    "error_description": "The decoupled validation has been cancelled by the PSU
    ...
}

"decoupled_expiration": Validating a payment with an expired decoupled authorization

This use case will trigger a decoupled validation and simulate an expiration of the authorization process.

Upon validation, the payment enters the validating state with a decoupled action.

GET /payments/{payment_id}
{
    "id": {payment_id},
    "state": "validating",
    "action": "decoupled",
    "error_description": "Please confirm the transaction in the phone app.",
    ...
}

After approximately 20 seconds, the payment state will change to rejected, with the error_code authenticationFailed.

POST /payments/{payment_id}
{
    "resume": true
}
{
    "id": {{payment_id}},
    "state": "rejected",
    "action": null,
    "error_code": "authenticationFailed",
    "error_description": "The decoupled validation has expired.",
    ...
}

"otp_sms": Validating a payment with a one time password

This use case will require the end user to input a one time password.

Upon validation, the payment enters the validating state with an additionalInformationNeeded action. The one time password is requested in the fields key of the response.

GET /payments/{{payment_id}}
{
    "id": {{payment_id}},
    "state": "validating",
    "action": "additionalInformationNeeded",
    "fields": [
        {
            "name": "openapisms",
            "type": "text",
            "label": "Please enter the one time password sent to phone number (+33)6 12 34 56 78 (code = 1234).",
            "regex": null,
            "required": true,
            "default": null
        }
    ],
    ...
}

From here there are two possibilities:

  • Provide the code 1234 to validate the payment. The payment state changes to done.

POST /payments/{{payment_id}}
{
    "fields": {
        "openapisms": "1234" 
    }
}
{
    "id": {{payment_id}},
    "state": "done",
    "action": null,
    ...
}

Or you can provide anything else to reject the payment with the authenticationFailed error_code.

POST /payments/{{payment_id}}
{
    "fields": {
        "openapisms": "a wrong password" 
    }
}
{
    "id": {{payment_id}},
    "state": "rejected",
    "action": null,
    "error_code": "authenticationFailed",
    ...
}

"otp_plus_decoupled": Validating a payment with a chained one time password and decoupled authorizations

This use case simulates a payment validation chaining a one time password prompt followed by a decoupled validation. This is useful to check that your webview implementation supports more than one validation mechanism at a time.

This chains the use case "One time password (SMS)" and "Decoupled with successful validation".

"account_choice": Validating a payment with a choice of payment account

This use case triggers a payment validation where the PSU needs to select the payment account to debit for this operation. This is done through an additional field of type list.

Upon validation, the payment enters the validating state with additionalInformationNeeded action. The dynamic field exposes a list of available payment accounts.

GET /payments/{{payment_id}}
{
    "id": {{payment_id}},
    "state": "validating",
    "action": "additionalInformationNeeded",
    "fields": [
        {
            "name": "account_id",
            "type": "list",
            "label": "account choice bank message",
            "regex": null,
            "required": true,
            "default": null,
            "values": [
                {
                    "value": "main",
                    "label": "My checking account (EX6713038781895300290000074)"
                },
                {
                    "value": "snd",
                    "label": "My secondary checking account (EX5813038781895400400000074)"
                }
            ]
        }
    ],
    ...
}

Depending on the chosen account, the operation result will vary:

  • If the main account is chosen, the payment will be successfully authorised and the state will change to done.

POST /payments/{{payment_id}}
{
    "fields": {
        "account_id": "main" 
    }
}
{
    "id": {{payment_id}},
    "state": "done",
    "action": null,
    ...
}
  • If the snd account is chosen, the payment will be rejected with the invalidEmitter error_code.

POST /payments/{{payment_id}}
{
    "fields": {
        "account_id": "snd" 
    }
}
{
    "id": {{payment_id}},
    "state": "rejected",
    "error_code": "invalidEmitter",
    "error_description": "Please select the main account.",
    ...
}

"redirect_no_confirmation": Validating a payment with a single redirection and no explicit client confirmation

This use case can be useful if you have disabled automatic payment confirmation on your domain, but still wants to test a case where such a confirmation is not available.

The validation mechanism is the same as the redirect use case, with the exception that the payment state will change to done without a confirm action beforehand.

"legacy": Validating a payment with intermediary states

The legacy use case allows you to specify the states that the payment will go through during its lifecycle. This can be used to test the webhooks, an early rejection or a pending state. This use case is fully covered in the following guide :Validating your implementation with the test connector.

It's useful whether you use your own webview or not. This use case is chosen by default if do not provide a value for the use_case_pay field when validating a payment on this connector.

Last updated