feat: add razorpay#15
Conversation
Summary of ChangesHello @bajrangCoder, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the platform's monetization capabilities by integrating Razorpay as a new payment gateway for web-based plugin purchases. It provides a complete end-to-end solution, from a user-friendly "Buy" button on the client-side to robust server-side APIs for secure transaction processing, payment verification, and webhook handling. The underlying database schema has been extended to support user-specific purchase records across different payment providers, ensuring that once a plugin is bought, it's permanently linked to the user's account for seamless access and download. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
Greptile SummaryThis PR integrates Razorpay as a web payment gateway for both individual plugin purchases and a new "Acode Pro" one-time purchase, alongside a 2-hour self-serve refund flow, webhook fallback handling, and a runtime admin config for pricing.
Confidence Score: 3/5Not safe to merge — two P1 bugs in the refund paths enable unlimited Pro refunds and a double-refund race on plugin purchases. Two P1 findings on financial code paths: a date parsing bug that bypasses the refund window entirely, and a missing pre-cancel lock that allows double-refund races. Both affect money movement directly and must be fixed before production. server/apis/razorpay.js — specifically the refund-pro (lines 473, 666) and refund-plugin (lines 769–782) handlers. Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant Server
participant Razorpay
participant DB
Client->>Server: POST /api/razorpay/create-order
Server->>Razorpay: orders.create(amount, notes)
Razorpay-->>Server: { orderId, amount }
Server-->>Client: { orderId, keyId, ... }
Client->>Razorpay: Open checkout modal
Razorpay-->>Client: Payment response (orderId, paymentId, signature)
Client->>Server: POST /api/razorpay/verify
Server->>Server: Verify HMAC signature
Server->>Razorpay: orders.fetch(orderId)
Server->>DB: INSERT purchase_order (STATE_PURCHASED)
Server-->>Client: { success: true }
Note over Server,Razorpay: Webhook fallback path
Razorpay->>Server: POST /api/razorpay/webhook
Server->>Server: Verify webhook HMAC (timingSafeEqual)
Server->>DB: INSERT or UPDATE order
Client->>Server: POST /api/razorpay/refund-pro
Server->>DB: UPDATE user SET acode_pro=0 (reset first)
Server->>Razorpay: payments.refund(token)
alt Refund fails
Server->>DB: UPDATE user SET acode_pro=1 (restore)
end
Server-->>Client: { success: true }
Reviews (2): Last reviewed commit: "refactor: enhance CORS handling by allow..." | Re-trigger Greptile |
- Removed the OAuth provider handling from `oauth.mjs` to streamline authentication flow. - Added new `appConfig.js` entity to manage application configuration, including Acode Pro pricing. - Updated `razorpay.js` to handle Acode Pro purchases, including order creation, verification, and refund processes. - Introduced new migration scripts to add Acode Pro related columns to the user table and update purchase orders. - Enhanced user entity to include Acode Pro status and purchase details. - Implemented CSRF protection in the main server file for state-changing requests. - Removed unused `authenticationProvider.mjs` and `authenticateWithProvider.mjs` files to clean up the codebase. - Updated earnings calculation to account for Razorpay orders. - Adjusted order update logic to skip Razorpay verification. - Cleaned up API routes by removing commented-out code.
…elf-service options
… middleware comment
|
@greptile_apps review once more with confidence score |
| const elapsed = Date.now() - new Date(`${purchasedAt}Z`).getTime(); | ||
| if (elapsed > REFUND_WINDOW_MS) { | ||
| res.status(400).send({ error: 'Refund window has expired. Refunds are only available within 2 hours of purchase.' }); |
There was a problem hiding this comment.
Refund window bypass — double
Z makes elapsed NaN
pro_purchased_at is stored via new Date().toISOString(), which already ends in Z (e.g. "2023-01-01T12:00:00.000Z"). Appending another Z produces "...000ZZ", which Date parses as Invalid Date. That makes elapsed = NaN, and NaN > REFUND_WINDOW_MS evaluates to false, so the refund window check never blocks the refund — users can request a Pro refund at any time, days or months after purchase.
The same double-Z pattern on line 473 makes elapsed <= REFUND_WINDOW_MS always false, so the UI never shows the refund button even when the user is within the 2-hour window.
| const elapsed = Date.now() - new Date(`${purchasedAt}Z`).getTime(); | |
| if (elapsed > REFUND_WINDOW_MS) { | |
| res.status(400).send({ error: 'Refund window has expired. Refunds are only available within 2 hours of purchase.' }); | |
| const elapsed = Date.now() - new Date(purchasedAt).getTime(); |
|
|
||
| // Issue full refund via Razorpay | ||
| await getRazorpay().payments.refund(order.token, { | ||
| speed: 'normal', | ||
| }); | ||
|
|
||
| // Update order state to canceled and zero out amount | ||
| await Order.update( | ||
| [ | ||
| [Order.STATE, Order.STATE_CANCELED], | ||
| [Order.AMOUNT, 0], | ||
| ], | ||
| [Order.ID, order.id], | ||
| ); |
There was a problem hiding this comment.
Race condition: order state not locked before calling Razorpay
The Razorpay refund API is called (line 771) before the order state is updated to STATE_CANCELED (line 776). Two concurrent POST /refund-plugin requests for the same order will both pass the Number(order.state) !== STATE_PURCHASED check on line 747, both call getRazorpay().payments.refund(...), and both succeed — issuing a double refund.
The /refund-pro handler on line 674 correctly follows the "reset first, restore on failure" pattern. The same approach should be applied here.
ENV to be Updated before Merger.
PG_KEY_IDPG_KEY_SECRETPG_WEBHOOK_SECRET