- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi all,
I am integrating PayPal into a Next JS front-end, hosted on AWS Amplify using Lambda and API Gateway to communicate with PayPal API.
For now this is Sandbox to test integration.
No matter what I try, I keep getting 400 Invalid Request (Which is an else statement in my Lambda function). I am sure I have followed PayPal documentation as closely as possible.
In terms of API gateway config I have ensured CORS is setup correctly and the request is definitely hitting the Lambda but failing for some reason I can not figure out.
My Front-end code is like so:
// src/utils/paypalUtils.js
const API_ENDPOINT = 'https://1uwgpcbn4f.execute-api.eu-west-1.amazonaws.com';
export const createOrder = async (course) => {
try {
console.log('Creating order for course:', JSON.stringify(course, null, 2));
const response = await fetch(`${API_ENDPOINT}/create-paypal-order`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
cart: [
{
id: course.slug,
name: course.title,
price: course.price,
quantity: "1",
deliveryOption: course.deliveryOption
}
]
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`Failed to create order: ${response.status} ${response.statusText}. ${JSON.stringify(errorData)}`);
}
const orderData = await response.json();
if (!orderData.id) {
throw new Error(`Order ID is missing from the response: ${JSON.stringify(orderData)}`);
}
console.log('Order created successfully:', orderData.id);
return orderData.id;
} catch (error) {
console.error('Error creating order:', error);
throw error;
}
};
export const onApprove = async (data, actions) => {
try {
const response = await fetch(`${API_ENDPOINT}/capture-paypal-order?orderID=${data.orderID}`, {
method: 'POST',
});
if (!response.ok) {
throw new Error('Failed to capture order');
}
const captureData = await response.json();
const captureStatus = captureData.status;
if (captureStatus === "COMPLETED") {
console.log('Payment completed successfully', captureData);
return { success: true, data: captureData };
} else {
throw new Error(`Payment not completed. Status: ${captureStatus}`);
}
} catch (error) {
console.error('Error capturing order:', error);
throw error;
}
};
My Lambda function is like so:
import https from 'https';
import { v4 as uuidv4 } from 'uuid'; // Import the uuid function
const PAYPAL_API_BASE = 'api-m.sandbox.paypal.com'; // Use 'api-m.paypal.com' for production
const PAYPAL_CLIENT_ID = process.env.PAYPAL_CLIENT_ID;
const PAYPAL_CLIENT_SECRET = process.env.PAYPAL_CLIENT_SECRET;
const headers = {
"Access-Control-Allow-Origin": "*", // Adjust this to your frontend URL for production
"Access-Control-Allow-Headers": "Content-Type",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET"
};
async function getAccessToken() {
const auth = Buffer.from(`${PAYPAL_CLIENT_ID}:${PAYPAL_CLIENT_SECRET}`).toString('base64');
const options = {
hostname: PAYPAL_API_BASE,
path: '/v1/oauth2/token',
method: 'POST',
headers: {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/x-www-form-urlencoded'
}
};
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
const jsonData = JSON.parse(data);
resolve(jsonData.access_token);
});
});
req.on('error', reject);
req.write('grant_type=client_credentials');
req.end();
});
}
async function createPayPalOrder(accessToken, orderData) {
const options = {
hostname: PAYPAL_API_BASE,
path: '/v2/checkout/orders',
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
'PayPal-Request-Id': uuidv4() // Generate a unique ID for idempotency
}
};
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(JSON.parse(data));
} else {
reject(new Error(`Failed to create order: ${res.statusCode} ${data}`));
}
});
});
req.on('error', reject);
req.write(JSON.stringify(orderData));
req.end();
});
}
async function capturePayPalOrder(accessToken, orderId) {
const options = {
hostname: PAYPAL_API_BASE,
path: `/v2/checkout/orders/${orderId}/capture`,
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
};
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(JSON.parse(data));
} else {
reject(new Error(`Failed to capture order: ${res.statusCode} ${data}`));
}
});
});
req.on('error', reject);
req.end();
});
}
export const handler = async (event) => {
console.log('Received event:', JSON.stringify(event, null, 2));
const { httpMethod, path, body, queryStringParameters } = event;
try {
const accessToken = await getAccessToken();
if (httpMethod === 'POST' && path === '/create-paypal-order') {
const cartData = JSON.parse(body);
console.log('Parsed cart data:', cartData);
// Construct the order data
const orderData = {
intent: 'CAPTURE',
purchase_units: cartData.cart.map(item => ({
amount: {
currency_code: 'GBP',
value: item.price,
breakdown: {
item_total: {
currency_code: 'GBP',
value: item.price
}
}
},
items: [
{
name: item.name,
description: `${item.name} - ${item.deliveryOption} delivery`,
sku: item.id,
unit_amount: {
currency_code: 'GBP',
value: item.price
},
quantity: item.quantity
}
]
}))
};
console.log('PayPal order data:', orderData);
const result = await createPayPalOrder(accessToken, orderData);
console.log('PayPal API response:', result);
return {
statusCode: 200,
headers: headers,
body: JSON.stringify({ id: result.id }),
};
} else if (httpMethod === 'POST' && path === '/capture-paypal-order') {
const orderId = queryStringParameters.orderID;
const result = await capturePayPalOrder(accessToken, orderId);
return {
statusCode: 200,
headers: headers,
body: JSON.stringify(result),
};
} else {
return {
statusCode: 400,
headers: headers,
body: JSON.stringify({ message: 'Invalid request' }),
};
}
} catch (error) {
console.error('Error:', error);
return {
statusCode: 500,
headers: headers,
body: JSON.stringify({ error: 'Internal server error', details: error.message })
};
}
};
A cloudwatch log of said event:
2024-10-01T21:59:28.272Z 26115af9-00e1-4d66-a278-52a3e903684a INFO Received event: {
"version": "2.0",
"routeKey": "POST /create-paypal-order",
"rawPath": "/create-paypal-order",
"rawQueryString": "",
"headers": {
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
"content-length": "136",
"content-type": "application/json",
"host": "1uwgpcbn4f.execute-api.eu-west-1.amazonaws.com",
"origin": "https://main.d2ige300djz6d5.amplifyapp.com",
"priority": "u=1, i",
"referer": "https://main.d2ige300djz6d5.amplifyapp.com/",
"sec-ch-ua": "\"Google Chrome\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "cross-site",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
"x-amzn-trace-id": "Root=1-66fc70bf-04fc2b5c0ea975cd522342ed",
"x-forwarded-for": "154.60.94.168",
"x-forwarded-port": "443",
"x-forwarded-proto": "https"
},
"requestContext": {
"accountId": "682033468988",
"apiId": "1uwgpcbn4f",
"domainName": "1uwgpcbn4f.execute-api.eu-west-1.amazonaws.com",
"domainPrefix": "1uwgpcbn4f",
"http": {
"method": "POST",
"path": "/create-paypal-order",
"protocol": "HTTP/1.1",
"sourceIp": "154.60.94.168",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
},
"requestId": "e_aOCj50DoEEJwA=",
"routeKey": "POST /create-paypal-order",
"stage": "$default",
"time": "01/Oc[Removed. Phone #s not permitted]+0000",
"timeEpoch": 1727819967904
},
"body": "{\"cart\":[{\"id\":\"ecg-course-for-beginners\",\"name\":\"ECG Course for Beginners\",\"price\":\"299.99\",\"quantity\":\"1\",\"deliveryOption\":\"remote\"}]}",
"isBase64Encoded": false
}
I have a package.json for the uuid component setup so there is no issue there, and I have setup environment variables within my Lambda that have the paypal client and secret.
Any insights would be appreciated.
Solved! Go to Solution.
Accepted Solutions
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
OK I have found a solution to this - I could delete post, but figured someone else may find it useful if coming across - the issue was that when using Lambda integration vs Lambda proxy integration (I was using direct lambda integration, not proxy) - the httpMethod and path needed to be accessed using the `event.` handle i.e.
As the failure point was the `if` statement for httpmethod and path, I used a combination of cloudwatch logs to observe the `event` object being sent through to verify the structure of it.
The way to access the lambda method and path is like below:
event.requestContext.http.method
event.requestContext.http.path
Once I changed this, the if was accessed properly and I was able to successfully proceed to entering email/password within the PayPal window as the order was successfully created.
Please mark this as resolved.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
OK I have found a solution to this - I could delete post, but figured someone else may find it useful if coming across - the issue was that when using Lambda integration vs Lambda proxy integration (I was using direct lambda integration, not proxy) - the httpMethod and path needed to be accessed using the `event.` handle i.e.
As the failure point was the `if` statement for httpmethod and path, I used a combination of cloudwatch logs to observe the `event` object being sent through to verify the structure of it.
The way to access the lambda method and path is like below:
event.requestContext.http.method
event.requestContext.http.path
Once I changed this, the if was accessed properly and I was able to successfully proceed to entering email/password within the PayPal window as the order was successfully created.
Please mark this as resolved.
Haven't Found your Answer?
It happens. Hit the "Login to Ask the community" button to create a question for the PayPal community.
- Failed to process the payment. Please try again or contact the shop admin. in REST APIs
- Getting "Invalid Transaction Type" Error When Calling Payflow Gateway API in Payflow
- Error creating an order for cupture - Issues with Authorization header in Braintree Server-side Integration (PHP, Java, .NET, Ruby, Python, NodeJS SDKs)
- Access Token Working to Generate Client Token but not Orders in REST APIs