@goweekdays/core 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @goweekdays/core
2
2
 
3
+ ## 2.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 260c0c8: Paypal webhook verification implementation
8
+
3
9
  ## 2.5.0
4
10
 
5
11
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -630,7 +630,7 @@ declare function useGitHubService(): {
630
630
  declare function useUtilController(): {
631
631
  healthCheck: (req: Request, res: Response, next: NextFunction) => Promise<void>;
632
632
  setGitHubVariables: (req: Request, res: Response, next: NextFunction) => Promise<void>;
633
- paypalWebhook: (req: Request, res: Response, next: NextFunction) => Promise<Response<any, Record<string, any>>>;
633
+ paypalWebhook: (req: Request, res: Response, next: NextFunction) => Promise<void>;
634
634
  };
635
635
 
636
636
  declare const transactionSchema: Joi.ObjectSchema<any>;
package/dist/index.js CHANGED
@@ -6361,6 +6361,9 @@ function usePlanController() {
6361
6361
 
6362
6362
  // src/resources/utils/paypal.service.ts
6363
6363
  var import_paypal_server_sdk = require("@paypal/paypal-server-sdk");
6364
+ var import_crypto3 = __toESM(require("crypto"));
6365
+ var import_buffer_crc32 = __toESM(require("buffer-crc32"));
6366
+ var certCache = /* @__PURE__ */ new Map();
6364
6367
  function usePaypalService() {
6365
6368
  const paypalClient = new import_paypal_server_sdk.Client({
6366
6369
  clientCredentialsAuthCredentials: {
@@ -6402,61 +6405,42 @@ function usePaypalService() {
6402
6405
  function captureOrder(id) {
6403
6406
  return new import_paypal_server_sdk.OrdersController(paypalClient).captureOrder({ id });
6404
6407
  }
6405
- async function verifyWebhook(headers, body, webhookId = PAYPAL_WEBHOOK_ID) {
6406
- const authAlgo = headers["paypal-auth-algo"];
6407
- const certUrl = headers["paypal-cert-url"];
6408
+ async function downloadAndCacheCert(url) {
6409
+ const cachedCert = certCache.get(url);
6410
+ if (cachedCert) {
6411
+ return cachedCert;
6412
+ }
6413
+ const response = await fetch(url);
6414
+ if (!response.ok) {
6415
+ throw new Error(
6416
+ `Failed to download PayPal certificate: ${response.statusText}`
6417
+ );
6418
+ }
6419
+ const certPem = await response.text();
6420
+ certCache.set(url, certPem);
6421
+ return certPem;
6422
+ }
6423
+ async function verifyWebhook(rawBody, headers, webhookId = PAYPAL_WEBHOOK_ID) {
6408
6424
  const transmissionId = headers["paypal-transmission-id"];
6425
+ const timeStamp = headers["paypal-transmission-time"];
6426
+ const certUrl = headers["paypal-cert-url"];
6409
6427
  const transmissionSig = headers["paypal-transmission-sig"];
6410
- const transmissionTime = headers["paypal-transmission-time"];
6411
- if (!authAlgo || !certUrl || !transmissionId || !transmissionSig || !transmissionTime) {
6412
- return {
6413
- verified: false,
6414
- verificationStatus: "FAILURE"
6415
- };
6416
- }
6417
- const auth = Buffer.from(
6418
- `${PAYPAL_CLIENT_ID}:${PAYPAL_CLIENT_SECRET}`
6419
- ).toString("base64");
6420
- const tokenResponse = await fetch(`${PAYPAL_API_URL}/v1/oauth2/token`, {
6421
- method: "POST",
6422
- headers: {
6423
- Authorization: `Basic ${auth}`,
6424
- "Content-Type": "application/x-www-form-urlencoded"
6425
- },
6426
- body: "grant_type=client_credentials"
6427
- });
6428
- if (!tokenResponse.ok) {
6429
- throw new Error("Failed to obtain PayPal access token");
6428
+ if (!transmissionId || !timeStamp || !certUrl || !transmissionSig) {
6429
+ return false;
6430
6430
  }
6431
- const tokenData = await tokenResponse.json();
6432
- const accessToken = tokenData.access_token;
6433
- const verifyResponse = await fetch(
6434
- `${PAYPAL_API_URL}/v1/notifications/verify-webhook-signature`,
6435
- {
6436
- method: "POST",
6437
- headers: {
6438
- Authorization: `Bearer ${accessToken}`,
6439
- "Content-Type": "application/json"
6440
- },
6441
- body: JSON.stringify({
6442
- auth_algo: authAlgo,
6443
- cert_url: certUrl,
6444
- transmission_id: transmissionId,
6445
- transmission_sig: transmissionSig,
6446
- transmission_time: transmissionTime,
6447
- webhook_id: webhookId,
6448
- webhook_event: body
6449
- })
6450
- }
6451
- );
6452
- if (!verifyResponse.ok) {
6453
- throw new Error("Failed to verify PayPal webhook signature");
6431
+ const eventBuffer = Buffer.isBuffer(rawBody) ? rawBody : Buffer.from(rawBody);
6432
+ const crcValue = parseInt("0x" + (0, import_buffer_crc32.default)(eventBuffer).toString("hex"));
6433
+ const message = `${transmissionId}|${timeStamp}|${webhookId}|${crcValue}`;
6434
+ try {
6435
+ const certPem = await downloadAndCacheCert(certUrl);
6436
+ const signatureBuffer = Buffer.from(transmissionSig, "base64");
6437
+ const verifier = import_crypto3.default.createVerify("SHA256");
6438
+ verifier.update(message);
6439
+ return verifier.verify(certPem, signatureBuffer);
6440
+ } catch (error) {
6441
+ console.error("PayPal webhook verification error:", error);
6442
+ throw new Error("Failed to verify PayPal webhook signature.");
6454
6443
  }
6455
- const verifyData = await verifyResponse.json();
6456
- return {
6457
- verified: verifyData.verification_status === "SUCCESS",
6458
- verificationStatus: verifyData.verification_status
6459
- };
6460
6444
  }
6461
6445
  return {
6462
6446
  addOrder,
@@ -9839,9 +9823,21 @@ function useUtilController() {
9839
9823
  }
9840
9824
  }
9841
9825
  }
9826
+ const { verifyWebhook } = usePaypalService();
9842
9827
  async function paypalWebhook(req, res, next) {
9843
- console.log(req.body);
9844
- return res.status(200).send("OK");
9828
+ console.log(req);
9829
+ try {
9830
+ await verifyWebhook(req.body, req.headers, PAYPAL_WEBHOOK_ID);
9831
+ res.status(200).send("OK");
9832
+ return;
9833
+ } catch (error) {
9834
+ import_utils55.logger.log({
9835
+ level: "error",
9836
+ message: "PayPal webhook verification failed"
9837
+ });
9838
+ res.status(400).send("Invalid webhook");
9839
+ return;
9840
+ }
9845
9841
  }
9846
9842
  return {
9847
9843
  healthCheck,