Checkout: only customer fields on order updates, verified PayPal capture, safe logging #6
No files matched your search
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
+26
-20
@@ -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
|
||||
|
||||
Reference in new issue
Block a user