Adds a payment provider service that can easily retrieve payment providers

This commit is contained in:
Sebastian Rindom
2020-02-18 14:58:11 +01:00
parent d114b806d6
commit f8fcd5a3dd
7 changed files with 313 additions and 4 deletions
@@ -0,0 +1,28 @@
export const DefaultProviderMock = {
getStatus: jest.fn().mockImplementation(data => {
if (data.money_id === "success") {
return Promise.resolve("authorized")
}
if (data.money_id === "fail") {
return Promise.resolve("fail")
}
return Promise.resolve("initial")
}),
}
export const PaymentProviderServiceMock = {
retrieveProvider: jest.fn().mockImplementation(providerId => {
if (providerId === "default_provider") {
return DefaultProviderMock
}
return undefined
}),
}
const mock = jest.fn().mockImplementation(() => {
return PaymentProviderServiceMock
})
export default mock
@@ -6,8 +6,8 @@ export const regions = {
name: "Test Region",
countries: ["DK", "US", "DE"],
tax_rate: 0.25,
payment_providers: ["default_provider"],
shipping_providers: ["test_shipper"],
payment_providers: ["default_provider", "unregistered"],
fulfillment_providers: ["test_shipper"],
currency_code: "usd",
},
regionFrance: {
@@ -1,6 +1,10 @@
import mongoose from "mongoose"
import { IdMap } from "medusa-test-utils"
import CartService from "../cart"
import {
PaymentProviderServiceMock,
DefaultProviderMock,
} from "../__mocks__/payment-provider"
import { ProductVariantServiceMock } from "../__mocks__/product-variant"
import { RegionServiceMock } from "../__mocks__/region"
import { CartModelMock, carts } from "../../models/__mocks__/cart"
@@ -588,4 +592,148 @@ describe("CartService", () => {
)
})
})
describe("setPaymentMethod", () => {
const cartService = new CartService({
cartModel: CartModelMock,
regionService: RegionServiceMock,
paymentProviderService: PaymentProviderServiceMock,
})
beforeEach(() => {
jest.clearAllMocks()
})
it("successfully sets a payment method", async () => {
const paymentMethod = {
provider_id: "default_provider",
data: {
money_id: "success",
},
}
await cartService.setPaymentMethod(
IdMap.getId("cartWithLine"),
paymentMethod
)
expect(RegionServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(RegionServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("testRegion")
)
expect(PaymentProviderServiceMock.retrieveProvider).toHaveBeenCalledTimes(
1
)
expect(PaymentProviderServiceMock.retrieveProvider).toHaveBeenCalledWith(
"default_provider"
)
expect(DefaultProviderMock.getStatus).toHaveBeenCalledTimes(1)
expect(DefaultProviderMock.getStatus).toHaveBeenCalledWith({
money_id: "success",
})
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
{
_id: IdMap.getId("cartWithLine"),
},
{
$set: { payment_method: paymentMethod },
}
)
})
it("fails if the region does not contain the provider_id", async () => {
const paymentMethod = {
provider_id: "unknown_provider",
data: {
money_id: "success",
},
}
try {
await cartService.setPaymentMethod(
IdMap.getId("cartWithLine"),
paymentMethod
)
} catch (err) {
expect(RegionServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(RegionServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("testRegion")
)
expect(err.message).toEqual(
`The payment method is not available in this region`
)
}
})
it("fails if the payment provider is not registered", async () => {
const paymentMethod = {
provider_id: "unregistered",
data: {
money_id: "success",
},
}
try {
await cartService.setPaymentMethod(
IdMap.getId("cartWithLine"),
paymentMethod
)
} catch (err) {
expect(RegionServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(RegionServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("testRegion")
)
expect(
PaymentProviderServiceMock.retrieveProvider
).toHaveBeenCalledTimes(1)
expect(
PaymentProviderServiceMock.retrieveProvider
).toHaveBeenCalledWith("unregistered")
expect(err.message).toEqual(
`The payment provider for the payment method was not found`
)
}
})
it("fails if the payment is not authorized", async () => {
const paymentMethod = {
provider_id: "default_provider",
data: {
money_id: "fail",
},
}
try {
await cartService.setPaymentMethod(
IdMap.getId("cartWithLine"),
paymentMethod
)
} catch (err) {
expect(RegionServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(RegionServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("testRegion")
)
expect(
PaymentProviderServiceMock.retrieveProvider
).toHaveBeenCalledTimes(1)
expect(
PaymentProviderServiceMock.retrieveProvider
).toHaveBeenCalledWith("default_provider")
expect(DefaultProviderMock.getStatus).toHaveBeenCalledTimes(1)
expect(DefaultProviderMock.getStatus).toHaveBeenCalledWith({
money_id: "fail",
})
expect(err.message).toEqual(`The payment method was not authorized`)
}
})
})
})
+79 -2
View File
@@ -9,10 +9,11 @@ import { BaseService } from "medusa-interfaces"
class CartService extends BaseService {
constructor({
cartModel,
regionService,
eventBusService,
paymentProviderService,
productService,
productVariantService,
eventBusService,
regionService,
}) {
super()
@@ -30,6 +31,9 @@ class CartService extends BaseService {
/** @private @const {RegionService} */
this.regionService_ = regionService
/** @private @const {PaymentProviderService} */
this.paymentProviderService_ = paymentProviderService
}
/**
@@ -381,6 +385,79 @@ class CartService extends BaseService {
)
}
/**
* @typedef {object} PaymentMethod
* @property {string} provider_id - the identifier of the payment method's
* provider
* @property {object} data - the data associated with the payment method
*/
/**
* Sets a payment method for a cart.
* @param {string} cartId - the id of the cart to add payment method to
* @param {PaymentMethod} paymentMethod - the method to be set to the cart
* @returns {Promise} result of update operation
*/
async setPaymentMethod(cartId, paymentMethod) {
const cart = await this.retrieve(cartId)
if (!cart) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
"The cart was not found"
)
}
const region = await this.regionService_.retrieve(cart.region_id)
if (!region) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`The cart does not have a region associated`
)
}
// The region must have the provider id in its providers array
if (
!(
region.payment_providers.length &&
region.payment_providers.includes(paymentMethod.provider_id)
)
) {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`The payment method is not available in this region`
)
}
// Check if the payment method has been authorized.
const provider = this.paymentProviderService_.retrieveProvider(
paymentMethod.provider_id
)
if (!provider) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`The payment provider for the payment method was not found`
)
}
const status = await provider.getStatus(paymentMethod.data)
if (status !== "authorized") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
`The payment method was not authorized`
)
}
// At this point we can register the payment method.
return this.cartModel_.updateOne(
{
_id: cart._id,
},
{
$set: { payment_method: paymentMethod },
}
)
}
/**
* Set's the region of a cart.
* @param {string} cartId - the id of the cart to set region on
@@ -0,0 +1,33 @@
import { MedusaError } from "medusa-core-utils"
/**
* Helps retrive payment providers
*/
class PaymentProviderService {
constructor(container) {
/** @private {logger} */
this.container_ = container
}
/**
* Handles incoming jobs.
* @param job {{ eventName: (string), data: (any) }}
* eventName - the name of the event to process
* data - data to send to the subscriber
*
* @returns {PaymentService} the payment provider
*/
retrieveProvider(provider_id) {
try {
const provider = this.container_.resolve(`pp_${provider_id}`)
return provider
} catch (err) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Could not find a payment provider with id: ${provider_id}`
)
}
}
}
export default PaymentProviderService