Skip to content

Instantly share code, notes, and snippets.

@marnixk
Created July 18, 2024 10:15
Show Gist options
  • Save marnixk/9ecf0d388446c6e4f3671f525d381eb6 to your computer and use it in GitHub Desktop.
Save marnixk/9ecf0d388446c6e4f3671f525d381eb6 to your computer and use it in GitHub Desktop.
Pocketbase Stripe Javascript Functions
/*
____ _ _ _ _ _ _ _
/ ___|| |_ _ __(_)_ __ ___ | | | | |_(_) |___
\___ \| __| '__| | '_ \ / _ \ | | | | __| | / __|
___) | |_| | | | |_) | __/ | |_| | |_| | \__ \
|____/ \__|_| |_| .__/ \___| \___/ \__|_|_|___/
|_|
Purpose:
Contains a number of functions that help with Stripe interactions directly to their Web API.
*/
module.exports = {
/**
* Implementation of base 64 encoding.
*
* @param str {string} the string to encode
* @returns {string} the hex of encoded base64
*/
base64Encode(str) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
let encoded = '';
let i = 0;
while (i < str.length) {
let char1 = str.charCodeAt(i++);
let char2 = i < str.length ? str.charCodeAt(i++) : NaN;
let char3 = i < str.length ? str.charCodeAt(i++) : NaN;
let enc1 = char1 >> 2;
let enc2 = ((char1 & 3) << 4) | (char2 >> 4);
let enc3 = ((char2 & 15) << 2) | (char3 >> 6);
let enc4 = char3 & 63;
if (isNaN(char2)) {
enc3 = enc4 = 64;
} else if (isNaN(char3)) {
enc4 = 64;
}
encoded += chars.charAt(enc1) + chars.charAt(enc2) + chars.charAt(enc3) + chars.charAt(enc4);
}
return encoded;
},
/**
* Parse the stripe header
* @param header
* @returns {object}
*/
parseStripeHeader : function(header) {
const [tPart, vPart] = header.split(",");
return {
timestamp: tPart.substring(2),
signature: vPart.substring(3)
};
},
/**
* Is this a context with a stripe payload that adheres to the signature?
*
* @param context the request context
* @returns {{valid: boolean, rawBody: string}} validation result and request body
*/
validateStripePayload(context) {
// get secret
const {webhookSecret} = this.getStripeKeys();
// get the unmodified contents of the body.
const rawBody = readerToString(context.request().body);
// get the header
const verificationHeader = context.request().header.get("stripe-signature");
const {timestamp, signature} = this.parseStripeHeader(verificationHeader);
// calculate signature
const expectedSignature = $security.hs256(`${timestamp}.${rawBody}`, webhookSecret);
// make sure sent signature and calculated signature are equal.
return {valid: signature === expectedSignature, rawBody};
},
/**
* Create metadata parameters.
*
* @param metadata {object} is the object to convert to a list of metadata keys.
* @returns {string[]} a list of metadata items.
*/
createMetadataParameters(metadata) {
const entryToUrlComponent = ([key, value]) => `metadata[${encodeURIComponent(key)}]=${encodeURIComponent(value)}`;
const metadataItems = Object.entries(metadata).map(entryToUrlComponent);
return metadataItems;
},
/**
* Retrieve a payment link from stripe using a price identifier and metadata
* that is to associate the payment link with a particular user/context.
*
* @param priceKey {string} the price key
* @param metadata {object} metadata to add
* @param redirectTo {string} the URL to redirect to after the purchase is complete.
* @returns {any|null}
*/
getPaymentLink(priceKey, metadata, redirectTo) {
// get secret
const {stripeSecretKey} = this.getStripeKeys();
const metadataItems = this.createMetadataParameters(metadata);
const requestParameters = [
`line_items[0][price]=${encodeURIComponent(priceKey)}`,
`line_items[0][quantity]=1`,
`after_completion[type]=redirect`,
`after_completion[redirect][url]=${encodeURIComponent(redirectTo)}`,
...metadataItems
];
try {
const sessionResponse = $http.send({
url: `https://api.stripe.com/v1/payment_links?${requestParameters.join("&")}`,
method: "POST",
headers: {
"Authorization": `Basic ${this.base64Encode(stripeSecretKey + ":")}`
}
});
return sessionResponse.json;
}
catch (err) {
console.log(`[stripe_utils] couldn't retrieve session id: ${sessionId}, caused by:`, err);
return null;
}
},
/**
* Clean up the payment link after payment has completed/failed.
*
* @param linkId {string} the payment link identifier to clean up
*/
deactivatePaymentLink(linkId) {
console.log(`[stripe_utils] about to deactivate payment link: ${linkId}`);
const {stripeSecretKey} = this.getStripeKeys();
try {
const sessionResponse = $http.send({
url: `https://api.stripe.com/v1/payment_links/${linkId}?active=false`,
method: "POST",
headers: {
"Authorization": `Basic ${this.base64Encode(stripeSecretKey + ":")}`
}
});
console.log("[stripe_utils] Payment link successfully deactivated");
return sessionResponse.json;
}
catch (err) {
console.log(`[stripe_utils] couldn't deactive the payment link: ${linkId}, caused by:`, err);
return null;
}
},
/**
* Get the stripe session.
* @param sessionId
*/
getCheckoutSession(sessionId) {
// get secret
const {stripeSecretKey} = this.getStripeKeys();
try {
const sessionResponse = $http.send({
url: `https://api.stripe.com/v1/checkout/sessions/${sessionId}?expand[]=line_items`,
method: "GET",
headers: {
"Authorization": `Basic ${this.base64Encode(stripeSecretKey + ":")}`
}
});
return sessionResponse.json;
}
catch (err) {
console.log(`[stripe_utils] couldn't retrieve session id: ${sessionId}, caused by:`, err);
return null;
}
},
/**
* Retrieve stripe keys from the site configuration.
*
* @returns {StripeKeys}
*/
getStripeKeys() {
// NOTE: you'd probably want to replace this with whatever way you use to expose your keys.
const utils = require(`${__hooks}/utils.js`);
const site = utils.siteRecord();
const stripePublicKey = site.getString("stripe_public_key");
const stripeSecretKey = site.getString("stripe_secret_key");
const webhookSecret = site.getString("stripe_webhook_secret");
// return all stripe keys
return {
stripePublicKey,
stripeSecretKey,
webhookSecret
};
}
};
/**
* @typedef StripeKeys
*
* @propert {string} stripePublicKey - public stripe key
* @property {string} stripeSecretKey - the secret key
* @property {string} webhookSecret - the webhook secret.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment