diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..889171b --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,53 @@ +name: lint-and-test +run-name: lint and test + +on: + push: + branches: ["main"] + pull_request: + branches: ["*"] + +jobs: + lint-and-test: + name: Lint and Test + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22.x + + - name: Set up MongoDB + uses: supercharge/mongodb-github-action@v1 + with: + mongodb-version: 7.0 + + - name: Install Dependencies + run: | + sudo apt update + sudo apt install net-tools -y + mkdir -p tmp + ls + curl -s https://api.github.com/repos/stripe/stripe-mock/releases/latest | jq -r '.assets[] | select(.name | contains("linux_amd64") and endswith(".tar.gz")) | .browser_download_url' | xargs -I{} sh -c 'echo "Downloading {}" && curl -L {} -o tmp/stripe-mock.tar.gz' + tar -xvf tmp/stripe-mock.tar.gz -C tmp + mv tmp/stripe-mock /usr/local/bin/ + chmod -R 755 /usr/local/bin/stripe-mock + rm tmp/stripe-mock.tar.gz + npm install + npm install pm2 -g + + - name: Start stripe-mock + run: pm2 start --name stripe-mock stripe-mock -- -http-addr 127.0.0.1:12111 -https-addr 127.0.0.1:12112 + + - name: Display active connections + run: sleep 5 && netstat -tuln + + - name: Run ESLint + run: npm run eslint + + - name: Run Mocha Tests + run: npm run mocha diff --git a/README.md b/README.md index 377c4ce..7dd1dad 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,13 @@ License +
+
+ + Unit Tests +

@@ -42,7 +49,7 @@ This module adds a custom route to initiate a Stripe Checkout instance and anoth ## Installation -Use your preferred package manager to install the module. You'll also need to install the [read-only-field](https://github.com/) package alongside it: +Use your preferred package manager to install the module. You'll also need to install the [read-only-field](https://github.com/stepanjakl/apostrophe-read-only-field) package alongside it: ```zsh npm install stripe-checkout@npm:@stepanjakl/apostrophe-stripe-checkout @@ -175,6 +182,4 @@ pm2 start --name stripe-listener "stripe listen --events checkout.session.comple ## TODOs (Limitations) -- Option for one-time and recurring payments - Enable checkout session with more than 99 items -- Add other extra checkout session options (e.g. custom styles) diff --git a/index.js b/index.js index ee91973..bb126c4 100644 --- a/index.js +++ b/index.js @@ -4,16 +4,16 @@ const path = require('path'); const Stripe = require('stripe'); // Importing the Stripe SDK let stripe = null; -if (process.env.STRIPE_TEST_MODE === 'false') { - // Using Stripe production mode settings with the API key - stripe = Stripe(process.env.STRIPE_KEY); -} else { - // Using Stripe test mode settings +if (process.env.STRIPE_MOCK_TEST_MODE === 'true') { + // Using Stripe mock test mode settings stripe = new Stripe('sk_test_xyz', { - host: 'localhost', + host: '127.0.0.1', protocol: 'http', port: 12111 }); +} else { + // Using Stripe production mode settings with the API key + stripe = Stripe(process.env.STRIPE_KEY); } const bodyParser = require('body-parser'); // Importing the body-parser middleware @@ -149,15 +149,9 @@ module.exports = { '/api/v1/stripe/checkout/sessions/create': async function (req) { try { // Create a new checkout session with the provided parameters - const checkoutSession = await stripe.checkout.sessions.create({ - line_items: req.body.line_items, - locale: 'en', - mode: 'payment', - success_url: req.body.success_url, - cancel_url: req.body.cancel_url - }); + const checkoutSession = await stripe.checkout.sessions.create(req.body); // Return the URL of the created checkout session - return checkoutSession.url; + return checkoutSession; } catch (error) { req.res.status(500).send(error); } diff --git a/modules/stripe-checkout/session/index.js b/modules/stripe-checkout/session/index.js index b6aa56e..6658ae7 100644 --- a/modules/stripe-checkout/session/index.js +++ b/modules/stripe-checkout/session/index.js @@ -179,18 +179,4 @@ module.exports = { filters: { remove: [ 'visibility' ] } - /* init(self) { - self.addReadOnlyFieldType() - }, - methods(self) { - return { - addReadOnlyFieldType() { - self.apos.schema.addFieldType({ - name: 'readOnly', - convert: self.convertInput, - vueComponent: 'InputReadOnly' - }) - } - } - } */ }; diff --git a/package.json b/package.json index d69cc01..9353414 100644 --- a/package.json +++ b/package.json @@ -34,12 +34,12 @@ "eslint-plugin-mocha": "^10.4.3", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-vue": "^9.25.0", + "eslint-plugin-vue": "^9.26.0", "mocha": "^10.4.0", "read-only-field": "npm:@stepanjakl/apostrophe-read-only-field@latest", - "stripe": "^15.3.0", - "stylelint": "^15.x.x", - "stylelint-config-apostrophe": "^3.0.2" + "stripe": "^15.7.0", + "stylelint": "^16.x.x", + "stylelint-config-apostrophe": "^4.x.x" }, "publishConfig": { "access": "public" diff --git a/test/test.js b/test/test.js index adad7d4..6099f8e 100644 --- a/test/test.js +++ b/test/test.js @@ -2,7 +2,7 @@ const assert = require('assert'); const t = require('apostrophe/test-lib/test'); process.env.STRIPE_WEBHOOK_ENDPOINT_SECRET = 'whsec_xyz'; -process.env.STRIPE_TEST_MODE = 'true'; +process.env.STRIPE_MOCK_TEST_MODE = 'true'; describe('Apostrophe - Stripe Checkout Integration Tests', function () { let apos; @@ -34,12 +34,34 @@ describe('Apostrophe - Stripe Checkout Integration Tests', function () { }); it('should properly instantiate the read-only field module', function () { - assert(apos.readOnlyField); + assert(apos.modules['read-only-field'], 'Read-only field module should be instantiated'); }); it('should properly instantiate the checkout and checkout session modules', function () { - assert(apos.stripeCheckout); - assert(apos.stripeCheckoutSession); + assert(apos.modules['stripe-checkout'], 'Stripe checkout module should be instantiated'); + assert(apos.modules['stripe-checkout/session'], 'Stripe checkout session module should be instantiated'); + }); + + it('should connect to Stripe API', async function() { + const Stripe = require('stripe'); + const stripe = new Stripe('sk_test_xyz', { + host: '127.0.0.1', + protocol: 'http', + port: 12111, + maxNetworkRetries: 3, + timeout: 10 * 1000 + }); + + try { + const paymentMethods = await stripe.paymentMethods.list({ limit: 1 }); + assert.strictEqual(paymentMethods.data.length > 0, true, 'Should connect to Stripe API successfully'); + } catch (error) { + console.error('Error connecting to Stripe API:', error); + if (error.detail?.errors?.length > 0) { + console.error('Error detail:', ...error.detail.errors); + } + throw error; + } }); it('should create a test checkout session and return a valid URL', async function () { @@ -51,7 +73,7 @@ describe('Apostrophe - Stripe Checkout Integration Tests', function () { Accept: 'application/json', 'Content-Type': 'application/json' }, - body: { + body: JSON.stringify({ line_items: [ { price: 'price_test_abc', @@ -64,41 +86,39 @@ describe('Apostrophe - Stripe Checkout Integration Tests', function () { ], success_url: apos.baseUrl, cancel_url: apos.baseUrl - } + }) }); } catch (error) { console.error('An error occurred:', error); throw error; } - function isURL(string) { - const urlRegex = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i; - return urlRegex.test(string); - } - - assert.strictEqual(isURL(response), true); + assert.strictEqual(typeof response, 'object', 'Response should be an object'); + assert(response.id, 'Response should contain an "id" parameter'); }); it('should send request to webhook endpoint and save the completed checkout session to the database', async function () { - - await apos.http.post('/api/v1/stripe/checkout/webhook', { - headers: { - 'stripe-signature': 't=1711059559,v1=9dd216ac7ffc2d07d3edd4b4de4a67200705c52f435e92bc3b21a605f3af91af,v0=4251a0f2bbd73dd1622bb01aedb334cab148be2a84bb3b1daea4af931e0172e2' - }, - body: { - id: 'evt_xyz', - object: 'event' - } - }).catch(error => { + try { + await apos.http.post('/api/v1/stripe/checkout/webhook', { + headers: { + 'stripe-signature': 't=1711059559,v1=9dd216ac7ffc2d07d3edd4b4de4a67200705c52f435e92bc3b21a605f3af91af,v0=4251a0f2bbd73dd1622bb01aedb334cab148be2a84bb3b1daea4af931e0172e2' + }, + body: JSON.stringify({ + id: 'evt_xyz', + object: 'event' + }) + }); + } catch (error) { console.error('An error occurred:', error); throw error; - }); + } - const sessionDoc = await apos.stripeCheckoutSession.find(apos.task.getReq(), { + const sessionDoc = await apos.modules['stripe-checkout/session'].find(apos.task.getReq(), { slug: 'cs_xyz', aposMode: 'published' }).toObject(); - assert(sessionDoc); + assert(sessionDoc, 'The session document should be saved in the database'); + assert.strictEqual(sessionDoc.slug, 'cs_xyz', 'Session document should have the correct slug'); }); });