diff --git a/src/api/order/controllers/order.ts b/src/api/order/controllers/order.ts index 6d45cfe..8c9302e 100644 --- a/src/api/order/controllers/order.ts +++ b/src/api/order/controllers/order.ts @@ -358,7 +358,7 @@ export default factories.createCoreController("api::order.order", ({ strapi }) = try { return await paypalApi.handleWebhook(ctx.request.body); } catch (error) { - strapi.log.error(`paypal-api: ${JSON.stringify({ error })}`); + strapi.log.error("app:e:order-controller: Handling the PayPal webhook failed", { error: describeError(error) }); return ctx.internalServerError(); } } diff --git a/src/services/PayPalApi.ts b/src/services/PayPalApi.ts index 00c7415..e5f2a15 100644 --- a/src/services/PayPalApi.ts +++ b/src/services/PayPalApi.ts @@ -16,10 +16,12 @@ class PayPalApi { }, timeout: 0, environment, + // The SDK logs method, URL and status of each request to the console. Not the bodies: a request body holds the buyer's name + // and address. Not the headers: they hold the token. logging: { logLevel: LogLevel.Info, - logRequest: { logBody: true }, - logResponse: { logHeaders: true } + logRequest: { logBody: false, logHeaders: false }, + logResponse: { logBody: false, logHeaders: false } } }); this.ordersController = new OrdersController(this.client); @@ -29,28 +31,25 @@ class PayPalApi { * Creates the PayPal order for the order and returns PayPal's id for it, which the capture accepts, and its links. Throws * CheckoutNotReadyError or ProductUnavailableError before PayPal is asked, for an order PayPal cannot take. */ - async createSessionOrThrow(returnUrl: string, order: PayPalOrderSource): Promise<{ id: string; links: unknown }> { + async createSessionOrThrow(returnUrl: string, order: PayPalOrderSource & { id?: number | string }): Promise<{ id: string; links: unknown }> { // Outside the try block: an order that is not ready is the customer's to fix, not a PayPal failure. const request = buildPayPalOrderRequest(returnUrl, order) as OrderRequest; try { - const collect = { body: request, prefer: "return=minimal" }; + const { body } = await this.ordersController.ordersCreate({ body: request, prefer: "return=minimal" }); + const session = JSON.parse(body as string); + if (typeof session?.id !== "string" || session.id === "") throw new Error("PayPal answered without an order id"); - strapi.log.silly("app:d:paypal-api: Creating PayPal order " + JSON.stringify({ collect })); - - const { body, ...httpResponse } = await this.ordersController.ordersCreate(collect); - const sessionData = JSON.parse(body as string); - - strapi.log.info("app:i:paypal-api: " + JSON.stringify({ session: sessionData })); - - if (typeof sessionData?.id !== "string" || sessionData.id === "") throw new Error("PayPal answered without an order id"); - return { - id: sessionData.id, - links: sessionData.links - }; + // The PayPal order's id and status only: the request and PayPal's order hold the buyer's name and address. + strapi.log.info("app:i:paypal-api: Created the PayPal order", { + order: order.id, + paypalOrderId: session.id, + status: typeof session.status === "string" ? session.status : undefined + }); + return { id: session.id, links: session.links }; } catch (error) { // Never the error object: an SDK or axios error carries the request with its Authorization header. - strapi.log.error("app:e:paypal-api: Creating the PayPal order failed", { error: describeError(error) }); + strapi.log.error("app:e:paypal-api: Creating the PayPal order failed", { order: order.id, error: describeError(error) }); throw error; } } @@ -71,9 +70,16 @@ class PayPalApi { return JSON.parse(body as string); } - async handleWebhook(req: any) { - // Implement PayPal webhook validation and processing - strapi.log.verbose("app:i:paypal-api: " + JSON.stringify({ req })); + async handleWebhook(req: unknown) { + // Implement PayPal webhook validation and processing. The event's resource holds the buyer's name and address, so only the + // event type and the resource's id and status are logged. + const event = (typeof req === "object" && req !== null ? req : {}) as { event_type?: unknown; resource?: { id?: unknown; status?: unknown } }; + const textOf = (value: unknown) => (typeof value === "string" ? value : undefined); + strapi.log.verbose("app:v:paypal-api: Webhook received", { + eventType: textOf(event.event_type), + resource: textOf(event.resource?.id), + status: textOf(event.resource?.status) + }); // Note: PayPal webhook handling requires specific implementation // This is a placeholder and should be replaced with actual PayPal webhook validation