NAV Navbar
  • Payment Exception Handling
  • Payment Exception Handling

    Sending and receiving payments with the Form3 Payments API is quick and simple, but sometimes a payment submission fails for one reason or another. If this happens, the API provides an error message to make it easy to identify the cause of the error.

    This tutorial looks at different errors that can occur during a payment submission and explains how they can be diagnosed. This tutorial covers the following topics:

    Prerequisites

    It is recommended that you complete the previous tutorials about sending and receiving a payment before reading this one.

    Before getting started, make sure you have the following things ready to follow along:

    The code example is written in Python using the requests package to access the Form3 API. Each code snippet is a standalone Python program, but make sure to paste the required data for each program at the top. The snippets are tested with Python 3.5, but most likely will also work with other Python versions.

    Invalid Payment Data

    If an outbound payment is created with invalid or incomplete data, it cannot be processed correctly.

    Incorrect Syntax

    Depending on the kind of error, the server will respond differently. In case the request is made with incorrect JSON syntax, the server will send a response with the status 400 Bad Request.

    If the syntax is correct, but a field contains invalid data (for example, the amount attribute contains letters), the creation of the resource will fail and an error message will be returned:

    {
        "error_code": "5fa8f56c-d0d1-422b-aba7-f8c9522d6388",
        "error_message": "Validation failed: Field data.attributes.amount : must match \"^[0-9.]{0,20}$\" "
    }
    

    Missing Attributes

    If the syntax is correct, the creation of a payment resource will be successful. You can then send the payment by creating a submission resource. Invalid data will cause the submission to fail, resulting in a "delivery_failed" status.

    To check the status of the submission, you can use the fetch call for the submission resource itself, you can use audits to see the entire history of the resource, or you can subscribe to the creation event of submission resources to receive a notification whenever a new submission resource is created.

    Using either of these methods, you can read the status attribute of the submission. If the submission has failed, the status will say: delivery_failed. In this case, the status_reason attribute provides further information about the error. If, for example, the amount attribute in the payment resource was missing, the status reason says "attributes.amount:may not be null. Value passed was null".

    The status reason will change for different error cases. In case several attributes are missing, it will list all missing fields.

    The Python example below sends an FPS payment with a missing amount attribute and prints the results.

    Create an invalid payment resource with a missing amount attribute

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    import json, math, requests, time, uuid
    from pprint import pprint
    
    ### Replace these variables with your own data! ###
    client_id = 'YOUR CLIENT ID HERE'
    client_secret = 'YOUR CLIENT SECRET HERE'
    organisation_id = 'YOUR ORGANISATION ID HERE'
    bank_id = 'YOUR BANK ID HERE'
    
    ### Generate IDs for all calls ###
    payment_id = uuid.uuid4()
    submission_id = uuid.uuid4()
    print("Payment ID: %s" % payment_id)
    print("Submission ID: %s" % submission_id)
    
    base_url = 'https://api.staging-form3.tech'
    
    ### Authenticate ###
    print("Getting bearer token...")
    auth_payload = "grant_type=client_credentials"
    auth_url = '%s/v1/oauth2/token' % base_url
    auth_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    auth_request = requests.auth.HTTPBasicAuth(client_id, client_secret)
    
    auth = requests.request('post', auth_url, data=auth_payload, auth=auth_request, headers=auth_headers)
    
    auth_token = auth.json().get('access_token')
    
    ### Creating Payment Resource with missing amount attribute ###
    print("Creating payment resource...")
    payment_url = "%s/v1/transaction/payments" % base_url
    payment_payload = """
    {
        "data": {
            "type": "payments",
            "id": "%s",
            "version": 0,
            "organisation_id": "%s",
            "attributes": {
                "beneficiary_party": {
                    "account_name": "Mrs Receiving Test",
                    "account_number": "71268996",
                    "account_number_code": "BBAN",
                    "account_with": {
                        "bank_id": "400302",
                        "bank_id_code": "GBDSC"
                    }
                },
                "currency": "GBP",
                "debtor_party": {
                    "account_name": "Mr Sending Test",
                    "account_number": "87654321",
                    "account_number_code": "BBAN",
                    "account_with": {
                        "bank_id": "%s",
                        "bank_id_code": "GBDSC"
                    }
                },
                "processing_date": "%s",
                "reference": "Something",
                "scheme_payment_sub_type": "TelephoneBanking",
                "scheme_payment_type": "ImmediatePayment"
            }
        }
    }
    """ % (payment_id, organisation_id, bank_id, time.strftime("%Y-%m-%d"))
    
    payment_headers = {
        'authorization': "bearer " + auth_token,
        'accept': "application/json",
        'content-type': "application/json",
        'cache-control': "no-cache",
        'postman-token': "3323f800-6191-f13b-4c4d-f62179caa978"
        }
    
    payment = requests.request("POST", payment_url, data=payment_payload, headers=payment_headers)
    parsed_payment = json.loads(payment.text)
    print(json.dumps(parsed_payment, indent=4, sort_keys=True))
    
    ### Creating Submission ###
    print("Creating submission resource...")
    submission_url = "%s/v1/transaction/payments/%s/submissions" % (base_url, payment_id)
    
    submission_payload = """
    {
        "data": {
            "id": "%s",
            "type": "paymentsubmissions",
            "organisation_id": "%s"
        }
    }
    """ % (submission_id, organisation_id)
    
    submission_headers = {
        'authorization': "bearer %s" % auth_token,
        'accept': "application/json",
        'content-type': "application/json",
        'cache-control': "no-cache",
        'postman-token': "f09ad285-4223-ef05-0d4c-bc1048546a82"
        }
    
    submission = requests.request("POST", submission_url, data=submission_payload, headers=submission_headers)
    
    parsed_submission = json.loads(payment.text)
    print(json.dumps(parsed_submission, indent=4, sort_keys=True))
    
    ### Give the server some time to make sure the answer is ready ###
    print("Waiting for 5 seconds...")
    time.sleep(5)
    
    ### Query submission resource ###
    print("Querying submission resource...")
    get_subm_url = "%s/v1/transaction/payments/%s/submissions/%s" % (base_url, payment_id, submission_id)
    
    get_subm_headers = {
        'authorization': "bearer %s" % auth_token,
        'cache-control': "no-cache",
        'postman-token': "ab4adba7-f0fb-4707-d1e1-437ea32d81a8"
        }
    
    get_subm = requests.request("GET", get_subm_url, headers=get_subm_headers)
    
    parsed_response = json.loads(get_subm.text)
    print(json.dumps(parsed_response, indent=4, sort_keys=True))
    


    The resulting response contains an explanation of the error in the status_reason attribute.

    Payment submission fails due to missing amount attribute in the payment resource.

    {
        "data": {
            "type": "payment_submissions",
            "id": "9d924d72-fbe7-44be-97e2-5f60a77f9b59",
            "version": 2,
            "organisation_id": "f2098716-8c51-4843-9498-61bf3bd5d0f5",
            "attributes": {
                "status": "delivery_failed",
                "status_reason": "attributes.amount:may not be null. Value passed was null",
                "submission_datetime": "2017-12-11T15:45:10.887Z"
            },
            "relationships": {
                "payment": {
                    "data": [
                        {
                            "type": "payments",
                            "id": "11c324d9-97dc-4420-a004-9cecaa2a5a8a"
                        }
                    ]
                }
            }
        }
    }
    

    Invalid Receiving Account Data

    Another problem can occur if all the attributes in the payment resource are correct, but the account number or the sort code for the recipient are invalid.

    Since the payment is formally correct, it is submitted without an error. In case the sort code is invalid, the transaction scheme will notice the invalid code and return with an error.

    Likewise, an invalid receiver bank account will be noticed by the receiving bank. In case a non-existing account number is specified, the receiving bank will reject the inbound payment and an error message is relayed back to the sender.

    In both cases, the status attribute of the submission resource will contain the status "delivery_failed". The status_reason attribute provides a more detailed error message that can vary depending on which scheme is used to process the payment.

    For example, an unknown account number or invalid sort code using Faster Payments will be indicated by the status_reason attribute by this text: "beneficiary-sort-code/account-number-unknown".

    A complete list of return codes for different payment schemes can be found here.

    Triggering a Rejected Payment

    The transaction simulator can be used to create an outbound payment that will be rejected. This is done by sending an FPS payment with the amount 216.16 in the test environment. Submitting this payment will result in a failed delivery with the status reason mentioned above.

    See here to learn more about how to send a payment.

    Send an FPS payment with amount 216.16 that triggers a delivery failure

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    import json, math, requests, time, uuid
    from pprint import pprint
    
    ### Replace these variables with your own data! ###
    client_id = 'YOUR CLIENT ID HERE'
    client_secret = 'YOUR CLIENT SECRET HERE'
    organisation_id = 'YOUR ORGANISATION ID HERE'
    bank_id = 'YOUR BANK ID HERE'
    
    ### Generate IDs for all calls ###
    payment_id = uuid.uuid4()
    submission_id = uuid.uuid4()
    print("Payment ID: %s" % payment_id)
    print("Submission ID: %s" % submission_id)
    
    base_url = 'https://api.staging-form3.tech'
    
    ### Authenticate ###
    print("Getting bearer token...")
    auth_payload = "grant_type=client_credentials"
    auth_url = '%s/v1/oauth2/token' % base_url
    auth_headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    auth_request = requests.auth.HTTPBasicAuth(client_id, client_secret)
    
    auth = requests.request('post', auth_url, data=auth_payload, auth=auth_request, headers=auth_headers)
    
    auth_token = auth.json().get('access_token')
    
    ### Creating Payment Resource with amount 216.16 ###
    print("Creating payment resource...")
    payment_url = "%s/v1/transaction/payments" % base_url
    payment_payload = """
    {
        "data": {
            "type": "payments",
            "id": "%s",
            "version": 0,
            "organisation_id": "%s",
            "attributes": {
                "amount": "216.16",
                "beneficiary_party": {
                    "account_name": "Mrs Receiving Test",
                    "account_number": "71268996",
                    "account_number_code": "BBAN",
                    "account_with": {
                        "bank_id": "400302",
                        "bank_id_code": "GBDSC"
                    }
                },
                "currency": "GBP",
                "debtor_party": {
                    "account_name": "Mr Sending Test",
                    "account_number": "87654321",
                    "account_number_code": "BBAN",
                    "account_with": {
                        "bank_id": "%s",
                        "bank_id_code": "GBDSC"
                    }
                },
                "processing_date": "%s",
                "reference": "Something",
                "scheme_payment_sub_type": "TelephoneBanking",
                "scheme_payment_type": "ImmediatePayment"
            }
        }
    }
    """ % (payment_id, organisation_id, bank_id, time.strftime("%Y-%m-%d"))
    
    payment_headers = {
        'authorization': "bearer " + auth_token,
        'accept': "application/json",
        'content-type': "application/json",
        'cache-control': "no-cache",
        'postman-token': "3323f800-6191-f13b-4c4d-f62179caa978"
        }
    
    payment = requests.request("POST", payment_url, data=payment_payload, headers=payment_headers)
    parsed_payment = json.loads(payment.text)
    print(json.dumps(parsed_payment, indent=4, sort_keys=True))
    
    ### Creating Submission ###
    print("Creating submission resource...")
    submission_url = "%s/v1/transaction/payments/%s/submissions" % (base_url, payment_id)
    
    submission_payload = """
    {
        "data": {
            "id": "%s",
            "type": "paymentsubmissions",
            "organisation_id": "%s"
        }
    }
    """ % (submission_id, organisation_id)
    
    submission_headers = {
        'authorization': "bearer %s" % auth_token,
        'accept': "application/json",
        'content-type': "application/json",
        'cache-control': "no-cache",
        'postman-token': "f09ad285-4223-ef05-0d4c-bc1048546a82"
        }
    
    submission = requests.request("POST", submission_url, data=submission_payload, headers=submission_headers)
    
    parsed_submission = json.loads(payment.text)
    print(json.dumps(parsed_submission, indent=4, sort_keys=True))
    
    ### Give the server some time to process ###
    print("Waiting for 5 seconds...")
    time.sleep(5)
    
    ### Query submission resource ###
    print("Querying submission resource...")
    get_subm_url = "%s/v1/transaction/payments/%s/submissions/%s" % (base_url, payment_id, submission_id)
    
    get_subm_headers = {
        'authorization': "bearer %s" % auth_token,
        'cache-control': "no-cache",
        'postman-token': "ab4adba7-f0fb-4707-d1e1-437ea32d81a8"
        }
    
    get_subm = requests.request("GET", get_subm_url, headers=get_subm_headers)
    
    parsed_response = json.loads(get_subm.text)
    print(json.dumps(parsed_response, indent=4, sort_keys=True))
    


    The resulting response contains an error message in the status_reason attribute. Note that schemes may also provide further information in the scheme_status_code attribute.

    Payment submission failed due to unknown sort code or account number

    {
        "data": {
            "type": "payment_submissions",
            "id": "190d8768-0775-4839-a6c5-73402029e5a3",
            "version": 4,
            "organisation_id": "f2098716-8c51-4843-9498-61bf3bd5d0f5",
            "attributes": {
                "status": "delivery_failed",
                "scheme_status_code": "1114",
                "status_reason": "beneficiary-sort-code/account-number-unknown",
                "submission_datetime": "2017-12-11T14:59:52.804Z",
                "settlement_date": "2017-12-11",
                "settlement_cycle": 1
            },
            "relationships": {
                "payment": {
                    "data": [
                        {
                            "type": "payments",
                            "id": "08ee2634-7c99-4735-91e2-4ec070db2677"
                        }
                    ]
                }
            }
        }
    }