Skip to content

Commit

Permalink
#37070 webhook handling
Browse files Browse the repository at this point in the history
  • Loading branch information
mbe1987 committed Sep 10, 2024
1 parent 0f36636 commit 248a083
Show file tree
Hide file tree
Showing 7 changed files with 40 additions and 106 deletions.
8 changes: 2 additions & 6 deletions paypal-commercetools-extension/src/connector/post-deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import dotenv from 'dotenv';
dotenv.config();

import { createApiRoot } from '../client/create.client';
import { PAYPAL_EXTENSION_PATH } from '../routes/service.route';
import { PAYPAL_WEBHOOKS_PATH } from '../routes/webhook.route';
import { createOrUpdateWebhook } from '../service/paypal.service';
import { createWebhook } from '../service/paypal.service';
import { assertError, assertString } from '../utils/assert.utils';
import { readConfiguration } from '../utils/config.utils';
import {
Expand All @@ -31,9 +29,7 @@ async function postDeploy(properties: Map<string, unknown>): Promise<void> {
await createCustomPaymentType(apiRoot);
await createCustomCustomerType(apiRoot);
await createCustomPaymentInteractionType(apiRoot);
await createOrUpdateWebhook(
applicationUrl.replace(PAYPAL_EXTENSION_PATH, PAYPAL_WEBHOOKS_PATH)
);
await createWebhook();
await createAndSetCustomObject(apiRoot);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import {
handleOrderWebhook,
handlePaymentTokenWebhook,
} from '../service/commercetools.service';
import { getWebhookId } from '../service/config.service';
import { validateSignature } from '../service/paypal.service';
import { getWebhookId, validateSignature } from '../service/paypal.service';
import { logger } from '../utils/logger.utils';

async function verifyWebhookSignature(request: Request) {
const webhookIdField = await getWebhookId();
if (!webhookIdField?.value) {
const webhookId = await getWebhookId();
if (!webhookId) {
throw new CustomError(500, 'WebhookId is missing');
}
const verificationRequest: VerifyWebhookSignature = {
Expand All @@ -23,7 +22,7 @@ async function verifyWebhookSignature(request: Request) {
transmission_sig: request.header('paypal-transmission-sig') ?? '',
transmission_time: request.header('paypal-transmission-time') ?? '',
webhook_event: request.body,
webhook_id: webhookIdField.value,
webhook_id: webhookId,
};
const response = await validateSignature(verificationRequest);
logger.info(JSON.stringify(response));
Expand Down
46 changes: 0 additions & 46 deletions paypal-commercetools-extension/src/service/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,52 +58,6 @@ export const cacheAccessToken = async (
.execute();
};

export const getWebhookId = async () => {
try {
const apiRoot = createApiRoot();
return (
await apiRoot
.customObjects()
.withContainerAndKey({
container: 'paypal-commercetools-connector',
key: 'webhookId',
})
.get()
.execute()
).body;
} catch (e) {
logger.warn('Failed to load stored webhookId', e);
return undefined;
}
};

export const storeWebhookId = async (webhookId: string, version: number) => {
const apiRoot = createApiRoot();
return apiRoot
.customObjects()
.post({
body: {
container: 'paypal-commercetools-connector',
key: 'webhookId',
value: webhookId,
version: version,
},
})
.execute();
};

export const deleteWebhookId = async () => {
const apiRoot = createApiRoot();
return apiRoot
.customObjects()
.withContainerAndKey({
container: 'paypal-commercetools-connector',
key: 'webhookId',
})
.delete()
.execute();
};

export const deleteAccessToken = async () => {
const apiRoot = createApiRoot();
return apiRoot
Expand Down
64 changes: 24 additions & 40 deletions paypal-commercetools-extension/src/service/paypal.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,11 @@ import {
VerifyWebhookSignatureApi,
WebhooksApi,
} from '../paypal/webhooks_api';
import { PAYPAL_EXTENSION_PATH } from '../routes/service.route';
import { PAYPAL_WEBHOOKS_PATH } from '../routes/webhook.route';
import { Order } from '../types/index.types';
import { logger } from '../utils/logger.utils';
import {
cacheAccessToken,
deleteWebhookId,
getCachedAccessToken,
getWebhookId,
storeWebhookId,
} from './config.service';
import { cacheAccessToken, getCachedAccessToken } from './config.service';

const PAYPAL_API_SANDBOX = 'https://api-m.sandbox.paypal.com';
const PAYPAL_API_LIVE = 'https://api-m.paypal.com';
Expand Down Expand Up @@ -363,51 +358,22 @@ const getAPIEndpoint = () => {
: PAYPAL_API_SANDBOX;
};

export const createOrUpdateWebhook = async (url: string) => {
export const createWebhook = async () => {
const gateway = await getPayPalWebhooksGateway();
const webhooks = await gateway.webhooksList('APPLICATION');
logger.info(JSON.stringify(webhooks.data.webhooks));
const oldWebhook = webhooks.data.webhooks?.find((webhook) =>
webhook.url.endsWith(PAYPAL_WEBHOOKS_PATH)
);
if (oldWebhook && oldWebhook?.id) {
const webhookIdField = await getWebhookId();
if (webhookIdField?.value !== oldWebhook?.id) {
await storeWebhookId(oldWebhook?.id, webhookIdField?.version ?? 0);
}
if (oldWebhook?.url === url) {
logger.info('Webhook URL did not change');
return oldWebhook;
}
const response = await gateway.webhooksUpdate(oldWebhook.id, [
{
op: 'replace',
path: '/url',
value: url,
},
]);
logger.info(`Webhook url updated from ${oldWebhook.url} to ${url}`);
return response.data;
}
const response = await gateway.webhooksPost({
url: url,
url: getWebhookUrl(),
event_types: [
{
name: '*',
} as EventType,
],
});
if (response?.data?.id) {
const webhookIdField = await getWebhookId();
await storeWebhookId(response.data.id, webhookIdField?.version ?? 0);
}
return response.data;
};

export const deleteWebhook = async () => {
const gateway = await getPayPalWebhooksGateway();
const webhookIdField = await getWebhookId();
const webhookId = webhookIdField?.value;
const webhookId = await getWebhookId();
if (!webhookId) {
return;
}
Expand All @@ -421,7 +387,25 @@ export const deleteWebhook = async () => {
throw e;
}
}
await deleteWebhookId();
};

export const getWebhookId = async () => {
const webhookUrl = getWebhookUrl();
const gateway = await getPayPalWebhooksGateway();
const webhooks = await gateway.webhooksList('APPLICATION');
const webhook = webhooks.data.webhooks?.find(
(webhook) => webhook.url === webhookUrl
);
return webhook?.id;
};

export const getWebhookUrl = () => {
return (
process.env.CONNECT_SERVICE_URL?.replace(
PAYPAL_EXTENSION_PATH,
PAYPAL_WEBHOOKS_PATH
) ?? ''
);
};

export const addDeliveryData = async (
Expand Down
8 changes: 4 additions & 4 deletions paypal-commercetools-extension/tests/post-deploy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ describe('Testing post deploy', () => {
}));
require('../src/connector/post-deploy');
await sleep(10000);
expect(apiRoot.post).toBeCalledTimes(8);
expect(apiRoot.post).toBeCalledTimes(7);
expect(apiRoot.delete).toBeCalledTimes(1);
expect(apiRoot.get).toBeCalledTimes(9);
expect(apiRequest.execute).toBeCalledTimes(18);
expect(webhooksApi.webhooksList).toBeCalledTimes(1);
expect(apiRoot.get).toBeCalledTimes(8);
expect(apiRequest.execute).toBeCalledTimes(16);
expect(webhooksApi.webhooksList).toBeCalledTimes(0);
expect(webhooksApi.webhooksPost).toBeCalledTimes(1);
}, 20000);
});
1 change: 1 addition & 0 deletions paypal-commercetools-extension/tests/pre-undeploy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const mockConfigModule = () => {
})),
};
apiRoot = {
customObjects: jest.fn(() => apiRoot),
extensions: jest.fn(() => apiRoot),
withKey: jest.fn(() => apiRoot),
delete: jest.fn(() => apiRequest),
Expand Down
10 changes: 5 additions & 5 deletions paypal-commercetools-extension/tests/webhooks.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const mockConfigModule = () => {
});
jest.mock('../src/service/paypal.service', () => ({
validateSignature: () => ({ verification_status: 'SUCCESS' }),
getWebhookId: () => 1,
getPayPalOrder: () => ({
status: 'COMPLETED',
}),
Expand All @@ -37,7 +38,6 @@ beforeEach(() => {
apiRequest = {
execute: jest
.fn()
.mockReturnValueOnce({ body: { value: 'VALUE' } })
.mockReturnValueOnce({
body: {
total: 1,
Expand Down Expand Up @@ -86,31 +86,31 @@ describe('Testing webhook controller', () => {
resource_type: 'capture',
resource: { id: 1 },
action: 'changeTransactionState',
executeCalls: 3,
executeCalls: 2,
actionsCount: 3,
},
{
name: 'test payment_token',
resource_type: 'payment_token',
resource: { id: 1, customer: { id: 123 }, metadata: { order_id: 2 } },
action: 'setCustomType',
executeCalls: 5,
executeCalls: 4,
actionsCount: 1,
},
{
name: 'test order with authorization with missing transaction',
resource_type: 'checkout-order',
resource: { id: 1, intent: CheckoutPaymentIntent.Authorize },
action: 'setStatusInterfaceCode',
executeCalls: 3,
executeCalls: 2,
actionsCount: 2,
},
{
name: 'test authorization with missing transaction',
resource_type: 'authorization',
resource: { id: 1 },
action: 'addTransaction',
executeCalls: 3,
executeCalls: 2,
actionsCount: 3,
},
])(
Expand Down

0 comments on commit 248a083

Please sign in to comment.