Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(adyen-template): added a first version of adyen APIs #22

Merged
merged 5 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions processor/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ CTP_CLIENT_SECRET=[composable-commerce-client-secret]
CTP_PROJECT_KEY=[composable-commerce-project-key]

# Merchant website
SELLER_RETURN_URL=[Merchant-website-return-url]
SELLER_SEND_NOTIFICATION_ENABLED=[true/false]
SELLER_NOTIFICATION_URL=[Merchant-website-return-url]
MERCHANT_RETURN_URL=[Merchant-website-return-url]

# Adyen credentials
ADYEN_ENVIRONMENT=[Adyen-environment]
ADYEN_API_KEY=[Adyen-api-key]
ADYEN_CLIENT_KEY=[Adyen-client-key]
ADYEN_LIVE_URL_PREFIX=
ADYEN_NOTIFICATION_HMAC_KEY=[Adyen-hmac-key]
ADYEN_MERCHANT_ACCOUNT=[Adyen-merchant-account]
8 changes: 4 additions & 4 deletions processor/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions processor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"dependencies": {
"@adyen/api-library": "16.0.1",
"@commercetools-backend/loggers": "22.17.2",
"@commercetools/connect-payments-sdk": "0.1.0",
"@commercetools/connect-payments-sdk": "0.2.0",
"@commercetools/platform-sdk": "7.3.0",
"@commercetools/sdk-client-v2": "2.3.0",
"@fastify/autoload": "5.8.0",
Expand Down Expand Up @@ -56,4 +56,4 @@
"ts-node": "10.9.2",
"typescript": "5.3.3"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Client, CheckoutAPI } from '@adyen/api-library';
import { config } from '../../config/config';
import { config } from '../config/config';

export const AdyenAPI = (): CheckoutAPI => {
export const AdyenApi = (): CheckoutAPI => {
const apiClient = new Client({
apiKey: config.adyenApiKey,
environment: config.adyenEnvironment.toUpperCase() as Environment,
Expand Down
10 changes: 1 addition & 9 deletions processor/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,13 @@ export const config = {
// Required by logger
loggerLevel: process.env.LOGGER_LEVEL || 'info',

// Update with specific payment providers config
mockClientKey: process.env.MOCK_CLIENT_KEY,
mockEnvironment: process.env.MOCK_ENVIRONMENT,

// Payment Providers config
adyenEnvironment: process.env.ADYEN_ENVIRONMENT || '',
adyenClientKey: process.env.ADYEN_CLIENT_KEY || '',
adyenApiKey: process.env.ADYEN_API_KEY || '',
adyenHMACKey: process.env.ADYEN_NOTIFICATION_HMAC_KEY || '',
adyenLiveUrlPrefix: process.env.ADYEN_LIVE_URL_PREFIX || '',
adyenMerchantAccount: process.env.ADYEN_MERCHANT_ACCOUNT || '',
adyenReturnUrl: process.env.ADYEN_RETURN_URL || '',

// TODO review these configurations
// supportedUIElements: convertStringCommaSeparatedValuesToArray(process.env.SUPPORTED_UI_ELEMENTS),
// enableStoreDetails: process.env.ENABLE_STORE_DETAILS === 'true' ? true : false,
// sellerReturnUrl: process.env.SELLER_RETURN_URL || ''
merchantReturnUrl: process.env.MERCHANT_RETURN_URL || '',
};
65 changes: 65 additions & 0 deletions processor/src/dtos/adyen-payment.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { CreateCheckoutSessionRequest } from '@adyen/api-library/lib/src/typings/checkout/createCheckoutSessionRequest';
import { CreateCheckoutSessionResponse } from '@adyen/api-library/lib/src/typings/checkout/createCheckoutSessionResponse';
import { PaymentDetailsRequest } from '@adyen/api-library/lib/src/typings/checkout/paymentDetailsRequest';
import { PaymentDetailsResponse } from '@adyen/api-library/lib/src/typings/checkout/paymentDetailsResponse';
import { PaymentMethodsRequest } from '@adyen/api-library/lib/src/typings/checkout/paymentMethodsRequest';
import { PaymentMethodsResponse } from '@adyen/api-library/lib/src/typings/checkout/paymentMethodsResponse';
import { PaymentRequest } from '@adyen/api-library/lib/src/typings/checkout/paymentRequest';
import { PaymentResponse } from '@adyen/api-library/lib/src/typings/checkout/paymentResponse';
import { Notification } from '@adyen/api-library/lib/src/typings/notification/notification';

export type PaymentMethodsRequestDTO = Omit<PaymentMethodsRequest, 'amount' | 'merchantAccount' | 'countryCode'>;
export type PaymentMethodsResponseDTO = PaymentMethodsResponse;

export type CreateSessionRequestDTO = Omit<
CreateCheckoutSessionRequest,
| 'amount'
| 'merchantAccount'
| 'countryCode'
| 'returnUrl'
| 'reference'
| 'storePaymentMethod'
| 'shopperReference'
| 'recurringProcessingModel'
| 'storePaymentMethodMode'
>;

export type CreateSessionResponseDTO = {
sessionData: CreateCheckoutSessionResponse;
paymentReference: string;
};

export type CreatePaymentRequestDTO = Omit<
PaymentRequest,
| 'amount'
| 'additionalAmount'
| 'merchantAccount'
| 'countryCode'
| 'returnUrl'
| 'lineItems'
| 'reference'
| 'shopperReference'
| 'recurringProcessingModel'
> & {
paymentReference?: string;
};

export type CreatePaymentResponseDTO = Pick<
PaymentResponse,
'action' | 'resultCode' | 'threeDS2ResponseData' | 'threeDS2Result' | 'threeDSPaymentData'
> & {
paymentReference: string;
};

export type ConfirmPaymentRequestDTO = PaymentDetailsRequest & {
paymentReference: string;
};

export type ConfirmPaymentResponseDTO = Pick<
PaymentDetailsResponse,
'resultCode' | 'threeDS2ResponseData' | 'threeDS2Result' | 'threeDSPaymentData'
> & {
paymentReference: string;
};

export type NotificationRequestDTO = Notification;
3 changes: 0 additions & 3 deletions processor/src/dtos/adyen-payment.dts.ts

This file was deleted.

1 change: 0 additions & 1 deletion processor/src/dtos/operations/config.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Static, Type } from '@sinclair/typebox';
export const ConfigResponseSchema = Type.Object({
clientKey: Type.String(),
environment: Type.String(),
returnUrl: Type.String(),
});

export type ConfigResponseSchemaDTO = Static<typeof ConfigResponseSchema>;
10 changes: 10 additions & 0 deletions processor/src/libs/fastify/context/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ export const getAllowedPaymentMethodsFromContext = (): string[] => {
return authentication?.getPrincipal().allowedPaymentMethods;
};

export const getPaymentInterfaceFromContext = (): string | undefined => {
const authentication = getRequestContext().authentication as SessionAuthentication;
return authentication?.getPrincipal().paymentInterface;
};

export const getProcessorUrlFromContext = (): string => {
const authentication = getRequestContext().authentication as SessionAuthentication;
return authentication?.getPrincipal().processorUrl;
};

export const requestContextPlugin = fp(async (fastify: FastifyInstance) => {
// Enance the request object with a correlationId property
fastify.decorateRequest('correlationId', '');
Expand Down
25 changes: 25 additions & 0 deletions processor/src/libs/fastify/hooks/hmac-auth.hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import HmacValidator from '@adyen/api-library/lib/src/utils/hmacValidator';
import { config } from '../../../config/config';
import { FastifyRequest } from 'fastify';
import { ErrorAuthErrorResponse } from '@commercetools/connect-payments-sdk';
import { NotificationRequestDTO } from '../../../dtos/adyen-payment.dto';

export class HmacAuthHook {
constructor() {}

public authenticate() {
return async (request: FastifyRequest) => {
const data = request.body as NotificationRequestDTO;
if (!data.notificationItems || data.notificationItems.length === 0) {
throw new ErrorAuthErrorResponse('Unexpected payload');
}

const validator = new HmacValidator();
const item = data.notificationItems[0].NotificationRequestItem;

if (!validator.validateHMAC(item, config.adyenHMACKey)) {
throw new ErrorAuthErrorResponse('HMAC is not valid');
}
};
}
}
112 changes: 101 additions & 11 deletions processor/src/routes/adyen-payment.route.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,114 @@
import { SessionAuthenticationHook } from '@commercetools/connect-payments-sdk';
import { FastifyInstance, FastifyPluginOptions } from 'fastify';
import { PaymentNotificationSchemaDTO } from '../dtos/adyen-payment.dts';
import {
ConfirmPaymentRequestDTO,
ConfirmPaymentResponseDTO,
CreatePaymentRequestDTO,
CreatePaymentResponseDTO,
CreateSessionRequestDTO,
CreateSessionResponseDTO,
NotificationRequestDTO,
PaymentMethodsRequestDTO,
PaymentMethodsResponseDTO,
} from '../dtos/adyen-payment.dto';
import { AdyenPaymentService } from '../services/adyen-payment.service';

const ACK_NOTIFICATION = '[accepted]';
import { config } from '../config/config';
import { HmacAuthHook } from '../libs/fastify/hooks/hmac-auth.hook';

type PaymentRoutesOptions = {
paymentService: AdyenPaymentService;
sessionAuthHook: SessionAuthenticationHook;
hmacAuthHook: HmacAuthHook;
};

export const paymentRoutes = async (fastify: FastifyInstance, opts: FastifyPluginOptions & PaymentRoutesOptions) => {
/**
* Listen to the notification from Adyen
*/
fastify.post<{ Body: PaymentNotificationSchemaDTO; Reply: any }>('/notifications', {}, async (request, reply) => {
await opts.notificationService.processNotification({
data: request.body,
export const adyenPaymentRoutes = async (
fastify: FastifyInstance,
opts: FastifyPluginOptions & PaymentRoutesOptions,
) => {
fastify.post<{ Body: PaymentMethodsRequestDTO; Reply: PaymentMethodsResponseDTO }>(
'/payment-methods',
{
preHandler: [opts.sessionAuthHook.authenticate()],
},
async (request, reply) => {
const resp = await opts.paymentService.getPaymentMethods({
data: request.body,
});

return reply.status(200).send(resp);
},
);

fastify.post<{ Body: CreateSessionRequestDTO; Reply: CreateSessionResponseDTO }>(
'/sessions',
{
preHandler: [opts.sessionAuthHook.authenticate()],
},
async (request, reply) => {
const resp = await opts.paymentService.createSession({
data: request.body,
});

return reply.status(200).send(resp);
},
);

fastify.post<{ Body: CreatePaymentRequestDTO; Reply: CreatePaymentResponseDTO }>(
'/payments',
{
preHandler: [opts.sessionAuthHook.authenticate()],
},
async (request, reply) => {
const resp = await opts.paymentService.createPayment({
data: request.body,
});

return reply.status(200).send(resp);
},
);

fastify.get<{ Reply: ConfirmPaymentResponseDTO }>('/payments/details', {}, async (request, reply) => {
const queryParams = request.query as any;
const res = await opts.paymentService.confirmPayment({
data: {
details: {
redirectResult: queryParams.redirectResult as string,
},
paymentReference: queryParams.paymentReference as string,
},
});

return reply.status(200).send(ACK_NOTIFICATION);
return reply.redirect(buildRedirectUrl(res.paymentReference));
});

fastify.post<{ Body: ConfirmPaymentRequestDTO; Reply: ConfirmPaymentResponseDTO }>(
'/payments/details',
{},
async (request, reply) => {
const res = await opts.paymentService.confirmPayment({
data: request.body,
});
return reply.status(200).send(res);
},
);

fastify.post<{ Body: NotificationRequestDTO }>(
'/notifications',
{
preHandler: [opts.hmacAuthHook.authenticate()],
},
async (request, reply) => {
await opts.notificationService.processNotification({
data: request.body,
});

return reply.status(200).send('[accepted]');
},
);
};

const buildRedirectUrl = (paymentReference: string) => {
const redirectUrl = new URL(config.merchantReturnUrl);
redirectUrl.searchParams.append('paymentReference', paymentReference);
return redirectUrl.toString();
};
12 changes: 7 additions & 5 deletions processor/src/server/plugins/adyen-payment.plugin.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { FastifyInstance } from 'fastify';
import { paymentSDK } from '../../payment-sdk';
import { paymentRoutes } from '../../routes/mock-payment.route';
import { MockPaymentService } from '../../services/mock-payment.service';
import { adyenPaymentRoutes } from '../../routes/adyen-payment.route';
import { AdyenPaymentService } from '../../services/adyen-payment.service';
import { HmacAuthHook } from '../../libs/fastify/hooks/hmac-auth.hook';

export default async function (server: FastifyInstance) {
const mockPaymentService = new MockPaymentService({
const paymentService = new AdyenPaymentService({
ctCartService: paymentSDK.ctCartService,
ctPaymentService: paymentSDK.ctPaymentService,
});

await server.register(paymentRoutes, {
paymentService: mockPaymentService,
await server.register(adyenPaymentRoutes, {
paymentService,
sessionAuthHook: paymentSDK.sessionAuthHookFn,
hmacAuthHook: new HmacAuthHook(),
});
}
Loading
Loading