Skip to content

Commit

Permalink
feat(paypal): Added better error logging for PayPal requests
Browse files Browse the repository at this point in the history
- Removed unused method in helpers
  • Loading branch information
sonntag-philipp committed Oct 10, 2024
1 parent 44e9d3c commit a46fc50
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 86 deletions.
1 change: 1 addition & 0 deletions package-lock.json

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

20 changes: 0 additions & 20 deletions packages/payments-plugin/e2e/payment-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,26 +108,6 @@ export async function addManualPayment(server: TestServer, orderId: ID, amount:
});
}

export async function getAllOrders(server: TestServer) {
const orderService = server.app.get(OrderService);
const channel = await server.app.get(ChannelService).getDefaultChannel();
const ctx = new RequestContext({
channel,
authorizedAsOwnerOnly: true,
apiType: 'shop',
isAuthorized: true,
session: {
activeOrderId: undefined,
activeChannelId: 1,
user: {
id: 1,
},
} as any,
});

return await orderService.findAll(ctx);
}

/**
* Create a coupon with the given code and discount amount.
*/
Expand Down
10 changes: 10 additions & 0 deletions packages/payments-plugin/e2e/paypal-sdk.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,22 @@ export interface PayPalSdkPluginOptions {
}

// @ts-ignore
/**
* This tagged template literal has no special functionality. It is used for syntax highlighting and formatting
* purposes in the IDE.
*/
function html(strings, ...values) {
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
return strings.reduce((acc, str, i) => acc + str + (values[i] || ''), '');
}

/**
* Format GraphQL strings to they can be used directly within a http request send
* from the Client.
*
* All queries will be formatted within a single line.
*/
function formatQuery(gql: DocumentNode): string {
return print(gql).replace(/\n|\r/g, '');
}
Expand Down
131 changes: 89 additions & 42 deletions packages/payments-plugin/src/paypal/paypal-authorization.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Inject } from '@nestjs/common';
import { InternalServerError, PaymentMethodService, RequestContext } from '@vendure/core';
import { InternalServerError, Logger, PaymentMethodService, RequestContext } from '@vendure/core';

import { PAYPAL_PAYMENT_PLUGIN_OPTIONS } from './constants';
import { loggerCtx, PAYPAL_PAYMENT_PLUGIN_OPTIONS } from './constants';
import { PayPalBaseService } from './paypal-base.service';
import { PayPalOrderInformation, PayPalPluginOptions } from './types';

Expand All @@ -27,52 +27,87 @@ export class PayPalAuthorizationService extends PayPalBaseService {
});

if (!response.ok) {
throw new Error('Failed to get PayPal order details.');
throw response;
}

return (await response.json()) as PayPalOrderInformation;
} catch (error) {
throw new InternalServerError('PayPal integration failed');
} catch (error: any) {
const message = 'Failed to get PayPal authorization details';
if (error instanceof Response) {
const responseClone = error.clone();
Logger.error(message, loggerCtx, await responseClone.text());
}

Logger.error(message, loggerCtx, error?.stack);
// Throw a generic error to avoid leaking sensitive information
throw new InternalServerError(message);
}
}

async reauthorizeOrder(ctx: RequestContext, authorizationId: string): Promise<PayPalOrderInformation> {
const accessToken = (await this.authenticate(ctx)).access_token;

const response = await fetch(
`${this.options.apiUrl}/v2/payments/authorizations/${authorizationId}/reauthorize`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
try {
const response = await fetch(
`${this.options.apiUrl}/v2/payments/authorizations/${authorizationId}/reauthorize`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
},
},
);
);

if (!response.ok) {
throw new Error('Failed to reauthorize PayPal order.');
}
if (!response.ok) {
throw response;
}

return (await response.json()) as PayPalOrderInformation;
return (await response.json()) as PayPalOrderInformation;
} catch (error: any) {
const message = 'PayPal reauthorization failed';
if (error instanceof Response) {
const responseClone = error.clone();
Logger.error(message, loggerCtx, await responseClone.text());
}

Logger.error(message, loggerCtx, error?.stack);
// Throw a generic error to avoid leaking sensitive information
throw new InternalServerError(message);
}
}

async authorizeOrder(ctx: RequestContext, paypalOrderId: string): Promise<PayPalOrderInformation> {
const accessToken = (await this.authenticate(ctx)).access_token;

const response = await fetch(`${this.options.apiUrl}/v2/checkout/orders/${paypalOrderId}/authorize`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
});
try {
const response = await fetch(
`${this.options.apiUrl}/v2/checkout/orders/${paypalOrderId}/authorize`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
},
);

if (!response.ok) {
throw new Error('Failed to authorize PayPal order.');
}
if (!response.ok) {
throw response;
}

return (await response.json()) as PayPalOrderInformation;
} catch (error: any) {
const message = 'PayPal authorization failed';
if (error instanceof Response) {
const responseClone = error.clone();
Logger.error(message, loggerCtx, await responseClone.text());
}

return (await response.json()) as PayPalOrderInformation;
Logger.error(message, loggerCtx, error?.stack);
// Throw a generic error to avoid leaking sensitive information
throw new InternalServerError(message);
}
}

async captureAuthorization(
Expand All @@ -81,21 +116,33 @@ export class PayPalAuthorizationService extends PayPalBaseService {
): Promise<PayPalOrderInformation> {
const accessToken = (await this.authenticate(ctx)).access_token;

const response = await fetch(
`${this.options.apiUrl}/v2/payments/authorizations/${authorizationId}/capture`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
try {
const response = await fetch(
`${this.options.apiUrl}/v2/payments/authorizations/${authorizationId}/capture`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
},
},
);
);

if (!response.ok) {
throw new Error('Failed to capture payment.');
}
if (!response.ok) {
throw response;
}

return (await response.json()) as PayPalOrderInformation;
return (await response.json()) as PayPalOrderInformation;
} catch (error: any) {
const message = 'PayPal capture failed';
if (error instanceof Response) {
const responseClone = error.clone();
Logger.error(message, loggerCtx, await responseClone.text());
}

Logger.error(message, loggerCtx, error?.stack);
// Throw a generic error to avoid leaking sensitive information
throw new InternalServerError(message);
}
}
}
8 changes: 1 addition & 7 deletions packages/payments-plugin/src/paypal/paypal-base.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,10 @@ export abstract class PayPalBaseService {
protected async authenticate(ctx: RequestContext): Promise<PayPalAuthorizationResponse> {
const args = await this.getPaymentHandlerArgs(ctx);

if (args.length <= 0) {
throw new InternalServerError('PayPal payment method is not configured correctly.');
}

const clientId = args.find(arg => arg.name === 'clientId')?.value;
const clientSecret = args.find(arg => arg.name === 'clientSecret')?.value;
if (!clientId || !clientSecret) {
throw new InternalServerError(
'PayPal payment method is not configured correctly. Please set clientId and clientSecret.',
);
throw new InternalServerError('PayPal payment method is not configured correctly');
}

const authenticationToken = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
Expand Down
24 changes: 14 additions & 10 deletions packages/payments-plugin/src/paypal/paypal-capture.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@ import {
ConfigService,
CreatePaymentErrorResult,
InternalServerError,
Logger,
Order,
PaymentMethodService,
RequestContext,
} from '@vendure/core';

import { PAYPAL_PAYMENT_PLUGIN_OPTIONS } from './constants';
import { loggerCtx, PAYPAL_PAYMENT_PLUGIN_OPTIONS } from './constants';
import { PayPalBaseService } from './paypal-base.service';
import { convertAmount } from './paypal-order.helpers';
import {
PayPalOrderInformation,
PayPalPluginOptions,
PayPalRefundRequest,
PayPalRefundResponse,
} from './types';
import { PayPalPluginOptions, PayPalRefundRequest, PayPalRefundResponse } from './types';

export class PayPalCaptureService extends PayPalBaseService {
private readonly precision: number;
Expand Down Expand Up @@ -64,12 +60,20 @@ export class PayPalCaptureService extends PayPalBaseService {
});

if (!response.ok) {
throw new Error();
throw response;
}

return (await response.json()) as PayPalRefundResponse;
} catch (error) {
throw new InternalServerError('PayPal refund failed.');
} catch (error: any) {
const message = 'PayPal refund failed';
if (error instanceof Response) {
const responseClone = error.clone();
Logger.error(message, loggerCtx, await responseClone.text());
}

Logger.error(message, loggerCtx, error?.stack);
// Throw a generic error to avoid leaking sensitive information
throw new InternalServerError(message);
}
}
}
8 changes: 1 addition & 7 deletions packages/payments-plugin/src/paypal/paypal-order.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,9 @@ export class PayPalOrderService extends PayPalBaseService {
): Promise<CreatePaymentErrorResult | undefined> {
const args = await this.getPaymentHandlerArgs(ctx);

if (args.length <= 0) {
throw new InternalServerError('PayPal payment method is not configured correctly.');
}

const merchantId = args.find(arg => arg.name === 'merchantId')?.value;
if (!merchantId) {
throw new InternalServerError(
'PayPal payment method is not configured correctly. Please set merchantId.',
);
throw new InternalServerError('PayPal payment method is not configured correctly');
}

if (paypalOrder.status !== 'APPROVED') {
Expand Down

0 comments on commit a46fc50

Please sign in to comment.