Skip to main content

Command Palette

Search for a command to run...

Combining Sandbox Payment Processor with AWS

Updated
•4 min read•View as Markdown
Combining Sandbox Payment Processor with AWS
D
Learning to Code whilst having several projects under my belt.

As I now had a working prototype based in VS Code Python Script, I was eager to apply my AWS modest learnings with this problem.

Step 1.

I added my API Key and ID into AWS Secrets Manager under other type of secret.

Step 2.

With the support of AI for speed, I created a very modest index.html which consisted of a form with 2 questions

  1. Item Name

  2. Item Price

(The Final Code is at the bottom)

The script used a Javascript Script to fire the submission

<script>
        document.getElementById("paymentForm").addEventListener("submit", async function(event) {
            event.preventDefault(); 

            // Make sure this is your actual API Gateway URL ending in /pay
            const apiUrl = "https://vyle17q52f.execute-api.eu-west-2.amazonaws.com/pay"; 
            
            const formData = new URLSearchParams(new FormData(this));

            try {
                const response = await fetch(apiUrl, {
                    method: "POST",
                    body: formData
                });

                const result = await response.json();
                
                // If an action URL exists, redirect the browser to complete 3DS
                if (result.action_url) {
                    window.location.href = result.action_url;
                } else if (result.cashflows_status === 200 || result.cashflows_status === 201) {
                    alert("Success! Payment approved.");
                } else if (result.response && result.response.errorReport) {
                    alert("Payment Failed:\n" + result.response.errorReport.errors[0].message);
                } else {
                    alert("Payment Error:\n" + JSON.stringify(result));
                }
                
            } catch (error) {
                alert("Network error: Could not reach the server.");
            }
        });
    </script>

Step 3.

Using AWS Amplify, I linked by Git account and created a live site of my code. Using VS Code I will update that as we go through.

Step 4.

Using AWS IAM, I added a policy called 'CashflowsSecretReadAccess' which gave the script access to the Secrets Manager and created a role for;

AWSLambdaBasicExecutionRole - For Logging purposes

Step 5.

Using LAMBDA I added a Python script that ran when the form was submitted

import json
import hashlib
import urllib.request
import urllib.parse
import base64
import boto3
from botocore.exceptions import ClientError

def get_secrets():
    client = boto3.client('secretsmanager', region_name="eu-west-2")
    try:
        response = client.get_secret_value(SecretId="cashflows/credentials")
        return json.loads(response['SecretString'])
    except ClientError as e:
        print(f"Error fetching secret: {e}")
        return None

def lambda_handler(event, context):
    # 1. Fetch credentials from AWS Secrets Manager
    secrets = get_secrets()
    if not secrets:
        return {
            "statusCode": 500, 
            "headers": {"Access-Control-Allow-Origin": "*", "Content-Type": "application/json"},
            "body": json.dumps({"message": "Failed to load credentials"})
        }
    
    api_key = secrets.get('API_KEY')
    config_id = secrets.get('CONFIG_ID')
    endpoint = "https://gateway-int.cashflows.com/api/gateway/payment-jobs"

    # 2. Extract data from incoming HTML form submission
    try:
        raw_body = event.get('body', '')
        if event.get('isBase64Encoded', False):
            raw_body = base64.b64decode(raw_body).decode('utf-8')
        
        parsed_body = urllib.parse.parse_qs(raw_body)
        amount = parsed_body.get('amountToCollect', ['10.00'])[0]
        order_number = parsed_body.get('orderNumber', ['Test-Order-01'])[0]
    except Exception as e:
        print(f"Extraction error: {e}")
        amount = "10.00"
        order_number = "Test-Order-01"

    # 3. Build Cashflows Payload using test card & 3DS bypass name
    payload_dict = {
        "type": "Payment",
        "paymentMethodsToUse": ["creditcard"],
        "amountToCollect": str(amount),
        "currency": "GBP",
        "locale": "en_GB",
        "parameters": {
            "cardNumber": "4510400400099005",
            "cardHolderName": "Luke Skywalker",  # Triggers automatic 3DS success in test environment
            "cardCvc": "123",
            "cardExpiryMonth": "12",
            "cardExpiryYear": "28"
        },
        "order": {
            "orderNumber": str(order_number)
        }
    }

    # 4. Generate SHA-512 Hash
    raw_json_body = json.dumps(payload_dict, separators=(',', ':'))
    hash_input = api_key + raw_json_body
    request_hash = hashlib.sha512(hash_input.encode('utf-8')).hexdigest().upper()

    headers = {
        "ConfigurationId": config_id,
        "Hash": request_hash,
        "Content-Type": "application/json"
    }

    # 5. Send request to Cashflows
    req = urllib.request.Request(endpoint, data=raw_json_body.encode('utf-8'), headers=headers, method='POST')
    
    try:
        with urllib.request.urlopen(req) as response:
            response_data = json.loads(response.read().decode('utf-8'))
            status_code = response.getcode()
    except urllib.error.HTTPError as e:
        response_data = json.loads(e.read().decode('utf-8'))
        status_code = e.code

    # 6. Extract action URL if Cashflows returns a 3DS redirect link
    action_url = None
    if isinstance(response_data, dict):
        action_url = response_data.get("links", {}).get("action", {}).get("url")

    return {
        "statusCode": 200,
        "headers": {
            "Access-Control-Allow-Origin": "https://main.dna7h844op3tm.amplifyapp.com",
            "Content-Type": "application/json"
        },
        "body": json.dumps({
            "cashflows_status": status_code,
            "action_url": action_url,
            "response": response_data
        })
    }

Some important steps in this code:

  1. Luke Skywalker is a success message for testing purposes.

  2. The Lambda will run for 15 seconds but it will fall back on fake data if it doesn't process it in time.

Step 6.

Using AWS API Gateway, I create a POST gateway and linked it to my LAMBDA code with a deployed stage URL ending with /pay.

The gateway has a burst limit of 10 and a rate limit of 5 per second to help prevent bad actors.

Cross-Origin Resource Sharing (CORS) is also directed to the Amplify URL to prevent the form being used from a different source.

Step 7.

Once uploaded, this now produces a working model.

Next Steps

This is just the start of this, as it is a very modest E-Commerce engine. We could;

  1. Add Products

  2. Add Card Holder Details

  3. Add CAPTCHA

  4. Success and Failure redirects.

Challenges

Working with AI, I managed to get the code working in some form very early on, but getting working Statuses was a challenge. Looking over the Specs, I found out I needed Luke Skywalker involved and to change my action_url link to one given by the Payment Software.

9 views