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 payment gateway for purchasing paid plugins and an "Acode Pro" subscription tier. It adds server-side order creation, signature-verified payment confirmation, webhook handling, and a 2-hour refund window for both products.
Confidence Score: 3/5Not safe to merge without fixing the migration script and the previously-flagged refund-window and double-refund bugs. The purchase_order migration script will silently skip adding the server/migrations/add_purchase_order_columns.js (missing provider column), server/apis/razorpay.js (refund-window and race condition bugs noted in existing threads) Important Files Changed
Sequence DiagramsequenceDiagram
participant C as Client
participant S as Server
participant RZP as Razorpay
participant DB as Database
C->>S: "POST /api/razorpay/create-order {pluginId}"
S->>DB: Check existing PURCHASED order
S->>RZP: orders.create(amount, notes)
RZP-->>S: orderId, amount, currency
S-->>C: orderId + keyId
C->>RZP: Open Razorpay Checkout UI
RZP-->>C: payment success (order_id, payment_id, signature)
C->>S: "POST /api/razorpay/verify {signature, pluginId}"
S->>S: Verify HMAC signature (timingSafeEqual)
S->>RZP: "orders.fetch(order_id) status=paid"
S->>DB: INSERT purchase_order (STATE_PURCHASED)
S-->>C: "{success: true}"
Note over RZP,S: Webhook (fallback if verify missed)
RZP->>S: POST /api/razorpay/webhook (payment.captured)
S->>S: Verify webhook HMAC signature
S->>DB: Upsert order STATE_PURCHASED
S-->>RZP: 200 OK
C->>S: "POST /api/razorpay/refund-plugin {orderId}"
S->>S: Check elapsed 2h refund window
S->>RZP: payments.refund(token)
S->>DB: Update order STATE_CANCELED
S-->>C: "{success: true}"
Reviews (3): Last reviewed commit: "refactor: conditionally enable Razorpay ..." | 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.
|
@greptile_apps review once more |
|
|
||
| const queries = [ | ||
| // Add user_id column if it doesn't exist | ||
| `ALTER TABLE purchase_order ADD COLUMN user_id INTEGER REFERENCES user(id)`, | ||
| ]; | ||
|
|
There was a problem hiding this comment.
Migration missing
provider column
The migration comment and title both say it adds "user_id and provider columns to purchase_order," but the queries array only includes the ALTER TABLE statement for user_id. The provider column is never created on existing databases. After this migration runs, any query that SELECTs or filters on provider — including the /download/:id endpoint (verifyPurchase), my-purchases, and the minColumns getter — will fail with "table purchase_order has no column named provider."
Add the missing statement to the queries array:
ALTER TABLE purchase_order ADD COLUMN provider TEXT DEFAULT 'google_play'
ENV to be Updated before Merger.
PG_KEY_IDPG_KEY_SECRETPG_WEBHOOK_SECRET