Skip to content

Commit

Permalink
Merge pull request #8 from commercetools/ddeliziact/initial_setup
Browse files Browse the repository at this point in the history
feat(operations): basic operation implementation
  • Loading branch information
dasanorct authored Feb 20, 2024
2 parents 2e03bbc + f3a0540 commit f24055f
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 2 deletions.
4 changes: 2 additions & 2 deletions .github/dependabot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ updates:
- package-ecosystem: "npm"
directory: "/processor"
schedule:
interval: "daily"
interval: "weekly"
open-pull-requests-limit: 10
- package-ecosystem: "npm"
directory: "/enabler"
schedule:
interval: "daily"
interval: "weekly"
open-pull-requests-limit: 10
44 changes: 44 additions & 0 deletions processor/package-lock.json

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

1 change: 1 addition & 0 deletions processor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"@adyen/api-library": "16.0.1",
"@commercetools-backend/loggers": "22.17.2",
"@commercetools/connect-payments-sdk": "0.1.0",
"@commercetools/platform-sdk": "7.3.0",
Expand Down
14 changes: 14 additions & 0 deletions processor/src/clients/adyen/adyen.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Client, CheckoutAPI } from '@adyen/api-library';
import { config } from '../../config/config';

export const AdyenAPI = (): CheckoutAPI => {
const apiClient = new Client({
apiKey: config.adyenApiKey,
environment: config.adyenEnvironment.toUpperCase() as Environment,
...(config.adyenLiveUrlPrefix && {
liveEndpointUrlPrefix: config.adyenLiveUrlPrefix,
}),
});

return new CheckoutAPI(apiClient);
};
7 changes: 7 additions & 0 deletions processor/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ export const config = {

// Payment Providers config
returnUrl: process.env.RETURN_URL,
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),
Expand Down
3 changes: 3 additions & 0 deletions processor/src/dtos/adyen-payment.dts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Notification } from '@adyen/api-library/lib/src/typings/notification/notification';

export type PaymentNotificationSchemaDTO = Notification;
24 changes: 24 additions & 0 deletions processor/src/routes/adyen-payment.route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { SessionAuthenticationHook } from '@commercetools/connect-payments-sdk';
import { FastifyInstance, FastifyPluginOptions } from 'fastify';
import { PaymentNotificationSchemaDTO } from '../dtos/adyen-payment.dts';
import { AdyenPaymentService } from '../services/adyen-payment.service';

const ACK_NOTIFICATION = '[accepted]';

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

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,
});

return reply.status(200).send(ACK_NOTIFICATION);
});
};
16 changes: 16 additions & 0 deletions processor/src/server/plugins/adyen-payment.plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FastifyInstance } from 'fastify';
import { paymentSDK } from '../../payment-sdk';
import { paymentRoutes } from '../../routes/mock-payment.route';
import { MockPaymentService } from '../../services/mock-payment.service';

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

await server.register(paymentRoutes, {
paymentService: mockPaymentService,
sessionAuthHook: paymentSDK.sessionAuthHookFn,
});
}
42 changes: 42 additions & 0 deletions processor/src/services/adyen-payment.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { CommercetoolsCartService, CommercetoolsPaymentService } from '@commercetools/connect-payments-sdk';
import { NotificationConverter } from './converters/notification.converter';
import { ProcessNotification as ProcessNotificationRequest } from './types/adyen-payment.type';
import { hmacValidator } from '@adyen/api-library';
import { config } from '../config/config';

export type AdyenPaymentServiceOptions = {
ctCartService: CommercetoolsCartService;
ctPaymentService: CommercetoolsPaymentService;
notificationConverter: NotificationConverter;
};

export class AdyenPaymentService {
private ctCartService: CommercetoolsCartService;
private ctPaymentService: CommercetoolsPaymentService;
private notificationConverter: NotificationConverter;

constructor(opts: AdyenPaymentServiceOptions) {
this.ctCartService = opts.ctCartService;
this.ctPaymentService = opts.ctPaymentService;
this.notificationConverter = opts.notificationConverter;
}

public async processNotification(opts: ProcessNotificationRequest): Promise<void> {
await this.validateHmac(opts);
const updateData = await this.notificationConverter.convert(opts);
await this.ctPaymentService.updatePayment(updateData);
}

private async validateHmac(opts: ProcessNotificationRequest): Promise<void> {
if (!opts.data.notificationItems || opts.data.notificationItems.length === 0) {
//TODO: throw an error 401
}

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

if (!validator.validateHMAC(item, config.adyenHMACKey)) {
//TODO: throw an error 401
}
}
}
84 changes: 84 additions & 0 deletions processor/src/services/converters/notification.converter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Money } from '@commercetools/platform-sdk';

import { NotificationRequestItem } from '@adyen/api-library/lib/src/typings/notification/notificationRequestItem';

import { ProcessNotification } from '../types/adyen-payment.type';
import { TransactionData, UpdatePayment } from '@commercetools/connect-payments-sdk';

export class NotificationConverter {
constructor() {}

public async convert(opts: ProcessNotification): Promise<UpdatePayment> {
const item = opts.data.notificationItems[0].NotificationRequestItem;

return {
id: item.merchantReference,
pspReference: item.pspReference,
transaction: this.populateTransaction(item),
};
}

private populateTransaction(item: NotificationRequestItem): TransactionData {
switch (item.eventCode) {
case NotificationRequestItem.EventCodeEnum.Authorisation:
return {
type: 'Authorization',
state: item.success ? 'Success' : 'Failure',
amount: this.populateAmount(item),
interactionId: item.pspReference,
};
case NotificationRequestItem.EventCodeEnum.Capture:
return {
type: 'Charge',
state: 'Failure',
amount: this.populateAmount(item),
interactionId: item.pspReference,
};
case NotificationRequestItem.EventCodeEnum.CaptureFailed:
return {
type: 'Charge',
state: 'Failure',
amount: this.populateAmount(item),
interactionId: item.pspReference,
};
case NotificationRequestItem.EventCodeEnum.Cancellation:
return {
type: 'CancelAuthorization',
state: item.success ? 'Success' : 'Failure',
amount: this.populateAmount(item),
interactionId: item.pspReference,
};
case NotificationRequestItem.EventCodeEnum.Refund:
return {
type: 'Refund',
state: item.success ? 'Success' : 'Failure',
amount: this.populateAmount(item),
interactionId: item.pspReference,
};
case NotificationRequestItem.EventCodeEnum.RefundFailed:
return {
type: 'Refund',
state: 'Failure',
amount: this.populateAmount(item),
interactionId: item.pspReference,
};
case NotificationRequestItem.EventCodeEnum.Chargeback:
return {
type: 'Chargeback',
state: 'Success',
amount: this.populateAmount(item),
interactionId: item.pspReference,
};
default:
//TODO: throw unsupported notification error
throw new Error('Unsupported notification');
}
}

private populateAmount(item: NotificationRequestItem): Money {
return {
centAmount: item.amount.value as number,
currencyCode: item.amount.currency as string,
};
}
}
Loading

0 comments on commit f24055f

Please sign in to comment.