diff --git a/processor/src/clients/PaymentConnector.ts b/processor/src/clients/PaymentConnector.ts new file mode 100644 index 0000000..cea3e35 --- /dev/null +++ b/processor/src/clients/PaymentConnector.ts @@ -0,0 +1,12 @@ +import { + CreatePaymentRequest, + MockPaymentProviderModificationResponse, + MockPaymentProviderResponse +} from '../services/types/payment.type'; +import { Payment } from '@commercetools/platform-sdk'; + +export interface PaymentConnector { + + processPayment: (request: CreatePaymentRequest) => Promise + modifyPaymentByPspReference: (pspReference: string, payment: Payment) => Promise +} diff --git a/processor/src/clients/mockPaymentAPI.ts b/processor/src/clients/mockPaymentAPI.ts deleted file mode 100644 index 6cf6a7c..0000000 --- a/processor/src/clients/mockPaymentAPI.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { v4 as uuid } from 'uuid'; -import { - CreatePaymentRequest, MockPaymentProviderModificationResponse, - MockPaymentProviderResponse -} from '../services/types/payment.type'; -import { PaymentModificationStatus, PaymentOutcome } from '../dtos/payment.dto'; -import { Payment } from '@commercetools/platform-sdk'; - -// TODO: Make it class , add interface and provide implementation -export const paymentProviderApi = (): any => { - const allowedCreditCards = ['4111111111111111', '5555555555554444', '341925950237632']; - /** - * @param request - * @returns - */ - const processPayment = async (request: CreatePaymentRequest): Promise => { - const paymentMethod = request.data.paymentMethod; - const isAuthorized = isCreditCardAllowed(paymentMethod.cardNumber); - - return { - resultCode: isAuthorized ? PaymentOutcome.AUTHORIZED : PaymentOutcome.REJECTED, - pspReference: uuid(), - paymentMethodType: paymentMethod.type, - }; - }; - - const cancelAuthorisedPaymentByPspReference = - async (pspReference: string, payment: Payment): Promise => { - - return { outcome: PaymentModificationStatus.RECEIVED, pspReference: pspReference } - } - - const captureAuthorisedPayment = - async (pspReference: string, payment: Payment): Promise => { - - return { outcome: PaymentModificationStatus.RECEIVED, pspReference: pspReference } - } - - const refundCapturedPayment = - async (pspReference: string, payment: Payment): Promise => { - - return { outcome: PaymentModificationStatus.RECEIVED, pspReference: pspReference } - } - - const isCreditCardAllowed = (cardNumber: string) => allowedCreditCards.includes(cardNumber); - - return { - processPayment, - cancelAuthorisedPaymentByPspReference, - captureAuthorisedPayment, - refundCapturedPayment, - }; -}; diff --git a/processor/src/clients/mockPaymentConnector.ts b/processor/src/clients/mockPaymentConnector.ts new file mode 100644 index 0000000..6ab38f7 --- /dev/null +++ b/processor/src/clients/mockPaymentConnector.ts @@ -0,0 +1,37 @@ +import { v4 as uuid } from 'uuid'; +import { + CreatePaymentRequest, MockPaymentProviderModificationResponse, + MockPaymentProviderResponse +} from '../services/types/payment.type'; +import { PaymentModificationStatus, PaymentOutcome } from '../dtos/payment.dto'; +import { Payment } from '@commercetools/platform-sdk'; +import {PaymentConnector} from "./PaymentConnector"; + +export class MockPaymentConnector implements PaymentConnector { + + private allowedCreditCards = ['4111111111111111', '5555555555554444', '341925950237632']; + + /** + * @param request + * @returns + */ + async processPayment(request: CreatePaymentRequest): Promise { + const paymentMethod = request.data.paymentMethod; + const isAuthorized = this.isCreditCardAllowed(paymentMethod.cardNumber); + + return { + resultCode: isAuthorized ? PaymentOutcome.AUTHORIZED : PaymentOutcome.REJECTED, + pspReference: uuid(), + paymentMethodType: paymentMethod.type, + }; + } + + async modifyPaymentByPspReference(pspReference: string, payment: Payment): Promise { + + return { outcome: PaymentModificationStatus.APPROVED, pspReference: pspReference } + } + + isCreditCardAllowed(cardNumber: string) { + return this.allowedCreditCards.includes(cardNumber); + } +} diff --git a/processor/src/dtos/payment.dto.ts b/processor/src/dtos/payment.dto.ts index a033622..abeda27 100644 --- a/processor/src/dtos/payment.dto.ts +++ b/processor/src/dtos/payment.dto.ts @@ -15,6 +15,14 @@ export const PaymentRequestSchema = Type.Object({ paymentMethod: Type.Composite([CardPaymentMethod]), }); +export const PaymentModificationRequestSchema = Type.Object({ + action: Type.String(), + amount: Type.Object({ + centAmount: Type.Number(), + currencyCode: Type.String(), + }) +}) + export enum PaymentOutcome { AUTHORIZED = 'Authorized', REJECTED = 'Rejected', @@ -27,31 +35,18 @@ export const PaymentResponseSchema = Type.Object({ paymentReference: Type.String(), }); -export const CapturePaymentRequestSchema = Type.Object({ - amount: Type.Object({ - centAmount: Type.Number(), - currencyCode: Type.String(), - }), -}); - -export const RefundPaymentRequestSchema = Type.Object({ - amount: Type.Object({ - centAmount: Type.Number(), - currencyCode: Type.String(), - }), -}); - export enum PaymentModificationStatus { - RECEIVED = 'received', + SUCCESS = 'success', + APPROVED = 'approved', + REJECTED = 'rejected', } const PaymentModificationSchema = Type.Enum(PaymentModificationStatus); export const PaymentModificationResponseSchema = Type.Object({ - status: PaymentModificationSchema, + outcome: PaymentModificationSchema, }); export type PaymentRequestSchemaDTO = Static; +export type PaymentModificationRequestSchemaDTO = Static; export type PaymentResponseSchemaDTO = Static; -export type CapturePaymentRequestDTO = Static; -export type RefundPaymentRequestDTO = Static; export type PaymentModificationResponseDTO = Static; diff --git a/processor/src/routes/payment-modification.route.ts b/processor/src/routes/payment-modification.route.ts index a01c038..6778193 100644 --- a/processor/src/routes/payment-modification.route.ts +++ b/processor/src/routes/payment-modification.route.ts @@ -1,6 +1,11 @@ import { FastifyInstance, FastifyPluginOptions } from 'fastify'; import { PaymentService } from '../services/types/payment.type'; -import { CapturePaymentRequestDTO, PaymentModificationResponseDTO, RefundPaymentRequestDTO } from '../dtos/payment.dto'; +import { + PaymentModificationRequestSchema, + PaymentModificationRequestSchemaDTO, + PaymentModificationResponseDTO, + PaymentModificationResponseSchema, +} from '../dtos/payment.dto'; type PaymentRoutesOptions = { paymentService: PaymentService; @@ -10,44 +15,26 @@ export const paymentModificationRoutes = async ( fastify: FastifyInstance, opts: FastifyPluginOptions & PaymentRoutesOptions, ) => { - fastify.post<{ Body: any; Reply: PaymentModificationResponseDTO }>( - '/payments/:id/cancels', - {}, - async (request, reply) => { - const params = request.params as any; - const resp = await opts.paymentService.cancelPayment({ - paymentId: params.id, - }); + fastify.post<{ Body: PaymentModificationRequestSchemaDTO; Reply: PaymentModificationResponseDTO }>( + '/payment-intents/{id}', + { + // TODO: use the oauth hook + onRequest: [opts.sessionAuthHook.authenticate()], + schema: { + body: PaymentModificationRequestSchema, + response: { + 200: PaymentModificationResponseSchema, + }, + }, + }, + async (request, reply) => { + const params = request.params as any; + const resp = await opts.paymentService.modifyPayment({ + paymentId: params.id, + data: request.body, + }); - return reply.status(200).send(resp); - }, - ); - - fastify.post<{ Body: CapturePaymentRequestDTO; Reply: PaymentModificationResponseDTO }>( - '/payments/:id/captures', - {}, - async (request, reply) => { - const params = request.params as any; - const resp = await opts.paymentService.capturePayment({ - paymentId: params.id, - data: request.body, - }); - - return reply.status(200).send(resp); - }, - ); - - fastify.post<{ Body: RefundPaymentRequestDTO; Reply: PaymentModificationResponseDTO }>( - '/payments/:id/refunds', - {}, - async (request, reply) => { - const params = request.params as any; - const resp = await opts.paymentService.refundPayment({ - paymentId: params.id, - data: request.body, - }); - - return reply.status(200).send(resp); - }, - ); + return reply.status(200).send(resp); + }, + ); }; diff --git a/processor/src/server.ts b/processor/src/server.ts index 725559e..97846c9 100644 --- a/processor/src/server.ts +++ b/processor/src/server.ts @@ -11,6 +11,7 @@ import { paymentRoutes } from './routes/payment.route'; import { statusRoutes } from './routes/status.route'; import { DefaultPaymentService } from './services/payment.service'; import {paymentModificationRoutes} from "./routes/payment-modification.route"; +import {MockPaymentConnector} from "./clients/mockPaymentConnector"; /** * Setup Fastify server instance diff --git a/processor/src/services/payment.service.ts b/processor/src/services/payment.service.ts index 2d3d090..b4fa0f1 100644 --- a/processor/src/services/payment.service.ts +++ b/processor/src/services/payment.service.ts @@ -1,11 +1,8 @@ import { CommercetoolsCartService, CommercetoolsPaymentService } from '@commercetools/connect-payments-sdk'; -import { paymentProviderApi } from '../clients/mockPaymentAPI'; import { - CancelPayment, - CapturePayment, - CreatePayment, + CreatePayment, ModifyPayment, PaymentService, - PaymentServiceOptions, RefundPayment + PaymentServiceOptions } from './types/payment.type'; import { PaymentModificationResponseDTO, @@ -14,10 +11,13 @@ import { PaymentResponseSchemaDTO } from '../dtos/payment.dto'; import { getCartIdFromContext } from '../libs/fastify/context/context'; +import {PaymentConnector} from "../clients/PaymentConnector"; +import {MockPaymentConnector} from "../clients/mockPaymentConnector"; export class DefaultPaymentService implements PaymentService { private ctCartService: CommercetoolsCartService; private ctPaymentService: CommercetoolsPaymentService; + private paymentConnector: PaymentConnector = new MockPaymentConnector(); constructor(opts: PaymentServiceOptions) { this.ctCartService = opts.ctCartService; @@ -60,7 +60,8 @@ export class DefaultPaymentService implements PaymentService { payment: ctPayment, }; - const res = await paymentProviderApi().processPayment(data); + + const res = await this.paymentConnector.processPayment(data); const updatedPayment = await this.ctPaymentService.updatePayment({ id: ctPayment.id, @@ -80,21 +81,23 @@ export class DefaultPaymentService implements PaymentService { }; } - public async cancelPayment(opts: CancelPayment): Promise { + public async modifyPayment(opts: ModifyPayment): Promise { const ctPayment = await this.ctPaymentService.getPayment({ id: opts.paymentId, }); + const transactionType = this.getPaymentTransactionType(opts.data.action); + await this.ctPaymentService.updatePayment({ id: ctPayment.id, transaction: { - type: 'CancelAuthorization', + type: transactionType, amount: ctPayment.amountPlanned, state: 'Initial', }, }); - const res = await paymentProviderApi().cancelAuthorisedPaymentByPspReference( + const res = await this.paymentConnector.modifyPaymentByPspReference( ctPayment.interfaceId as string, ctPayment, ); @@ -102,97 +105,18 @@ export class DefaultPaymentService implements PaymentService { await this.ctPaymentService.updatePayment({ id: ctPayment.id, transaction: { - type: 'CancelAuthorization', + type: transactionType, amount: ctPayment.amountPlanned, interactionId: res.pspReference, - state: 'Pending', - }, - }); - - return { - status: PaymentModificationStatus.RECEIVED, - }; - } - - public async capturePayment(opts: CapturePayment): Promise { - const ctPayment = await this.ctPaymentService.getPayment({ - id: opts.paymentId, - }); - - const data = { payment: ctPayment, data: opts.data }; - - await this.ctPaymentService.updatePayment({ - id: ctPayment.id, - transaction: { - type: 'Charge', - amount: { - currencyCode: opts.data.amount.currencyCode, - centAmount: opts.data.amount.centAmount, - }, - state: 'Initial', - }, - }); - - const res = await paymentProviderApi().captureAuthorisedPayment(ctPayment.interfaceId as string, ctPayment); - - await this.ctPaymentService.updatePayment({ - id: ctPayment.id, - transaction: { - type: 'Charge', - amount: { - currencyCode: opts.data.amount.currencyCode, - centAmount: opts.data.amount.centAmount, - }, - interactionId: res.pspReference, - state: 'Pending', - }, - }); - - return { - status: PaymentModificationStatus.RECEIVED, - }; - } - - public async refundPayment(opts: RefundPayment): Promise { - const ctPayment = await this.ctPaymentService.getPayment({ - id: opts.paymentId, - }); - - const data = { payment: ctPayment, data: opts.data }; - - await this.ctPaymentService.updatePayment({ - id: ctPayment.id, - transaction: { - type: 'Refund', - amount: { - currencyCode: opts.data.amount.currencyCode, - centAmount: opts.data.amount.centAmount, - }, - state: 'Initial', - }, - }); - - const res = await paymentProviderApi().refundCapturedPayment(ctPayment.interfaceId as string, ctPayment); - - await this.ctPaymentService.updatePayment({ - id: ctPayment.id, - transaction: { - type: 'Refund', - amount: { - currencyCode: opts.data.amount.currencyCode, - centAmount: opts.data.amount.centAmount, - }, - interactionId: res.pspReference, - state: 'Pending', + state: 'Success', }, }); return { - status: PaymentModificationStatus.RECEIVED, + outcome: PaymentModificationStatus.APPROVED, }; } - private convertPaymentResultCode(resultCode: PaymentOutcome): string { switch (resultCode) { case PaymentOutcome.AUTHORIZED: @@ -203,4 +127,18 @@ export class DefaultPaymentService implements PaymentService { return 'Initial'; } } + + private getPaymentTransactionType(action: string): string { + switch (action) { + case 'cancelPayment': { + return 'CancelAuthorization'; + } + case 'capturePayment': { + return 'Charge'; + } + case 'refundPayment': { + return 'Refund'; + } + } + } } diff --git a/processor/src/services/types/payment.type.ts b/processor/src/services/types/payment.type.ts index d6ec0eb..684b052 100644 --- a/processor/src/services/types/payment.type.ts +++ b/processor/src/services/types/payment.type.ts @@ -1,14 +1,14 @@ import { Cart, Payment } from '@commercetools/platform-sdk'; import { CommercetoolsCartService, CommercetoolsPaymentService } from '@commercetools/connect-payments-sdk'; import { - CapturePaymentRequestDTO, + PaymentModificationRequestSchemaDTO, PaymentModificationResponseDTO, PaymentModificationStatus, PaymentOutcome, PaymentRequestSchemaDTO, PaymentResponseSchemaDTO, - RefundPaymentRequestDTO } from '../../dtos/payment.dto'; +import {MockPaymentConnector} from "../../clients/mockPaymentConnector"; export type CreatePayment = { data: PaymentRequestSchemaDTO; @@ -31,25 +31,14 @@ export type MockPaymentProviderModificationResponse = { pspReference: string; }; -export type CancelPayment = { +export type ModifyPayment = { paymentId: string; -}; - -export type CapturePayment = { - paymentId: string; - data: CapturePaymentRequestDTO; -}; - -export type RefundPayment = { - paymentId: string; - data: RefundPaymentRequestDTO; + data: PaymentModificationRequestSchemaDTO; }; export interface PaymentService { createPayment(opts: CreatePayment): Promise; - cancelPayment(opts: CancelPayment): Promise; - capturePayment(opts: CapturePayment): Promise; - refundPayment(opts: RefundPayment): Promise; + modifyPayment(opts: ModifyPayment): Promise; } export type PaymentServiceOptions = {