-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat/add checkout for lemonsqueezy (#4)
* feat(sails-lemonsqueezy): add checkout method * feat(sails-pay): add basic setup for hook * chore(playground): change node engine * chore(sails-pay): add lemonsqueezy
- Loading branch information
1 parent
fb0e576
commit ae1e14c
Showing
17 changed files
with
377 additions
and
946 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
node_modules | ||
package |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,4 @@ | ||
{ | ||
"name": "sails-pay", | ||
"version": "0.0.1", | ||
"private": true, | ||
"keywords": [ | ||
"Lemon Squeezy", | ||
|
This file was deleted.
Oops, something went wrong.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
const methods = require('./machines') | ||
module.exports = { | ||
identity: 'sails-lemonsqueezy', | ||
config: {}, | ||
checkout: methods.checkout | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
const { fetch: undiciFetch } = require('undici') | ||
|
||
const baseUrl = 'https://api.lemonsqueezy.com' | ||
const defaultHeaders = { | ||
Accept: 'application/vnd.api+json', | ||
'Content-Type': 'application/vnd.api+json' | ||
} | ||
|
||
const fetchImpl = | ||
typeof global.fetch !== 'undefined' ? global.fetch : undiciFetch | ||
|
||
const fetch = async (path, options = {}) => { | ||
const url = new URL(`/v1${path}`, baseUrl).toString() | ||
const mergedOptions = { | ||
...options, | ||
headers: { | ||
...defaultHeaders, | ||
...(options.headers || {}) | ||
} | ||
} | ||
|
||
try { | ||
const response = await fetchImpl(url, mergedOptions) | ||
|
||
const jsonResponse = await response.json() | ||
|
||
return jsonResponse | ||
} catch (error) { | ||
console.error('Error occurred during fetch:', error) | ||
throw error | ||
} | ||
} | ||
|
||
module.exports = fetch |
21 changes: 21 additions & 0 deletions
21
packages/sails-lemonsqueezy/helpers/generate-json-api-payload.js
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/** | ||
* Generates a JSON:API payload for making requests. | ||
* @param {string} type - The type of the resource. | ||
* @param {Object} data - The attributes of the resource. | ||
* @param {Object} [relationships={}] - The relationships of the resource. | ||
* @returns {string} - A JSON string representing the JSON:API request body. | ||
*/ | ||
module.exports = function generateJsonApiPayload( | ||
type, | ||
data, | ||
relationships = {} | ||
) { | ||
const payload = { | ||
data: { | ||
type: type, | ||
attributes: data, | ||
relationships: relationships | ||
} | ||
} | ||
return JSON.stringify(payload) | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
/** | ||
* Common input definitions (i.e. parameter definitions) that are shared by multiple files. | ||
* | ||
* @type {Dictionary} | ||
* @constant | ||
*/ | ||
|
||
module.exports = { | ||
LEMON_SQUEEZY_API_KEY: { | ||
type: 'string', | ||
friendlyName: 'API Key', | ||
description: 'A valid Lemon Squeezy API Key', | ||
protect: true, | ||
whereToGet: { | ||
url: 'https://app.lemonsqueezy.com/settings/api', | ||
description: 'Generate an API key in your Lemon Squeezy dashboard.', | ||
extendedDescription: | ||
'To generate an API key, you will first need to log in to your Lemon Squeezy account, or sign up for one if you have not already done so.' | ||
} | ||
}, | ||
LEMON_SQUEEZY_STORE_ID: { | ||
type: 'string', | ||
friendlyName: 'Store ID', | ||
description: 'A valid Lemon Squeezy store ID', | ||
whereToGet: { | ||
url: 'https://app.lemonsqueezy.com/settings/stores', | ||
description: 'The ID is the number next to the store name.', | ||
extendedDescription: | ||
'To find your Lemon Squeezy Store ID, visit your Stores page in the Lemon Squeezy dashboard.' | ||
} | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
const fetch = require('../helpers/fetch') | ||
const generateJsonApiPayload = require('../helpers/generate-json-api-payload') | ||
module.exports = require('machine').build({ | ||
friendlyName: 'Checkout', | ||
description: | ||
'Creates and return a unique checkout URL for a specific variant.', | ||
moreInfoUrl: 'https://docs.lemonsqueezy.com/api/checkouts', | ||
inputs: { | ||
apiKey: require('../helpers/parameters').LEMON_SQUEEZY_API_KEY, | ||
store: require('../helpers/parameters').LEMON_SQUEEZY_STORE_ID, | ||
variant: { | ||
type: 'string', | ||
description: 'The ID of the variant associated with this checkout.' | ||
}, | ||
customPrice: { | ||
type: 'number', | ||
description: | ||
'Represents a positive integer in cents representing the custom price of the variant.' | ||
}, | ||
productOptions: { | ||
type: 'ref', | ||
description: | ||
'An object containing any overridden product options for this checkout. ', | ||
example: { | ||
name: '', | ||
description: '', | ||
media: [], | ||
redirect_url: '', | ||
receipt_button_text: '', | ||
receipt_link_url: '', | ||
receipt_thank_you_note: '', | ||
enabled_variants: [1] | ||
} | ||
}, | ||
checkoutOptions: { | ||
type: 'ref', | ||
description: 'An object containing checkout options for this checkout.', | ||
example: { | ||
embed: false, | ||
media: true, | ||
logo: true, | ||
desc: true, | ||
discount: true, | ||
dark: false, | ||
subscription_preview: true, | ||
button_color: '#2DD272' | ||
} | ||
}, | ||
checkoutData: { | ||
type: 'ref', | ||
description: | ||
'An object containing any prefill or custom data to be used in the checkout.', | ||
example: { | ||
email: '', | ||
name: '', | ||
billing_address: [], | ||
tax_number: '', | ||
discount_code: '', | ||
custom: [], | ||
variant_quantities: [] | ||
} | ||
}, | ||
preview: { | ||
type: 'boolean', | ||
description: | ||
'A boolean indicating whether to return a preview of the checkout. If true, the checkout will include a preview object with the checkout preview data.', | ||
example: true | ||
}, | ||
testMode: { | ||
type: 'boolean', | ||
description: | ||
'A boolean indicating whether the checkout should be created in test mode.', | ||
example: false | ||
}, | ||
expiresAt: { | ||
type: 'string', | ||
description: | ||
'An ISO 8601 formatted date-time string indicating when the checkout expires. Can be null if the checkout is perpetual.', | ||
example: '2022-10-30T15:20:06.000000Z' | ||
} | ||
}, | ||
exits: { | ||
success: { | ||
description: 'The checkout url.', | ||
outputVariableName: 'checkoutUrl', | ||
outputType: 'string' | ||
}, | ||
couldNotCreateCheckoutUrl: { | ||
description: 'Checkout URL could not be created.', | ||
extendedDescription: | ||
'This indicates that an error was encountered during checkout url creation.', | ||
outputFriendlyName: 'Create checkout URL error report.', | ||
outputVariableName: 'errors', | ||
outputType: [ | ||
{ | ||
detail: | ||
'The POST method is not supported for route checkouts. Supported methods: GET, HEAD.', | ||
status: '405', | ||
title: 'Method Not Allowed' | ||
} | ||
] | ||
} | ||
}, | ||
|
||
fn: async function ( | ||
{ | ||
apiKey, | ||
store, | ||
variant, | ||
customPrice, | ||
productOptions, | ||
checkoutOptions, | ||
checkoutData, | ||
preview, | ||
testMode, | ||
expiresAt | ||
}, | ||
exits | ||
) { | ||
const adapterConfig = require('../adapter').config | ||
const payload = generateJsonApiPayload( | ||
'checkouts', | ||
{ | ||
custom_price: customPrice || null, | ||
product_options: { | ||
redirect_url: adapterConfig.redirectUrl || null, | ||
...productOptions | ||
}, | ||
checkout_options: checkoutOptions, | ||
checkout_data: checkoutData, | ||
preview, | ||
test_mode: testMode, | ||
expires_at: expiresAt || null | ||
}, | ||
{ | ||
store: { | ||
data: { | ||
type: 'stores', | ||
id: store || adapterConfig.store | ||
} | ||
}, | ||
variant: { | ||
data: { | ||
type: 'variants', | ||
id: variant | ||
} | ||
} | ||
} | ||
) | ||
|
||
const checkout = await fetch('/checkouts', { | ||
method: 'POST', | ||
headers: { | ||
authorization: `Bearer ${apiKey || adapterConfig.apiKey}` | ||
}, | ||
body: payload | ||
}) | ||
if (checkout.errors) { | ||
const errors = checkout.errors | ||
return exits.couldNotCreateCheckoutUrl(errors) | ||
} | ||
|
||
const checkoutUrl = checkout.data.attributes.url | ||
return exits.success(checkoutUrl) | ||
} | ||
}) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = { | ||
checkout: require('./checkout') | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,8 @@ | ||
{ | ||
"name": "sails-lemonsqueezy", | ||
"name": "@sails-pay/lemonsqueezy", | ||
"version": "0.0.1", | ||
"description": "Lemon Squeezy adapter for Sails Pay", | ||
"main": "index.js", | ||
"main": "adapter.js", | ||
"scripts": { | ||
"test": "npm run test" | ||
}, | ||
|
@@ -32,5 +32,9 @@ | |
"ecommerce" | ||
], | ||
"author": "Kelvin Omereshone <[email protected]>", | ||
"license": "MIT" | ||
"license": "MIT", | ||
"dependencies": { | ||
"machine": "^15.2.3", | ||
"undici": "^6.6.2" | ||
} | ||
} |
File renamed without changes.
File renamed without changes.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/** | ||
* pay hook | ||
* | ||
* @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions, and/or initialization logic. | ||
* @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks | ||
*/ | ||
|
||
module.exports = function (sails) { | ||
function extractProviderName(fullName) { | ||
const parts = fullName.split('/') | ||
return parts[parts.length - 1] | ||
} | ||
return { | ||
defaults: { | ||
pay: { | ||
provider: 'default', | ||
providers: {} | ||
} | ||
}, | ||
/** | ||
* Runs when this Sails app loads/lifts. | ||
*/ | ||
initialize: async function () { | ||
function getPaymentProvider(provider) { | ||
if (!sails.config.pay.providers[provider]) { | ||
throw new Error('The provided payment provider coult not be found.') | ||
} | ||
const providerName = extractProviderName( | ||
sails.config.pay.providers[provider].adapter | ||
) | ||
switch (providerName) { | ||
case 'lemonsqueezy': | ||
console.log() | ||
const paymentProvider = require( | ||
sails.config.pay.providers[provider].adapter | ||
) | ||
paymentProvider.config = sails.config.pay.providers[provider] | ||
return paymentProvider | ||
default: | ||
throw new Error( | ||
'Invalid payment provider provided, supported stores are redis or memcached.' | ||
) | ||
} | ||
} | ||
|
||
sails.hooks.pay.paymentProvider = getPaymentProvider( | ||
sails.config.pay.provider | ||
) | ||
sails.hooks.pay.paymentProvider.provider = function (provider) { | ||
return getPaymentProvider(provider) | ||
} | ||
|
||
sails.pay = sails.hooks.pay.paymentProvider | ||
|
||
sails.log.info('Initializing custom hook (`pay`)') | ||
} | ||
} | ||
} |
Oops, something went wrong.