Skip to content

feat: add razorpay#15

Open
bajrangCoder wants to merge 18 commits intomainfrom
razorpay
Open

feat: add razorpay#15
bajrangCoder wants to merge 18 commits intomainfrom
razorpay

Conversation

@bajrangCoder
Copy link
Copy Markdown
Member

@bajrangCoder bajrangCoder commented Jan 2, 2026

ENV to be Updated before Merger.

  • PG_KEY_ID
  • PG_KEY_SECRET
  • PG_WEBHOOK_SECRET

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @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

  • Razorpay Integration: Introduced a new payment gateway for web-based plugin purchases.
  • Client-Side Purchase UI: Added a "BuyButton" component and logic to display purchase status (owned, buy, login) on plugin pages.
  • Server-Side Payment APIs: Implemented API endpoints for creating Razorpay orders, verifying payments, checking plugin ownership, and handling webhooks.
  • Database Enhancements: Updated the purchase_order schema to link purchases to user accounts and support multiple payment providers (Google Play, Razorpay).
  • Dynamic Plugin Download: Modified the plugin download API to recognize user-linked purchases from any provider, allowing access to paid plugins.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@bajrangCoder bajrangCoder requested review from a team and deadlyjack January 2, 2026 13:40
gemini-code-assist[bot]

This comment was marked as resolved.

gemini-code-assist[bot]

This comment was marked as resolved.

@bajrangCoder
Copy link
Copy Markdown
Member Author

@greptileai

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jan 11, 2026

Greptile Summary

This 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.

  • P1 — Refund window bypass: pro_purchased_at is stored as new Date().toISOString() (already ending in Z). Lines 473 and 666 append another Z, producing an invalid date (elapsed = NaN). NaN > REFUND_WINDOW_MS is false, so the expiry check in refund-pro never blocks — users can refund Acode Pro at any time after purchase.
  • P1 — Double-refund race in refund-plugin: The Razorpay API is called before the order state is updated to STATE_CANCELED. Two concurrent requests can both pass the state check and both trigger a refund. The refund-pro handler correctly resets state first; refund-plugin should follow the same pattern.

Confidence Score: 3/5

Not 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

Filename Overview
server/apis/razorpay.js Core Razorpay API routes — contains two P1 bugs: a double-Z date issue that bypasses the Pro refund window entirely, and a missing pre-cancel lock in refund-plugin that allows double refunds via a race condition.
server/apis/plugin.js Download endpoint updated to check user-linked Razorpay orders before falling back to Google Play token; correctly calls verifyPurchase which handles Razorpay payment status checks.
server/entities/appConfig.js New key-value config entity for runtime settings like acode_pro_price; seeds defaults on init and exposes getValue/setValue helpers.
server/entities/user.js Adds acode_pro, pro_purchase_token, and pro_purchased_at columns; pro_purchase_token is correctly excluded from safeColumns.
client/components/razorpayCheckout/index.js Client-side Razorpay checkout component; dynamically loads Razorpay script, creates orders, and verifies payments with good error handling in the verification callback.
server/main.js Adds express.raw() middleware scoped to /api/razorpay/webhook before global express.json(), which is required for webhook signature verification.
server/lib/calcEarnings.js Correctly applies a 70% payout rate for Razorpay orders and falls through to existing Google Play report logic for other providers.

Sequence Diagram

sequenceDiagram
    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 }
Loading

Reviews (2): Last reviewed commit: "refactor: enhance CORS handling by allow..." | Re-trigger Greptile

greptile-apps[bot]

This comment was marked as resolved.

bajrangCoder and others added 7 commits January 11, 2026 18:56
- 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.
@deadlyjack
Copy link
Copy Markdown
Member

@greptile_apps review once more with confidence score

Comment thread server/apis/razorpay.js
Comment on lines +666 to +668
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.' });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Suggested change
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();

Comment thread server/apis/razorpay.js
Comment on lines +769 to +782

// 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],
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants