From 8783de17cdb57a6076012ee2cc664af518ddcb5f Mon Sep 17 00:00:00 2001 From: Sebastian Rindom Date: Fri, 16 Jul 2021 17:03:35 +0200 Subject: [PATCH] Feat/manual payment (#318) What A dummy payment provider for testing and potentially other relevant use cases like accepting cash payments. Also makes it possible to start server without notification/payment/fulfillment providers. Why When setting up a starter project it introduces unnecessary overhead to have to set up an account with a payment provider (e.g. Stripe). Adding a dummy provider will remove friction. How Extremely simple wrapper functionality. --- packages/medusa-payment-manual/.babelrc | 14 +++ packages/medusa-payment-manual/.eslintrc | 9 ++ packages/medusa-payment-manual/.gitignore | 15 +++ packages/medusa-payment-manual/.npmignore | 13 +++ packages/medusa-payment-manual/.prettierrc | 7 ++ packages/medusa-payment-manual/index.js | 1 + packages/medusa-payment-manual/package.json | 40 ++++++++ .../src/services/manual-payment.js | 95 +++++++++++++++++++ packages/medusa/src/loaders/defaults.js | 45 ++++++++- 9 files changed, 236 insertions(+), 3 deletions(-) create mode 100644 packages/medusa-payment-manual/.babelrc create mode 100644 packages/medusa-payment-manual/.eslintrc create mode 100644 packages/medusa-payment-manual/.gitignore create mode 100644 packages/medusa-payment-manual/.npmignore create mode 100644 packages/medusa-payment-manual/.prettierrc create mode 100644 packages/medusa-payment-manual/index.js create mode 100644 packages/medusa-payment-manual/package.json create mode 100644 packages/medusa-payment-manual/src/services/manual-payment.js diff --git a/packages/medusa-payment-manual/.babelrc b/packages/medusa-payment-manual/.babelrc new file mode 100644 index 0000000000..75cbf1558b --- /dev/null +++ b/packages/medusa-payment-manual/.babelrc @@ -0,0 +1,14 @@ +{ + "plugins": [ + "@babel/plugin-proposal-optional-chaining", + "@babel/plugin-proposal-class-properties", + "@babel/plugin-transform-instanceof", + "@babel/plugin-transform-classes" + ], + "presets": ["@babel/preset-env"], + "env": { + "test": { + "plugins": ["@babel/plugin-transform-runtime"] + } + } +} diff --git a/packages/medusa-payment-manual/.eslintrc b/packages/medusa-payment-manual/.eslintrc new file mode 100644 index 0000000000..2a889697f0 --- /dev/null +++ b/packages/medusa-payment-manual/.eslintrc @@ -0,0 +1,9 @@ +{ + "plugins": ["prettier"], + "extends": ["prettier"], + "rules": { + "prettier/prettier": "error", + "semi": "error", + "no-unused-expressions": "true" + } +} diff --git a/packages/medusa-payment-manual/.gitignore b/packages/medusa-payment-manual/.gitignore new file mode 100644 index 0000000000..880606e4d1 --- /dev/null +++ b/packages/medusa-payment-manual/.gitignore @@ -0,0 +1,15 @@ +/lib +node_modules +.DS_store +.env* +/*.js +!index.js +yarn.lock + +/dist + +/api +/services +/models +/subscribers +/__mocks__ \ No newline at end of file diff --git a/packages/medusa-payment-manual/.npmignore b/packages/medusa-payment-manual/.npmignore new file mode 100644 index 0000000000..73122644c5 --- /dev/null +++ b/packages/medusa-payment-manual/.npmignore @@ -0,0 +1,13 @@ +/lib +node_modules +.DS_store +.env* +/*.js +!index.js +yarn.lock +src +.gitignore +.eslintrc +.babelrc +.prettierrc + diff --git a/packages/medusa-payment-manual/.prettierrc b/packages/medusa-payment-manual/.prettierrc new file mode 100644 index 0000000000..70175ce150 --- /dev/null +++ b/packages/medusa-payment-manual/.prettierrc @@ -0,0 +1,7 @@ +{ + "endOfLine": "lf", + "semi": false, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "es5" +} \ No newline at end of file diff --git a/packages/medusa-payment-manual/index.js b/packages/medusa-payment-manual/index.js new file mode 100644 index 0000000000..172f1ae6a4 --- /dev/null +++ b/packages/medusa-payment-manual/index.js @@ -0,0 +1 @@ +// noop diff --git a/packages/medusa-payment-manual/package.json b/packages/medusa-payment-manual/package.json new file mode 100644 index 0000000000..bde819f0a7 --- /dev/null +++ b/packages/medusa-payment-manual/package.json @@ -0,0 +1,40 @@ +{ + "name": "medusa-payment-manual", + "version": "1.0.0", + "description": "A dummy payment provider to be used for testing or manual payments", + "main": "index.js", + "repository": { + "type": "git", + "url": "https://github.com/medusajs/medusa", + "directory": "packages/medusa-payment-manual" + }, + "author": "Sebastian Rindom", + "license": "MIT", + "devDependencies": { + "@babel/cli": "^7.7.5", + "@babel/core": "^7.7.5", + "@babel/node": "^7.7.4", + "@babel/plugin-proposal-class-properties": "^7.7.4", + "@babel/plugin-proposal-optional-chaining": "^7.12.7", + "@babel/plugin-transform-classes": "^7.9.5", + "@babel/plugin-transform-instanceof": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.7.6", + "@babel/preset-env": "^7.7.5", + "@babel/register": "^7.7.4", + "@babel/runtime": "^7.9.6", + "cross-env": "^5.2.1", + "eslint": "^6.8.0", + "jest": "^25.5.2", + "medusa-test-utils": "^1.1.21" + }, + "scripts": { + "build": "babel src -d . --ignore **/__tests__", + "prepare": "cross-env NODE_ENV=production npm run build", + "watch": "babel -w src --out-dir . --ignore **/__tests__", + "test": "jest" + }, + "peerDependencies": { + "medusa-interfaces": "1.x" + }, + "gitHead": "db9d6c0cf55ff60a90415b16bc7582cc4795768f" +} diff --git a/packages/medusa-payment-manual/src/services/manual-payment.js b/packages/medusa-payment-manual/src/services/manual-payment.js new file mode 100644 index 0000000000..775721a278 --- /dev/null +++ b/packages/medusa-payment-manual/src/services/manual-payment.js @@ -0,0 +1,95 @@ +import { PaymentService } from "medusa-interfaces" + +class ManualPaymentService extends PaymentService { + static identifier = "manual" + + constructor() { + super() + } + + /** + * Returns the currently held status. + * @param {object} paymentData - payment method data from cart + * @returns {string} the status of the payment + */ + async getStatus(paymentData) { + const { status } = paymentData + return status + } + + /** + * Creates a manual payment with status "pending" + * @param {object} cart - cart to create a payment for + * @returns {object} an object with staus + */ + async createPayment() { + return { status: "pending" } + } + + /** + * Retrieves payment + * @param {object} data - the data of the payment to retrieve + * @returns {Promise} returns data + */ + async retrievePayment(data) { + return data + } + + /** + * Updates the payment status to authorized + * @returns {Promise<{ status: string, data: object }>} result with data and status + */ + async authorizePayment() { + return { status: "authorized", data: { status: "authorized" } } + } + + /** + * Noop, simply returns existing data. + * @param {object} sessionData - payment session data. + * @returns {object} same data + */ + async updatePayment(sessionData) { + return sessionData.data + } + + async deletePayment() { + return + } + + /** + * Updates the payment status to captured + * @param {object} paymentData - payment method data from cart + * @returns {object} object with updated status + */ + async capturePayment() { + return { status: "captured" } + } + + /** + * Returns the data currently held in a status + * @param {object} paymentData - payment method data from cart + * @returns {object} the current data + */ + async getPaymentData(session) { + return session.data + } + + /** + * Noop, resolves to allow manual refunds. + * @param {object} payment - payment method data from cart + * @returns {string} same data + */ + async refundPayment(payment) { + return payment.data + } + + /** + * Updates the payment status to cancled + * @returns {object} object with canceled status + */ + async cancelPayment() { + return { status: "canceled" } + } +} + +export default ManualPaymentService diff --git a/packages/medusa/src/loaders/defaults.js b/packages/medusa/src/loaders/defaults.js index d8da4d1f9a..620fbeee12 100644 --- a/packages/medusa/src/loaders/defaults.js +++ b/packages/medusa/src/loaders/defaults.js @@ -1,6 +1,36 @@ +const silentResolution = (container, name, logger) => { + try { + const resolved = container.resolve(name) + return resolved + } catch (err) { + if (err.name !== "AwilixResolutionError") { + throw err + } else { + let identifier + switch (name) { + case "paymentProviders": + identifier = "payment" + break + case "notificationProviders": + identifier = "notification" + break + case "fulfillmentProviders": + identifier = "fulfillment" + break + default: + identifier = name + } + logger.warn( + `You don't have any ${identifier} provider plugins installed. You may want to add one to your project.` + ) + } + } +} + export default async ({ container }) => { const storeService = container.resolve("storeService") const profileService = container.resolve("shippingProfileService") + const logger = container.resolve("logger") const entityManager = container.resolve("manager") @@ -9,19 +39,28 @@ export default async ({ container }) => { let payIds const pProviderService = container.resolve("paymentProviderService") - const payProviders = container.resolve("paymentProviders") + + const payProviders = + silentResolution(container, "paymentProviders", logger) || [] + payIds = payProviders.map(p => p.getIdentifier()) await pProviderService.registerInstalledProviders(payIds) let notiIds const nProviderService = container.resolve("notificationService") - const notiProviders = container.resolve("notificationProviders") + + const notiProviders = + silentResolution(container, "notificationProviders", logger) || [] + notiIds = notiProviders.map(p => p.getIdentifier()) await nProviderService.registerInstalledProviders(notiIds) let fulfilIds const fProviderService = container.resolve("fulfillmentProviderService") - const fulfilProviders = container.resolve("fulfillmentProviders") + + const fulfilProviders = + silentResolution(container, "fulfillmentProviders", logger) || [] + fulfilIds = fulfilProviders.map(p => p.getIdentifier()) await fProviderService.registerInstalledProviders(fulfilIds)