-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(adyen-template): refactored operations to be included in an abst…
…ract service
- Loading branch information
Showing
24 changed files
with
497 additions
and
337 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Errorx, ErrorxAdditionalOpts } from '@commercetools/connect-payments-sdk'; | ||
|
||
export type AdyenApiErrorData = { | ||
status: number; | ||
errorCode: string; | ||
message: string; | ||
errorType?: string; | ||
}; | ||
|
||
export class AdyenApiError extends Errorx { | ||
constructor(errorData: AdyenApiErrorData, additionalOpts?: ErrorxAdditionalOpts) { | ||
super({ | ||
code: `AdyenError-${errorData.errorCode}`, | ||
httpErrorStatus: errorData.status, | ||
message: errorData.message, | ||
...additionalOpts, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { Static, Type } from '@sinclair/typebox'; | ||
|
||
/** | ||
* Represents https://docs.commercetools.com/api/errors#errorobject | ||
*/ | ||
export const ErrorObject = Type.Object( | ||
{ | ||
code: Type.String(), | ||
message: Type.String(), | ||
}, | ||
{ additionalProperties: true }, | ||
); | ||
|
||
/** | ||
* Represents https://docs.commercetools.com/api/errors#errorresponse | ||
*/ | ||
export const ErrorResponse = Type.Object({ | ||
statusCode: Type.Integer(), | ||
message: Type.String(), | ||
errors: Type.Array(ErrorObject), | ||
}); | ||
|
||
/** | ||
* Represents https://docs.commercetools.com/api/errors#autherrorresponse | ||
*/ | ||
export const AuthErrorResponse = Type.Composite([ | ||
ErrorResponse, | ||
Type.Object({ | ||
error: Type.String(), | ||
error_description: Type.Optional(Type.String()), | ||
}), | ||
]); | ||
|
||
export type TErrorObject = Static<typeof ErrorObject>; | ||
export type TErrorResponse = Static<typeof ErrorResponse>; | ||
export type TAuthErrorResponse = Static<typeof AuthErrorResponse>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,92 +1,122 @@ | ||
import { type FastifyReply, type FastifyRequest } from 'fastify'; | ||
import { FastifyError, type FastifyReply, type FastifyRequest } from 'fastify'; | ||
|
||
import { ErrorInvalidField, ErrorRequiredField, Errorx, MultiErrorx } from '@commercetools/connect-payments-sdk'; | ||
import { FastifySchemaValidationError } from 'fastify/types/schema'; | ||
import { log } from '../logger'; | ||
import { | ||
ErrorAuthErrorResponse, | ||
ErrorGeneral, | ||
ErrorInvalidField, | ||
ErrorInvalidJsonInput, | ||
ErrorRequiredField, | ||
Errorx, | ||
MultiErrorx, | ||
} from '@commercetools/connect-payments-sdk'; | ||
import { TAuthErrorResponse, TErrorObject, TErrorResponse } from './dtos/error.dto'; | ||
|
||
const getKeys = (path: string) => path.replace(/^\//, '').split('/'); | ||
function isFastifyValidationError(error: Error): error is FastifyError { | ||
return (error as unknown as FastifyError).validation != undefined; | ||
} | ||
|
||
const getPropertyFromPath = (path: string, obj: any): any => { | ||
const keys = getKeys(path); | ||
let value = obj; | ||
for (const key of keys) { | ||
value = value[key]; | ||
export const errorHandler = (error: Error, req: FastifyRequest, reply: FastifyReply) => { | ||
if (isFastifyValidationError(error) && error.validation) { | ||
return handleErrors(transformValidationErrors(error.validation, req), reply); | ||
} else if (error instanceof ErrorAuthErrorResponse) { | ||
return handleAuthError(error, reply); | ||
} else if (error instanceof Errorx) { | ||
return handleErrors([error], reply); | ||
} else if (error instanceof MultiErrorx) { | ||
return handleErrors(error.errors, reply); | ||
} | ||
return value; | ||
|
||
// If it isn't any of the cases above (for example a normal Error is thrown) then fallback to a general 500 internal server error | ||
return handleErrors([new ErrorGeneral('Internal server error.', { cause: error, skipLog: false })], reply); | ||
}; | ||
|
||
type ValidationObject = { | ||
validation: object; | ||
const handleAuthError = (error: ErrorAuthErrorResponse, reply: FastifyReply) => { | ||
const transformedErrors: TErrorObject[] = transformErrorxToHTTPModel([error]); | ||
|
||
const response: TAuthErrorResponse = { | ||
message: error.message, | ||
statusCode: error.httpErrorStatus, | ||
errors: transformedErrors, | ||
error: transformedErrors[0].code, | ||
error_description: transformedErrors[0].message, | ||
}; | ||
|
||
return reply.code(error.httpErrorStatus).send(response); | ||
}; | ||
|
||
type TError = { | ||
statusCode: number; | ||
code: string; | ||
message: string; | ||
errors?: object[]; | ||
const handleErrors = (errorxList: Errorx[], reply: FastifyReply) => { | ||
const transformedErrors: TErrorObject[] = transformErrorxToHTTPModel(errorxList); | ||
|
||
// Based on CoCo specs, the root level message attribute is always set to the values from the first error. MultiErrorx enforces the same HTTP status code. | ||
const response: TErrorResponse = { | ||
message: errorxList[0].message, | ||
statusCode: errorxList[0].httpErrorStatus, | ||
errors: transformedErrors, | ||
}; | ||
|
||
return reply.code(errorxList[0].httpErrorStatus).send(response); | ||
}; | ||
|
||
export const errorHandler = (error: Error, req: FastifyRequest, reply: FastifyReply) => { | ||
if (error instanceof Object && (error as unknown as ValidationObject).validation) { | ||
const errorsList: Errorx[] = []; | ||
|
||
// Transforming the validation errors | ||
for (const err of (error as any).validation) { | ||
switch (err.keyword) { | ||
case 'required': | ||
errorsList.push(new ErrorRequiredField(err.params.missingProperty)); | ||
break; | ||
case 'enum': | ||
errorsList.push( | ||
new ErrorInvalidField( | ||
getKeys(err.instancePath).join('.'), | ||
getPropertyFromPath(err.instancePath, req.body), | ||
err.params.allowedValues, | ||
), | ||
); | ||
} | ||
|
||
if (errorsList.length > 1) { | ||
error = new MultiErrorx(errorsList); | ||
} else { | ||
error = errorsList[0]; | ||
} | ||
const transformErrorxToHTTPModel = (errors: Errorx[]): TErrorObject[] => { | ||
const errorObjectList: TErrorObject[] = []; | ||
|
||
for (const err of errors) { | ||
if (err.skipLog) { | ||
log.debug(err.message, err); | ||
} else { | ||
log.error(err.message, err); | ||
} | ||
} | ||
|
||
if (error instanceof Errorx) { | ||
return handleErrorx(error, reply); | ||
} else { | ||
const { message, ...meta } = error; | ||
log.error(message, meta); | ||
return reply.code(500).send({ | ||
code: 'General', | ||
message: 'Internal server error.', | ||
statusCode: 500, | ||
}); | ||
const tErrObj: TErrorObject = { | ||
code: err.code, | ||
message: err.message, | ||
...(err.fields ? err.fields : {}), // Add any additional field to the response object (which will differ per type of error) | ||
}; | ||
|
||
errorObjectList.push(tErrObj); | ||
} | ||
|
||
return errorObjectList; | ||
}; | ||
|
||
const handleErrorx = (error: Errorx, reply: FastifyReply) => { | ||
const { message, ...meta } = error; | ||
log.error(message, meta); | ||
const errorBuilder: TError = { | ||
statusCode: error.httpErrorStatus, | ||
code: error.code, | ||
message: error.message, | ||
}; | ||
const transformValidationErrors = (errors: FastifySchemaValidationError[], req: FastifyRequest): Errorx[] => { | ||
const errorxList: Errorx[] = []; | ||
|
||
const errors: object[] = []; | ||
if (error.fields) { | ||
errors.push({ | ||
code: error.code, | ||
message: error.message, | ||
...error.fields, | ||
}); | ||
for (const err of errors) { | ||
switch (err.keyword) { | ||
case 'required': | ||
errorxList.push(new ErrorRequiredField(err.params.missingProperty as string)); | ||
break; | ||
case 'enum': | ||
errorxList.push( | ||
new ErrorInvalidField( | ||
getKeys(err.instancePath).join('.'), | ||
getPropertyFromPath(err.instancePath, req.body), | ||
err.params.allowedValues as string, | ||
), | ||
); | ||
break; | ||
} | ||
} | ||
|
||
if (errors.length > 0) { | ||
errorBuilder.errors = errors; | ||
// If we cannot map the validation error to a CoCo error then return a general InvalidJsonError | ||
if (errorxList.length === 0) { | ||
errorxList.push(new ErrorInvalidJsonInput()); | ||
} | ||
|
||
return reply.code(error.httpErrorStatus).send(errorBuilder); | ||
return errorxList; | ||
}; | ||
|
||
const getKeys = (path: string) => path.replace(/^\//, '').split('/'); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const getPropertyFromPath = (path: string, obj: any): any => { | ||
const keys = getKeys(path); | ||
let value = obj; | ||
for (const key of keys) { | ||
value = value[key]; | ||
} | ||
return value; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { HmacAuthHook } from '../libs/fastify/hooks/hmac-auth.hook'; | ||
import { paymentSDK } from '../payment-sdk'; | ||
import { AdyenPaymentService } from '../services/adyen-payment.service'; | ||
|
||
const paymentService = new AdyenPaymentService({ | ||
ctCartService: paymentSDK.ctCartService, | ||
ctPaymentService: paymentSDK.ctPaymentService, | ||
}); | ||
|
||
export const app = { | ||
services: { | ||
paymentService, | ||
}, | ||
hooks: { | ||
hmacAuthHook: new HmacAuthHook(), | ||
}, | ||
}; |
Oops, something went wrong.