Created
July 18, 2024 10:15
-
-
Save marnixk/9ecf0d388446c6e4f3671f525d381eb6 to your computer and use it in GitHub Desktop.
Pocketbase Stripe Javascript Functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
____ _ _ _ _ _ _ _ | |
/ ___|| |_ _ __(_)_ __ ___ | | | | |_(_) |___ | |
\___ \| __| '__| | '_ \ / _ \ | | | | __| | / __| | |
___) | |_| | | | |_) | __/ | |_| | |_| | \__ \ | |
|____/ \__|_| |_| .__/ \___| \___/ \__|_|_|___/ | |
|_| | |
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