Sep 16 2022
Aug 31 2022
Resource
BlogTopic
Edge DeliveryDate
Jun 05, 2019From time to time there are needs that require a quick solution to automate business processes. One recent example at StackPath was the need to automate the tracking of changes to PayPal’s Billing Agreements. When a customer cancels a billing agreement, we need to get notified so we can update our billing system and trigger other billing-related activities.
To achieve this, we set up an endpoint to accept PayPal’s IPN Pingbacks and leveraged our own serverless scripting product to get this done.
There were quite a few benefits to this approach:
There are various ways in which you can use serverless scripts as a secure endpoint. You can implement basic auth, require special header values, use passwords sent in via GET or POST, or use JWT (here’s an example).
In this post, we’ll explore a simple solution for using Serverless Scripts as an endpoint for accepting PayPal IPN Pingbacks and processing those requests to an internal backend system.
For demonstration purposes, the following example uses a simple password via GET to authenticate the request. You may also want to limit what IPs can hit the endpoint, and this example includes that logic as well.
addEventListener(>addEventListener(>addEventListener("fetch", event => {
//const response = handleRequest(event.request);
event.respondWith(handleRequest(event.request));
});
/**
* Inspect the request, parse the post values, and if txn_type == "mp_cancel", send an API request to an internal endpoint for processing.
* @param {Request} request
*/
async function handleRequest(request) {
try {
const url = new URL(request.url);
const password = url.searchParams.get('password'); //this captures the password sent in via a url parameter
const paypalData = await request.formData();
const parsed = new URL('http://localhost/?'+paypalData); //an easy way to turn post data into url parameters
const parsedType = parsed.searchParams.get('txn_type');
//Don't open this to the world. Get the client IP making the request and check against it
var clientIp = request.headers.get('x-sp-client-ip');
//This is how PayPal will authenticate to this endpoint. If the wrong password is sent in, the EdgeEngine eplies with a 403
//change to whatever IP you want or ip ranges or whatever you like.
if (password != 'SECRETPASSWORD_PLEASE_CHANGE_THIS' || clientIp != '127.0.0.1') {
return new Response('{"success": false, "error": "Bad password or bad IP address."}',{
headers: {
'Content-Type': 'application/json'
},
status: 403
});
}
//if this is a Paypal Billing Agreement Cancellation, the transaction type is "mp_cancel"
if (parsedType == "mp_cancel") {
// we have a cancelled paypal billing agreement!
const billingAgreement = parsed.searchParams.get('mp_id');
var myHeaders = new Headers();
myHeaders.append('API-Token','MYSECRETPASSWORD_TO_AN_INTERNAL_ENDPOINT');
var myInit = {
method: 'GET',
headers: myHeaders,
cache: 'default'
};
const runWorkflow = new Request('https://internal_company_server/api/endpoint?billingAgreement'+ billingAgreement, myInit);
const response1 = await fetch(runWorkflow);
const responsebody = await response1.json();
const workflow_id = responsebody.name;
// catch any errors here from the API request above and send notifications to your team. This will be custom to your needs, but can be configured to communicate with any API just lie the request above.
// ...
// ...
//if all is well, send the success response.
return new Response(JSON.stringify(responsebody),{
headers: {
'Content-Type': 'application/json'
},
status: 200
})
} else {
//NOTHING TO DO
return new Response('{"success": true, "error": "nothing to do for this transaction type."}',{
headers: {
'Content-Type': 'application/json'
},
status: 200
})
}
} catch (e) {
return new Response(e.stack || e, { status: 500 });
}
}
When you’re done writing the script, pick a route for it, update PayPal with the URL to your Serverless script, and watch the pingbacks roll in.