paypal in python

PayPal is one of the most popular payment solution for online business. In the PHP era, we can find lots of Paypal payment gateway, implemented by PHP, hosted on WordPress or Joomla. For example, my PayPal Digital Goods Express Checkout tools which was created several years ago. To be surprised, it still works properly and helps lots of people open their business online.

In my last post, Make Money within 4 Steps, I demonstracted how to use the latest PayPal REST API in the website. All these tools and toturials are all in PHP. In this article, I will use my Python skills to expose the possibility of Paypal in Python. Now let’s get it started.

Get Ready for Paypal in Python

If your backend services are implemented by Python. For example, you want to monitize your AI services and receive service fee by Paypal. Integrate PayPal in your Python backend will be a good idea. Leverage the convenience of PayPal REST API, monitizing Python service becomes possible. Here are tools and materials you need to get ready before start:

The PayPal App page and its credentials looks like below:

3 Steps to Get PayPal Payment in Python

There are 3 steps to finish a PayPal transaction in Python. Powered by PayPal latest REST api, the job becomes very easy. To make this post easy to follow, I will use the simplest case to instruct the steps.

  1. Get token to API calling
  2. Init PayPal Order
  3. Verify PayPal Order Complete Status

Sell Digital Product with PayPal

In this story, I had a website powered by Python, which is selling PDF tutorials. So my customers will see my article, click the buy button, finish the payment, then download the pdf via clicking the download link.

Get Token

Using python to get PayPal api token is very easy. It’s a simple HTTP POST request. Please see below example code:

def getToken(filePath, fileName, logger):
    API_ClientId = "Your client id from PayPal developer app"
    API_Secret = "Your app secret"

    url = "https://api-m.sandbox.paypal.com/v1/oauth2/token"
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }

    data = {
        "grant_type":"client_credentials"
    }

    try:
        auth = HTTPBasicAuth(API_ClientId, API_Secret)
        response = requests.post(url=url, data=data, headers=headers, auth=auth)
        response.raise_for_status()

        dataFolder = os.path.join(filePath, "tmp")
        if not os.path.exists(dataFolder):
            os.makedirs(dataFolder)

        htmlFile = os.path.join(dataFolder, fileName)
        f = open(htmlFile, 'w', encoding='utf-8')
        f.write(response.text)
        f.close()

        result = ujson.loads(response.text)
        print(result['access_token'])
    except Exception as e:
        traceback.print_exc()
        logger.error(e)

After running above code, the final result will be wroten into “token.json” file, which will look like:

Init Paypal Order

Once I have the token, I can go through the later PayPal API calling procedue with this token within the expiration duration. Now, I can init Paypal order. After initing the order successfully, I will redirect my customer to this order page, and let him or her to finish the payment. In PayPal document, “init order” step is called “create order”.

Init order needs more information like product name, price, tax, shipping info (which I don’t need in this scenario). The example source code is a little bit long, the majority is about order data structure.

def createOrder(filePath, fileName, logger):
    token = "A21AALDkZA4m909ER6O44wO_NFLv5JxUlu0P79UYpAIFEwOdsRlM5oooLEYM-2IuPCQ2hv7b9SYf39qYDrOBrLmfVvR4aUpng"
    url = "https://api-m.sandbox.paypal.com/v2/checkout/orders"
    headers = {
        'Content-Type': 'application/json',
        'Authorization': f"Bearer {token}"
    }

    randNo= random.randrange(10000,20000)
    itemFee = 1
    shippingFee = 0.11
    tax = 0.05 # 5% tax
    taxFee = tax * itemFee
    taxFee = math.floor(taxFee * 100)/100
    handlingFee = 0
    insuranceFee = 0
    discount = 0.01

    totalAmount = itemFee + shippingFee + taxFee + handlingFee + insuranceFee - discount
    totalAmount = math.floor(totalAmount * 100)/100

    data = {
        "intent": "CAPTURE",
        "purchase_units": [
            {
                "description":"PDF Version",
                "custom_id":"file_id_1", # digital product track id in your system
                "invoice_id":f"INV-EbookShop-{randNo}",
                "amount": {
                    "currency_code":"USD",
                    "value": totalAmount,  # total amount, sum of item fee, shipping fee, tax fee, handling fee, insurance fee, and shipping discount
                    "breakdown": {
                        "item_total": {
                            "currency_code":"USD",
                            "value":itemFee
                        },
                        "shipping": {
                            "currency_code":"USD",
                            "value":shippingFee
                        },
                        "tax_total": {
                            "currency_code":"USD",
                            "value":taxFee
                        },
                        "handling": {
                            "currency_code":"USD",
                            "value":handlingFee
                        },
                        "insurance": {
                            "currency_code":"USD",
                            "value":insuranceFee
                        },
                        "shipping_discount": {
                            "currency_code":"USD",
                            "value":discount
                        }
                    }
                },
                "items": [{
                    "name":"Secrets of Making Money",
                    "description":"The book in PDF version",
                    "sku":"pdf001",
                    "unit_amount": {
                        "currency_code":"USD",
                        "value":itemFee
                    },
                    "quantity":1,
                    "category":"DIGITAL_GOODS"
                }],
                # shipping info when SET_PROVIDED_ADDRESS is set below
                "shipping":{
                    "name":{
                        "full_name":"James Liu"
                    },
                    "address":{
                        "address_line_1":"5 TEMASEK BOULEVARD",
                        "address_line_2":"#09-01 SUNTEC TOWER FIVE",
                        "admin_area_1":"Singapore", # City
                        "admin_area_2":"Singapore", # State
                        "postal_code":"038985",
                        "country_code":"SG", # country code by PayPal defined
                    }
                }
            }
        ],
        "application_context": {
            "brand_namestring": "James Test Store",
            "cancel_url": "https://127.0.0.1/",
            "return_url": "https://127.0.0.1/captureOrder.php",
            "user_action": "PAY_NOW",
            # not necessary for digital goods, if set, have to provide shipping info in purchase_units
            "shipping_preference": "SET_PROVIDED_ADDRESS"
        }
    }
    
    
    
    # result = json_decode(result)
    # //header("Location: ".result->{'links'}[1]->{'href'})
    # print_r(result)

    try:
        response = requests.post(url=url, json=data, headers=headers)
        response.raise_for_status()

        dataFolder = os.path.join(filePath, "tmp")
        if not os.path.exists(dataFolder):
            os.makedirs(dataFolder)

        htmlFile = os.path.join(dataFolder, fileName)
        f = open(htmlFile, 'w', encoding='utf-8')
        f.write(response.text)
        f.close()

        result = ujson.loads(response.text)
        print(result['links'][1]['href'])
    except Exception as e:
        traceback.print_exc()
        logger.error(e)

For debugging easily, I also write the result in a file. Once initializing the order, PayPal will return 4 urls. The second one is for user to finish the payment, and the last one is for me to verify the payment if it is paid.

Then, I can redirect my customer to check out the payment. In above result, it’s the second url.

PayPal-Pay-Now

Verify PayPal Order

Once my customer finish the payment, he or she will be redirected back to my website, so that I can verify the Paypal order status and decide to show the download link. In this step, PayPal document call it as “capture order”. Let’s see the source code:

def captureOrder(filePath, fileName, logger):
    token = "A21AALDkZA4m909ER6O44wO_NFLv5JxUlu0P79UYpAIFEwOdsRlM5oooLEYM-2IuPCQ2hv7b9SYf39qYDrOBrLmfVvR4aUpng"
    # This is what paypal calls back with return url: https://127.0.0.1/captureOrder.php?token=5KA89135HV799060L&PayerID=ZB9VSG2VCXMN8
    orderId = "5KA89135HV799060L" # order id is the token in return url
    payerId = "ZB9VSG2VCXMN8"

    url = f"https://api.sandbox.paypal.com/v2/checkout/orders/{orderId}/capture"
    headers = {
        'Content-Type': 'application/json',
        'Authorization': f"Bearer {token}"
    }

    # order infomation
    randNo= random.randrange(10000,20000)
    itemFee = 1
    shippingFee = 0.11
    tax = 0.05 # 5% tax
    taxFee = tax * itemFee
    taxFee = math.floor(taxFee * 100)/100
    handlingFee = 0
    insuranceFee = 0
    discount = 0.01

    totalAmount = itemFee + shippingFee + taxFee + handlingFee + insuranceFee - discount
    totalAmount = math.floor(totalAmount * 100)/100

    data = {
        "intent": "CAPTURE",
        "purchase_units": [
            {
                "description":"PDF Version",
                "custom_id":"file_id_1", # digital product track id in your system
                "invoice_id":f"INV-EbookShop-{randNo}",
                "amount": {
                    "currency_code":"USD",
                    "value": totalAmount,  # total amount, sum of item fee, shipping fee, tax fee, handling fee, insurance fee, and shipping discount
                    "breakdown": {
                        "item_total": {
                            "currency_code":"USD",
                            "value":itemFee
                        },
                        "shipping": {
                            "currency_code":"USD",
                            "value":shippingFee
                        },
                        "tax_total": {
                            "currency_code":"USD",
                            "value":taxFee
                        },
                        "handling": {
                            "currency_code":"USD",
                            "value":handlingFee
                        },
                        "insurance": {
                            "currency_code":"USD",
                            "value":insuranceFee
                        },
                        "shipping_discount": {
                            "currency_code":"USD",
                            "value":discount
                        }
                    }
                },
                "items": [{
                    "name":"Secrets of Making Money",
                    "description":"The book in PDF version",
                    "sku":"pdf001",
                    "unit_amount": {
                        "currency_code":"USD",
                        "value":itemFee
                    },
                    "quantity":1,
                    "category":"DIGITAL_GOODS"
                }],
                # shipping info when SET_PROVIDED_ADDRESS is set below
                "shipping":{
                    "name":{
                        "full_name":"James Liu"
                    },
                    "address":{
                        "address_line_1":"5 TEMASEK BOULEVARD",
                        "address_line_2":"#09-01 SUNTEC TOWER FIVE",
                        "admin_area_1":"Singapore", # City
                        "admin_area_2":"Singapore", # State
                        "postal_code":"038985",
                        "country_code":"SG", # country code by PayPal defined
                    }
                }
            }
        ],
        "application_context": {
            "brand_namestring": "James Test Store",
            "cancel_url": "https://127.0.0.1/",
            "return_url": "https://127.0.0.1/captureOrder.php",
            "user_action": "PAY_NOW",
            # not necessary for digital goods, if set, have to provide shipping info in purchase_units
            "shipping_preference": "SET_PROVIDED_ADDRESS"
        }
    }

    try:
        response = requests.post(url=url, json=data, headers=headers)
        response.raise_for_status()

        dataFolder = os.path.join(filePath, "tmp")
        if not os.path.exists(dataFolder):
            os.makedirs(dataFolder)

        htmlFile = os.path.join(dataFolder, fileName)
        f = open(htmlFile, 'w', encoding='utf-8')
        f.write(response.text)
        f.close()

        result = ujson.loads(response.text)
        if result['status'] == 'COMPLETED' \
            and result['id'] == orderId \
            and result['payer']['payer_id'] == payerId:
            
            customerName = result["payment_source"]["paypal"]["email_address"]
            transactionID = result["purchase_units"][0]["payments"]["captures"][0]["id"]
            invoiceId = result["purchase_units"][0]["payments"]["captures"][0]["invoice_id"]
            createTime = result["purchase_units"][0]["payments"]["captures"][0]["create_time"] #UTC e.g. 2022-10-12T10:48:35Z
            grossAmt = result["purchase_units"][0]["payments"]["captures"][0]["seller_receivable_breakdown"]["gross_amount"]["value"]
            paypalFee = result["purchase_units"][0]["payments"]["captures"][0]["seller_receivable_breakdown"]["paypal_fee"]["value"]
            netAmt = result["purchase_units"][0]["payments"]["captures"][0]["seller_receivable_breakdown"]["net_amount"]["value"]
            # show download link
            custom_id = result["purchase_units"][0]["payments"]["captures"][0]["custom_id"]
            print("https://127.0.0.1/download?fileId=" + custom_id)
        else:
            raise Exception("Failed to confirm order. Details: " + result["details"][0]["description"])
    except Exception as e:
        traceback.print_exc()
        logger.error(e)

In the capture phrase, I will send the order information and order id to PayPal server by calling the API, PayPal will return the order status. If the payment is successful, it will also mark the status as “COMPLETED”. Addtionally, I can double check the order id, payer id, order transaction id, invoice id, gross fee, paypal processing fee, and net profit. With all the details, I will decide to show the final digital goods downloading url for my customer. At this time, I finish the whole digital goods sell, pay, and download processes.

Get Example Source Code at $9.99

If you’d like to get the whole runnable source code in this tutorial, simply clicking the buy button below. The download link will show up once you finish the payment. You will own the full copyright of the souce code and use it wherever you want.

The source code package includes 3 files:

  • token.py ( for step #1)
  • createOrder.py (for step #2)
  • captureOrder.py (for step #3)
Previous PostNext Post

Leave a Reply

Your email address will not be published. Required fields are marked *