From fdaec8d0e10576760f611575c946037b1acb2494 Mon Sep 17 00:00:00 2001 From: Philipp Sonntag Date: Thu, 10 Oct 2024 15:58:48 +0200 Subject: [PATCH] feat(payments-plugin): Added better error logging for PayPal requests - Removed unused method in helpers --- package-lock.json | 1 + .../payments-plugin/e2e/payment-helpers.ts | 20 --- .../payments-plugin/e2e/paypal-sdk.plugin.ts | 10 ++ .../paypal/paypal-authorization.service.ts | 131 ++++++++++++------ .../src/paypal/paypal-base.service.ts | 8 +- .../src/paypal/paypal-capture.service.ts | 24 ++-- .../src/paypal/paypal-order.service.ts | 8 +- 7 files changed, 116 insertions(+), 86 deletions(-) diff --git a/package-lock.json b/package-lock.json index c011193a0e..4d99ace0c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4338,6 +4338,7 @@ }, "node_modules/@clack/prompts/node_modules/is-unicode-supported": { "version": "1.3.0", + "extraneous": true, "inBundle": true, "license": "MIT", "engines": { diff --git a/packages/payments-plugin/e2e/payment-helpers.ts b/packages/payments-plugin/e2e/payment-helpers.ts index 91b7042337..35a08348dd 100644 --- a/packages/payments-plugin/e2e/payment-helpers.ts +++ b/packages/payments-plugin/e2e/payment-helpers.ts @@ -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. */ diff --git a/packages/payments-plugin/e2e/paypal-sdk.plugin.ts b/packages/payments-plugin/e2e/paypal-sdk.plugin.ts index 3e07c46b6a..52bd289a3d 100644 --- a/packages/payments-plugin/e2e/paypal-sdk.plugin.ts +++ b/packages/payments-plugin/e2e/paypal-sdk.plugin.ts @@ -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, ''); } diff --git a/packages/payments-plugin/src/paypal/paypal-authorization.service.ts b/packages/payments-plugin/src/paypal/paypal-authorization.service.ts index bd2632a09a..48c12cc376 100644 --- a/packages/payments-plugin/src/paypal/paypal-authorization.service.ts +++ b/packages/payments-plugin/src/paypal/paypal-authorization.service.ts @@ -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'; @@ -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 { 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 { 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( @@ -81,21 +116,33 @@ export class PayPalAuthorizationService extends PayPalBaseService { ): Promise { 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); + } } } diff --git a/packages/payments-plugin/src/paypal/paypal-base.service.ts b/packages/payments-plugin/src/paypal/paypal-base.service.ts index d45b1dfc96..99188c471f 100644 --- a/packages/payments-plugin/src/paypal/paypal-base.service.ts +++ b/packages/payments-plugin/src/paypal/paypal-base.service.ts @@ -38,16 +38,10 @@ export abstract class PayPalBaseService { protected async authenticate(ctx: RequestContext): Promise { 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'); diff --git a/packages/payments-plugin/src/paypal/paypal-capture.service.ts b/packages/payments-plugin/src/paypal/paypal-capture.service.ts index 02bbf711ec..ee8646a8ed 100644 --- a/packages/payments-plugin/src/paypal/paypal-capture.service.ts +++ b/packages/payments-plugin/src/paypal/paypal-capture.service.ts @@ -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; @@ -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); } } } diff --git a/packages/payments-plugin/src/paypal/paypal-order.service.ts b/packages/payments-plugin/src/paypal/paypal-order.service.ts index 81ec612276..fcf9ea5aac 100644 --- a/packages/payments-plugin/src/paypal/paypal-order.service.ts +++ b/packages/payments-plugin/src/paypal/paypal-order.service.ts @@ -116,15 +116,9 @@ export class PayPalOrderService extends PayPalBaseService { ): Promise { 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') {