# How to calculate a PayFast signature using Python

# Introduction

To initiate a payment through PayFast using their custom integration (opens new window), you need to calculate a security signature (opens new window) that gets calculated from the data in your checkout form.

# Issues in the provided example code

The provided Python code in the PayFast documentation is incorrect at the time of writing and will return the incorrect signature.

The three issues in their provided code are:

  1. Plus signs (+s) are replaced by spaces, instead of spaces which should be replaced by plus signs. This can be fixed by removing the .replace("+", " ") call. The urllib.parse.quote_plus will automatically replace spaces with pluses.
  2. Blank variables aren't automatically removed.
  3. The variables aren't automatically ordered in the correct order. The documentation is a bit vague on the order, but it should be ordered in the same order in which the parameters are listed in the documentation under "Create your checkout form" (opens new window).

# Solution code

Below you can find a Python script that worked for me to generate a Payfast security signature.

import logging
import urllib
from hashlib import md5


logger = logging.getLogger(__name__)

CHECKOUT_SIGNATURE_FIELD_ORDER = [
    # Merchant Details
    "merchant_id",
    "merchant_key",
    "return_url",
    "cancel_url",
    "notify_url",
    # Buyer Detail
    "name_first",
    "name_last",
    "email_address",
    "cell_number",
    # Transaction Details
    "m_payment_id",
    "amount",
    "item_name",
    "item_description",
    "custom_int1",
    "custom_int2",
    "custom_int3",
    "custom_int4",
    "custom_int5",
    "custom_str1",
    "custom_str2",
    "custom_str3",
    "custom_str4",
    "custom_str5",
    # Transaction Options
    "email_confirmation",
    "confirmation_address",
    # Set Payment Method
    "payment_method",
    # Recurring Billing Details
    "subscription_type",
    "billing_date",
    "recurring_amount",
    "frequency",
    "cycles",
]


def sort_by_priority_list(values, priority):
    """
    Sorts an iterable according to a list of priority items.
    Usage:
    >>> sort_by_priority_list(values=[1,2,2,3], priority=[2,3,1])
    [2, 2, 3, 1]
    >>> sort_by_priority_list(values=set([1,2,3]), priority=[2,3])
    [2, 3, 1]
    """
    priority_dict = {k: i for i, k in enumerate(priority)}

    def priority_getter(value):
        return priority_dict.get(value, len(values))

    return sorted(values, key=priority_getter)


def calculate_signature(input_data, passphrase):
    """
    Calculate a Payfast signature according to the documentation:
    https://developers.payfast.co.za/docs#step_2_signature
    """
    output_data = {}
    for item in input_data.items():
        # Filter out blank values and remove surrounding spaces
        if item[1].strip():
            output_data[item[0]] = item[1].strip()

    # Sort the keys in the required order
    keys = sort_by_priority_list(output_data.keys(), CHECKOUT_SIGNATURE_FIELD_ORDER)

    # Prepare the parameter string
    payfast_string = ""
    for key in keys:
        if key != "signature":
            payfast_string += (
                key + "=" + urllib.parse.quote_plus(output_data[key]) + "&"
            )

    # Append passphrase
    payfast_string = payfast_string[:-1]
    payfast_string += f"&passphrase={passphrase}"

    # Hash the concatenated string
    return md5(payfast_string.encode()).hexdigest()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

Newsletter

If you'd like to subscribe to my blog, please enter your details below. You can unsubscribe at any time.

Powered by Buttondown.

Last Updated: 11/20/2023, 10:04:51 AM