Payment Initiation

How to start using the Exthand:Gateway with this nuget package?

  1. Register a user.

Before being able to get transactions or initiate payement, you have to send to Exthand:Gateway (E:G) information about your user (PSU). See PSU registration in Gateway.
For PIS, your external reference (username, user id, etc) and (an email address or cell phone number) are mandatory.

Once you get the consent of the PSU, you have to register him on E:G.

Call CreateUserAsync

See above to know how much data you have to provide to this method.
This will return a UserRegisterResponse object.
Normal case, action property should be == "OK", then you have to store the userContext property with your PSU data.
You will need userContext for all operations, it's important to store it attached to your user and be able to provide it to E/G.

  1. Payment Initiation (PIS)

Payment initiation is a four step process:

  • You get the bank connector behaviour by calling GetBankPaymentAccessOptionsAsync
  • You initiate the payment and redirect your PSU to his bank for signing the payment.
  • You call the finalise method when you get called back by the bank.
  • Later, you call the status method to get a finalised status of your payment (executed, canceled, etc).

Call GetBankPaymentAccessOptionsAsync

This call returns information, that helps you build the payment request object for a specific connector.
For example, if you want to check if SEPA INSTANT payments are supported by a bank, check if the following property is true: InstantSepaCreditTransfers.singlePayments.supported

PaymentProduct

When initiating a payment, you must tell which kind of payment (aka payment product) you want to do in the paymentProduct of payment initiation request.
The available payment products for a connector are found in the payment options /ob/pis/payments/options/{connectorId}.
The values for this field are:

  • 0: Domestic
    For this type of payment, the debtor and creditor must be in the same country. Identification of the accounts can generaly be done using BBAN instead of IBAN;
  • 1: SEPA
    The transfer between banks from countries from SEPA (Single Euro Payments Area) uses only IBAN as account identifier. The SEPA Credit Transfer can be instant or not (see payment priority below);
  • 2: CrossBorder
    The debtor and creditor's banks are located in different countries and one or both are located outside the SEPA.

Check the connector's payment options for the availability.

Payment Priority

The paymentPriority field, only used for SEPA Credit Transfer, can be

  • 0: Normal
    The bank will do the SEPA Credit Transfer the "normal" way. The time to get the money on creditor account vary from bank to bank. It can take up to several days;
  • 1: Instant Payment
    The bank will do the SCT "instantly". This means that the money should be available on the creditor account in a handful of seconds!

Be sure to check in payment options for the availability in /instantSepaCreditTransfers/singlePayments/supported.

Some bank are doing instant SCT by default whatever priority you set and, if they cannot do the instant, they fallback on normal payment.

Specific payment date

Some bank doesn't allow you to specify a date for a payment. To prevent the removal of supplied date in the connector and let you think that the payment will be done at that date but it will be done ASAP, we've added a new payment option: /{payment product}/{payment type}/specificPaymentDate. E.g. /sepaCreditTransfers/singlePayments/specificPaymentDate.

It's an integer:

NOT_ALLOWED (0) – The date cannot be specified.
ALLOWED (1) – The date can be specified. Null (= today), today or future date.

Call PaymentInitiateAsync

This call needs a PaymentInitRequest object.

You'll have to create a Flow object on our side. That Flow should have a unique identifier. You'll use the Flow id in all calls (init/finalize) which are linked together.

You can fill it in like this sample code (Priority is set to 1 for Instant Payment, Amount --> we noticed lots of banks don't support payments for less than 1€).

PaymentInitRequest paymentInitRequest = new()
{
     connectorId = 1, // Calls ING in BELGIUM
     userContext = userContext, // You do remember this one ;)
     tppContext = new TppContext() 
     {
       TppId = _options.TPPName, // Your name.
       App = _options.AppName,   // Your app name.
       Flow = flow.Id.ToString() // An unique identifier of the flow in your system.
     },
     paymentInitiationRequest = new PaymentInitiationRequest()
     {
       amount = payment.Amount,  // Amount to be paid.
       currency = "EUR",         // Currency
       PaymentPriority = PaymentPriority.Instant,	 // 0 = Normal, 1 = Instant Payment.
       recipient = new RecipientInfo()
       {
         iban = payment.IBAN.BankAccount,   
         name = payment.Person.FirstName + " " + payment.Person.LastName
       },
       debtor = new DebtorInfo()
       {
         currency = "EUR",
         iban = debtorIbanAccount,
         name = payment.CounterpartyName,
         email = payment.ToEmail
       },
       remittanceInformationUnstructured = payment.Remittance,  // Remittance information (MAX 140 CHAR)
       endToEndId = flow.Id.ToString().Replace("-", ""),        // Unique identifier for this transaction (sent to the bank, MAX 35 CHAR)
       flowId = flow.Id.ToString(),                             // Unique identifier for this transaction.
       redirectUrl = _options.RedirectURL + redirectURL,        // Your redirect URL
       psuIp = IP,                                              // The IP Address (IPv4, IPv6) of the PSU
       requestedExecutionDate = "2022-12-31"                    // Requested payment date only
     }
};

Once the call is executed, you get PaymentInitResponse object. Save the FlowContext included in the result. It will be needed in the next step.
In that response, the ResultStatus indicates if the payment can be initiated.
Value should be REDIRECT, and redirect url can be found in dataString property like in the following example:

switch ((ResultStatus)flow.ResponseInitStatus)
{
  case ResultStatus.UNKNOW:
    break;
  case ResultStatus.DONE:
    break;
  case ResultStatus.REDIRECT:
    return Redirect(flow.ResponseInitDataString);
  case ResultStatus.DECOUPLED:
    return RedirectToPage("/bank/handlerSCA", new { id = flow.Id });
  case ResultStatus.PASSWORD:
    return RedirectToPage("/bank/handlerSCA", new { id = flow.Id });
  case ResultStatus.MORE_INFO:
    return RedirectToPage("/bank/handlerSCA", new { id = flow.Id });
  case ResultStatus.SELECT_OPTION:
    return RedirectToPage("/bank/handlerSCA", new { id = flow.Id });
  case ResultStatus.ERROR:
    break;
}

Calls to Finalize PaymentFinalizeAsync

This call is executed to finalise the payment process.
You have to execute it when your redirect URL is called back after the PSU signed the payment on the bank's app or website.

You should first call FindFlowIdAsync, give it the QueryString and get back your flowId.

string query = Request.QueryString.ToString();
string flowId = await FindFlowIdAsync(query);

Once you have you flowId you can recover previously saved FlowContex and call the PaymentFinalizeAsync method with a PaymentFinalizeRequest.

PaymentFinalizeRequest paymentFinalizeRequest = new PaymentFinalizeRequest()
{
flow = flow.ResponseInitFlowContext, // Pay attention to the fact we are speaking now about FlowContext and not Flow's ID.
tppContext = new()
{
TppId = _options.TPPName,
App = _options.AppName,
Flow = flow.Id.ToString()
},
userContext = flow.UserContext,
dataString = query  // The querystring you sent to FindFlowIdAsync.
};

The PaymentFinalizeResponse object will be returned.
The resultStatus property will contain a result code based on PaymentStatusISO20022.
You can handle that property in a way like this.

switch ((PaymentStatusISO20022)flow.ResponsePaymentStatus)
{
  case ResultStatus.UNKNOW:
    payment = await _paymentService.SetPendingAsync(flow.PaymentId.Value);
    // As we don't know the final status, we set it as pending in our system.
    return RedirectToAction("PayPending");
  case ResultStatus.DONE:
    switch ((PaymentStatusISO20022)flow.ResponsePaymentStatus)
    {
      case PaymentStatusISO20022.ACCC:
        // Payment accepted.
        payment = await _paymentService.SetPaidAsync(flow.PaymentId.Value);
        return RedirectToAction("ithankyou");
      case PaymentStatusISO20022.RJCT:
      case PaymentStatusISO20022.BLCK:
      case PaymentStatusISO20022.CANC:
        // Payment has been refused.
        payment = await _paymentService.SetRejectedAsync(flow.PaymentId.Value);
        return RedirectToAction("PayRejected");
      default:
        // Payment status is unknow. Should be good, best is to call PaymentStatusAsync later. 
        payment = await _paymentService.SetPendingAsync(flow.PaymentId.Value);
        return RedirectToAction("PayPending");
    }
    break;
  case ResultStatus.REDIRECT:
    // One more redirection requested by the bank, let's play.
    return Redirect(flow.ResponseFinalizeDataString);
  case ResultStatus.ERROR:
    // Handle the error here.
    break;
}

Call PaymentStatusAsync

This call allows you to get the latests status of a payment. Most of the time, status received in the Finalise call are the correct and final ones, but not always... ;)
Just call the method with the PaymentStatusRequest initialised.

You'll get an answer object PaymentStatusResponse. Structure is similar to the one you receive when you call the FinalizeAsync method.