diff --git a/.changeset/strong-radios-scream.md b/.changeset/strong-radios-scream.md new file mode 100644 index 0000000000..ffde62a009 --- /dev/null +++ b/.changeset/strong-radios-scream.md @@ -0,0 +1,6 @@ +--- +"medusa-payment-stripe": patch +"@medusajs/medusa": patch +--- + +feat(medusa,medusa-payment-stripe): Move database mutation from plugin to core diff --git a/.eslintignore b/.eslintignore index d74f74baf2..83458b3442 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,8 +8,6 @@ packages/* # List of packages to Lint !packages/medusa -integration-tests/* -#!integration-tests/api **/models/* @@ -18,6 +16,5 @@ integration-tests/* **/node_modules/* **/migrations/* **/__mocks__/* -**/__tests__/* .eslintrc.js diff --git a/integration-tests/api/__tests__/admin/__snapshots__/price-list.js.snap b/integration-tests/api/__tests__/admin/__snapshots__/price-list.js.snap index 287c4f6b99..5d7eb27d4a 100644 --- a/integration-tests/api/__tests__/admin/__snapshots__/price-list.js.snap +++ b/integration-tests/api/__tests__/admin/__snapshots__/price-list.js.snap @@ -149,37 +149,6 @@ Object { } `; -exports[`/admin/price-lists POST /admin/price-lists/:id updates price list prices (inser a new MA for a specific region) 1`] = ` -Array [ - Object { - "amount": 101, - "created_at": Any, - "currency_code": "eur", - "deleted_at": null, - "id": Any, - "max_quantity": null, - "min_quantity": null, - "price_list_id": "pl_with_some_ma", - "region_id": "region-pl", - "updated_at": Any, - "variant_id": "test-variant", - }, - Object { - "amount": 1001, - "created_at": Any, - "currency_code": "usd", - "deleted_at": null, - "id": "ma_test_4", - "max_quantity": null, - "min_quantity": null, - "price_list_id": "pl_with_some_ma", - "region_id": null, - "updated_at": Any, - "variant_id": "test-variant", - }, -] -`; - exports[`/admin/price-lists POST /admin/price-lists/:id updates the amount and currency of a price in the price list 1`] = ` Object { "amount": 250, diff --git a/integration-tests/api/__tests__/admin/price-list.js b/integration-tests/api/__tests__/admin/price-list.js index ddbbcffb04..b4647575bd 100644 --- a/integration-tests/api/__tests__/admin/price-list.js +++ b/integration-tests/api/__tests__/admin/price-list.js @@ -638,32 +638,34 @@ describe("/admin/price-lists", () => { expect(response.status).toEqual(200) expect(response.data.price_list.prices.length).toEqual(2) - expect(response.data.price_list.prices).toMatchSnapshot([ - { - id: expect.any(String), - currency_code: "eur", - amount: 101, - min_quantity: null, - max_quantity: null, - price_list_id: "pl_with_some_ma", - variant_id: "test-variant", - region_id: "region-pl", - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - }, - { - id: "ma_test_4", - currency_code: "usd", - amount: 1001, - price_list_id: "pl_with_some_ma", - variant_id: "test-variant", - region_id: null, - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - }, - ]) + expect(response.data.price_list.prices).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + currency_code: "eur", + amount: 101, + min_quantity: null, + max_quantity: null, + price_list_id: "pl_with_some_ma", + variant_id: "test-variant", + region_id: "region-pl", + created_at: expect.any(String), + updated_at: expect.any(String), + deleted_at: null, + }), + expect.objectContaining({ + id: "ma_test_4", + currency_code: "usd", + amount: 1001, + price_list_id: "pl_with_some_ma", + variant_id: "test-variant", + region_id: null, + created_at: expect.any(String), + updated_at: expect.any(String), + deleted_at: null, + }), + ]) + ) }) }) diff --git a/packages/medusa-core-utils/src/index.ts b/packages/medusa-core-utils/src/index.ts index 0a5caa85b4..270aa175d8 100644 --- a/packages/medusa-core-utils/src/index.ts +++ b/packages/medusa-core-utils/src/index.ts @@ -10,4 +10,5 @@ export { parseCorsOrigins } from "./parse-cors-origins" export { transformIdableFields } from "./transform-idable-fields" export { default as Validator } from "./validator" export { default as zeroDecimalCurrencies } from "./zero-decimal-currencies" +export * from "./is-defined" diff --git a/packages/medusa/src/utils/is-defined.ts b/packages/medusa-core-utils/src/is-defined.ts similarity index 100% rename from packages/medusa/src/utils/is-defined.ts rename to packages/medusa-core-utils/src/is-defined.ts diff --git a/packages/medusa-payment-stripe/package.json b/packages/medusa-payment-stripe/package.json index 37339a50a8..297cd72bff 100644 --- a/packages/medusa-payment-stripe/package.json +++ b/packages/medusa-payment-stripe/package.json @@ -29,9 +29,9 @@ "medusa-test-utils": "^1.1.37" }, "scripts": { - "build": "babel src -d . --ignore **/__tests__", + "build": "babel src -d . --ignore **/__tests__ --ignore **/__mocks__", "prepare": "cross-env NODE_ENV=production yarn run build", - "watch": "babel -w src --out-dir . --ignore **/__tests__", + "watch": "babel -w src --out-dir . --ignore **/__tests__ --ignore **/__mocks__", "test": "jest" }, "peerDependencies": { diff --git a/packages/medusa-payment-stripe/src/__mocks__/cart.js b/packages/medusa-payment-stripe/src/__mocks__/cart.js index c3a5bd01d0..c301361bda 100644 --- a/packages/medusa-payment-stripe/src/__mocks__/cart.js +++ b/packages/medusa-payment-stripe/src/__mocks__/cart.js @@ -91,6 +91,8 @@ export const carts = { customer: IdMap.getId("not-lebron"), }, }, + region: { currency_code: "usd" }, + total: 100, shipping_address: {}, billing_address: {}, discounts: [], diff --git a/packages/medusa-payment-stripe/src/__mocks__/stripe.js b/packages/medusa-payment-stripe/src/__mocks__/stripe.js index 23b250a650..deb6bafc85 100644 --- a/packages/medusa-payment-stripe/src/__mocks__/stripe.js +++ b/packages/medusa-payment-stripe/src/__mocks__/stripe.js @@ -20,7 +20,7 @@ export const StripeMock = { if (data.customer === "cus_123456789_new") { return Promise.resolve({ id: "pi_lebron", - amount: 100, + amount: data.amount, customer: "cus_123456789_new", description: data?.description, }) @@ -28,7 +28,7 @@ export const StripeMock = { if (data.customer === "cus_lebron") { return Promise.resolve({ id: "pi_lebron", - amount: 100, + amount: data.amount, customer: "cus_lebron", description: data?.description, }) diff --git a/packages/medusa-payment-stripe/src/services/__tests__/stripe-provider.js b/packages/medusa-payment-stripe/src/helpers/__tests__/stripe-base.js similarity index 51% rename from packages/medusa-payment-stripe/src/services/__tests__/stripe-provider.js rename to packages/medusa-payment-stripe/src/helpers/__tests__/stripe-base.js index b6eed7d2e2..7bd3cf247d 100644 --- a/packages/medusa-payment-stripe/src/services/__tests__/stripe-provider.js +++ b/packages/medusa-payment-stripe/src/helpers/__tests__/stripe-base.js @@ -1,58 +1,13 @@ -import { IdMap } from "medusa-test-utils" -import StripeProviderService from "../stripe-provider" -import { CustomerServiceMock } from "../../__mocks__/customer" import { carts } from "../../__mocks__/cart" -import { TotalsServiceMock } from "../../__mocks__/totals" +import StripeBase from "../stripe-base"; -const RegionServiceMock = { - withTransaction: function () { - return this - }, - retrieve: jest.fn().mockReturnValue(Promise.resolve({})), -} - -describe("StripeProviderService", () => { - describe("createCustomer", () => { - let result - beforeAll(async () => { - jest.clearAllMocks() - const stripeProviderService = new StripeProviderService( - { - customerService: CustomerServiceMock, - regionService: RegionServiceMock, - totalsService: TotalsServiceMock, - }, - { - api_key: "test", - } - ) - - result = await stripeProviderService.createCustomer({ - _id: IdMap.getId("vvd"), - first_name: "Virgil", - last_name: "Van Dijk", - email: "virg@vvd.com", - password_hash: "1234", - metadata: {}, - }) - }) - - it("returns created stripe customer", () => { - expect(result).toEqual({ - id: "cus_vvd", - email: "virg@vvd.com", - }) - }) - }) +const fakeContainer = {} +describe("StripeBase", () => { describe("createPayment", () => { let result - const stripeProviderService = new StripeProviderService( - { - customerService: CustomerServiceMock, - regionService: RegionServiceMock, - totalsService: TotalsServiceMock, - }, + const stripeBase = new StripeBase( + fakeContainer, { api_key: "test" } @@ -63,45 +18,88 @@ describe("StripeProviderService", () => { }) it("returns created stripe payment intent for cart with existing customer", async () => { - result = await stripeProviderService.createPayment(carts.frCart) + const cart = carts.frCart + const context = { + cart, + amount: cart.total, + currency_code: cart.region?.currency_code, + } + Object.assign(context, cart) + + result = await stripeBase.createPayment(context) expect(result).toEqual({ - id: "pi_lebron", - customer: "cus_123456789_new", - amount: 100, + session_data: { + id: "pi_lebron", + customer: "cus_lebron", + description: undefined, + amount: 100, + }, + update_requests: { + customer_metadata: { + stripe_id: "cus_lebron" + } + } }) }) it("returns created stripe payment intent for cart with no customer", async () => { - carts.frCart.customer_id = "" - carts.frCart.context.payment_description = 'some description' - result = await stripeProviderService.createPayment(carts.frCart) + const cart = carts.frCart + const context = { + cart, + amount: cart.total, + currency_code: cart.region?.currency_code, + } + Object.assign(context, cart) + + context.cart.context.payment_description = 'some description' + + result = await stripeBase.createPayment(context) expect(result).toEqual({ - id: "pi_lebron", - customer: "cus_lebron", - amount: 100, - description: 'some description', + session_data: { + id: "pi_lebron", + customer: "cus_lebron", + description: 'some description', + amount: 100, + }, + update_requests: { + customer_metadata: { + stripe_id: "cus_lebron" + } + } }) }) it("returns created stripe payment intent for cart with no customer and the options default description", async () => { - const localStripeProviderService = new StripeProviderService({ - customerService: CustomerServiceMock, - regionService: RegionServiceMock, - totalsService: TotalsServiceMock, - }, + const localStripeProviderService = new StripeBase( + fakeContainer, { api_key: "test", payment_description: "test options description" }) - carts.frCart.customer_id = "" - carts.frCart.context.payment_description = null - result = await localStripeProviderService.createPayment(carts.frCart) + const cart = carts.frCart + const context = { + cart, + amount: cart.total, + currency_code: cart.region?.currency_code, + } + Object.assign(context, cart) + + context.cart.context.payment_description = null + + result = await localStripeProviderService.createPayment(context) expect(result).toEqual({ - id: "pi_lebron", - customer: "cus_lebron", - amount: 100, - description: "test options description", + session_data: { + id: "pi_lebron", + customer: "cus_lebron", + description: "test options description", + amount: 100, + }, + update_requests: { + customer_metadata: { + stripe_id: "cus_lebron" + } + } }) }) }) @@ -110,18 +108,14 @@ describe("StripeProviderService", () => { let result beforeAll(async () => { jest.clearAllMocks() - const stripeProviderService = new StripeProviderService( - { - customerService: CustomerServiceMock, - regionService: RegionServiceMock, - totalsService: TotalsServiceMock, - }, + const stripeBase = new StripeBase( + fakeContainer, { api_key: "test", } ) - result = await stripeProviderService.retrievePayment({ + result = await stripeBase.retrievePayment({ payment_method: { data: { id: "pi_lebron", @@ -142,18 +136,14 @@ describe("StripeProviderService", () => { let result beforeAll(async () => { jest.clearAllMocks() - const stripeProviderService = new StripeProviderService( - { - customerService: CustomerServiceMock, - regionService: RegionServiceMock, - totalsService: TotalsServiceMock, - }, + const stripeBase = new StripeBase( + fakeContainer, { api_key: "test", } ) - result = await stripeProviderService.updatePayment( + result = await stripeBase.updatePayment( { id: "pi_lebron", amount: 800, @@ -177,18 +167,14 @@ describe("StripeProviderService", () => { let result beforeAll(async () => { jest.clearAllMocks() - const stripeProviderService = new StripeProviderService( - { - customerService: CustomerServiceMock, - regionService: RegionServiceMock, - totalsService: TotalsServiceMock, - }, + const stripeBase = new StripeBase( + fakeContainer, { api_key: "test", } ) - result = await stripeProviderService.updatePaymentIntentCustomer( + result = await stripeBase.updatePaymentIntentCustomer( "pi_lebron", "cus_lebron_2" ) @@ -207,14 +193,14 @@ describe("StripeProviderService", () => { let result beforeAll(async () => { jest.clearAllMocks() - const stripeProviderService = new StripeProviderService( - {}, + const stripeBase = new StripeBase( + fakeContainer, { api_key: "test", } ) - result = await stripeProviderService.capturePayment({ + result = await stripeBase.capturePayment({ data: { id: "pi_lebron", customer: "cus_lebron", @@ -237,14 +223,14 @@ describe("StripeProviderService", () => { let result beforeAll(async () => { jest.clearAllMocks() - const stripeProviderService = new StripeProviderService( - {}, + const stripeBase = new StripeBase( + fakeContainer, { api_key: "test", } ) - result = await stripeProviderService.refundPayment( + result = await stripeBase.refundPayment( { data: { id: "re_123", @@ -271,14 +257,14 @@ describe("StripeProviderService", () => { let result beforeAll(async () => { jest.clearAllMocks() - const stripeProviderService = new StripeProviderService( - {}, + const stripeBase = new StripeBase( + fakeContainer, { api_key: "test", } ) - result = await stripeProviderService.cancelPayment({ + result = await stripeBase.cancelPayment({ data: { id: "pi_lebron", customer: "cus_lebron", diff --git a/packages/medusa-payment-stripe/src/helpers/stripe-base.js b/packages/medusa-payment-stripe/src/helpers/stripe-base.js index c5e49b4333..ac3194cc01 100644 --- a/packages/medusa-payment-stripe/src/helpers/stripe-base.js +++ b/packages/medusa-payment-stripe/src/helpers/stripe-base.js @@ -1,29 +1,12 @@ -import { AbstractPaymentService, PaymentSessionData } from "@medusajs/medusa" +import { AbstractPaymentService } from "@medusajs/medusa" import Stripe from "stripe" +import { PaymentSessionStatus } from "@medusajs/medusa/dist"; class StripeBase extends AbstractPaymentService { static identifier = null - constructor( - { - stripeProviderService, - customerService, - totalsService, - regionService, - manager, - }, - options - ) { - super( - { - stripeProviderService, - customerService, - totalsService, - regionService, - manager, - }, - options - ) + constructor(_, options) { + super(_, options) /** * Required Stripe options: @@ -38,21 +21,6 @@ class StripeBase extends AbstractPaymentService { /** @private @const {Stripe} */ this.stripe_ = Stripe(options.api_key) - - /** @private @const {CustomerService} */ - this.stripeProviderService_ = stripeProviderService - - /** @private @const {CustomerService} */ - this.customerService_ = customerService - - /** @private @const {RegionService} */ - this.regionService_ = regionService - - /** @private @const {TotalsService} */ - this.totalsService_ = totalsService - - /** @private @const {EntityManager} */ - this.manager_ = manager } getPaymentIntentOptions() { @@ -74,22 +42,48 @@ class StripeBase extends AbstractPaymentService { return options } - /** - * Fetches Stripe payment intent. Check its status and returns the - * corresponding Medusa status. - * @param {PaymentSessionData} paymentSessionData - payment method data from cart - * @return {Promise} the status of the payment intent + /** + * Get payment session status + * statuses. + * @param {PaymentSessionData} paymentData - the data stored with the payment session + * @return {Promise} the status of the order */ - async getStatus(paymentSessionData) { - return await this.stripeProviderService_.getStatus(paymentSessionData) + async getStatus(paymentData) { + const { id } = paymentData + const paymentIntent = await this.stripe_.paymentIntents.retrieve(id) + + switch (paymentIntent.status) { + case "requires_payment_method": + case "requires_confirmation": + case "processing": + return PaymentSessionStatus.PENDING + case "requires_action": + return PaymentSessionStatus.REQUIRES_MORE + case "canceled": + return PaymentSessionStatus.CANCELED + case "requires_capture": + case "succeeded": + return PaymentSessionStatus.AUTHORIZED + default: + return PaymentSessionStatus.PENDING + } } /** * Fetches a customers saved payment methods if registered in Stripe. - * @param {object} customer - customer to fetch saved cards for + * @param {Customer} customer - customer to fetch saved cards for * @return {Promise} saved payments methods */ async retrieveSavedMethods(customer) { + if (customer.metadata && customer.metadata.stripe_id) { + const methods = await this.stripe_.paymentMethods.list({ + customer: customer.metadata.stripe_id, + type: "card", + }) + + return methods.data + } + return [] } @@ -99,49 +93,68 @@ class StripeBase extends AbstractPaymentService { * @return {Promise} Stripe customer */ async retrieveCustomer(customerId) { - return await this.stripeProviderService_.retrieveCustomer(customerId) - } - - /** - * Creates a Stripe customer using a Medusa customer. - * @param {object} customer - Customer data from Medusa - * @return {Promise} Stripe customer - */ - async createCustomer(customer) { - return await this.stripeProviderService_ - .withTransaction(this.manager_) - .createCustomer(customer) + if (!customerId) { + return + } + return await this.stripe_.customers.retrieve(customerId) } /** * Creates a Stripe payment intent. * If customer is not registered in Stripe, we do so. - * @param {Cart} cart - cart to create a payment for - * @return {Promise} Stripe payment intent + * @param {Cart & PaymentContext} context - context to use to create a payment for + * @return {Promise} Stripe payment intent */ - async createPayment(cart) { + async createPayment(context) { const intentRequestData = this.getPaymentIntentOptions() + const { id: cart_id, email, context: cart_context, currency_code, amount, resource_id, customer } = context - return await this.stripeProviderService_ - .withTransaction(this.manager_) - .createPayment(cart, intentRequestData) - } + const intentRequest = { + description: + cart_context.payment_description ?? + this.options_?.payment_description, + amount: Math.round(amount), + currency: currency_code, + metadata: { cart_id, resource_id }, + capture_method: this.options_.capture ? "automatic" : "manual", + ...intentRequestData, + } - async createPaymentNew(paymentInput) { - const intentRequestData = this.getPaymentIntentOptions() + if (this.options_?.automatic_payment_methods) { + intentRequest.automatic_payment_methods = { enabled: true } + } - return await this.stripeProviderService_ - .withTransaction(this.manager_) - .createPaymentNew(paymentInput, intentRequestData) + if (customer?.metadata?.stripe_id) { + intentRequest.customer = customer?.metadata?.stripe_id + } else { + const stripeCustomer = await this.stripe_.customers.create({ + email, + }) + + intentRequest.customer = stripeCustomer.id + } + + const session_data = await this.stripe_.paymentIntents.create( + intentRequest + ) + + return { + session_data, + update_requests: { + customer_metadata: { + stripe_id: intentRequest.customer + } + } + } } /** * Retrieves Stripe payment intent. - * @param {PaymentData} paymentData - the data of the payment to retrieve + * @param {PaymentData} data - the data of the payment to retrieve * @return {Promise} Stripe payment intent */ - async retrievePayment(paymentData) { - return await this.stripeProviderService_.retrievePayment(paymentData) + async retrievePayment(data) { + return await this.stripe_.paymentIntents.retrieve(data.id) } /** @@ -150,67 +163,73 @@ class StripeBase extends AbstractPaymentService { * @return {Promise} Stripe payment intent */ async getPaymentData(paymentSession) { - return await this.stripeProviderService_.getPaymentData(paymentSession) + return await this.stripe_.paymentIntents.retrieve(paymentSession.data.id) } /** * Authorizes Stripe payment intent by simply returning * the status for the payment intent in use. * @param {PaymentSession} paymentSession - payment session data - * @param {object} context - properties relevant to current context - * @return {Promise<{data: PaymentSessionData; status: PaymentSessionStatus}>} result with data and status + * @param {Data} context - properties relevant to current context + * @return {Promise<{ data: PaymentSessionData; status: PaymentSessionStatus }>} result with data and status */ async authorizePayment(paymentSession, context = {}) { - return await this.stripeProviderService_.authorizePayment( - paymentSession, - context - ) + const stat = await this.getStatus(paymentSession.data) + return { data: paymentSession.data, status: stat } } - async updatePaymentData(paymentSessionData, data) { - return await this.stripeProviderService_.updatePaymentData( - paymentSessionData, - data - ) + async updatePaymentData(sessionData, update) { + return await this.stripe_.paymentIntents.update(sessionData.id, { + ...update.data, + }) } /** * Updates Stripe payment intent. * @param {PaymentSessionData} paymentSessionData - payment session data. - * @param {Cart} cart + * @param {Cart & PaymentContext} context * @return {Promise} Stripe payment intent */ - async updatePayment(paymentSessionData, cart) { - const intentRequestData = this.getPaymentIntentOptions() + async updatePayment(paymentSessionData, context) { + const { amount, customer } = context + const stripeId = customer?.metadata?.stripe_id || undefined - return await this.stripeProviderService_ - .withTransaction(this.manager_) - .updatePayment(paymentSessionData, cart, intentRequestData) + if (stripeId !== paymentSessionData.customer) { + return await this.createPayment(context) + } else { + if ( + amount && + paymentSessionData.amount === Math.round(amount) + ) { + return paymentSessionData + } + + return await this.stripe_.paymentIntents.update(paymentSessionData.id, { + amount: Math.round(amount), + }) + } } - async updatePaymentNew(paymentSessionData, paymentInput) { - const intentRequestData = this.getPaymentIntentOptions() - - return await this.stripeProviderService_ - .withTransaction(this.manager_) - .updatePaymentNew(paymentSessionData, paymentInput, intentRequestData) - } - - async deletePayment(paymentSession) { - return await this.stripeProviderService_.deletePayment(paymentSession) + async deletePayment(payment) { + const { id } = payment.data + return this.stripe_.paymentIntents.cancel(id).catch((err) => { + if (err.statusCode === 400) { + return + } + throw err + }) } /** * Updates customer of Stripe payment intent. * @param {string} paymentIntentId - id of payment intent to update - * @param {string} customerId - id of new Stripe customer + * @param {string} customerId - id of \ Stripe customer * @return {object} Stripe payment intent */ async updatePaymentIntentCustomer(paymentIntentId, customerId) { - return await this.stripeProviderService_.updatePaymentIntentCustomer( - paymentIntentId, - customerId - ) + return await this.stripe_.paymentIntents.update(paymentIntentId, { + customer: customerId, + }) } /** @@ -219,7 +238,18 @@ class StripeBase extends AbstractPaymentService { * @return {Promise} Stripe payment intent */ async capturePayment(payment) { - return await this.stripeProviderService_.capturePayment(payment) + const { id } = payment.data + try { + const intent = await this.stripe_.paymentIntents.capture(id) + return intent + } catch (error) { + if (error.code === "payment_intent_unexpected_state") { + if (error.payment_intent.status === "succeeded") { + return error.payment_intent + } + } + throw error + } } /** @@ -228,11 +258,14 @@ class StripeBase extends AbstractPaymentService { * @param {number} refundAmount - amount to refund * @return {Promise} refunded payment intent */ - async refundPayment(payment, refundAmount) { - return await this.stripeProviderService_.refundPayment( - payment, - refundAmount - ) + async refundPayment(payment, amountToRefund) { + const { id } = payment.data + await this.stripe_.refunds.create({ + amount: Math.round(amountToRefund), + payment_intent: id, + }) + + return payment.data } /** @@ -241,7 +274,31 @@ class StripeBase extends AbstractPaymentService { * @return {Promise} canceled payment intent */ async cancelPayment(payment) { - return await this.stripeProviderService_.cancelPayment(payment) + const { id } = payment.data + try { + return await this.stripe_.paymentIntents.cancel(id) + } catch (error) { + if (error.payment_intent.status === "canceled") { + return error.payment_intent + } + + throw error + } + } + + /** + * Constructs Stripe Webhook event + * @param {object} data - the data of the webhook request: req.body + * @param {object} signature - the Stripe signature on the event, that + * ensures integrity of the webhook event + * @return {object} Stripe Webhook event + */ + constructWebhookEvent(data, signature) { + return this.stripe_.webhooks.constructEvent( + data, + signature, + this.options_.webhook_secret + ) } } diff --git a/packages/medusa-payment-stripe/src/services/__mocks__/stripe-provider.js b/packages/medusa-payment-stripe/src/services/__mocks__/stripe-provider.js deleted file mode 100644 index e49467719e..0000000000 --- a/packages/medusa-payment-stripe/src/services/__mocks__/stripe-provider.js +++ /dev/null @@ -1,55 +0,0 @@ -import { IdMap } from "medusa-test-utils" - -export const StripeProviderServiceMock = { - withTransaction: function () { - return this - }, - retrievePayment: jest.fn().mockImplementation((payData) => { - if (payData.id === "pi_123456789") { - return Promise.resolve({ - id: "pi", - customer: "cus_123456789", - }) - } - if (payData.id === "pi_no") { - return Promise.resolve({ - id: "pi_no", - }) - } - return Promise.resolve(undefined) - }), - cancelPayment: jest.fn().mockImplementation((cart) => { - return Promise.resolve() - }), - updatePaymentIntentCustomer: jest.fn().mockImplementation((cart) => { - return Promise.resolve() - }), - retrieveCustomer: jest.fn().mockImplementation((customerId) => { - if (customerId === "cus_123456789_new") { - return Promise.resolve({ - id: "cus_123456789_new", - }) - } - return Promise.resolve(undefined) - }), - createCustomer: jest.fn().mockImplementation((customer) => { - if (customer._id === IdMap.getId("vvd")) { - return Promise.resolve({ - id: "cus_123456789_new_vvd", - }) - } - return Promise.resolve(undefined) - }), - createPayment: jest.fn().mockImplementation((cart) => { - return Promise.resolve({ - id: "pi_new", - customer: "cus_123456789_new", - }) - }), -} - -const mock = jest.fn().mockImplementation(() => { - return StripeProviderServiceMock -}) - -export default mock diff --git a/packages/medusa-payment-stripe/src/services/stripe-bancontact.js b/packages/medusa-payment-stripe/src/services/stripe-bancontact.js index a92ace64df..a12f9d5251 100644 --- a/packages/medusa-payment-stripe/src/services/stripe-bancontact.js +++ b/packages/medusa-payment-stripe/src/services/stripe-bancontact.js @@ -3,26 +3,8 @@ import StripeBase from "../helpers/stripe-base" class BancontactProviderService extends StripeBase { static identifier = "stripe-bancontact" - constructor( - { - stripeProviderService, - customerService, - totalsService, - regionService, - manager, - }, - options - ) { - super( - { - stripeProviderService, - customerService, - totalsService, - regionService, - manager, - }, - options - ) + constructor(_, options) { + super(_, options) } get paymentIntentOptions() { diff --git a/packages/medusa-payment-stripe/src/services/stripe-blik.js b/packages/medusa-payment-stripe/src/services/stripe-blik.js index 52f4c215c6..6e18a17fcc 100644 --- a/packages/medusa-payment-stripe/src/services/stripe-blik.js +++ b/packages/medusa-payment-stripe/src/services/stripe-blik.js @@ -3,26 +3,8 @@ import StripeBase from "../helpers/stripe-base" class BlikProviderService extends StripeBase { static identifier = "stripe-blik" - constructor( - { - stripeProviderService, - customerService, - totalsService, - regionService, - manager, - }, - options - ) { - super( - { - stripeProviderService, - customerService, - totalsService, - regionService, - manager, - }, - options - ) + constructor(_, options) { + super(_, options) } get paymentIntentOptions() { diff --git a/packages/medusa-payment-stripe/src/services/stripe-giropay.js b/packages/medusa-payment-stripe/src/services/stripe-giropay.js index 8b04bde24a..94b19415f0 100644 --- a/packages/medusa-payment-stripe/src/services/stripe-giropay.js +++ b/packages/medusa-payment-stripe/src/services/stripe-giropay.js @@ -3,26 +3,8 @@ import StripeBase from "../helpers/stripe-base" class GiropayProviderService extends StripeBase { static identifier = "stripe-giropay" - constructor( - { - stripeProviderService, - customerService, - totalsService, - regionService, - manager, - }, - options - ) { - super( - { - stripeProviderService, - customerService, - totalsService, - regionService, - manager, - }, - options - ) + constructor(_, options) { + super(_, options) } get paymentIntentOptions() { diff --git a/packages/medusa-payment-stripe/src/services/stripe-ideal.js b/packages/medusa-payment-stripe/src/services/stripe-ideal.js index b230a78460..6d20df143c 100644 --- a/packages/medusa-payment-stripe/src/services/stripe-ideal.js +++ b/packages/medusa-payment-stripe/src/services/stripe-ideal.js @@ -3,26 +3,8 @@ import StripeBase from "../helpers/stripe-base" class IdealProviderService extends StripeBase { static identifier = "stripe-ideal" - constructor( - { - stripeProviderService, - customerService, - totalsService, - regionService, - manager, - }, - options - ) { - super( - { - stripeProviderService, - customerService, - totalsService, - regionService, - manager, - }, - options - ) + constructor(_, options) { + super(_, options) } get paymentIntentOptions() { diff --git a/packages/medusa-payment-stripe/src/services/stripe-provider.js b/packages/medusa-payment-stripe/src/services/stripe-provider.js index 47ea452408..ac2f47a232 100644 --- a/packages/medusa-payment-stripe/src/services/stripe-provider.js +++ b/packages/medusa-payment-stripe/src/services/stripe-provider.js @@ -1,424 +1,14 @@ -import { - AbstractPaymentService, - PaymentSessionData, - PaymentSessionStatus, -} from "@medusajs/medusa" -import Stripe from "stripe" +import StripeBase from "../helpers/stripe-base"; -class StripeProviderService extends AbstractPaymentService { +class StripeProviderService extends StripeBase { static identifier = "stripe" - constructor( - { customerService, totalsService, regionService, manager }, - options - ) { - super({ customerService, totalsService, regionService, manager }, options) - - /** - * Required Stripe options: - * { - * api_key: "stripe_secret_key", REQUIRED - * webhook_secret: "stripe_webhook_secret", REQUIRED - * // Use this flag to capture payment immediately (default is false) - * capture: true - * } - */ - this.options_ = options - - /** @private @const {Stripe} */ - this.stripe_ = Stripe(options.api_key) - - /** @private @const {CustomerService} */ - this.customerService_ = customerService - - /** @private @const {RegionService} */ - this.regionService_ = regionService - - /** @private @const {TotalsService} */ - this.totalsService_ = totalsService - - /** @private @const {EntityManager} */ - this.manager_ = manager + constructor(_, options) { + super(_, options) } - /** - * Get payment session status - * statuses. - * @param {PaymentSessionData} paymentData - the data stored with the payment session - * @return {Promise} the status of the order - */ - async getStatus(paymentData) { - const { id } = paymentData - const paymentIntent = await this.stripe_.paymentIntents.retrieve(id) - - switch (paymentIntent.status) { - case "requires_payment_method": - case "requires_confirmation": - case "processing": - return PaymentSessionStatus.PENDING - case "requires_action": - return PaymentSessionStatus.REQUIRES_MORE - case "canceled": - return PaymentSessionStatus.CANCELED - case "requires_capture": - case "succeeded": - return PaymentSessionStatus.AUTHORIZED - default: - return PaymentSessionStatus.PENDING - } - } - - /** - * Fetches a customers saved payment methods if registered in Stripe. - * @param {Customer} customer - customer to fetch saved cards for - * @return {Promise} saved payments methods - */ - async retrieveSavedMethods(customer) { - if (customer.metadata && customer.metadata.stripe_id) { - const methods = await this.stripe_.paymentMethods.list({ - customer: customer.metadata.stripe_id, - type: "card", - }) - - return methods.data - } - - return [] - } - - /** - * Fetches a Stripe customer - * @param {string} customerId - Stripe customer id - * @return {Promise} Stripe customer - */ - async retrieveCustomer(customerId) { - if (!customerId) { - return Promise.resolve() - } - return this.stripe_.customers.retrieve(customerId) - } - - /** - * Creates a Stripe customer using a Medusa customer. - * @param {object} customer - Customer data from Medusa - * @return {Promise} Stripe customer - */ - async createCustomer(customer) { - try { - const stripeCustomer = await this.stripe_.customers.create({ - email: customer.email, - }) - - if (customer.id) { - await this.customerService_ - .withTransaction(this.manager_) - .update(customer.id, { - metadata: { stripe_id: stripeCustomer.id }, - }) - } - - return stripeCustomer - } catch (error) { - throw error - } - } - - /** - * Creates a Stripe payment intent. - * If customer is not registered in Stripe, we do so. - * @param {Cart} cart - cart to create a payment for - * @param intentRequestData - * @return {Promise} Stripe payment intent - */ - async createPayment(cart, intentRequestData = {}) { - const { customer_id, region_id, email } = cart - const { currency_code } = await this.regionService_ - .withTransaction(this.manager_) - .retrieve(region_id) - - const amount = cart.total - - const intentRequest = { - description: - cart?.context?.payment_description ?? - this.options_?.payment_description, - amount: Math.round(amount), - currency: currency_code, - metadata: { cart_id: `${cart.id}` }, - capture_method: this.options_.capture ? "automatic" : "manual", - ...intentRequestData, - } - - if (this.options_?.automatic_payment_methods) { - intentRequest.automatic_payment_methods = { enabled: true } - } - - if (customer_id) { - const customer = await this.customerService_ - .withTransaction(this.manager_) - .retrieve(customer_id) - - if (customer.metadata?.stripe_id) { - intentRequest.customer = customer.metadata.stripe_id - } else { - const stripeCustomer = await this.createCustomer({ - email, - id: customer_id, - }) - - intentRequest.customer = stripeCustomer.id - } - } else { - const stripeCustomer = await this.createCustomer({ - email, - }) - - intentRequest.customer = stripeCustomer.id - } - - return await this.stripe_.paymentIntents.create(intentRequest) - } - - async createPaymentNew(paymentInput, intentRequestData = {}) { - const { customer, currency_code, amount, resource_id, cart } = paymentInput - const { id: customer_id, email } = customer ?? {} - - const intentRequest = { - description: - cart?.context?.payment_description ?? - this.options_?.payment_description, - amount: Math.round(amount), - currency: currency_code, - metadata: { resource_id }, - capture_method: this.options_.capture ? "automatic" : "manual", - ...intentRequestData, - } - - if (customer_id) { - if (customer.metadata?.stripe_id) { - intentRequest.customer = customer.metadata.stripe_id - } else { - const stripeCustomer = await this.createCustomer({ - email, - id: customer_id, - }) - - intentRequest.customer = stripeCustomer.id - } - } else if (email) { - const stripeCustomer = await this.createCustomer({ - email, - }) - - intentRequest.customer = stripeCustomer.id - } - - return await this.stripe_.paymentIntents.create(intentRequest) - } - - /** - * Retrieves Stripe payment intent. - * @param {PaymentData} paymentData - the data of the payment to retrieve - * @return {Promise} Stripe payment intent - */ - async retrievePayment(data) { - try { - return await this.stripe_.paymentIntents.retrieve(data.id) - } catch (error) { - throw error - } - } - - /** - * Gets a Stripe payment intent and returns it. - * @param {PaymentSession} paymentSession - the data of the payment to retrieve - * @return {Promise} Stripe payment intent - */ - async getPaymentData(paymentSession) { - try { - return await this.stripe_.paymentIntents.retrieve(paymentSession.data.id) - } catch (error) { - throw error - } - } - - /** - * Authorizes Stripe payment intent by simply returning - * the status for the payment intent in use. - * @param {PaymentSession} paymentSession - payment session data - * @param {Data} context - properties relevant to current context - * @return {Promise<{ data: PaymentSessionData; status: PaymentSessionStatus }>} result with data and status - */ - async authorizePayment(paymentSession, context = {}) { - const stat = await this.getStatus(paymentSession.data) - try { - return { data: paymentSession.data, status: stat } - } catch (error) { - throw error - } - } - - async updatePaymentData(sessionData, update) { - try { - return await this.stripe_.paymentIntents.update(sessionData.id, { - ...update.data, - }) - } catch (error) { - throw error - } - } - - /** - * Updates Stripe payment intent. - * @param {PaymentSessionData} paymentSessionData - payment session data. - * @param {Cart} cart - * @param intentRequestData - * @return {Promise} Stripe payment intent - */ - async updatePayment(paymentSessionData, cart, intentRequestData) { - try { - const stripeId = cart.customer?.metadata?.stripe_id || undefined - - if (stripeId !== paymentSessionData.customer) { - return await this.createPayment(cart, intentRequestData) - } else { - if ( - cart.total && - paymentSessionData.amount === Math.round(cart.total) - ) { - return paymentSessionData - } - - return await this.stripe_.paymentIntents.update(paymentSessionData.id, { - amount: Math.round(cart.total), - }) - } - } catch (error) { - throw error - } - } - - async updatePaymentNew(paymentSessionData, paymentInput, intentRequestData) { - try { - const stripeId = paymentInput.customer?.metadata?.stripe_id - - if (stripeId !== paymentSessionData.customer) { - return await this.createPaymentNew(paymentInput, intentRequestData) - } else { - if (paymentSessionData.amount === Math.round(paymentInput.amount)) { - return paymentSessionData - } - - return await this.stripe_.paymentIntents.update(paymentSessionData.id, { - amount: Math.round(paymentInput.amount), - }) - } - } catch (error) { - throw error - } - } - - async deletePayment(payment) { - try { - const { id } = payment.data - return this.stripe_.paymentIntents.cancel(id).catch((err) => { - if (err.statusCode === 400) { - return - } - throw err - }) - } catch (error) { - throw error - } - } - - /** - * Updates customer of Stripe payment intent. - * @param {string} paymentIntentId - id of payment intent to update - * @param {string} customerId - id of new Stripe customer - * @return {object} Stripe payment intent - */ - async updatePaymentIntentCustomer(paymentIntentId, customerId) { - try { - return await this.stripe_.paymentIntents.update(paymentIntentId, { - customer: customerId, - }) - } catch (error) { - throw error - } - } - - /** - * Captures payment for Stripe payment intent. - * @param {Payment} payment - payment method data from cart - * @return {Promise} Stripe payment intent - */ - async capturePayment(payment) { - const { id } = payment.data - try { - const intent = await this.stripe_.paymentIntents.capture(id) - return intent - } catch (error) { - if (error.code === "payment_intent_unexpected_state") { - if (error.payment_intent.status === "succeeded") { - return error.payment_intent - } - } - throw error - } - } - - /** - * Refunds payment for Stripe payment intent. - * @param {Payment} payment - payment method data from cart - * @param {number} refundAmount - amount to refund - * @return {Promise} refunded payment intent - */ - async refundPayment(payment, amountToRefund) { - const { id } = payment.data - try { - await this.stripe_.refunds.create({ - amount: Math.round(amountToRefund), - payment_intent: id, - }) - - return payment.data - } catch (error) { - throw error - } - } - - /** - * Cancels payment for Stripe payment intent. - * @param {Payment} payment - payment method data from cart - * @return {Promise} canceled payment intent - */ - async cancelPayment(payment) { - const { id } = payment.data - try { - return await this.stripe_.paymentIntents.cancel(id) - } catch (error) { - if (error.payment_intent.status === "canceled") { - return error.payment_intent - } - - throw error - } - } - - /** - * Constructs Stripe Webhook event - * @param {object} data - the data of the webhook request: req.body - * @param {object} signature - the Stripe signature on the event, that - * ensures integrity of the webhook event - * @return {object} Stripe Webhook event - */ - constructWebhookEvent(data, signature) { - return this.stripe_.webhooks.constructEvent( - data, - signature, - this.options_.webhook_secret - ) + get paymentIntentOptions() { + return {} } } diff --git a/packages/medusa-payment-stripe/src/services/stripe-przelewy24.js b/packages/medusa-payment-stripe/src/services/stripe-przelewy24.js index 9e10c43ac4..236d8c38b0 100644 --- a/packages/medusa-payment-stripe/src/services/stripe-przelewy24.js +++ b/packages/medusa-payment-stripe/src/services/stripe-przelewy24.js @@ -3,26 +3,8 @@ import StripeBase from "../helpers/stripe-base" class Przelewy24ProviderService extends StripeBase { static identifier = "stripe-przelewy24" - constructor( - { - stripeProviderService, - customerService, - totalsService, - regionService, - manager, - }, - options - ) { - super( - { - stripeProviderService, - customerService, - totalsService, - regionService, - manager, - }, - options - ) + constructor(_, options) { + super(_, options) } get paymentIntentOptions() { diff --git a/packages/medusa-payment-stripe/src/subscribers/cart.js b/packages/medusa-payment-stripe/src/subscribers/cart.js deleted file mode 100644 index 7def274536..0000000000 --- a/packages/medusa-payment-stripe/src/subscribers/cart.js +++ /dev/null @@ -1,60 +0,0 @@ -class CartSubscriber { - constructor({ - manager, - cartService, - paymentProviderService, - eventBusService, - }) { - this.cartService_ = cartService - this.paymentProviderService_ = paymentProviderService - this.eventBus_ = eventBusService - this.manager_ = manager - - this.eventBus_.subscribe("cart.customer_updated", async (cart) => { - await this.onCustomerUpdated(cart) - }) - } - - async onCustomerUpdated(cartId) { - await this.manager_.transaction(async (transactionManager) => { - const cart = await this.cartService_ - .withTransaction(transactionManager) - .retrieve(cartId, { - select: [ - "subtotal", - "tax_total", - "shipping_total", - "discount_total", - "gift_card_total", - "total", - ], - relations: [ - "billing_address", - "shipping_address", - "region", - "region.payment_providers", - "items", - "items.adjustments", - "payment_sessions", - "customer", - ], - }) - - if (!cart.payment_sessions?.length) { - return Promise.resolve() - } - - const session = cart.payment_sessions.find( - (ps) => ps.provider_id === "stripe" - ) - - if (session) { - return await this.paymentProviderService_ - .withTransaction(transactionManager) - .updateSession(session, cart) - } - }) - } -} - -export default CartSubscriber diff --git a/packages/medusa-plugin-economic/utils/eu-countries.js b/packages/medusa-plugin-economic/utils/eu-countries.js old mode 100755 new mode 100644 diff --git a/packages/medusa/src/api/routes/admin/batch/list-batch-jobs.ts b/packages/medusa/src/api/routes/admin/batch/list-batch-jobs.ts index 0c4ebf329c..3461b71a03 100644 --- a/packages/medusa/src/api/routes/admin/batch/list-batch-jobs.ts +++ b/packages/medusa/src/api/routes/admin/batch/list-batch-jobs.ts @@ -6,7 +6,7 @@ import { DateComparisonOperator } from "../../../../types/common" import { IsType } from "../../../../utils/validators/is-type" import { Request } from "express" import { pickBy } from "lodash" -import { isDefined } from "../../../../utils" +import { isDefined } from "medusa-core-utils" /** * @oas [get] /batch-jobs diff --git a/packages/medusa/src/api/routes/admin/gift-cards/list-gift-cards.ts b/packages/medusa/src/api/routes/admin/gift-cards/list-gift-cards.ts index 39c8e72d79..626edd548b 100644 --- a/packages/medusa/src/api/routes/admin/gift-cards/list-gift-cards.ts +++ b/packages/medusa/src/api/routes/admin/gift-cards/list-gift-cards.ts @@ -4,7 +4,7 @@ import { GiftCardService } from "../../../../services" import { Type } from "class-transformer" import { pickBy } from "lodash" import { validator } from "../../../../utils/validator" -import { isDefined } from "../../../../utils" +import { isDefined } from "medusa-core-utils" /** * @oas [get] /gift-cards diff --git a/packages/medusa/src/api/routes/admin/orders/request-return.ts b/packages/medusa/src/api/routes/admin/orders/request-return.ts index 42e9c9fa2d..36ea080e74 100644 --- a/packages/medusa/src/api/routes/admin/orders/request-return.ts +++ b/packages/medusa/src/api/routes/admin/orders/request-return.ts @@ -14,11 +14,10 @@ import { } from "../../../../services" import { Type } from "class-transformer" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" import { Order, Return } from "../../../../models" import { OrdersReturnItem } from "../../../../types/orders" -import { isDefined } from "../../../../utils" import { validator } from "../../../../utils/validator" /** diff --git a/packages/medusa/src/api/routes/admin/price-lists/list-price-list-products.ts b/packages/medusa/src/api/routes/admin/price-lists/list-price-list-products.ts index 0e0a0c79cd..3980af7875 100644 --- a/packages/medusa/src/api/routes/admin/price-lists/list-price-list-products.ts +++ b/packages/medusa/src/api/routes/admin/price-lists/list-price-list-products.ts @@ -17,7 +17,7 @@ import { ProductStatus } from "../../../../models" import { Request } from "express" import { Type } from "class-transformer" import { pickBy } from "lodash" -import { isDefined } from "../../../../utils" +import { isDefined } from "medusa-core-utils" /** * @oas [get] /price-lists/{id}/products diff --git a/packages/medusa/src/api/routes/admin/returns/receive-return.ts b/packages/medusa/src/api/routes/admin/returns/receive-return.ts index 3f61842eb2..09084a73bd 100644 --- a/packages/medusa/src/api/routes/admin/returns/receive-return.ts +++ b/packages/medusa/src/api/routes/admin/returns/receive-return.ts @@ -10,7 +10,7 @@ import { OrderService, ReturnService, SwapService } from "../../../../services" import { EntityManager } from "typeorm" import { Type } from "class-transformer" import { validator } from "../../../../utils/validator" -import { isDefined } from "../../../../utils" +import { isDefined } from "medusa-core-utils" /** * @oas [post] /returns/{id}/receive diff --git a/packages/medusa/src/api/routes/admin/tax-rates/create-tax-rate.ts b/packages/medusa/src/api/routes/admin/tax-rates/create-tax-rate.ts index 4c2e642860..f3b435d067 100644 --- a/packages/medusa/src/api/routes/admin/tax-rates/create-tax-rate.ts +++ b/packages/medusa/src/api/routes/admin/tax-rates/create-tax-rate.ts @@ -3,12 +3,11 @@ import { getRetrieveConfig, pickByConfig } from "./utils/get-query-config" import { EntityManager } from "typeorm" import { IsType } from "../../../../utils/validators/is-type" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { TaxRate } from "../../../.." import { TaxRateService } from "../../../../services" import { omit } from "lodash" import { validator } from "../../../../utils/validator" -import { isDefined } from "../../../../utils" /** * @oas [post] /tax-rates diff --git a/packages/medusa/src/api/routes/admin/tax-rates/update-tax-rate.ts b/packages/medusa/src/api/routes/admin/tax-rates/update-tax-rate.ts index 984dbb6e05..be11f888ce 100644 --- a/packages/medusa/src/api/routes/admin/tax-rates/update-tax-rate.ts +++ b/packages/medusa/src/api/routes/admin/tax-rates/update-tax-rate.ts @@ -7,7 +7,7 @@ import { TaxRate } from "../../../.." import { TaxRateService } from "../../../../services" import { omit } from "lodash" import { validator } from "../../../../utils/validator" -import { isDefined } from "../../../../utils" +import { isDefined } from "medusa-core-utils" /** * @oas [post] /tax-rates/{id} diff --git a/packages/medusa/src/api/routes/admin/tax-rates/utils/get-query-config.ts b/packages/medusa/src/api/routes/admin/tax-rates/utils/get-query-config.ts index 36e1952631..55a530c2f0 100644 --- a/packages/medusa/src/api/routes/admin/tax-rates/utils/get-query-config.ts +++ b/packages/medusa/src/api/routes/admin/tax-rates/utils/get-query-config.ts @@ -2,7 +2,7 @@ import { pick } from "lodash" import { defaultAdminTaxRatesFields, defaultAdminTaxRatesRelations } from "../" import { TaxRate } from "../../../../.." import { FindConfig } from "../../../../../types/common" -import { isDefined } from "../../../../../utils" +import { isDefined } from "medusa-core-utils" export function pickByConfig( obj: T | T[], diff --git a/packages/medusa/src/api/routes/store/carts/create-cart.ts b/packages/medusa/src/api/routes/store/carts/create-cart.ts index cbeb8a26f7..383f5dffc0 100644 --- a/packages/medusa/src/api/routes/store/carts/create-cart.ts +++ b/packages/medusa/src/api/routes/store/carts/create-cart.ts @@ -1,5 +1,5 @@ import { EntityManager } from "typeorm" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import reqIp from "request-ip" import { Type } from "class-transformer" import { @@ -22,7 +22,6 @@ import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators import { FlagRouter } from "../../../../utils/flag-router" import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels" import { CartCreateProps } from "../../../../types/cart" -import { isDefined } from "../../../../utils" import PublishableAPIKeysFeatureFlag from "../../../../loaders/feature-flags/publishable-api-keys" /** diff --git a/packages/medusa/src/api/routes/store/products/list-products.ts b/packages/medusa/src/api/routes/store/products/list-products.ts index 393e81f397..2ea7ad9b60 100644 --- a/packages/medusa/src/api/routes/store/products/list-products.ts +++ b/packages/medusa/src/api/routes/store/products/list-products.ts @@ -13,14 +13,13 @@ import { ProductService, RegionService, } from "../../../../services" - +import { isDefined } from "medusa-core-utils" import { defaultStoreProductsRelations } from "." import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels" import { Product } from "../../../../models" import PricingService from "../../../../services/pricing" import { DateComparisonOperator } from "../../../../types/common" import { PriceSelectionParams } from "../../../../types/price-selection" -import { isDefined } from "../../../../utils" import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators" import { validator } from "../../../../utils/validator" import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean" diff --git a/packages/medusa/src/controllers/customers/list-customers.ts b/packages/medusa/src/controllers/customers/list-customers.ts index f787086f81..0d8660fdb8 100644 --- a/packages/medusa/src/controllers/customers/list-customers.ts +++ b/packages/medusa/src/controllers/customers/list-customers.ts @@ -5,7 +5,7 @@ import { CustomerService } from "../../services" import { FindConfig } from "../../types/common" import { validator } from "../../utils/validator" import { Customer } from "../../models/customer" -import { isDefined } from "../../utils" +import { isDefined } from "medusa-core-utils" const listAndCount = async ( scope, diff --git a/packages/medusa/src/interfaces/payment-service.ts b/packages/medusa/src/interfaces/payment-service.ts index 6094ef8a2a..e4141ac8f6 100644 --- a/packages/medusa/src/interfaces/payment-service.ts +++ b/packages/medusa/src/interfaces/payment-service.ts @@ -1,18 +1,38 @@ import { TransactionBaseService } from "./transaction-base-service" import { + Address, Cart, Customer, Payment, PaymentSession, PaymentSessionStatus, + ShippingMethod, } from "../models" import { PaymentService } from "medusa-interfaces" -import { PaymentProviderDataInput } from "../types/payment-collection" export type Data = Record export type PaymentData = Data export type PaymentSessionData = Data +export type PaymentContext = { + cart: { + context: Record + id: string + email: string + shipping_address: Address | null + shipping_methods: ShippingMethod[] + } + currency_code: string + amount: number + resource_id?: string + customer?: Customer +} + +export type PaymentSessionResponse = { + update_requests: { customer_metadata: Record } + session_data: Record +} + export interface PaymentService extends TransactionBaseService { getIdentifier(): string @@ -23,6 +43,16 @@ export interface PaymentService extends TransactionBaseService { data: Data ): Promise + /** + * @param context The type of this argument is meant to be temporary and once the previous method signature + * will be removed, the type will only be PaymentContext instead of Cart & PaymentContext + */ + createPayment(context: Cart & PaymentContext): Promise + + /** + * @deprecated use createPayment(context: Cart & PaymentContext): Promise instead + * @param cart + */ createPayment(cart: Cart): Promise retrievePayment(paymentData: PaymentData): Promise @@ -76,23 +106,42 @@ export abstract class AbstractPaymentService data: Data ): Promise + /** + * @param context The type of this argument is meant to be temporary and once the previous method signature + * will be removed, the type will only be PaymentContext instead of Cart & PaymentContext + */ + public abstract createPayment( + context: Cart & PaymentContext + ): Promise + + /** + * @deprecated use createPayment(context: Cart & PaymentContext): Promise instead + * @param cart + */ public abstract createPayment(cart: Cart): Promise - public abstract createPaymentNew( - paymentInput: PaymentProviderDataInput - ): Promise public abstract retrievePayment(paymentData: PaymentData): Promise + /** + * @param paymentSessionData + * @param context The type of this argument is meant to be temporary and once the previous method signature + * will be removed, the type will only be PaymentContext instead of Cart & PaymentContext + */ + public abstract updatePayment( + paymentSessionData: PaymentSessionData, + context: Cart & PaymentContext + ): Promise + + /** + * @deprecated use updatePayment(paymentSessionData: PaymentSessionData, context: Cart & PaymentContext): Promise instead + * @param paymentSessionData + * @param cart + */ public abstract updatePayment( paymentSessionData: PaymentSessionData, cart: Cart ): Promise - public abstract updatePaymentNew( - paymentSessionData: PaymentSessionData, - paymentInput: PaymentProviderDataInput - ): Promise - public abstract authorizePayment( paymentSession: PaymentSession, context: Data diff --git a/packages/medusa/src/loaders/feature-flags/index.ts b/packages/medusa/src/loaders/feature-flags/index.ts index 1bb6abffda..31f38fad3a 100644 --- a/packages/medusa/src/loaders/feature-flags/index.ts +++ b/packages/medusa/src/loaders/feature-flags/index.ts @@ -4,7 +4,7 @@ import path from "path" import { trackFeatureFlag } from "medusa-telemetry" import { FlagSettings } from "../../types/feature-flags" import { Logger } from "../../types/global" -import { isDefined } from "../../utils" +import { isDefined } from "medusa-core-utils" import { FlagRouter } from "../../utils/flag-router" const isTruthy = (val: string | boolean | undefined): boolean => { diff --git a/packages/medusa/src/loaders/services.ts b/packages/medusa/src/loaders/services.ts index 67f297b26f..90a0d12792 100644 --- a/packages/medusa/src/loaders/services.ts +++ b/packages/medusa/src/loaders/services.ts @@ -2,7 +2,7 @@ import { asFunction } from "awilix" import glob from "glob" import path from "path" import { ConfigModule, MedusaContainer } from "../types/global" -import { isDefined } from "../utils" +import { isDefined } from "medusa-core-utils" import formatRegistrationName from "../utils/format-registration-name" type Options = { diff --git a/packages/medusa/src/loaders/strategies.ts b/packages/medusa/src/loaders/strategies.ts index e161b83de8..22808c3444 100644 --- a/packages/medusa/src/loaders/strategies.ts +++ b/packages/medusa/src/loaders/strategies.ts @@ -5,7 +5,7 @@ import { aliasTo, asFunction } from "awilix" import formatRegistrationName from "../utils/format-registration-name" import { isBatchJobStrategy } from "../interfaces" import { MedusaContainer } from "../types/global" -import { isDefined } from "../utils" +import { isDefined } from "medusa-core-utils" type LoaderOptions = { container: MedusaContainer diff --git a/packages/medusa/src/models/payment-session.ts b/packages/medusa/src/models/payment-session.ts index a5bcb429d1..ddfaa61b0c 100644 --- a/packages/medusa/src/models/payment-session.ts +++ b/packages/medusa/src/models/payment-session.ts @@ -1,12 +1,4 @@ -import { - BeforeInsert, - Column, - Entity, - Index, - JoinColumn, - ManyToOne, - Unique, -} from "typeorm" +import { BeforeInsert, Column, Entity, Index, JoinColumn, ManyToOne, Unique, } from "typeorm" import { BaseEntity } from "../interfaces" import { Cart } from "./cart" @@ -24,12 +16,13 @@ export enum PaymentSessionStatus { } @Unique("OneSelected", ["cart_id", "is_selected"]) +// TODO: This uniq constraint should be updated once the order edit flag is dropped and should add a where clause on cart_id is not null @Unique("UniqPaymentSessionCartIdProviderId", ["cart_id", "provider_id"]) @Entity() export class PaymentSession extends BaseEntity { @Index() @Column({ nullable: true }) - cart_id: string + cart_id: string | null @ManyToOne(() => Cart, (cart) => cart.payment_sessions) @JoinColumn({ name: "cart_id" }) diff --git a/packages/medusa/src/repositories/payment-collection.ts b/packages/medusa/src/repositories/payment-collection.ts index 382181f9dc..99a5190ef3 100644 --- a/packages/medusa/src/repositories/payment-collection.ts +++ b/packages/medusa/src/repositories/payment-collection.ts @@ -2,7 +2,6 @@ import { MedusaError } from "medusa-core-utils" import { PaymentCollection } from "./../models/payment-collection" import { EntityRepository, Repository } from "typeorm" import { FindConfig } from "../types/common" -import { PaymentSession } from "../models" @EntityRepository(PaymentCollection) // eslint-disable-next-line max-len @@ -61,12 +60,4 @@ export class PaymentCollectionRepository extends Repository { return paymentCollection[0] } - - async deleteMultiple(ids: string[]): Promise { - await this.createQueryBuilder() - .delete() - .from(PaymentSession) - .where("id IN (:...ids)", { ids }) - .execute() - } } diff --git a/packages/medusa/src/repositories/tax-rate.ts b/packages/medusa/src/repositories/tax-rate.ts index bd78337685..5b231555f8 100644 --- a/packages/medusa/src/repositories/tax-rate.ts +++ b/packages/medusa/src/repositories/tax-rate.ts @@ -1,22 +1,23 @@ import { unionBy } from "lodash" import { - In, - Not, DeleteResult, - SelectQueryBuilder, EntityRepository, FindManyOptions, FindOptionsUtils, + In, + Not, Repository, + SelectQueryBuilder, } from "typeorm" -import { TaxRate } from "../models/tax-rate" -import { ProductTaxRate } from "../models/product-tax-rate" -import { ProductTypeTaxRate } from "../models/product-type-tax-rate" -import { ShippingTaxRate } from "../models/shipping-tax-rate" -import { Product } from "../models/product" -import { ShippingMethod } from "../models/shipping-method" +import { + Product, + ProductTaxRate, + ProductTypeTaxRate, + ShippingTaxRate, + TaxRate, +} from "../models" import { TaxRateListByConfig } from "../types/tax-rate" -import { isDefined } from "../utils" +import { isDefined } from "medusa-core-utils" const resolveableFields = [ "product_count", diff --git a/packages/medusa/src/services/__fixtures__/payment-provider.ts b/packages/medusa/src/services/__fixtures__/payment-provider.ts new file mode 100644 index 0000000000..c33ff60d1e --- /dev/null +++ b/packages/medusa/src/services/__fixtures__/payment-provider.ts @@ -0,0 +1,18 @@ +import { asClass, asValue, createContainer } from "awilix" +import { MockManager, MockRepository } from "medusa-test-utils" +import PaymentProviderService from "../payment-provider"; +import { PaymentProviderServiceMock } from "../__mocks__/payment-provider"; +import { CustomerServiceMock } from "../__mocks__/customer"; +import { FlagRouter } from "../../utils/flag-router"; +import Logger from "../../loaders/logger"; + +export const defaultContainer = createContainer() +defaultContainer.register("paymentProviderService", asClass(PaymentProviderService)) +defaultContainer.register("manager", asValue(MockManager)) +defaultContainer.register("paymentSessionRepository", asValue(MockRepository())) +defaultContainer.register("paymentProviderRepository", asValue(PaymentProviderServiceMock)) +defaultContainer.register("paymentRepository", asValue(MockRepository())) +defaultContainer.register("refundRepository", asValue(MockRepository())) +defaultContainer.register("customerService", asValue(CustomerServiceMock)) +defaultContainer.register("featureFlagRouter", asValue(new FlagRouter({}))) +defaultContainer.register("logger", asValue(Logger)) diff --git a/packages/medusa/src/services/__mocks__/payment-provider.js b/packages/medusa/src/services/__mocks__/payment-provider.js index d32c1e3a31..8c2236a45b 100644 --- a/packages/medusa/src/services/__mocks__/payment-provider.js +++ b/packages/medusa/src/services/__mocks__/payment-provider.js @@ -1,3 +1,5 @@ +import { isString } from "../../utils"; + export const DefaultProviderMock = { getStatus: jest.fn().mockImplementation((data) => { if (data.money_id === "success") { @@ -28,12 +30,6 @@ export const PaymentProviderServiceMock = { return this }, updateSession: jest.fn().mockImplementation((session, cart) => { - return Promise.resolve({ - ...session.data, - id: `${session.data.id}_updated`, - }) - }), - updateSessionNew: jest.fn().mockImplementation((session, sessionInput) => { return Promise.resolve({ ...session, id: `${session.id}_updated`, @@ -45,16 +41,17 @@ export const PaymentProviderServiceMock = { registerInstalledProviders: jest.fn().mockImplementation(() => { return Promise.resolve() }), - createSession: jest.fn().mockImplementation((providerId, cart) => { - return Promise.resolve({ - id: `${providerId}_session`, - cartId: cart._id, - }) - }), - createSessionNew: jest.fn().mockImplementation((sessionInput) => { - return Promise.resolve({ - id: `${sessionInput.providerId}_session`, - }) + createSession: jest.fn().mockImplementation((providerIdOrSessionInput, cart) => { + if (isString(providerIdOrSessionInput)) { + return Promise.resolve({ + id: `${providerIdOrSessionInput}_session`, + cartId: cart._id, + }) + } else { + return Promise.resolve({ + id: `${providerIdOrSessionInput.providerId}_session`, + }) + } }), retrieveProvider: jest.fn().mockImplementation((providerId) => { if (providerId === "default_provider") { @@ -62,9 +59,9 @@ export const PaymentProviderServiceMock = { } throw new Error("Provider Not Found") }), - refreshSessionNew: jest.fn().mockImplementation((session, inputData) => { + refreshSession: jest.fn().mockImplementation((session, inputData) => { DefaultProviderMock.deletePayment() - PaymentProviderServiceMock.createSessionNew(inputData) + PaymentProviderServiceMock.createSession(inputData) return Promise.resolve({ ...session, id: `${session.id}_refreshed`, @@ -73,7 +70,7 @@ export const PaymentProviderServiceMock = { authorizePayment: jest .fn() .mockReturnValue(Promise.resolve({ status: "authorized" })), - createPaymentNew: jest.fn().mockImplementation((session, inputData) => { + createPayment: jest.fn().mockImplementation((session, inputData) => { Promise.resolve(inputData) }), } diff --git a/packages/medusa/src/services/__tests__/cart.js b/packages/medusa/src/services/__tests__/cart.js index ba0ded1d45..007e88c7db 100644 --- a/packages/medusa/src/services/__tests__/cart.js +++ b/packages/medusa/src/services/__tests__/cart.js @@ -1528,12 +1528,22 @@ describe("CartService", () => { expect(paymentProviderService.createSession).toHaveBeenCalledTimes(2) expect(paymentProviderService.createSession).toHaveBeenCalledWith( - "provider_1", - cart1 + { + cart: cart1, + customer: cart1.customer, + amount: cart1.total, + currency_code: cart1.region.currency_code, + provider_id: "provider_1", + } ) expect(paymentProviderService.createSession).toHaveBeenCalledWith( - "provider_2", - cart1 + { + cart: cart1, + customer: cart1.customer, + amount: cart1.total, + currency_code: cart1.region.currency_code, + provider_id: "provider_2", + } ) }) diff --git a/packages/medusa/src/services/__tests__/payment-collection.ts b/packages/medusa/src/services/__tests__/payment-collection.ts index ad2de73bfb..9ec0cedf6f 100644 --- a/packages/medusa/src/services/__tests__/payment-collection.ts +++ b/packages/medusa/src/services/__tests__/payment-collection.ts @@ -1,21 +1,8 @@ import { IdMap, MockManager, MockRepository } from "medusa-test-utils" -import { - CustomerService, - EventBusService, - PaymentCollectionService, - PaymentProviderService, - PaymentService, -} from "../index" -import { - PaymentCollectionStatus, - PaymentCollectionType, - PaymentCollection, -} from "../../models" +import { CustomerService, EventBusService, PaymentCollectionService, PaymentProviderService, } from "../index" +import { PaymentCollection, PaymentCollectionStatus, PaymentCollectionType, } from "../../models" import { EventBusServiceMock } from "../__mocks__/event-bus" -import { - DefaultProviderMock, - PaymentProviderServiceMock, -} from "../__mocks__/payment-provider" +import { DefaultProviderMock, PaymentProviderServiceMock, } from "../__mocks__/payment-provider" import { CustomerServiceMock } from "../__mocks__/customer" import { PaymentCollectionsSessionsBatchInput } from "../../types/payment-collection" @@ -395,7 +382,7 @@ describe("PaymentCollectionService", () => { `Cannot set payment sessions for a payment collection with status ${PaymentCollectionStatus.AUTHORIZED}` ) ) - expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0) + expect(PaymentProviderServiceMock.createSession).toBeCalledTimes(0) }) it("should ignore session if provider doesn't belong to the region", async () => { @@ -408,7 +395,7 @@ describe("PaymentCollectionService", () => { ) expect(multiRet).rejects.toThrow(`Payment provider not found`) - expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0) + expect(PaymentProviderServiceMock.createSession).toBeCalledTimes(0) }) it("should add a new session", async () => { @@ -420,7 +407,7 @@ describe("PaymentCollectionService", () => { "lebron" ) - expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes( 1 ) expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1) @@ -436,10 +423,10 @@ describe("PaymentCollectionService", () => { "lebron" ) - expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes( 0 ) - expect(PaymentProviderServiceMock.updateSessionNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes( 1 ) expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1) @@ -459,13 +446,13 @@ describe("PaymentCollectionService", () => { IdMap.getId("lebron") ) - expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes( 1 ) - expect(PaymentProviderServiceMock.updateSessionNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes( 0 ) - expect(paymentCollectionRepository.deleteMultiple).toHaveBeenCalledTimes( + expect(paymentCollectionRepository.delete).toHaveBeenCalledTimes( 1 ) @@ -497,7 +484,7 @@ describe("PaymentCollectionService", () => { ) ) - expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0) + expect(PaymentProviderServiceMock.createSession).toBeCalledTimes(0) }) it("should throw error if amount is different than requested", async () => { @@ -514,7 +501,7 @@ describe("PaymentCollectionService", () => { "customer1" ) - expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes( 0 ) expect(ret).rejects.toThrow( @@ -537,7 +524,7 @@ describe("PaymentCollectionService", () => { "customer1" ) - expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes( 0 ) expect(multiRet).rejects.toThrow( @@ -565,7 +552,7 @@ describe("PaymentCollectionService", () => { expect(multiRet).rejects.toThrow( `The sum of sessions is not equal to 100 on Payment Collection` ) - expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0) + expect(PaymentProviderServiceMock.createSession).toBeCalledTimes(0) }) it("should add a new session and update existing one", async () => { @@ -586,10 +573,10 @@ describe("PaymentCollectionService", () => { "lebron" ) - expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes( 1 ) - expect(PaymentProviderServiceMock.updateSessionNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes( 1 ) expect(CustomerServiceMock.retrieve).toHaveBeenCalledTimes(1) @@ -609,13 +596,13 @@ describe("PaymentCollectionService", () => { IdMap.getId("lebron") ) - expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes( 1 ) - expect(PaymentProviderServiceMock.updateSessionNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.updateSession).toHaveBeenCalledTimes( 0 ) - expect(paymentCollectionRepository.deleteMultiple).toHaveBeenCalledTimes( + expect(paymentCollectionRepository.delete).toHaveBeenCalledTimes( 1 ) @@ -630,10 +617,10 @@ describe("PaymentCollectionService", () => { ) expect( - PaymentProviderServiceMock.refreshSessionNew + PaymentProviderServiceMock.refreshSession ).toHaveBeenCalledTimes(1) expect(DefaultProviderMock.deletePayment).toHaveBeenCalledTimes(1) - expect(PaymentProviderServiceMock.createSessionNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.createSession).toHaveBeenCalledTimes( 1 ) }) @@ -650,9 +637,9 @@ describe("PaymentCollectionService", () => { "payCol_session-not-found" )} was not found` ) - expect(PaymentProviderServiceMock.refreshSessionNew).toBeCalledTimes(0) + expect(PaymentProviderServiceMock.refreshSession).toBeCalledTimes(0) expect(DefaultProviderMock.deletePayment).toBeCalledTimes(0) - expect(PaymentProviderServiceMock.createSessionNew).toBeCalledTimes(0) + expect(PaymentProviderServiceMock.createSession).toBeCalledTimes(0) }) }) @@ -695,7 +682,7 @@ describe("PaymentCollectionService", () => { expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes( 2 ) - expect(PaymentProviderServiceMock.createPaymentNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes( 2 ) expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1) @@ -710,7 +697,7 @@ describe("PaymentCollectionService", () => { expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes( 1 ) - expect(PaymentProviderServiceMock.createPaymentNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes( 1 ) expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(1) @@ -725,7 +712,7 @@ describe("PaymentCollectionService", () => { expect(PaymentProviderServiceMock.authorizePayment).toHaveBeenCalledTimes( 0 ) - expect(PaymentProviderServiceMock.createPaymentNew).toHaveBeenCalledTimes( + expect(PaymentProviderServiceMock.createPayment).toHaveBeenCalledTimes( 0 ) expect(EventBusServiceMock.emit).toHaveBeenCalledTimes(0) diff --git a/packages/medusa/src/services/__tests__/payment-provider.js b/packages/medusa/src/services/__tests__/payment-provider.js index 12ef0f32bd..99a3b26eb4 100644 --- a/packages/medusa/src/services/__tests__/payment-provider.js +++ b/packages/medusa/src/services/__tests__/payment-provider.js @@ -1,17 +1,15 @@ -import { MockManager, MockRepository } from "medusa-test-utils" +import { asValue, createContainer } from "awilix" +import { MockRepository } from "medusa-test-utils" import PaymentProviderService from "../payment-provider" +import { defaultContainer } from "../__fixtures__/payment-provider" import { testPayServiceMock } from "../__mocks__/test-pay" -import { FlagRouter } from "../../utils/flag-router" describe("PaymentProviderService", () => { describe("retrieveProvider", () => { - const container = { - manager: MockManager, - paymentSessionRepository: MockRepository(), - pp_default_provider: "good", - } + const container = createContainer({}, defaultContainer) + container.register("pp_default_provider", asValue("good")) - const providerService = new PaymentProviderService(container) + const providerService = container.resolve("paymentProviderService") it("successfully retrieves payment provider", () => { const provider = providerService.retrieveProvider("default_provider") @@ -30,56 +28,78 @@ describe("PaymentProviderService", () => { }) describe("createSession", () => { - const createPayment = jest.fn().mockReturnValue(Promise.resolve()) - const container = { - manager: MockManager, - paymentSessionRepository: MockRepository(), - pp_default_provider: { + const container = createContainer({}, defaultContainer) + container.register( + "pp_default_provider", + asValue({ withTransaction: function () { return this }, - createPayment, - }, - } + createPayment: jest.fn().mockReturnValue(Promise.resolve({})), + }) + ) - const providerService = new PaymentProviderService(container) + const providerService = container.resolve("paymentProviderService") it("successfully creates session", async () => { await providerService.createSession("default_provider", { + object: "cart", + region: { + currency_code: "usd", + }, total: 100, }) - expect(createPayment).toBeCalledTimes(1) - expect(createPayment).toBeCalledWith({ + const defaultProvider = container.resolve("pp_default_provider") + + expect(defaultProvider.createPayment).toBeCalledTimes(1) + expect(defaultProvider.createPayment).toBeCalledWith({ + amount: 100, + object: "cart", total: 100, + region: { + currency_code: "usd", + }, + cart: { + context: undefined, + email: undefined, + id: undefined, + shipping_address: undefined, + shipping_methods: undefined, + }, + currency_code: "usd", }) }) }) describe("updateSession", () => { - const updatePayment = jest.fn().mockReturnValue(Promise.resolve()) - - const container = { - manager: MockManager, - paymentSessionRepository: MockRepository({ - findOne: () => - Promise.resolve({ - id: "session", - provider_id: "default_provider", - data: { - id: "1234", - }, - }), - }), - pp_default_provider: { + const container = createContainer({}, defaultContainer) + container.register( + "paymentSessionRepository", + asValue( + MockRepository({ + findOne: () => + Promise.resolve({ + id: "session", + provider_id: "default_provider", + data: { + id: "1234", + }, + }), + }) + ) + ) + container.register( + "pp_default_provider", + asValue({ withTransaction: function () { return this }, - updatePayment, - }, - } + updatePayment: jest.fn().mockReturnValue(Promise.resolve()), + }) + ) - const providerService = new PaymentProviderService(container) + const providerService = container.resolve("paymentProviderService") it("successfully creates session", async () => { await providerService.updateSession( @@ -91,15 +111,28 @@ describe("PaymentProviderService", () => { }, }, { + object: "cart", total: 100, } ) - expect(updatePayment).toBeCalledTimes(1) - expect(updatePayment).toBeCalledWith( + const defaultProvider = container.resolve("pp_default_provider") + + expect(defaultProvider.updatePayment).toBeCalledTimes(1) + expect(defaultProvider.updatePayment).toBeCalledWith( { id: "1234" }, { + object: "cart", + amount: 100, total: 100, + cart: { + context: undefined, + email: undefined, + id: undefined, + shipping_address: undefined, + shipping_methods: undefined, + }, + currency_code: undefined, } ) }) @@ -107,50 +140,53 @@ describe("PaymentProviderService", () => { }) describe(`PaymentProviderService`, () => { - const featureFlagRouter = new FlagRouter({ - order_editing: false, - }) - - const container = { - manager: MockManager, - paymentSessionRepository: MockRepository({ - findOne: () => - Promise.resolve({ - id: "session", - provider_id: "default_provider", - data: { - id: "1234", - }, - }), - }), - paymentRepository: MockRepository({ - findOne: () => - Promise.resolve({ - id: "pay_jadazdjk", - provider_id: "default_provider", - data: { - id: "1234", - }, - }), - find: () => - Promise.resolve([ - { + const container = createContainer({}, defaultContainer) + container.register("pp_default_provider", asValue(testPayServiceMock)) + container.register( + "paymentSessionRepository", + asValue( + MockRepository({ + findOne: () => + Promise.resolve({ + id: "session", + provider_id: "default_provider", + data: { + id: "1234", + }, + }), + }) + ) + ) + container.register( + "paymentRepository", + asValue( + MockRepository({ + findOne: () => + Promise.resolve({ id: "pay_jadazdjk", provider_id: "default_provider", data: { id: "1234", }, - captured_at: new Date(), - amount: 100, - amount_refunded: 0, - }, - ]), - }), - refundRepository: MockRepository(), - pp_default_provider: testPayServiceMock, - featureFlagRouter, - } - const providerService = new PaymentProviderService(container) + }), + find: () => + Promise.resolve([ + { + id: "pay_jadazdjk", + provider_id: "default_provider", + data: { + id: "1234", + }, + captured_at: new Date(), + amount: 100, + amount_refunded: 0, + }, + ]), + }) + ) + ) + + const providerService = container.resolve("paymentProviderService") afterEach(() => { jest.clearAllMocks() @@ -163,12 +199,29 @@ describe(`PaymentProviderService`, () => { it("successfully creates session", async () => { await providerService.createSession("default_provider", { + object: "cart", + region: { + currency_code: "usd", + }, total: 100, }) expect(testPayServiceMock.createPayment).toBeCalledTimes(1) expect(testPayServiceMock.createPayment).toBeCalledWith({ + amount: 100, + object: "cart", total: 100, + region: { + currency_code: "usd", + }, + cart: { + context: undefined, + email: undefined, + id: undefined, + shipping_address: undefined, + shipping_methods: undefined, + }, + currency_code: "usd", }) }) @@ -182,6 +235,7 @@ describe(`PaymentProviderService`, () => { }, }, { + object: "cart", total: 100, } ) @@ -190,7 +244,16 @@ describe(`PaymentProviderService`, () => { expect(testPayServiceMock.updatePayment).toBeCalledWith( { id: "1234" }, { + amount: 100, + object: "cart", total: 100, + cart: { + context: undefined, + email: undefined, + id: undefined, + shipping_address: undefined, + shipping_methods: undefined, + }, } ) }) @@ -205,7 +268,9 @@ describe(`PaymentProviderService`, () => { }, }, { - total: 100, + provider_id: "default_provider", + amount: 100, + currency_code: "usd", } ) diff --git a/packages/medusa/src/services/batch-job.ts b/packages/medusa/src/services/batch-job.ts index 5f682bfc11..e331e21f69 100644 --- a/packages/medusa/src/services/batch-job.ts +++ b/packages/medusa/src/services/batch-job.ts @@ -11,8 +11,8 @@ import { } from "../types/batch-job" import { FindConfig } from "../types/common" import { TransactionBaseService } from "../interfaces" -import { buildQuery, isDefined } from "../utils" -import { MedusaError } from "medusa-core-utils" +import { buildQuery } from "../utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EventBusService, StrategyResolverService } from "./index" import { Request } from "express" diff --git a/packages/medusa/src/services/cart.ts b/packages/medusa/src/services/cart.ts index 8fb3ccda1f..a07e692069 100644 --- a/packages/medusa/src/services/cart.ts +++ b/packages/medusa/src/services/cart.ts @@ -1,5 +1,5 @@ import { isEmpty, isEqual } from "lodash" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { DeepPartial, EntityManager, In } from "typeorm" import { IPriceSelectionStrategy, TransactionBaseService } from "../interfaces" import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels" @@ -34,7 +34,7 @@ import { TotalField, WithRequiredProperty, } from "../types/common" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" import { FlagRouter } from "../utils/flag-router" import { validateEmail } from "../utils/is-email" import CustomShippingOptionService from "./custom-shipping-option" @@ -54,6 +54,7 @@ import ShippingOptionService from "./shipping-option" import StoreService from "./store" import TaxProviderService from "./tax-provider" import TotalsService from "./totals" +import { PaymentSessionInput } from "../types/payment" type InjectedDependencies = { manager: EntityManager @@ -1678,6 +1679,12 @@ class CartService extends TransactionBaseService { ) const { total, region } = cart + const partialSessionInput: Omit = { + cart: cart as Cart, + customer: cart.customer, + amount: cart.total, + currency_code: cart.region.currency_code, + } // If there are existing payment sessions ensure that these are up to date const seen: string[] = [] @@ -1695,9 +1702,15 @@ class CartService extends TransactionBaseService { .deleteSession(paymentSession) } else { seen.push(paymentSession.provider_id) + + const paymentSessionInput = { + ...partialSessionInput, + provider_id: paymentSession.provider_id, + } + return this.paymentProviderService_ .withTransaction(transactionManager) - .updateSession(paymentSession, cart) + .updateSession(paymentSession, paymentSessionInput) } }) ) @@ -1707,9 +1720,14 @@ class CartService extends TransactionBaseService { // If only one payment session exists, we preselect it if (region.payment_providers.length === 1 && !cart.payment_session) { const paymentProvider = region.payment_providers[0] + const paymentSessionInput = { + ...partialSessionInput, + provider_id: paymentProvider.id, + } + const paymentSession = await this.paymentProviderService_ .withTransaction(transactionManager) - .createSession(paymentProvider.id, cart) + .createSession(paymentSessionInput) paymentSession.is_selected = true @@ -1718,9 +1736,14 @@ class CartService extends TransactionBaseService { await Promise.all( region.payment_providers.map(async (paymentProvider) => { if (!seen.includes(paymentProvider.id)) { + const paymentSessionInput = { + ...partialSessionInput, + provider_id: paymentProvider.id, + } + return this.paymentProviderService_ .withTransaction(transactionManager) - .createSession(paymentProvider.id, cart) + .createSession(paymentSessionInput) } return }) @@ -1792,7 +1815,7 @@ class CartService extends TransactionBaseService { ): Promise { return await this.atomicPhase_( async (transactionManager: EntityManager) => { - const cart = await this.retrieve(cartId, { + const cart = await this.retrieveWithTotals(cartId, { relations: ["payment_sessions"], }) @@ -1805,7 +1828,13 @@ class CartService extends TransactionBaseService { // Delete the session with the provider await this.paymentProviderService_ .withTransaction(transactionManager) - .refreshSession(paymentSession, cart) + .refreshSession(paymentSession, { + cart: cart as Cart, + customer: cart.customer, + amount: cart.total, + currency_code: cart.region.currency_code, + provider_id: providerId, + }) } } diff --git a/packages/medusa/src/services/claim-item.ts b/packages/medusa/src/services/claim-item.ts index e7e1fb9f30..bdc83b5489 100644 --- a/packages/medusa/src/services/claim-item.ts +++ b/packages/medusa/src/services/claim-item.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" import { TransactionBaseService } from "../interfaces" import { ClaimImage, ClaimItem, ClaimTag } from "../models" @@ -7,7 +7,7 @@ import { ClaimItemRepository } from "../repositories/claim-item" import { ClaimTagRepository } from "../repositories/claim-tag" import { CreateClaimItemInput } from "../types/claim" import { FindConfig, Selector } from "../types/common" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" import EventBusService from "./event-bus" import LineItemService from "./line-item" diff --git a/packages/medusa/src/services/claim.ts b/packages/medusa/src/services/claim.ts index bfe06eaa8e..38b05cf42b 100644 --- a/packages/medusa/src/services/claim.ts +++ b/packages/medusa/src/services/claim.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { DeepPartial, EntityManager } from "typeorm" import { TransactionBaseService } from "../interfaces" import { @@ -16,7 +16,7 @@ import { LineItemRepository } from "../repositories/line-item" import { ShippingMethodRepository } from "../repositories/shipping-method" import { CreateClaimInput, UpdateClaimInput } from "../types/claim" import { FindConfig } from "../types/common" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" import ClaimItemService from "./claim-item" import EventBusService from "./event-bus" import FulfillmentService from "./fulfillment" diff --git a/packages/medusa/src/services/customer-group.ts b/packages/medusa/src/services/customer-group.ts index 37afd0b64e..4d52dd9b4d 100644 --- a/packages/medusa/src/services/customer-group.ts +++ b/packages/medusa/src/services/customer-group.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { DeepPartial, EntityManager, ILike } from "typeorm" import { CustomerService } from "." import { CustomerGroup } from ".." @@ -8,13 +8,7 @@ import { } from "../repositories/customer-group" import { FindConfig, Selector } from "../types/common" import { CustomerGroupUpdate } from "../types/customer-groups" -import { - buildQuery, - isDefined, - isString, - PostgresError, - setMetadata, -} from "../utils" +import { buildQuery, isString, PostgresError, setMetadata } from "../utils" import { TransactionBaseService } from "../interfaces" type CustomerGroupConstructorProps = { @@ -35,6 +29,7 @@ class CustomerGroupService extends TransactionBaseService { customerGroupRepository, customerService, }: CustomerGroupConstructorProps) { + // eslint-disable-next-line prefer-rest-params super(arguments[0]) this.manager_ = manager diff --git a/packages/medusa/src/services/customer.ts b/packages/medusa/src/services/customer.ts index 52ba9b612a..784b22b760 100644 --- a/packages/medusa/src/services/customer.ts +++ b/packages/medusa/src/services/customer.ts @@ -1,5 +1,5 @@ import jwt from "jsonwebtoken" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import Scrypt from "scrypt-kdf" import { DeepPartial, EntityManager } from "typeorm" import { EventBusService } from "." @@ -10,7 +10,7 @@ import { AddressRepository } from "../repositories/address" import { CustomerRepository } from "../repositories/customer" import { AddressCreatePayload, FindConfig, Selector } from "../types/common" import { CreateCustomerInput, UpdateCustomerInput } from "../types/customers" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" type InjectedDependencies = { manager: EntityManager diff --git a/packages/medusa/src/services/discount-condition.ts b/packages/medusa/src/services/discount-condition.ts index 058b468ab5..1932883209 100644 --- a/packages/medusa/src/services/discount-condition.ts +++ b/packages/medusa/src/services/discount-condition.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" import { EventBusService } from "." import { @@ -14,7 +14,7 @@ import { DiscountConditionRepository } from "../repositories/discount-condition" import { FindConfig } from "../types/common" import { DiscountConditionInput } from "../types/discount" import { TransactionBaseService } from "../interfaces" -import { buildQuery, isDefined, PostgresError } from "../utils" +import { buildQuery, PostgresError } from "../utils" type InjectedDependencies = { manager: EntityManager diff --git a/packages/medusa/src/services/discount.ts b/packages/medusa/src/services/discount.ts index cfa436cce2..23ea97b274 100644 --- a/packages/medusa/src/services/discount.ts +++ b/packages/medusa/src/services/discount.ts @@ -1,6 +1,6 @@ import { parse, toSeconds } from "iso8601-duration" import { isEmpty, omit } from "lodash" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { Brackets, DeepPartial, @@ -36,7 +36,7 @@ import { UpdateDiscountInput, UpdateDiscountRuleInput, } from "../types/discount" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" import { isFuture, isPast } from "../utils/date-helpers" import { FlagRouter } from "../utils/flag-router" import CustomerService from "./customer" diff --git a/packages/medusa/src/services/draft-order.ts b/packages/medusa/src/services/draft-order.ts index ae89a726bc..58a7451c41 100644 --- a/packages/medusa/src/services/draft-order.ts +++ b/packages/medusa/src/services/draft-order.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { Brackets, EntityManager, FindManyOptions, UpdateResult } from "typeorm" import { TransactionBaseService } from "../interfaces" import { Cart, CartType, DraftOrder, DraftOrderStatus } from "../models" @@ -7,7 +7,7 @@ import { OrderRepository } from "../repositories/order" import { PaymentRepository } from "../repositories/payment" import { ExtendedFindConfig, FindConfig } from "../types/common" import { DraftOrderCreateProps } from "../types/draft-orders" -import { buildQuery, isDefined } from "../utils" +import { buildQuery } from "../utils" import CartService from "./cart" import CustomShippingOptionService from "./custom-shipping-option" import EventBusService from "./event-bus" diff --git a/packages/medusa/src/services/fulfillment.ts b/packages/medusa/src/services/fulfillment.ts index 56de2b12b9..7fab2dd642 100644 --- a/packages/medusa/src/services/fulfillment.ts +++ b/packages/medusa/src/services/fulfillment.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" import { ShippingProfileService } from "." import { TransactionBaseService } from "../interfaces" @@ -13,7 +13,7 @@ import { FulfillmentItemPartition, FulFillmentItemType, } from "../types/fulfillment" -import { buildQuery, isDefined } from "../utils" +import { buildQuery } from "../utils" import FulfillmentProviderService from "./fulfillment-provider" import LineItemService from "./line-item" import TotalsService from "./totals" diff --git a/packages/medusa/src/services/gift-card.ts b/packages/medusa/src/services/gift-card.ts index aff58df16f..f69f33824a 100644 --- a/packages/medusa/src/services/gift-card.ts +++ b/packages/medusa/src/services/gift-card.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import randomize from "randomatic" import { EntityManager } from "typeorm" import { EventBusService } from "." @@ -17,7 +17,7 @@ import { CreateGiftCardTransactionInput, UpdateGiftCardInput, } from "../types/gift-card" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" import RegionService from "./region" type InjectedDependencies = { diff --git a/packages/medusa/src/services/idempotency-key.ts b/packages/medusa/src/services/idempotency-key.ts index 5a492df8b8..d13cca507b 100644 --- a/packages/medusa/src/services/idempotency-key.ts +++ b/packages/medusa/src/services/idempotency-key.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { v4 } from "uuid" import { TransactionBaseService } from "../interfaces" import { DeepPartial, EntityManager } from "typeorm" @@ -8,7 +8,6 @@ import { CreateIdempotencyKeyInput, IdempotencyCallbackResult, } from "../types/idempotency-key" -import { isDefined } from "../utils" const KEY_LOCKED_TIMEOUT = 1000 diff --git a/packages/medusa/src/services/inventory.ts b/packages/medusa/src/services/inventory.ts index 73559f751c..7d6f26592f 100644 --- a/packages/medusa/src/services/inventory.ts +++ b/packages/medusa/src/services/inventory.ts @@ -1,9 +1,8 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { TransactionBaseService } from "../interfaces" import { EntityManager } from "typeorm" import ProductVariantService from "./product-variant" import { ProductVariant } from "../models" -import { isDefined } from "../utils" type InventoryServiceProps = { manager: EntityManager diff --git a/packages/medusa/src/services/line-item-adjustment.ts b/packages/medusa/src/services/line-item-adjustment.ts index caa2125742..a23184e7ce 100644 --- a/packages/medusa/src/services/line-item-adjustment.ts +++ b/packages/medusa/src/services/line-item-adjustment.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager, In } from "typeorm" import { Cart, DiscountRuleType, LineItem, LineItemAdjustment } from "../models" @@ -7,7 +7,7 @@ import { FindConfig } from "../types/common" import { FilterableLineItemAdjustmentProps } from "../types/line-item-adjustment" import DiscountService from "./discount" import { TransactionBaseService } from "../interfaces" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" import { CalculationContextData } from "../types/totals" type LineItemAdjustmentServiceProps = { diff --git a/packages/medusa/src/services/new-totals.ts b/packages/medusa/src/services/new-totals.ts index 50efa2c671..17a3aeec8c 100644 --- a/packages/medusa/src/services/new-totals.ts +++ b/packages/medusa/src/services/new-totals.ts @@ -18,8 +18,8 @@ import { TaxProviderService } from "./index" import { LineAllocationsMap } from "../types/totals" import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing" import { FlagRouter } from "../utils/flag-router" -import { calculatePriceTaxAmount, isDefined } from "../utils" -import { MedusaError } from "medusa-core-utils" +import { calculatePriceTaxAmount } from "../utils" +import { isDefined, MedusaError } from "medusa-core-utils" type LineItemTotals = { unit_price: number diff --git a/packages/medusa/src/services/note.ts b/packages/medusa/src/services/note.ts index d14513ebad..c284ff225e 100644 --- a/packages/medusa/src/services/note.ts +++ b/packages/medusa/src/services/note.ts @@ -1,11 +1,11 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" import { TransactionBaseService } from "../interfaces" import { NoteRepository } from "../repositories/note" import EventBusService from "./event-bus" import { FindConfig, Selector } from "../types/common" import { Note } from "../models" -import { buildQuery, isDefined } from "../utils" +import { buildQuery } from "../utils" import { CreateNoteInput } from "../types/note" type InjectedDependencies = { diff --git a/packages/medusa/src/services/oauth.ts b/packages/medusa/src/services/oauth.ts index 205e6d861a..4a79a81eb4 100644 --- a/packages/medusa/src/services/oauth.ts +++ b/packages/medusa/src/services/oauth.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" import { TransactionBaseService } from "../interfaces" import { Oauth as OAuthModel } from "../models" @@ -6,7 +6,7 @@ import { OauthRepository } from "../repositories/oauth" import { Selector } from "../types/common" import { MedusaContainer } from "../types/global" import { CreateOauthInput, UpdateOauthInput } from "../types/oauth" -import { buildQuery, isDefined } from "../utils" +import { buildQuery } from "../utils" import EventBusService from "./event-bus" type InjectedDependencies = MedusaContainer & { diff --git a/packages/medusa/src/services/order-edit.ts b/packages/medusa/src/services/order-edit.ts index df1cbedd0c..f72be7682f 100644 --- a/packages/medusa/src/services/order-edit.ts +++ b/packages/medusa/src/services/order-edit.ts @@ -1,8 +1,8 @@ import { DeepPartial, EntityManager, ILike, IsNull } from "typeorm" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { FindConfig, Selector } from "../types/common" -import { buildQuery, isDefined, isString } from "../utils" +import { buildQuery, isString } from "../utils" import { OrderEditRepository } from "../repositories/order-edit" import { Cart, diff --git a/packages/medusa/src/services/order.ts b/packages/medusa/src/services/order.ts index 99ade188d2..ff65646fb8 100644 --- a/packages/medusa/src/services/order.ts +++ b/packages/medusa/src/services/order.ts @@ -1,5 +1,4 @@ -import jwt, { JwtPayload } from "jsonwebtoken" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { Brackets, EntityManager } from "typeorm" import { TransactionBaseService } from "../interfaces" import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels" @@ -28,7 +27,7 @@ import { } from "../types/fulfillment" import { UpdateOrderInput } from "../types/orders" import { CreateShippingMethodDto } from "../types/shipping-options" -import { buildQuery, isDefined, isString, setMetadata } from "../utils" +import { buildQuery, isString, setMetadata } from "../utils" import { FlagRouter } from "../utils/flag-router" import CartService from "./cart" import CustomerService from "./customer" @@ -46,8 +45,6 @@ import ShippingOptionService from "./shipping-option" import ShippingProfileService from "./shipping-profile" import TotalsService from "./totals" import { NewTotalsService, TaxProviderService } from "./index" -import { ConfigModule } from "../types/global" -import logger from "../loaders/logger" export const ORDER_CART_ALREADY_EXISTS_ERROR = "Order from cart already exists" diff --git a/packages/medusa/src/services/payment-collection.ts b/packages/medusa/src/services/payment-collection.ts index bf86615b9d..fc4434a852 100644 --- a/packages/medusa/src/services/payment-collection.ts +++ b/packages/medusa/src/services/payment-collection.ts @@ -1,8 +1,8 @@ import { DeepPartial, EntityManager } from "typeorm" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { FindConfig } from "../types/common" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" import { PaymentCollectionRepository } from "../repositories/payment-collection" import { PaymentCollection, @@ -21,8 +21,8 @@ import { CreatePaymentCollectionInput, PaymentCollectionsSessionsBatchInput, PaymentCollectionsSessionsInput, - PaymentProviderDataInput, } from "../types/payment-collection" +import { CreatePaymentInput, PaymentSessionInput } from "../types/payment" type InjectedDependencies = { manager: EntityManager @@ -194,10 +194,10 @@ export default class PaymentCollectionService extends TransactionBaseService { } if ( - [ + ![ PaymentCollectionStatus.CANCELED, PaymentCollectionStatus.NOT_PAID, - ].includes(paymentCollection.status) === false + ].includes(paymentCollection.status) ) { throw new MedusaError( MedusaError.Types.NOT_ALLOWED, @@ -251,10 +251,14 @@ export default class PaymentCollectionService extends TransactionBaseService { ) } - sessionsInput = sessionsInput.filter((session) => { - return !!payCol.region.payment_providers.find(({ id }) => { - return id === session.provider_id + const payColRegionProviderMap = new Map( + payCol.region.payment_providers.map((provider) => { + return [provider.id, provider] }) + ) + + sessionsInput = sessionsInput.filter((session) => { + return !!payColRegionProviderMap.get(session.provider_id) }) if (!this.isValidTotalAmount(payCol.amount, sessionsInput)) { @@ -273,15 +277,30 @@ export default class PaymentCollectionService extends TransactionBaseService { }) .catch(() => null) + const payColSessionMap = new Map( + (payCol.payment_sessions ?? []).map((session) => { + return [session.id, session] + }) + ) + + const paymentProviderTx = + this.paymentProviderService_.withTransaction(manager) + const selectedSessionIds: string[] = [] const paymentSessions: PaymentSession[] = [] for (const session of sessionsInput) { - const existingSession = payCol.payment_sessions?.find( - (sess) => session.session_id === sess?.id - ) + const existingSession = + session.session_id && payColSessionMap.get(session.session_id) - const inputData: PaymentProviderDataInput = { + const inputData: PaymentSessionInput = { + cart: { + email: customer?.email || "", + context: {}, + shipping_methods: [], + shipping_address: null, + id: "", + }, resource_id: payCol.id, currency_code: payCol.currency_code, amount: session.amount, @@ -289,21 +308,19 @@ export default class PaymentCollectionService extends TransactionBaseService { customer, } + let paymentSession + if (existingSession) { - const paymentSession = await this.paymentProviderService_ - .withTransaction(manager) - .updateSessionNew(existingSession, inputData) - - selectedSessionIds.push(existingSession.id) - paymentSessions.push(paymentSession) + paymentSession = await paymentProviderTx.updateSession( + existingSession, + inputData + ) } else { - const paymentSession = await this.paymentProviderService_ - .withTransaction(manager) - .createSessionNew(inputData) - - selectedSessionIds.push(paymentSession.id) - paymentSessions.push(paymentSession) + paymentSession = await paymentProviderTx.createSession(inputData) } + + selectedSessionIds.push(paymentSession.id) + paymentSessions.push(paymentSession) } if (payCol.payment_sessions?.length) { @@ -312,15 +329,16 @@ export default class PaymentCollectionService extends TransactionBaseService { ) if (removeSessions.length) { - await paymentCollectionRepository.deleteMultiple( + await paymentCollectionRepository.delete( removeSessions.map((sess) => sess.id) ) + const paymentProviderTx = + this.paymentProviderService_.withTransaction(manager) + Promise.all( removeSessions.map(async (sess) => - this.paymentProviderService_ - .withTransaction(manager) - .deleteSessionNew(sess) + paymentProviderTx.deleteSession(sess) ) ).catch(() => void 0) } @@ -335,7 +353,7 @@ export default class PaymentCollectionService extends TransactionBaseService { /** * Manages a single payment sessions of a payment collection. * @param paymentCollectionId - the id of the payment collection - * @param sessionsInput - object containing payment session info + * @param sessionInput - object containing payment session info * @param customerId - the id of the customer * @return the payment collection and its payment session. */ @@ -381,7 +399,15 @@ export default class PaymentCollectionService extends TransactionBaseService { .catch(() => null) const paymentSessions: PaymentSession[] = [] - const inputData: PaymentProviderDataInput = { + + const inputData: PaymentSessionInput = { + cart: { + email: customer?.email || "", + context: {}, + shipping_methods: [], + shipping_address: null, + id: "", + }, resource_id: payCol.id, currency_code: payCol.currency_code, amount: payCol.amount, @@ -396,13 +422,13 @@ export default class PaymentCollectionService extends TransactionBaseService { if (existingSession) { const paymentSession = await this.paymentProviderService_ .withTransaction(manager) - .updateSessionNew(existingSession, inputData) + .updateSession(existingSession, inputData) paymentSessions.push(paymentSession) } else { const paymentSession = await this.paymentProviderService_ .withTransaction(manager) - .createSessionNew(inputData) + .createSession(inputData) paymentSessions.push(paymentSession) @@ -411,15 +437,16 @@ export default class PaymentCollectionService extends TransactionBaseService { ) if (removeSessions.length) { - await paymentCollectionRepository.deleteMultiple( + await paymentCollectionRepository.delete( removeSessions.map((sess) => sess.id) ) + const paymentProviderTx = + this.paymentProviderService_.withTransaction(manager) + Promise.all( removeSessions.map(async (sess) => - this.paymentProviderService_ - .withTransaction(manager) - .deleteSessionNew(sess) + paymentProviderTx.deleteSession(sess) ) ).catch(() => void 0) } @@ -487,7 +514,14 @@ export default class PaymentCollectionService extends TransactionBaseService { }) .catch(() => null) - const inputData: PaymentProviderDataInput = { + const inputData: PaymentSessionInput = { + cart: { + email: customer?.email || "", + context: {}, + shipping_methods: [], + shipping_address: null, + id: "", + }, resource_id: payCol.id, currency_code: payCol.currency_code, amount: session.amount, @@ -497,7 +531,7 @@ export default class PaymentCollectionService extends TransactionBaseService { const sessionRefreshed = await this.paymentProviderService_ .withTransaction(manager) - .refreshSessionNew(session, inputData) + .refreshSession(session, inputData) payCol.payment_sessions = payCol.payment_sessions.map((sess) => { if (sess.id === sessionId) { @@ -581,6 +615,9 @@ export default class PaymentCollectionService extends TransactionBaseService { ) } + const paymentProviderTx = + this.paymentProviderService_.withTransaction(manager) + let authorizedAmount = 0 for (let i = 0; i < payCol.payment_sessions.length; i++) { const session = payCol.payment_sessions[i] @@ -594,32 +631,27 @@ export default class PaymentCollectionService extends TransactionBaseService { continue } - const auth = await this.paymentProviderService_ - .withTransaction(manager) - .authorizePayment(session, context) + const paymentSession = await paymentProviderTx.authorizePayment( + session, + context + ) - if (auth) { - payCol.payment_sessions[i] = auth + if (paymentSession) { + payCol.payment_sessions[i] = paymentSession } - if (auth?.status === PaymentSessionStatus.AUTHORIZED) { + if (paymentSession?.status === PaymentSessionStatus.AUTHORIZED) { authorizedAmount += session.amount - const inputData: Omit & { - payment_session: PaymentSession - } = { + const inputData: CreatePaymentInput = { amount: session.amount, currency_code: payCol.currency_code, provider_id: session.provider_id, resource_id: payCol.id, - payment_session: auth, + payment_session: paymentSession, } - payCol.payments.push( - await this.paymentProviderService_ - .withTransaction(manager) - .createPaymentNew(inputData) - ) + payCol.payments.push(await paymentProviderTx.createPayment(inputData)) } } diff --git a/packages/medusa/src/services/payment-provider.ts b/packages/medusa/src/services/payment-provider.ts index f10a9714f9..07cb1faa44 100644 --- a/packages/medusa/src/services/payment-provider.ts +++ b/packages/medusa/src/services/payment-provider.ts @@ -1,12 +1,17 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { BasePaymentService } from "medusa-interfaces" -import { AbstractPaymentService, TransactionBaseService } from "../interfaces" +import { + AbstractPaymentService, + PaymentContext, + PaymentSessionResponse, + TransactionBaseService, +} from "../interfaces" import { EntityManager } from "typeorm" import { PaymentSessionRepository } from "../repositories/payment-session" import { PaymentRepository } from "../repositories/payment" import { RefundRepository } from "../repositories/refund" import { PaymentProviderRepository } from "../repositories/payment-provider" -import { buildQuery } from "../utils" +import { buildQuery, isString } from "../utils" import { FindConfig, Selector } from "../types/common" import { Cart, @@ -16,11 +21,12 @@ import { PaymentSessionStatus, Refund, } from "../models" -import { PaymentProviderDataInput } from "../types/payment-collection" import { FlagRouter } from "../utils/flag-router" import OrderEditingFeatureFlag from "../loaders/feature-flags/order-editing" import PaymentService from "./payment" import { Logger } from "../types/global" +import { CreatePaymentInput, PaymentSessionInput } from "../types/payment" +import { CustomerService } from "./index" type PaymentProviderKey = `pp_${string}` | "systemPaymentProviderService" type InjectedDependencies = { @@ -30,6 +36,7 @@ type InjectedDependencies = { paymentRepository: typeof PaymentRepository refundRepository: typeof RefundRepository paymentService: PaymentService + customerService: CustomerService featureFlagRouter: FlagRouter logger: Logger } & { @@ -50,6 +57,7 @@ export default class PaymentProviderService extends TransactionBaseService { protected readonly paymentProviderRepository_: typeof PaymentProviderRepository protected readonly paymentRepository_: typeof PaymentRepository protected readonly refundRepository_: typeof RefundRepository + protected readonly customerService_: CustomerService protected readonly logger_: Logger protected readonly featureFlagRouter_: FlagRouter @@ -63,6 +71,7 @@ export default class PaymentProviderService extends TransactionBaseService { this.paymentProviderRepository_ = container.paymentProviderRepository this.paymentRepository_ = container.paymentRepository this.refundRepository_ = container.refundRepository + this.customerService_ = container.customerService this.featureFlagRouter_ = container.featureFlagRouter this.logger_ = container.logger } @@ -165,55 +174,59 @@ export default class PaymentProviderService extends TransactionBaseService { /** * Creates a payment session with the given provider. - * @param providerId - the id of the provider to create payment with + * @param providerIdOrSessionInput - the id of the provider to create payment with or the input data * @param cart - a cart object used to calculate the amount, etc. from * @return the payment session */ - async createSession(providerId: string, cart: Cart): Promise { - return await this.atomicPhase_(async (transactionManager) => { - const provider = this.retrieveProvider(providerId) - const sessionData = await provider - .withTransaction(transactionManager) - .createPayment(cart) - - const sessionRepo = transactionManager.getCustomRepository( - this.paymentSessionRepository_ - ) - - const toCreate = { - cart_id: cart.id, - provider_id: providerId, - data: sessionData, - status: "pending", - } - - const created = sessionRepo.create(toCreate) - return await sessionRepo.save(created) - }) - } - - async createSessionNew( - sessionInput: PaymentProviderDataInput + async createSession< + TInput extends string | PaymentSessionInput = string | PaymentSessionInput + >( + providerIdOrSessionInput: TInput, + ...[cart]: TInput extends string ? [Cart] : [never?] ): Promise { return await this.atomicPhase_(async (transactionManager) => { - const provider = this.retrieveProvider(sessionInput.provider_id) - const sessionData = await provider - .withTransaction(transactionManager) - .createPaymentNew(sessionInput) + const providerId = isString(providerIdOrSessionInput) + ? providerIdOrSessionInput + : providerIdOrSessionInput.provider_id + const data = ( + isString(providerIdOrSessionInput) ? cart : providerIdOrSessionInput + ) as Cart | PaymentSessionInput - const sessionRepo = transactionManager.getCustomRepository( - this.paymentSessionRepository_ + const provider = this.retrieveProvider(providerId) + const context = this.buildPaymentContext(data) + + if (!isDefined(context.currency_code) || !isDefined(context.amount)) { + throw new MedusaError( + MedusaError.Types.INVALID_ARGUMENT, + "`currency_code` and `amount` are required to create payment session." + ) + } + + const paymentResponse = await provider + .withTransaction(transactionManager) + .createPayment(context) + + const sessionData = paymentResponse.session_data ?? paymentResponse + + await this.processUpdateRequestsData( + { + customer: { id: context.customer?.id }, + }, + paymentResponse ) - const toCreate = { - provider_id: sessionInput.provider_id, - data: sessionData, - status: "pending", - amount: sessionInput.amount, - } as PaymentSession + const amount = this.featureFlagRouter_.isFeatureEnabled( + OrderEditingFeatureFlag.key + ) + ? context.amount + : undefined - const created = sessionRepo.create(toCreate) - return await sessionRepo.save(created) + return await this.saveSession(providerId, { + cartId: context.id, + sessionData, + status: PaymentSessionStatus.PENDING, + amount, + }) }) } @@ -222,16 +235,22 @@ export default class PaymentProviderService extends TransactionBaseService { * This means, that we delete the current one and create a new. * @param paymentSession - the payment session object to * update - * @param cart - a cart object used to calculate the amount, etc. from + * @param sessionInput * @return the payment session */ async refreshSession( - paymentSession: PaymentSession, - cart: Cart + paymentSession: { + id: string + data: Record + provider_id: string + }, + sessionInput: PaymentSessionInput ): Promise { return this.atomicPhase_(async (transactionManager) => { const session = await this.retrieveSession(paymentSession.id) - const provider = this.retrieveProvider(paymentSession.provider_id) + const provider = this.retrieveProvider( + paymentSession.provider_id + ) await provider.withTransaction(transactionManager).deletePayment(session) const sessionRepo = transactionManager.getCustomRepository( @@ -239,88 +258,44 @@ export default class PaymentProviderService extends TransactionBaseService { ) await sessionRepo.remove(session) - - const sessionData = await provider - .withTransaction(transactionManager) - .createPayment(cart) - - const toCreate = { - cart_id: cart.id, - provider_id: session.provider_id, - data: sessionData, - is_selected: true, - status: "pending", - } - - const created = sessionRepo.create(toCreate) - return await sessionRepo.save(created) - }) - } - - async refreshSessionNew( - paymentSession: PaymentSession, - sessionInput: PaymentProviderDataInput - ): Promise { - return this.atomicPhase_(async (transactionManager) => { - const session = await this.retrieveSession(paymentSession.id) - const provider = this.retrieveProvider(paymentSession.provider_id) - - await provider.withTransaction(transactionManager).deletePayment(session) - - const sessionRepo = transactionManager.getCustomRepository( - this.paymentSessionRepository_ - ) - - await sessionRepo.remove(session) - - return await this.createSessionNew(sessionInput) + return await this.createSession(sessionInput) }) } /** - * Updates an existing payment session. - * @param paymentSession - the payment session object to - * update - * @param cart - the cart object to update for - * @return the updated payment session + * Update a payment session with the given provider. + * @param paymentSession - The paymentSession to update + * @param sessionInput + * @return the payment session */ async updateSession( - paymentSession: PaymentSession, - cart: Cart + paymentSession: { + id: string + data: Record + provider_id: string + }, + sessionInput: Cart | PaymentSessionInput ): Promise { return await this.atomicPhase_(async (transactionManager) => { - const session = await this.retrieveSession(paymentSession.id) - const provider = this.retrieveProvider(paymentSession.provider_id) - session.data = await provider - .withTransaction(transactionManager) - .updatePayment(paymentSession.data, cart) - - const sessionRepo = transactionManager.getCustomRepository( - this.paymentSessionRepository_ - ) - return await sessionRepo.save(session) - }) - } - - async updateSessionNew( - paymentSession: PaymentSession, - sessionInput: PaymentProviderDataInput - ): Promise { - return await this.atomicPhase_(async (transactionManager) => { - const session = await this.retrieveSession(paymentSession.id) const provider = this.retrieveProvider(paymentSession.provider_id) - session.amount = sessionInput.amount - paymentSession.data.amount = sessionInput.amount - session.data = await provider + const context = this.buildPaymentContext(sessionInput) + + const sessionData = await provider .withTransaction(transactionManager) - .updatePaymentNew(paymentSession.data, sessionInput) + .updatePayment(paymentSession.data, context) - const sessionRepo = transactionManager.getCustomRepository( - this.paymentSessionRepository_ + const amount = this.featureFlagRouter_.isFeatureEnabled( + OrderEditingFeatureFlag.key ) + ? context.amount + : undefined - return await sessionRepo.save(session) + return await this.saveSession(paymentSession.provider_id, { + payment_session_id: paymentSession.id, + sessionData, + amount, + }) }) } @@ -349,15 +324,6 @@ export default class PaymentProviderService extends TransactionBaseService { }) } - async deleteSessionNew(paymentSession: PaymentSession): Promise { - return await this.atomicPhase_(async (transactionManager) => { - const provider = this.retrieveProvider(paymentSession.provider_id) - return await provider - .withTransaction(transactionManager) - .deletePayment(paymentSession) - }) - } - /** * Finds a provider given an id * @param {string} providerId - the id of the provider to get @@ -387,26 +353,22 @@ export default class PaymentProviderService extends TransactionBaseService { } } - async createPayment(data: { - cart_id: string - amount: number - currency_code: string - payment_session: PaymentSession - }): Promise { + async createPayment(data: CreatePaymentInput): Promise { return await this.atomicPhase_(async (transactionManager) => { - const { payment_session: paymentSession, currency_code, amount } = data + const { payment_session, currency_code, amount, provider_id } = data + const providerId = provider_id ?? payment_session.provider_id - const provider = this.retrieveProvider(paymentSession.provider_id) + const provider = this.retrieveProvider(providerId) const paymentData = await provider .withTransaction(transactionManager) - .getPaymentData(paymentSession) + .getPaymentData(payment_session) const paymentRepo = transactionManager.getCustomRepository( this.paymentRepository_ ) const created = paymentRepo.create({ - provider_id: paymentSession.provider_id, + provider_id: providerId, amount, currency_code, data: paymentData, @@ -417,30 +379,6 @@ export default class PaymentProviderService extends TransactionBaseService { }) } - async createPaymentNew( - paymentInput: Omit & { - payment_session: PaymentSession - } - ): Promise { - return await this.atomicPhase_(async (transactionManager) => { - const { payment_session, currency_code, amount, provider_id } = - paymentInput - - const provider = this.retrieveProvider(provider_id) - const paymentData = await provider - .withTransaction(transactionManager) - .getPaymentData(payment_session) - - const paymentService = this.container_.paymentService - return await paymentService.withTransaction(transactionManager).create({ - provider_id, - amount, - currency_code, - data: paymentData, - }) - }) - } - async updatePayment( paymentId: string, data: { order_id?: string; swap_id?: string } @@ -696,4 +634,123 @@ export default class PaymentProviderService extends TransactionBaseService { return refund } + + /** + * Build the create session context for both legacy and new API + * @param cartOrData + * @protected + */ + protected buildPaymentContext( + cartOrData: Cart | PaymentSessionInput + ): Cart & PaymentContext { + const cart = + "object" in cartOrData && cartOrData.object === "cart" + ? cartOrData + : ((cartOrData as PaymentSessionInput).cart as Cart) + + const context = {} as Cart & PaymentContext + + // TODO: only to support legacy API. Once we are ready to break the API, the cartOrData will only support PaymentSessionInput + if ("object" in cartOrData && cartOrData.object === "cart") { + context.cart = { + context: cart.context, + shipping_address: cart.shipping_address, + id: cart.id, + email: cart.email, + shipping_methods: cart.shipping_methods, + } + context.amount = cart.total! + context.currency_code = cart.region?.currency_code + Object.assign(context, cart) + } else { + const data = cartOrData as PaymentSessionInput + context.cart = data.cart + context.amount = data.amount + context.currency_code = data.currency_code + Object.assign(context, cart) + } + + return context + } + + /** + * Create or update a Payment session data. + * @param providerId + * @param data + * @protected + */ + protected async saveSession( + providerId: string, + data: { + payment_session_id?: string + cartId?: string + amount?: number + sessionData: Record + isSelected?: boolean + status?: PaymentSessionStatus + } + ): Promise { + const manager = this.transactionManager_ ?? this.manager_ + + if ( + data.amount != null && + !this.featureFlagRouter_.isFeatureEnabled(OrderEditingFeatureFlag.key) + ) { + throw new MedusaError( + MedusaError.Types.INVALID_ARGUMENT, + "Amount on payment sessions is only available with the OrderEditing API currently guarded by feature flag `MEDUSA_FF_ORDER_EDITING`. Read more about feature flags here: https://docs.medusajs.com/advanced/backend/feature-flags/toggle/" + ) + } + + const sessionRepo = manager.getCustomRepository( + this.paymentSessionRepository_ + ) + + if (data.payment_session_id) { + const session = await this.retrieveSession(data.payment_session_id) + session.data = data.sessionData ?? session.data + session.status = data.status ?? session.status + session.amount = data.amount ?? session.amount + return await sessionRepo.save(session) + } else { + const toCreate: Partial = { + cart_id: data.cartId || null, + provider_id: providerId, + data: data.sessionData, + is_selected: data.isSelected, + status: data.status, + amount: data.amount, + } + + const created = sessionRepo.create(toCreate) + return await sessionRepo.save(created) + } + } + + /** + * Process the collected data. Can be used every time we need to process some collected data returned by the provider + * @param data + * @param paymentResponse + * @protected + */ + protected async processUpdateRequestsData( + data: { customer?: { id?: string } } = {}, + paymentResponse: PaymentSessionResponse | Record + ): Promise { + const { update_requests } = paymentResponse as PaymentSessionResponse + + if (!update_requests) { + return + } + + const manager = this.transactionManager_ ?? this.manager_ + + if (update_requests.customer_metadata && data.customer?.id) { + await this.customerService_ + .withTransaction(manager) + .update(data.customer.id, { + metadata: update_requests.customer_metadata, + }) + } + } } diff --git a/packages/medusa/src/services/payment.ts b/packages/medusa/src/services/payment.ts index ae8a6220a9..5c5b1473e6 100644 --- a/packages/medusa/src/services/payment.ts +++ b/packages/medusa/src/services/payment.ts @@ -1,11 +1,11 @@ import { PaymentRepository } from "./../repositories/payment" import { EntityManager } from "typeorm" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { Payment, Refund } from "../models" import { TransactionBaseService } from "../interfaces" import { EventBusService, PaymentProviderService } from "./index" -import { buildQuery, isDefined } from "../utils" +import { buildQuery } from "../utils" import { FindConfig } from "../types/common" type InjectedDependencies = { diff --git a/packages/medusa/src/services/price-list.ts b/packages/medusa/src/services/price-list.ts index f353f201d3..8dca976944 100644 --- a/packages/medusa/src/services/price-list.ts +++ b/packages/medusa/src/services/price-list.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { DeepPartial, EntityManager, FindOperator } from "typeorm" import { CustomerGroupService } from "." import { CustomerGroup, PriceList, Product, ProductVariant } from "../models" @@ -18,7 +18,7 @@ import { import ProductService from "./product" import RegionService from "./region" import { TransactionBaseService } from "../interfaces" -import { buildQuery, isDefined } from "../utils" +import { buildQuery } from "../utils" import { FilterableProductProps } from "../types/product" import ProductVariantService from "./product-variant" import { FilterableProductVariantProps } from "../types/product-variant" diff --git a/packages/medusa/src/services/product-collection.ts b/packages/medusa/src/services/product-collection.ts index e4eccb70a8..a34a3668ae 100644 --- a/packages/medusa/src/services/product-collection.ts +++ b/packages/medusa/src/services/product-collection.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { Brackets, EntityManager, ILike } from "typeorm" import { TransactionBaseService } from "../interfaces" import { ProductCollection } from "../models" @@ -9,7 +9,7 @@ import { CreateProductCollection, UpdateProductCollection, } from "../types/product-collection" -import { buildQuery, isDefined, isString, setMetadata } from "../utils" +import { buildQuery, isString, setMetadata } from "../utils" import EventBusService from "./event-bus" type InjectedDependencies = { diff --git a/packages/medusa/src/services/product-variant.ts b/packages/medusa/src/services/product-variant.ts index 5f8ab27ac4..4e29b3c04a 100644 --- a/packages/medusa/src/services/product-variant.ts +++ b/packages/medusa/src/services/product-variant.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { Brackets, EntityManager, ILike, SelectQueryBuilder } from "typeorm" import { IPriceSelectionStrategy, @@ -29,7 +29,7 @@ import { ProductVariantPrice, UpdateProductVariantInput, } from "../types/product-variant" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" class ProductVariantService extends TransactionBaseService { static Events = { diff --git a/packages/medusa/src/services/product.ts b/packages/medusa/src/services/product.ts index ae6f232c00..a5423d5f1b 100644 --- a/packages/medusa/src/services/product.ts +++ b/packages/medusa/src/services/product.ts @@ -1,6 +1,6 @@ import { FlagRouter } from "../utils/flag-router" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" import { ProductVariantService, SearchService } from "." import { TransactionBaseService } from "../interfaces" @@ -31,7 +31,7 @@ import { ProductSelector, UpdateProductInput, } from "../types/product" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" import EventBusService from "./event-bus" type InjectedDependencies = { diff --git a/packages/medusa/src/services/publishable-api-key.ts b/packages/medusa/src/services/publishable-api-key.ts index 584d24da23..f0ac4d7dd3 100644 --- a/packages/medusa/src/services/publishable-api-key.ts +++ b/packages/medusa/src/services/publishable-api-key.ts @@ -1,12 +1,12 @@ import { EntityManager, ILike } from "typeorm" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { PublishableApiKeyRepository } from "../repositories/publishable-api-key" import { FindConfig, Selector } from "../types/common" import { PublishableApiKey, SalesChannel } from "../models" import { TransactionBaseService } from "../interfaces" import EventBusService from "./event-bus" -import { buildQuery, isDefined, isString } from "../utils" +import { buildQuery, isString } from "../utils" import { CreatePublishableApiKeyInput, UpdatePublishableApiKeyInput, diff --git a/packages/medusa/src/services/region.ts b/packages/medusa/src/services/region.ts index 90119eb163..40aad7c70d 100644 --- a/packages/medusa/src/services/region.ts +++ b/packages/medusa/src/services/region.ts @@ -1,6 +1,6 @@ import { DeepPartial, EntityManager } from "typeorm" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { TransactionBaseService } from "../interfaces" import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing" @@ -13,7 +13,7 @@ import { RegionRepository } from "../repositories/region" import { TaxProviderRepository } from "../repositories/tax-provider" import { FindConfig, Selector } from "../types/common" import { CreateRegionInput, UpdateRegionInput } from "../types/region" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" import { countries } from "../utils/countries" import { FlagRouter } from "../utils/flag-router" import EventBusService from "./event-bus" diff --git a/packages/medusa/src/services/return-reason.ts b/packages/medusa/src/services/return-reason.ts index b1ff1fbf5f..a00e9c904c 100644 --- a/packages/medusa/src/services/return-reason.ts +++ b/packages/medusa/src/services/return-reason.ts @@ -1,11 +1,11 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" import { TransactionBaseService } from "../interfaces" -import { Return, ReturnReason } from "../models" +import { ReturnReason } from "../models" import { ReturnReasonRepository } from "../repositories/return-reason" import { FindConfig, Selector } from "../types/common" import { CreateReturnReason, UpdateReturnReason } from "../types/return-reason" -import { buildQuery, isDefined } from "../utils" +import { buildQuery } from "../utils" type InjectedDependencies = { manager: EntityManager diff --git a/packages/medusa/src/services/return.ts b/packages/medusa/src/services/return.ts index 959bd76733..3f90eb90f2 100644 --- a/packages/medusa/src/services/return.ts +++ b/packages/medusa/src/services/return.ts @@ -1,5 +1,4 @@ -import { isDefined } from "class-validator" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { DeepPartial, EntityManager } from "typeorm" import { TransactionBaseService } from "../interfaces" import { diff --git a/packages/medusa/src/services/sales-channel.ts b/packages/medusa/src/services/sales-channel.ts index 1bae7f4474..d25c71cbfe 100644 --- a/packages/medusa/src/services/sales-channel.ts +++ b/packages/medusa/src/services/sales-channel.ts @@ -6,12 +6,12 @@ import { FindConfig, QuerySelector, Selector } from "../types/common" import { EntityManager } from "typeorm" import EventBusService from "./event-bus" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { SalesChannel } from "../models" import { SalesChannelRepository } from "../repositories/sales-channel" import StoreService from "./store" import { TransactionBaseService } from "../interfaces" -import { buildQuery, isDefined } from "../utils" +import { buildQuery } from "../utils" type InjectedDependencies = { salesChannelRepository: typeof SalesChannelRepository diff --git a/packages/medusa/src/services/shipping-option.ts b/packages/medusa/src/services/shipping-option.ts index 4982372c64..c674ac6dd9 100644 --- a/packages/medusa/src/services/shipping-option.ts +++ b/packages/medusa/src/services/shipping-option.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" import { TransactionBaseService } from "../interfaces" import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing" @@ -21,7 +21,7 @@ import { UpdateShippingOptionInput, ValidatePriceTypeAndAmountInput, } from "../types/shipping-options" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" import { FlagRouter } from "../utils/flag-router" import FulfillmentProviderService from "./fulfillment-provider" import RegionService from "./region" diff --git a/packages/medusa/src/services/shipping-profile.ts b/packages/medusa/src/services/shipping-profile.ts index 518e0207f0..49a34192c9 100644 --- a/packages/medusa/src/services/shipping-profile.ts +++ b/packages/medusa/src/services/shipping-profile.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" import { TransactionBaseService } from "../interfaces" import { @@ -15,7 +15,7 @@ import { CreateShippingProfile, UpdateShippingProfile, } from "../types/shipping-profile" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" import CustomShippingOptionService from "./custom-shipping-option" import ProductService from "./product" import ShippingOptionService from "./shipping-option" diff --git a/packages/medusa/src/services/swap.ts b/packages/medusa/src/services/swap.ts index 2a36d9f8d2..6caf4eb527 100644 --- a/packages/medusa/src/services/swap.ts +++ b/packages/medusa/src/services/swap.ts @@ -1,7 +1,7 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" -import { buildQuery, isDefined, setMetadata, validateId } from "../utils" +import { buildQuery, setMetadata, validateId } from "../utils" import { TransactionBaseService } from "../interfaces" import LineItemAdjustmentService from "./line-item-adjustment" diff --git a/packages/medusa/src/services/tax-rate.ts b/packages/medusa/src/services/tax-rate.ts index 3ce370329b..272b6ab82c 100644 --- a/packages/medusa/src/services/tax-rate.ts +++ b/packages/medusa/src/services/tax-rate.ts @@ -1,9 +1,11 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { EntityManager } from "typeorm" -import { ProductTaxRate } from "../models/product-tax-rate" -import { ProductTypeTaxRate } from "../models/product-type-tax-rate" -import { ShippingTaxRate } from "../models/shipping-tax-rate" -import { TaxRate } from "../models/tax-rate" +import { + ProductTaxRate, + ProductTypeTaxRate, + ShippingTaxRate, + TaxRate, +} from "../models" import { TaxRateRepository } from "../repositories/tax-rate" import ProductService from "../services/product" import ProductTypeService from "../services/product-type" @@ -15,7 +17,7 @@ import { TaxRateListByConfig, UpdateTaxRateInput, } from "../types/tax-rate" -import { buildQuery, isDefined, PostgresError } from "../utils" +import { buildQuery, PostgresError } from "../utils" import { TransactionBaseService } from "../interfaces" import { FindConditions } from "typeorm/find-options/FindConditions" diff --git a/packages/medusa/src/services/totals.ts b/packages/medusa/src/services/totals.ts index 524225ca64..2433404ffd 100644 --- a/packages/medusa/src/services/totals.ts +++ b/packages/medusa/src/services/totals.ts @@ -1,4 +1,4 @@ -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import { ITaxCalculationStrategy, TaxCalculationContext, @@ -28,7 +28,7 @@ import { import TaxProviderService from "./tax-provider" import { EntityManager } from "typeorm" -import { calculatePriceTaxAmount, isDefined } from "../utils" +import { calculatePriceTaxAmount } from "../utils" import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing" import { FlagRouter } from "../utils/flag-router" diff --git a/packages/medusa/src/services/user.ts b/packages/medusa/src/services/user.ts index ae9e6a6950..eda257f93a 100644 --- a/packages/medusa/src/services/user.ts +++ b/packages/medusa/src/services/user.ts @@ -1,5 +1,5 @@ import jwt from "jsonwebtoken" -import { MedusaError } from "medusa-core-utils" +import { isDefined, MedusaError } from "medusa-core-utils" import Scrypt from "scrypt-kdf" import { EntityManager } from "typeorm" import { TransactionBaseService } from "../interfaces" @@ -12,7 +12,7 @@ import { FilterableUserProps, UpdateUserInput, } from "../types/user" -import { buildQuery, isDefined, setMetadata } from "../utils" +import { buildQuery, setMetadata } from "../utils" import { FlagRouter } from "../utils/flag-router" import { validateEmail } from "../utils/is-email" import AnalyticsConfigService from "./analytics-config" diff --git a/packages/medusa/src/strategies/price-selection.ts b/packages/medusa/src/strategies/price-selection.ts index 490e83f24b..42eb2e8d1e 100644 --- a/packages/medusa/src/strategies/price-selection.ts +++ b/packages/medusa/src/strategies/price-selection.ts @@ -7,11 +7,11 @@ import { PriceSelectionResult, PriceType, } from "../interfaces" +import { isDefined } from "medusa-core-utils" import TaxInclusivePricingFeatureFlag from "../loaders/feature-flags/tax-inclusive-pricing" import { MoneyAmountRepository } from "../repositories/money-amount" import { TaxServiceRate } from "../types/tax-service" import { FlagRouter } from "../utils/flag-router" -import { isDefined } from "../utils" class PriceSelectionStrategy extends AbstractPriceSelectionStrategy { protected manager_: EntityManager diff --git a/packages/medusa/src/subscribers/cart.ts b/packages/medusa/src/subscribers/cart.ts new file mode 100644 index 0000000000..36175add11 --- /dev/null +++ b/packages/medusa/src/subscribers/cart.ts @@ -0,0 +1,70 @@ +import EventBusService from "../services/event-bus" +import { CartService, PaymentProviderService } from "../services" +import { EntityManager } from "typeorm" + +type InjectedDependencies = { + eventBusService: EventBusService + cartService: CartService + paymentProviderService: PaymentProviderService + manager: EntityManager +} + +class CartSubscriber { + protected readonly manager_: EntityManager + protected readonly cartService_: CartService + protected readonly paymentProviderService_: PaymentProviderService + protected readonly eventBus_: EventBusService + + constructor({ + manager, + cartService, + paymentProviderService, + eventBusService, + }: InjectedDependencies) { + this.cartService_ = cartService + this.paymentProviderService_ = paymentProviderService + this.eventBus_ = eventBusService + this.manager_ = manager + + this.eventBus_.subscribe( + CartService.Events.CUSTOMER_UPDATED, + async (cartId) => { + await this.onCustomerUpdated(cartId) + } + ) + } + + async onCustomerUpdated(cartId) { + await this.manager_.transaction( + "SERIALIZABLE", + async (transactionManager) => { + const cart = await this.cartService_ + .withTransaction(transactionManager) + .retrieveWithTotals(cartId, { + relations: [ + "billing_address", + "region", + "region.payment_providers", + "payment_sessions", + "customer", + ], + }) + + if (!cart.payment_sessions?.length) { + return + } + + const paymentProviderServiceTx = + this.paymentProviderService_.withTransaction(transactionManager) + + return await Promise.all( + cart.payment_sessions.map(async (paymentSession) => { + return paymentProviderServiceTx.updateSession(paymentSession, cart) + }) + ) + } + ) + } +} + +export default CartSubscriber diff --git a/packages/medusa/src/types/payment-collection.ts b/packages/medusa/src/types/payment-collection.ts index 0463b990c3..6897958ae9 100644 --- a/packages/medusa/src/types/payment-collection.ts +++ b/packages/medusa/src/types/payment-collection.ts @@ -1,9 +1,4 @@ -import { - Cart, - Customer, - PaymentCollection, - PaymentCollectionType, -} from "../models" +import { PaymentCollection, PaymentCollectionType } from "../models" export type CreatePaymentCollectionInput = { region_id: string @@ -25,15 +20,6 @@ export type PaymentCollectionsSessionsInput = { provider_id: string } -export type PaymentProviderDataInput = { - resource_id: string - customer: Partial | null - currency_code: string - provider_id: string - amount: number - cart_id?: string - cart?: Cart -} export const defaultPaymentCollectionRelations = [ "region", "region.payment_providers", diff --git a/packages/medusa/src/types/payment.ts b/packages/medusa/src/types/payment.ts new file mode 100644 index 0000000000..b379672aab --- /dev/null +++ b/packages/medusa/src/types/payment.ts @@ -0,0 +1,34 @@ +import { + Address, + Cart, + Customer, + PaymentSession, + ShippingMethod, +} from "../models" + +export type PaymentSessionInput = { + provider_id: string + // TODO: Support legacy payment provider API> Once we are ready to break the api then we can remove the Cart type + cart: + | Cart + | { + context: Record + id: string + email: string + shipping_address: Address | null + shipping_methods: ShippingMethod[] + } + customer?: Customer | null + currency_code: string + amount: number + resource_id?: string +} + +export type CreatePaymentInput = { + cart_id?: string + amount: number + currency_code: string + provider_id?: string + payment_session: PaymentSession + resource_id?: string +} diff --git a/packages/medusa/src/utils/get-query-config.ts b/packages/medusa/src/utils/get-query-config.ts index 76c83f4776..377ebd2142 100644 --- a/packages/medusa/src/utils/get-query-config.ts +++ b/packages/medusa/src/utils/get-query-config.ts @@ -1,8 +1,7 @@ import { pick } from "lodash" import { FindConfig, QueryConfig, RequestQueryFields } from "../types/common" -import { MedusaError } from "medusa-core-utils/dist" +import { isDefined, MedusaError } from "medusa-core-utils" import { BaseEntity } from "../interfaces" -import { isDefined } from "." export function pickByConfig( obj: TModel | TModel[], diff --git a/packages/medusa/src/utils/index.ts b/packages/medusa/src/utils/index.ts index f4ae33fe78..398554bac6 100644 --- a/packages/medusa/src/utils/index.ts +++ b/packages/medusa/src/utils/index.ts @@ -3,7 +3,6 @@ export * from "./set-metadata" export * from "./validate-id" export * from "./generate-entity-id" export * from "./remove-undefined-properties" -export * from "./is-defined" export * from "./is-string" export * from "./calculate-price-tax-amount" export * from "./csv-cell-content-formatter" diff --git a/packages/medusa/src/utils/remove-undefined-properties.ts b/packages/medusa/src/utils/remove-undefined-properties.ts index cf1c82d060..edc1237419 100644 --- a/packages/medusa/src/utils/remove-undefined-properties.ts +++ b/packages/medusa/src/utils/remove-undefined-properties.ts @@ -1,4 +1,4 @@ -import { isDefined } from "./is-defined" +import { isDefined } from "medusa-core-utils" export function removeUndefinedProperties(inputObj: T): T { const removeProperties = (obj: T) => {