Payment Refunds

The OpenBanking spec allows you to request account details for the account that made a payment, and which account would make the refund for the payment if requested. Only a handful of OpenBanking providers allow for this functionality, and by extension, only those providers will support our reverse payment functionality.

🚧

Prerequisites

You will need an API client that is set up for payments:

  • payment, payee:read and payee:create scopes enabled
  • A valid JWKS or jwks_uri
  • request_object_signing_alg not equal to none
  • Additionally the reverse_payment scope enabled

🚧

Known Reversal Payment Support

Bank of Scotland
Barclays
Danske
First Direct
HSBC UK
Halifax
Lloyds
NatWest
Nationwide
RBS
Santander
Ulster Bank NI

Requesting a Payment That Can Be Reversed

If you want to make a payment that has the potential to be reversed at a later time, you'll need to specify an extra property in the payment claims when generating an auth URL or creating an auth request. This is readRefundAccount and it needs to be set to true for it to take effect. When making a payment that has the ability to be reversed later, we add an extra bit of text to our consent screen to show to the payer that account information will be stored, but only for the purpose of making a reverse payment later.

When you complete the original payment, when you retrieve said payment, whether it is reversible will be shown with the property isReversible The API specification for payments has all the information on the shape of the payment: https://docs.moneyhubenterprise.com/reference#payments

You will need to have the payment ID to be able to make the reverse payment (if it is possible). That can be found in the payment object as laid out above, or in the mh:payment claim from the id_token when completing a payment auth request.

Requesting Reverse Payment

With the payment ID of the payment that can be reversed, you can request to make the reverse payment. The flow will be very similar to that of a typical payment. The only main difference is in how the authorisation for it is generated. The main difference is that a mh:reverse_payment claim is needed, with a value which has a property of paymentId inside. This value will be the ID of the payment you wish to reverse.

Secondly the call will require a scope of reverse_payment to differentiate it from a non reverse payment.

This will generate an auth URL which will be used to make a payment. As a reverse payment will take information from the payment it is reversing (reference, account to, account from and amount) the payment ID is the only piece of information you need to generate the auth URL.

If you wish to specify your own amount for the reverse payment, you can specify so when generating the auth URL. The amount can be any amount, not necessarily the original payment's amount. If one isn't specified, it will default to the full amount of the original payment.

You'll need to also specify what banking provider will enact the payment. As you might not generally know which banking provider will be able to make the reverse payment, it may be best to specify this value as id:api This is put in the scopes, like all auth URL generations.

After authorisation is complete, the id_token will return the payment ID of the reverse payment just made (if you specify mh:payment claims with essential set to true - see example below for claims made) and the ID of the payment just reversed in mh:reverse_payment

Examples

Moneyhub API Client (requires v4.9.0+)

First generate an auth URL for a payment that has readRefundAccount set to true:

const url = await moneyhub.getPaymentAuthorizeUrl({
  bankId: '1ffe704d39629a929c8e293880fb449a', // bank ID for payment to come from
  payeeId: 'b46287b4-f512-4dc8-bc49-63a1e9e4c0b1', // payee ID 
  amount: '1', // payment amount in minor units
  payerRef: 'Payer ref',
  state: 'foo',
  nonce: 'bar',
  readRefundAccount: true
})

This URL can then be used by the user to enact the payment, when the payment is completed be exchanging token:

const tokens = await moneyhub.exchangeCodeForTokens({
    localParams: {
        nonce: "your nonce value",
        state: "your state value",
    },
    paramsFromCallback: {
        code: "code param from callback",
        state: "state from callback",
        id_token: "id_token from callback",
    },
})

You'll be returned an id_token that has an mh:payment claim which is the payment ID which can then be used to generate an auth URL for a reverse payment:

const url = await moneyhub.getReversePaymentAuthorizeUrl({
  bankId: "api", // bank ID to enact reverse payment (put api to have bank chooser shown)
  paymentId: "1c677d3-4de4-47b6-8899-2847c056e70e", // the ID of the payment to return
  amount: 60 // if you want to only partially refund a payment, you can specify an amount (requires v4.13.0 of client library)
  state: "foo",
  nonce: "bar",
  payerId: "598f6a14-88e0-43c5-a098-20b64f3cf600", // if you wish to specify the account making the reverse payment, it can be specified here
  payerType: "api-payee" // if payer ID is specified, the type needs to be stated too (api-payee or mh-user-account)
})

This will then go through the normal payment flow for a user, send the URL to whoever can enact the refund.

Claims

If you're not using the client library, the above can be achieved by just using the claims. As per the single payments documentation, this takes the following shape for creating a payment that will read refund account information:

{
    "scope": "openid payment id:1ffe704d39629a929c8e293880fb449a",
    "claims": {
        "id_token": {
            "mh:con_id": {
                "essential": true
            },
            "mh:payment": {
                "essential": true,
                "value": {
                    "payeeId": "3305bc2c-8848-4fe0-a529-a7f7d35a5722",
                    "payeeType": "api-payee",
                    "amount": 150,
                    "payerRef": "reference to display on payer's statement",
                    "payerName": "John Smith",
                    "payerEmail": "[email protected]",
                    "context": "PartyToParty",
                    "readRefundAccount": "true"
                }
            }
        }
    }
}

And for reverse payment it would be:

{
    "scope": "openid reverse_payment id:api"
    "claims": {
        "id_token": {
            "mh:con_id": {
                "essential": true
            },
            "mh:payment": {
                "essential": true,
            },
            "mh:reverse_payment": {
                "essential": true,
                "paymentId": "1c677d3-4de4-47b6-8899-2847c056e70e",
                "amount": 60,
                "payerId": "598f6a14-88e0-43c5-a098-20b64f3cf600",
                "payerType": "api-payee"
            }
        }
    }
}

Auth Requests

If you are able to create your own auth requests, you can do the above using the Auth Requests

{
   scope: "openid payment id:1ffe704d39629a929c8e293880fb449a", // required
   redirectUri: "your redirect uri", // required until separate bank registration is complete 
   payment: {
        payeeId: "the payee id", // required for payments
        amount: 100, // required for payments
        payerRef: "payer ref", // required for payments
        readRefundAccount: true // will read refund account information
   } // required for payments
}
{
   scope: "openid reverse_payment id:api", // required
   redirectUri: "your redirect uri", // required until separate bank registration is complete 
   reversePayment: {
  		paymentId: "payment id to reverse" // required for reverse payments
   } // required for reverse payments
}