diff --git a/processor/src/clients/mockPaymentAPI.ts b/processor/src/clients/mockPaymentAPI.ts index ff6e761..6cf6a7c 100644 --- a/processor/src/clients/mockPaymentAPI.ts +++ b/processor/src/clients/mockPaymentAPI.ts @@ -1,6 +1,10 @@ import { v4 as uuid } from 'uuid'; -import { CreatePaymentRequest, MockPaymentProviderResponse } from '../services/types/payment.type'; -import { PaymentOutcome } from '../dtos/payment.dto'; +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 => { @@ -20,9 +24,30 @@ export const paymentProviderApi = (): any => { }; }; + 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/dtos/payment.dto.ts b/processor/src/dtos/payment.dto.ts index a340921..a033622 100644 --- a/processor/src/dtos/payment.dto.ts +++ b/processor/src/dtos/payment.dto.ts @@ -27,5 +27,31 @@ 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', +} +const PaymentModificationSchema = Type.Enum(PaymentModificationStatus); + +export const PaymentModificationResponseSchema = Type.Object({ + status: PaymentModificationSchema, +}); + export type PaymentRequestSchemaDTO = 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 new file mode 100644 index 0000000..a01c038 --- /dev/null +++ b/processor/src/routes/payment-modification.route.ts @@ -0,0 +1,53 @@ +import { FastifyInstance, FastifyPluginOptions } from 'fastify'; +import { PaymentService } from '../services/types/payment.type'; +import { CapturePaymentRequestDTO, PaymentModificationResponseDTO, RefundPaymentRequestDTO } from '../dtos/payment.dto'; + +type PaymentRoutesOptions = { + paymentService: PaymentService; +}; + +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, + }); + + 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); + }, + ); +}; diff --git a/processor/src/server.ts b/processor/src/server.ts index d0931f8..725559e 100644 --- a/processor/src/server.ts +++ b/processor/src/server.ts @@ -10,6 +10,7 @@ import { configRoutes } from './routes/config.route'; 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"; /** * Setup Fastify server instance @@ -54,6 +55,9 @@ export const setupFastify = async () => { paymentService, sessionAuthHook: paymentSDK.sessionAuthHookFn, }); + await server.register(paymentModificationRoutes, { + paymentService, + }); return server; }; diff --git a/processor/src/services/payment.service.ts b/processor/src/services/payment.service.ts index f699741..2d3d090 100644 --- a/processor/src/services/payment.service.ts +++ b/processor/src/services/payment.service.ts @@ -1,7 +1,18 @@ import { CommercetoolsCartService, CommercetoolsPaymentService } from '@commercetools/connect-payments-sdk'; import { paymentProviderApi } from '../clients/mockPaymentAPI'; -import { CreatePayment, PaymentService, PaymentServiceOptions } from './types/payment.type'; -import { PaymentOutcome, PaymentResponseSchemaDTO } from '../dtos/payment.dto'; +import { + CancelPayment, + CapturePayment, + CreatePayment, + PaymentService, + PaymentServiceOptions, RefundPayment +} from './types/payment.type'; +import { + PaymentModificationResponseDTO, + PaymentModificationStatus, + PaymentOutcome, + PaymentResponseSchemaDTO +} from '../dtos/payment.dto'; import { getCartIdFromContext } from '../libs/fastify/context/context'; export class DefaultPaymentService implements PaymentService { @@ -69,6 +80,119 @@ export class DefaultPaymentService implements PaymentService { }; } + public async cancelPayment(opts: CancelPayment): Promise { + const ctPayment = await this.ctPaymentService.getPayment({ + id: opts.paymentId, + }); + + await this.ctPaymentService.updatePayment({ + id: ctPayment.id, + transaction: { + type: 'CancelAuthorization', + amount: ctPayment.amountPlanned, + state: 'Initial', + }, + }); + + const res = await paymentProviderApi().cancelAuthorisedPaymentByPspReference( + ctPayment.interfaceId as string, + ctPayment, + ); + + await this.ctPaymentService.updatePayment({ + id: ctPayment.id, + transaction: { + type: 'CancelAuthorization', + 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', + }, + }); + + return { + status: PaymentModificationStatus.RECEIVED, + }; + } + + private convertPaymentResultCode(resultCode: PaymentOutcome): string { switch (resultCode) { case PaymentOutcome.AUTHORIZED: diff --git a/processor/src/services/types/payment.type.ts b/processor/src/services/types/payment.type.ts index c3b9144..d6ec0eb 100644 --- a/processor/src/services/types/payment.type.ts +++ b/processor/src/services/types/payment.type.ts @@ -1,6 +1,14 @@ import { Cart, Payment } from '@commercetools/platform-sdk'; import { CommercetoolsCartService, CommercetoolsPaymentService } from '@commercetools/connect-payments-sdk'; -import { PaymentOutcome, PaymentRequestSchemaDTO, PaymentResponseSchemaDTO } from '../../dtos/payment.dto'; +import { + CapturePaymentRequestDTO, + PaymentModificationResponseDTO, + PaymentModificationStatus, + PaymentOutcome, + PaymentRequestSchemaDTO, + PaymentResponseSchemaDTO, + RefundPaymentRequestDTO +} from '../../dtos/payment.dto'; export type CreatePayment = { data: PaymentRequestSchemaDTO; @@ -18,8 +26,30 @@ export type MockPaymentProviderResponse = { paymentMethodType: string; }; +export type MockPaymentProviderModificationResponse = { + outcome: PaymentModificationStatus; + pspReference: string; +}; + +export type CancelPayment = { + paymentId: string; +}; + +export type CapturePayment = { + paymentId: string; + data: CapturePaymentRequestDTO; +}; + +export type RefundPayment = { + paymentId: string; + data: RefundPaymentRequestDTO; +}; + export interface PaymentService { createPayment(opts: CreatePayment): Promise; + cancelPayment(opts: CancelPayment): Promise; + capturePayment(opts: CapturePayment): Promise; + refundPayment(opts: RefundPayment): Promise; } export type PaymentServiceOptions = {