# Combining Sandbox Payment Processor with AWS

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.

![](https://cdn.hashnode.com/uploads/covers/6ac4f7f48571823aa9acce4d/e28b64db-b134-42fa-8638-1b7164b3eab8.png align="center")

**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)

![](https://cdn.hashnode.com/uploads/covers/6ac4f7f48571823aa9acce4d/70301055-70df-4e32-9d12-aa678ecd32a0.png align="center")

The script used a Javascript Script to fire the submission

```plaintext
<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

```plaintext
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.

![](https://cdn.hashnode.com/uploads/covers/6ac4f7f48571823aa9acce4d/d333f238-26b2-412d-902e-ac9cc8aa14c3.png align="center")

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.

![](https://cdn.hashnode.com/uploads/covers/6ac4f7f48571823aa9acce4d/16993291-48db-46a4-9ca7-dbe11586ae1a.png align="center")
