Skip to content

Commit

Permalink
feat(payments): Refactor core Payment related
Browse files Browse the repository at this point in the history
  • Loading branch information
adrien2p committed Aug 5, 2022
1 parent 6663a62 commit 4666e91
Show file tree
Hide file tree
Showing 19 changed files with 984 additions and 492 deletions.
8 changes: 4 additions & 4 deletions docs-util/fixture-gen/src/services/test-pay.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { PaymentService } from "medusa-interfaces";
import { AbstractPaymentService } from "@medusajs/medusa";

class TestPayService extends PaymentService {
class TestPayService extends AbstractPaymentService {
static identifier = "test-pay";

constructor() {
super();
constructor(_) {
super(_);
}

async getStatus(paymentData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ These methods are used at different points in the Checkout flow as well as when
The first step to create a payment provider is to create a file in `src/services` with the following content:

```jsx
import { PaymentService } from "medusa-interfaces"
import { AbstractPaymentService } from "@medusajs/medusa"

class MyPaymentService extends PaymentService {
class MyPaymentService extends AbstractPaymentService {

}

Expand Down
8 changes: 4 additions & 4 deletions integration-tests/api/src/services/test-pay.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { PaymentService } from "medusa-interfaces"
import { AbstractPaymentService } from "@medusajs/medusa"

class TestPayService extends PaymentService {
class TestPayService extends AbstractPaymentService {
static identifier = "test-pay"

constructor() {
super()
constructor(_) {
super(_)
}

async getStatus(paymentData) {
Expand Down
8 changes: 4 additions & 4 deletions integration-tests/plugins/src/services/test-pay.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { PaymentService } from "medusa-interfaces"
import { AbstractPaymentService } from "@medusajs/medusa"

class TestPayService extends PaymentService {
class TestPayService extends AbstractPaymentService {
static identifier = "test-pay"

constructor() {
super()
constructor(_) {
super(_)
}

async getStatus(paymentData) {
Expand Down
4 changes: 2 additions & 2 deletions packages/medusa-interfaces/src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export { default as BaseService } from "./base-service"
export { default as FileService } from "./file-service"
export { default as PaymentService } from "./payment-service"
export { default as FulfillmentService } from "./fulfillment-service"
export { default as FileService } from "./file-service"
export { default as NotificationService } from "./notification-service"
export { default as OauthService } from "./oauth-service"
export { default as PaymentService } from "./payment-service"
export { default as SearchService } from "./search-service"
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,5 @@ export default async (req, res) => {

export class StorePostCartsCartPaymentSessionUpdateReq {
@IsObject()
data: object
data: Record<string, unknown>
}
1 change: 1 addition & 0 deletions packages/medusa/src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./price-selection-strategy"
export * from "./models/base-entity"
export * from "./models/soft-deletable-entity"
export * from "./search-service"
export * from "./payment-service"
120 changes: 120 additions & 0 deletions packages/medusa/src/interfaces/payment-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { TransactionBaseService } from "./transaction-base-service"
import {
Cart,
Customer,
Payment,
PaymentSession,
PaymentSessionStatus,
} from "../models"
import { PaymentService } from "medusa-interfaces"

export type Data = Record<string, unknown>
export type PaymentData = Data
export type PaymentSessionData = Data

export interface PaymentService<T extends TransactionBaseService<never>>
extends TransactionBaseService<T> {
getIdentifier(): string

getPaymentData(paymentSession: PaymentSession): Promise<PaymentData>

updatePaymentData(
paymentSessionData: PaymentSessionData,
data: Data
): Promise<PaymentSessionData>

createPayment(cart: Cart): Promise<PaymentSessionData>

retrievePayment(paymentData: PaymentData): Promise<Data>

updatePayment(
paymentSessionData: PaymentSessionData,
cart: Cart
): Promise<PaymentSessionData>

authorizePayment(
paymentSession: PaymentSession,
context: Data
): Promise<{ data: PaymentSessionData; status: PaymentSessionStatus }>

capturePayment(payment: Payment): Promise<PaymentData>

refundPayment(payment: Payment, refundAmount: number): Promise<PaymentData>

cancelPayment(payment: Payment): Promise<PaymentData>

deletePayment(paymentSession: PaymentSession): Promise<void>

retrieveSavedMethods(customer: Customer): Promise<Data[]>

getStatus(
paymentSessionData: PaymentSessionData
): Promise<PaymentSessionStatus>
}

export abstract class AbstractPaymentService<
T extends TransactionBaseService<never>
>
extends TransactionBaseService<T>
implements PaymentService<T>
{
protected constructor(container: unknown, config?: Record<string, unknown>) {
super(container, config)
}

protected static identifier: string

public getIdentifier(): string {
if (!(<typeof AbstractPaymentService>this.constructor).identifier) {
throw new Error('Missing static property "identifier".')
}
return (<typeof AbstractPaymentService>this.constructor).identifier
}

public abstract getPaymentData(
paymentSession: PaymentSession
): Promise<PaymentData>

public abstract updatePaymentData(
paymentSessionData: PaymentSessionData,
data: Data
): Promise<PaymentSessionData>

public abstract createPayment(cart: Cart): Promise<PaymentSessionData>

public abstract retrievePayment(paymentData: PaymentData): Promise<Data>

public abstract updatePayment(
paymentSessionData: PaymentSessionData,
cart: Cart
): Promise<PaymentSessionData>

public abstract authorizePayment(
paymentSession: PaymentSession,
context: Data
): Promise<{ data: PaymentSessionData; status: PaymentSessionStatus }>

public abstract capturePayment(payment: Payment): Promise<PaymentData>

public abstract refundPayment(
payment: Payment,
refundAmount: number
): Promise<PaymentData>

public abstract cancelPayment(payment: Payment): Promise<PaymentData>

public abstract deletePayment(paymentSession: PaymentSession): Promise<void>

// eslint-disable-next-line @typescript-eslint/no-unused-vars
public retrieveSavedMethods(customer: Customer): Promise<Data[]> {
return Promise.resolve([])
}

public abstract getStatus(
paymentSessionData: PaymentSessionData
): Promise<PaymentSessionStatus>
}

export function isPaymentService(obj: unknown): boolean {
return obj instanceof AbstractPaymentService || obj instanceof PaymentService
}
72 changes: 55 additions & 17 deletions packages/medusa/src/loaders/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { BasePaymentService, BaseNotificationService, BaseFulfillmentService } from 'medusa-interfaces'
import {
BaseNotificationService,
BaseFulfillmentService,
BasePaymentService,
} from "medusa-interfaces"
import { currencies } from "../utils/currencies"
import { countries } from "../utils/countries"
import { AwilixContainer } from "awilix"
Expand All @@ -15,11 +19,15 @@ import {
TaxProviderService,
} from "../services"
import { CurrencyRepository } from "../repositories/currency"
import { AbstractTaxService } from "../interfaces"
import { FlagRouter } from "../utils/flag-router";
import SalesChannelFeatureFlag from "./feature-flags/sales-channels";
import { AbstractPaymentService, AbstractTaxService } from "../interfaces"

const silentResolution = <T>(container: AwilixContainer, name: string, logger: Logger): T | never | undefined => {
const silentResolution = <T>(
container: AwilixContainer,
name: string,
logger: Logger
): T | never | undefined => {
try {
return container.resolve<T>(name)
} catch (err) {
Expand All @@ -44,15 +52,23 @@ const silentResolution = <T>(container: AwilixContainer, name: string, logger: L
`You don't have any ${identifier} provider plugins installed. You may want to add one to your project.`
)
}
return;
return
}
}

export default async ({ container }: { container: AwilixContainer }): Promise<void> => {
export default async ({
container,
}: {
container: AwilixContainer
}): Promise<void> => {
const storeService = container.resolve<StoreService>("storeService")
const currencyRepository = container.resolve<typeof CurrencyRepository>("currencyRepository")
const countryRepository = container.resolve<typeof CountryRepository>("countryRepository")
const profileService = container.resolve<ShippingProfileService>("shippingProfileService")
const currencyRepository =
container.resolve<typeof CurrencyRepository>("currencyRepository")
const countryRepository =
container.resolve<typeof CountryRepository>("countryRepository")
const profileService = container.resolve<ShippingProfileService>(
"shippingProfileService"
)
const salesChannelService = container.resolve<SalesChannelService>("salesChannelService")
const logger = container.resolve<Logger>("logger")
const featureFlagRouter = container.resolve<FlagRouter>("featureFlagRouter")
Expand Down Expand Up @@ -104,32 +120,54 @@ export default async ({ container }: { container: AwilixContainer }): Promise<vo
await storeService.withTransaction(manager).create()

const payProviders =
silentResolution<typeof BasePaymentService[]>(container, "paymentProviders", logger) || []
silentResolution<(typeof BasePaymentService | AbstractPaymentService)[]>(
container,
"paymentProviders",
logger
) || []
const payIds = payProviders.map((p) => p.getIdentifier())

const pProviderService = container.resolve<PaymentProviderService>("paymentProviderService")
const pProviderService = container.resolve<PaymentProviderService>(
"paymentProviderService"
)
await pProviderService.registerInstalledProviders(payIds)

const notiProviders =
silentResolution<typeof BaseNotificationService[]>(container, "notificationProviders", logger) || []
silentResolution<typeof BaseNotificationService[]>(
container,
"notificationProviders",
logger
) || []
const notiIds = notiProviders.map((p) => p.getIdentifier())

const nProviderService = container.resolve<NotificationService>("notificationService")
const nProviderService = container.resolve<NotificationService>(
"notificationService"
)
await nProviderService.registerInstalledProviders(notiIds)


const fulfilProviders =
silentResolution<typeof BaseFulfillmentService[]>(container, "fulfillmentProviders", logger) || []
silentResolution<typeof BaseFulfillmentService[]>(
container,
"fulfillmentProviders",
logger
) || []
const fulfilIds = fulfilProviders.map((p) => p.getIdentifier())

const fProviderService = container.resolve<FulfillmentProviderService>("fulfillmentProviderService")
const fProviderService = container.resolve<FulfillmentProviderService>(
"fulfillmentProviderService"
)
await fProviderService.registerInstalledProviders(fulfilIds)

const taxProviders =
silentResolution<AbstractTaxService[]>(container, "taxProviders", logger) || []
silentResolution<AbstractTaxService[]>(
container,
"taxProviders",
logger
) || []
const taxIds = taxProviders.map((p) => p.getIdentifier())

const tProviderService = container.resolve<TaxProviderService>("taxProviderService")
const tProviderService =
container.resolve<TaxProviderService>("taxProviderService")
await tProviderService.registerInstalledProviders(taxIds)

await profileService.withTransaction(manager).createDefault()
Expand Down
4 changes: 2 additions & 2 deletions packages/medusa/src/loaders/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
FileService,
FulfillmentService,
OauthService,
PaymentService,
} from "medusa-interfaces"
import path from "path"
import { EntitySchema } from "typeorm"
Expand All @@ -19,6 +18,7 @@ import {
isBatchJobStrategy,
isFileService,
isNotificationService,
isPaymentService,
isPriceSelectionStrategy,
isSearchService,
isTaxCalculationStrategy,
Expand Down Expand Up @@ -349,7 +349,7 @@ export async function registerServices(
throw new Error(message)
}

if (loaded.prototype instanceof PaymentService) {
if (isPaymentService(loaded.prototype)) {
// Register our payment providers to paymentProviders
container.registerAdd(
"paymentProviders",
Expand Down
4 changes: 2 additions & 2 deletions packages/medusa/src/models/payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ export class Payment extends BaseEntity {
data: Record<string, unknown>

@Column({ type: resolveDbType("timestamptz"), nullable: true })
captured_at: Date
captured_at: Date | string

@Column({ type: resolveDbType("timestamptz"), nullable: true })
canceled_at: Date
canceled_at: Date | string

@DbAwareColumn({ type: "jsonb", nullable: true })
metadata: Record<string, unknown>
Expand Down
40 changes: 40 additions & 0 deletions packages/medusa/src/services/__mocks__/test-pay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export const testPayServiceMock = {
identifier: "test-pay",
getIdentifier: "test-pay",
getStatus: jest.fn().mockResolvedValue(Promise.resolve("authorised")),
retrieveSavedMethods: jest.fn().mockResolvedValue(Promise.resolve([])),
getPaymentData: jest.fn().mockResolvedValue(Promise.resolve({})),
createPayment: jest.fn().mockImplementation(() => {
return {}
}),
retrievePayment: jest.fn().mockImplementation(() => {
return {}
}),
updatePayment: jest.fn().mockImplementation(() => {
return {}
}),
deletePayment: jest.fn().mockImplementation(() => {
return {}
}),
authorizePayment: jest.fn().mockImplementation(() => {
return {}
}),
updatePaymentData: jest.fn().mockImplementation(() => {
return {}
}),
cancelPayment: jest.fn().mockImplementation(() => {
return {}
}),
capturePayment: jest.fn().mockImplementation(() => {
return {}
}),
refundPayment: jest.fn().mockImplementation(() => {
return {}
})
}

const mock = jest.fn().mockImplementation(() => {
return testPayServiceMock
})

export default mock
Loading

0 comments on commit 4666e91

Please sign in to comment.