
Security News
Browserslist-rs Gets Major Refactor, Cutting Binary Size by Over 1MB
Browserslist-rs now uses static data to reduce binary size by over 1MB, improving memory use and performance for Rust-based frontend tools.
@bigbinary/neeto-payments-frontend
Advanced tools
The neeto-payments-nano
is a comprehensive payment processing solution designed for the Neeto ecosystem. Implemented as a Ruby on Rails engine with associated React frontend components (@bigbinary/neeto-payments-frontend
), it provides a unified interface for managing payments across different providers, abstracting away provider-specific complexities.
This engine enables host applications to:
Follow these steps to integrate the neeto-payments-engine
into your host Rails application:
Add the gem to your application's Gemfile
:
# Gemfile
source "NEETO_GEM_SERVER_URL" do
gem "neeto-payments-engine"
end
Run bundler to install the gem and its dependencies:
bundle install
Copy the engine's database migrations into your host application. This step is crucial.
bundle exec rails neeto_payments_engine:install:migrations
Apply the migrations to your database:
bundle exec rails db:migrate
Add the engine's routes to your application's config/routes.rb
:
# config/routes.rb
mount NeetoPaymentsEngine::Engine, at: "/payments" # Or your preferred mount point
This makes the engine's API endpoints available, by default under /payments
.
Once the installation is done, we have to do some configuration for the engine to work as intended. Please go through the whole README to complete the process of setting up neeto-payments-nano
in your host app.
Create an initializer file config/initializers/neeto_payments_engine.rb
to configure the engine:
# config/initializers/neeto_payments_engine.rb
NeetoPaymentsEngine.is_card_preserving_enabled = false # Set to true to enable saving payment methods
# IMPORTANT: Configure which model holds the payment integration for each provider.
# Replace "Organization" or "User" with your actual model names.
# This configuration is MANDATORY.
NeetoPaymentsEngine.providers_holdable_class = {
stripe_standard: "Organization", # e.g., Organization or User that owns the Stripe Standard account
stripe_platform: "Organization", # Typically Organization that owns the Stripe Platform account
razorpay: "Organization", # e.g., Organization or User that owns the Razorpay account
upi: "Organization" # Typically Organization that owns the UPI VPA list
}
providers_holdable_class
: This hash maps each payment provider type supported by the engine to the class name (as a String) of the model in your host application that will "hold" or own the integration for that provider. The engine uses polymorphic associations (holdable_type
, holdable_id
) based on this setting to link payment provider accounts (like Stripe::Account
, Integrations::Razorpay
) to your application's models. Failing to configure this correctly will result in errors when the engine attempts to find or create payment provider integrations. Example: In NeetoCal each User
can connect their own stripe_standard
account, but in NeetoPay, only one stripe_standard
account can exist in an Organization
.Ensure the models specified in providers_holdable_class
have the correct has_one
or has_many
associations defined to link to the engine's integration models. Adapt the following examples based on your configuration:
# app/models/organization.rb (Example if Organization is a holdable)
class Organization < ApplicationRecord
# If Organization holds Stripe Standard Connect account(s)
has_one :stripe_connected_account, -> { where(payment_provider: :stripe_standard) }, class_name: "::NeetoPaymentsEngine::Stripe::Account", as: :holdable, dependent: :destroy
# OR if one Org can hold many standard accounts (less common)
# has_many :stripe_connected_accounts, -> { where(payment_provider: :stripe_standard) }, class_name: "::NeetoPaymentsEngine::Stripe::Account", as: :holdable, dependent: :destroy
# We have to add this association no matter what because
# it's internally required by the engine. Change the parent association
# to Organization/User/Whatever depending on your host app.
has_one :stripe_platform_account, class_name: "::NeetoPaymentsEngine::Stripe::PlatformAccount", inverse_of: :organization, dependent: :destroy
# If Organization holds the Razorpay account
has_one :razorpay_integration, -> { where(payment_provider: :razorpay) }, class_name: "::NeetoPaymentsEngine::Integration", as: :holdable, dependent: :destroy
# If Organization holds the UPI VPAs list
has_one :upi_integration, -> { where(payment_provider: :eupi) }, class_name: "::NeetoPaymentsEngine::Integration", as: :holdable, dependent: :destroy
# General association (optional, can be useful)
has_many :payment_integrations, class_name: "::NeetoPaymentsEngine::Integration", as: :holdable, dependent: :destroy
# If Organization itself can have fees associated
has_one :fee, class_name: "::NeetoPaymentsEngine::Fee", as: :feeable, dependent: :destroy
has_one :split, class_name: "::NeetoPaymentsEngine::Split", dependent: :destroy # If Org defines platform split %
end
# app/models/user.rb (Example if User is a holdable for Stripe Standard)
class User < ApplicationRecord
has_one :stripe_connected_account, -> { where(payment_provider: :stripe_standard) }, class_name: "::NeetoPaymentsEngine::Stripe::Account", as: :holdable, dependent: :destroy
# Add other associations if User can hold other provider accounts
end
Add associations to your Payable models (e.g., Invoice
, Booking
, Meeting
- the item being paid for) - you don't need to add this until you create your Payable
models in your host app:
# app/models/invoice.rb (Example Payable Model)
class Invoice < ApplicationRecord
# Association to the payment record for this invoice
has_one :payment, class_name: "::NeetoPaymentsEngine::Payment", as: :payable, dependent: :destroy
# Optional: If fees are directly configured on the payable item itself
has_one :fee, class_name: "::NeetoPaymentsEngine::Fee", as: :feeable, dependent: :destroy
end
Configure API keys and secrets securely using environment variables and secrets file.
Essential ENV variables for neeto-payments-engine
:
.env.development
(Commit this file): Contains non-sensitive defaults, placeholders, or development-specific configurations.
# .env.development
# Base URL of your host application (adjust port if needed)
APP_URL='http://app.lvh.me:<APP_PORT>/'
# --- Stripe ---
# Base URL for OAuth callback (using tunnelto 'connect' subdomain for dev)
STRIPE_CALLBACK_BASE_URL="https://connect.tunnelto.dev" # Or your tunnel URL
# --- Razorpay (if used) ---
RAZORPAY_CLIENT_SECRET="rzp_test_..." # Test Key ID can often be committed
RAZORPAY_CLIENT_ID="..." # OAuth Client ID is usually safe
RAZORPAY_WEBHOOK_SECRET=".." # webhook secret
RAZORPAY_CALLBACK_BASE_URL="https://connect.tunnelto.dev" # Or your tunnel URL
.env.local
(Add this file to .gitignore
- DO NOT COMMIT): Contains sensitive API keys, secrets, and private keys. This file overrides values in .env.development
.
# .env.local (DO NOT COMMIT THIS FILE)
# --- Stripe ---
STRIPE_SECRET_KEY="sk_test_..." # Your TEST Stripe Secret Key
STRIPE_WEBHOOK_SECRET="whsec_..." # Your TEST Stripe Webhook Signing Secret (from manual setup)
# Can find this from Stripe dashboard
STRIPE_PUBLISHABLE_KEY="pk_test_..."
# First sign up for Stripe Connect, then go to OAuth section of Stripe
# Connect to get the client id. You'd also have to register OAuth callback
# URI in that page.
STRIPE_CLIENT_ID="ca_..."
# --- Razorpay (if used) ---
RAZORPAY_KEY_SECRET="..." # Your TEST Razorpay Key Secret
RAZORPAY_WEBHOOK_SECRET="..." # Your TEST Razorpay Webhook Secret (you set this)
RAZORPAY_CLIENT_SECRET="..." # Your TEST Razorpay OAuth Client Secret
# --- JWT Keys ---
# Generate using: openssl genrsa -out private.pem 2048 && openssl rsa -in private.pem -pubout -out public.pem
# Copy the *entire* content including -----BEGIN...----- and -----END...----- lines.
CONNECT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
...your private key content...
-----END RSA PRIVATE KEY-----"
CONNECT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
...your public key content...
-----END PUBLIC KEY-----"
# --- Optional ---
# OPEN_EXCHANGE_RATE_API_KEY="your_actual_key" # Sensitive API key
Loading Secrets: Ensure these secrets are loaded into Rails.application.secrets
. An example config/secrets.yml
structure:
# config/secrets.yml
default: &default
host: <%= ENV['APP_URL'] %>
stripe:
publishable_key: <%= ENV['STRIPE_PUBLISHABLE_KEY'] %>
secret_key: <%= ENV['STRIPE_SECRET_KEY'] %>
client_id: <%= ENV['STRIPE_CLIENT_ID'] %>
webhooks:
secret: <%= ENV['STRIPE_WEBHOOK_SECRET'] %>
razorpay:
key_id: <%= ENV["RAZORPAY_KEY_ID"] %>
key_secret: <%= ENV["RAZORPAY_KEY_SECRET"] %>
client_id: <%= ENV["RAZORPAY_CLIENT_ID"] %>
client_secret: <%= ENV["RAZORPAY_CLIENT_SECRET"] %>
oauth_callback_base_url: <%= ENV["RAZORPAY_CALLBACK_BASE_URL"] %>
webhook:
secret: <%= ENV["RAZORPAY_WEBHOOK_SECRET"] %>
jwt:
connect_private_key: <%= ENV['CONNECT_PRIVATE_KEY'].inspect %>
connect_public_key: <%= ENV['CONNECT_PUBLIC_KEY'].inspect %>
# Optional: If using Open Exchange Rates for currency conversion
open_exchange_rates:
api_key: <%= ENV["OPEN_EXCHANGE_RATE_API_KEY"] %>
# ... other secrets ...
development:
<<: *default
# Development specific overrides
production:
<<: *default
# Production specific overrides (use credentials!)
Secrets Management:
.env.local
(which is typically gitignored) to store sensitive keys locally.You must sign up for Stripe Connect via the Stripe dashboard, even if you only intend to use a Stripe Platform account. This registration enables the necessary APIs for account management and OAuth flows used by the engine.
Register the following callback URIs in your respective payment provider dashboards:
https://<your_connect_subdomain_or_app_domain>/payments/api/v1/public/stripe/oauth/callback
<your_connect_subdomain_or_app_domain>
: This should be the publicly accessible URL that routes to your Rails app. In development, this is typically your tunnelto
URL using the connect
subdomain (e.g., https://connect.tunnelto.dev
). In production, it might be your main application domain or a dedicated subdomain.https://<your_razorpay_oauth_base_url>/payments/api/v1/public/razorpay/oauth/callback
<your_razorpay_oauth_base_url>
: This must match the value you configured for RAZORPAY_OAUTH_CALLBACK_BASE_URL
. Use the connect
subdomain via tunnelto
in development.Integrate the React components provided by the @bigbinary/neeto-payments-frontend
package.
yarn add @bigbinary/neeto-payments-frontend
If the host app already includes all of the following peer deps, then you don't have to install anything explicitly. Use the latest version of each of these peer dependencies if you need to install:
# DO NOT INSTALL THE VERSIONS MENTIONED BELOW AS IT MIGHT BE OUTDATED.
# ALWAYS PREFER INSTALLING THE LATEST VERSIONS.
# If something isn't working, then refer to these versions and see
# what's causing the breakage.
yarn add @babel/runtime@7.26.10 @bigbinary/neeto-cist@1.0.15 @bigbinary/neeto-commons-frontend@4.13.28 @bigbinary/neeto-editor@1.45.23 @bigbinary/neeto-filters-frontend@4.3.15 @bigbinary/neeto-icons@1.20.31 @bigbinary/neeto-molecules@3.16.1 @bigbinary/neetoui@8.2.75 @honeybadger-io/js@6.10.1 @honeybadger-io/react@6.1.25 @tailwindcss/container-queries@^0.1.1 @tanstack/react-query@5.59.20 @tanstack/react-query-devtools@5.59.20 antd@5.22.0 axios@1.8.2 buffer@^6.0.3 classnames@2.5.1 crypto-browserify@3.12.1 dompurify@^3.2.4 formik@2.4.6 https-browserify@1.0.0 i18next@22.5.1 js-logger@1.6.1 mixpanel-browser@^2.45.0 os-browserify@0.3.0 path-browserify@^1.0.1 qs@^6.11.2 ramda@0.29.0 react@18.2.0 react-dom@18.2.0 react-helmet@^6.1.0 react-i18next@12.3.1 react-router-dom@5.3.3 react-toastify@8.0.2 source-map-loader@4.0.1 stream-browserify@^3.0.0 stream-http@3.2.0 tailwindcss@3.4.14 tty-browserify@0.0.1 url@^0.11.0 util@^0.12.5 vm-browserify@1.1.2 yup@0.32.11 zustand@4.3.2
Note: Carefully manage potential version conflicts with your host application.
Import components into your React application as needed.
TaxesDashboard
(source code)
Props
feeId
: The unique identifier for the tax fee.breadcrumbs
: Data for rendering the header breadcrumbs.paymentUrl
(optional): The URL of the pricing page to redirect users if payments are not enabled.headerSize
(optional): Specifies the size of the header. Default size is small
noDataHelpText
(optional): Help text displayed when there is no data available.onTaxesChange
(optional): Callback function triggered after performing actions in the taxes dashboard.titleHelpPopoverProps
(optional): Data for the header help popover.Usage
import React from "react";
import { TaxesDashboard } from "@bigbinary/neeto-payments-frontend";
const App = () => {
return (
<TaxesDashboard
feeId={fee.id}
breadcrumbs={[{text: "Settings" ,link: routes.admin.form.settings}]}
/>
);
};
PaymentsDashboard
(source code)
Props
dashboardKind
: Accepts either connected
or platform
(default: platform
). Use connected
to display payments related to split transfers. Use platform
to display payments received from customers.payableEntityColumns
: To specify the columns from the payable entity
which need to be additionally displayed. It is an optional prop that defaults to []
if not specified.searchProps
: Allows specifying additional columns to be included in the search functionality. By default, only the identifier
column from the payments table is searchable.holdableIds
: Provide the holdable IDs for each payment provider when the holdable entity is not a User
or Organization
model.Usage
import React from "react";
import { PaymentsDashboard } from "@bigbinary/neeto-payments-frontend";
const App = () => {
// neeto-filters-engine search prop syntax.
const SEARCH_PROPS = {
node: "bookings:payable.meeting.name",
model: "Meeting",
placeholder: "Search by identifier or meeting name",
};
// Only required if the payment provider holdable modal is not a User or Organization
const holdableIds = { stripeStandard: formId, razorpay: formId }
const payableEntityColumns = {
title: "Submission",
dataIndex: ["payable", "id"],
key: "submission",
ellipsis: true,
width: "300px",,
},
return (
<PaymentsDashboard
{...{ payableEntityColumns, holdableIds }}
searchProps={SEARCH_PROPS}
/>
);
};
RefundsDashboard
(source code)
Props
payableEntityColumns
: To specify the columns from the payable entity
which need to be additionally displayed. It is an optional prop that defaults to []
if not specified.searchProps
: Allows specifying additional columns to be included in the search functionality. By default, only the identifier
column from the refunds table is searchable.Usage
import React from "react";
import { SplitTransfersDashboard } from "@bigbinary/neeto-payments-frontend";
const App = () => {
// neeto-filters-engine search prop syntax.
const SEARCH_PROPS = {
node: "bookings:payable.meeting.name",
model: "Meeting",
placeholder: "Search by identifier or meeting name",
};
const payableEntityColumns = {
title: "Submission",
dataIndex: ["payable", "id"],
key: "submission",
ellipsis: true,
width: "300px",,
},
return (
<SplitTransfersDashboard
{...{ payableEntityColumns }}
searchProps={SEARCH_PROPS}
/>
);
};
SplitTransfersDashboard
(source code)
Props
payableEntityColumns
: To specify the columns from the payable entity
which need to be additionally displayed. It is an optional prop that defaults to []
if not specified.searchProps
: Allows specifying additional columns to be included in the search functionality. By default, only the identifier
column from the payment splits table is searchable.
Usageimport React from "react";
import { RefundsDashboard } from "@bigbinary/neeto-payments-frontend";
const App = () => {
// neeto-filters-engine search prop syntax.
const SEARCH_PROPS = {
node: "bookings:payable.meeting.name",
model: "Meeting",
placeholder: "Search by identifier or meeting name",
};
const payableEntityColumns = {
title: "Submission",
dataIndex: ["payable", "id"],
key: "submission",
ellipsis: true,
width: "300px",,
},
return (
<RefundsDashboard
{...{ payableEntityColumns }}
searchProps={SEARCH_PROPS}
/>
);
};
AccountsDashboard
(source code)
Usage
import React from "react";
import { AccountsDashboard } from "@bigbinary/neeto-payments-frontend";
const App = () => {
return (<AccountsDashboard />);
};
PayoutsDashboard
(source code)
Props
isPlatformEnabled
: Indicates whether platform integration is enabled.payoutsPageRoute
: The route used to display detailed payout information.Usage
import React from "react";
import { PayoutsDashboard } from "@bigbinary/neeto-payments-frontend";
const App = () => {
return (
<PayoutsDashboard
isPlatformEnabled={true}
payoutsPageRoute={routes.admin.payments.payouts.show}
/>
);
};
RazorpayPaymentButton
(source code)
Props
label
: The button label. Defaults to Pay
.payableId
: The ID of the payable entity.discountCode
: The discount code to be applied, if any.email
: The customer's email address.name
: The customer's name.theme
: The theme configuration object.payableType
: The type of the payable entity.onBeforePayment
: A callback function triggered before the payment is initiated.onSuccessfulPayment
: A callback function triggered after a successful payment.onFailedPayment
: A callback function triggered after a failed payment.Usage
import React from "react";
import { RazorpayPaymentButton } from "@bigbinary/neeto-payments-frontend";
const App = () => {
return (
<RazorpayPaymentButton
email={customer.email}
name={customer.name}
discountCode={discountCode.code}
payableId={booking.id}
theme={meeting.theme}
/>
);
};
ConfirmUpiPaymentButton
(source code)
Props
vpas
: A list of UPI IDs presented for the user to select during confirmation.identifier
: The unique identifier associated with the payment.paymentId
: The ID of the payment.onSuccess
: A callback function triggered upon successful confirmation.Usage
import React from "react";
import { ConfirmUpiPaymentButton } from "@bigbinary/neeto-payments-frontend";
const App = () => {
return (
<ConfirmUpiPaymentButton
identifier={payment.identifier}
payableId={payment.payableId}
paymentId={payment.paymentId}
vpas={meeting.fee.vpas}
/>
);
};
useStripePromise
(source code)
This hook can used to provide the value for the stripe
prop of the Stripe Element
component.
Usage
import React from "react";
import { useStripePromise } from "@bigbinary/neeto-payments-frontend";
const App = () => {
const stripePromise = useStripePromise({
stripePlatformAccount // The integration object for the Stripe platform account. No value is required if the platform account integration is not configured.
stripeAccountIdentifier: paymentRecipient?.stripeConnectedAccount?.identifier, // Stripe Standard account integration identifier
});
return (
<Elements stripe={stripePromise}>
</Elements>
);
};
useRazorpayPayment
(source code)
This hook returns a function that can be used to initiate a Razorpay payment. Use it only if you want to trigger the payment flow through a custom button in the host application.
Usage
import React from "react";
import { useRazorpayPayment } from "@bigbinary/neeto-payments-frontend";
const App = () => {
const { makePayment } = useRazorpayPayment({
payableId, // The ID of the payable entity.
onBeforePayment, // A callback function triggered before the payment is initiated.
onSuccessfulPayment, // A callback function triggered after a successful payment.
onFailedPayment, // A callback function triggered after a failed payment.
customAmount, // The custom amount value to be used when a fee of type `range` or `variable` is configured.
});
};
CURRENCY_OPTIONS
A list of supported currencies in { value, label }
format. This can be used as options for the NeetoUI Select
component.
getFormattedAmount({ amount, taxes=[], isTaxEnabled = false})
Returns the final amount as a string, truncated to 2 decimal places, after applying taxes if isTaxEnabled
is set to true
and taxes are present.
amount
: The base amount to which taxes may be applied.taxes
: List of tax objects with value
and kind
("percentage"
or "fixed"
). Defaults is []
isTaxEnabled
: Whether taxes should be applied. Defaults to false
.string
: Final amount with up to 2 decimal places (truncated, not rounded).getFormattedAmount([{ kind: "percentage", value: 23 }], "39.59", true); // → "48.69"
getFormattedTaxAmount({ amount, taxes=[]})
Returns the total tax amount as a string, truncated to 2 decimal places
amount
: The base amount on which tax will be calculatedtaxes
: List of tax objects with value
and kind
("percentage"
or "fixed"
). Defaults is []
string
: Total tax amount with up to 2 decimal places (truncated, not rounded).getFormattedTaxAmount([{ kind: "percentage", value: 23 }], "100"); // → "23.00"
// Example: Payment Settings Page
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { PaymentsDashboard, StripeConnect } from '@bigbinary/neeto-payments-frontend';
import { useQueryParams } from "@bigbinary/neeto-commons-frontend"; // Assuming you use this hook
const PaymentSettingsPage = () => {
const [integrations, setIntegrations] = useState({});
const [isLoading, setIsLoading] = useState(true);
const [isStripeConnectOpen, setIsStripeConnectOpen] = useState(false);
const { app: returnedApp, error: oauthError } = useQueryParams(); // For handling OAuth callbacks
useEffect(() => {
const fetchIntegrations = async () => {
setIsLoading(true);
try {
// IMPORTANT: Specify ONLY the providers your application actually uses.
const usedProviders = ["stripe_standard"]; // Example: only using Stripe Standard
const response = await axios.get('/payments/api/v1/integrations', {
params: { providers: usedProviders }
});
setIntegrations(response.data);
} catch (err) {
console.error("Failed to fetch integrations:", err);
// Handle error appropriately (e.g., show toast notification)
} finally {
setIsLoading(false);
}
};
fetchIntegrations();
// Handle OAuth callback results
if (returnedApp === 'stripe' && oauthError) {
console.error("Stripe connection failed:", oauthError);
// Show error message to user
} else if (returnedApp === 'stripe') {
console.log("Stripe connection initiated successfully. Waiting for webhook verification.");
// Maybe show a success message, fetchIntegrations will update the state eventually
}
// Add similar checks for Razorpay if using OAuth
}, [returnedApp, oauthError]);
// Determine holdableId based on your app's logic (e.g., current organization)
// This might come from context, props, or another state hook
const currentHoldableId = integrations.stripe_standard_account?.holdable_id || 'your_org_or_user_id'; // Replace with actual logic
if (isLoading) {
return <div>Loading payment settings...</div>;
}
return (
<div>
<h1>Payment Settings</h1>
{/* Conditionally render based on integration status */}
{!integrations.stripe_standard_account?.is_connected && (
<button onClick={() => setIsStripeConnectOpen(true)}>
Connect Stripe Account
</button>
)}
{integrations.stripe_standard_account?.is_connected && (
<p>Stripe Account Connected: {integrations.stripe_standard_account.identifier}</p>
// Add disconnect button here
)}
{/* Stripe Connect Modal */}
<StripeConnect
isOpen={isStripeConnectOpen}
onClose={() => setIsStripeConnectOpen(false)}
holdableId={currentHoldableId} // Pass the ID of the Organization/User model instance
returnUrl={`${window.location.origin}/payment-settings`} // URL Stripe redirects back to (should match registered URI)
isPlatform={false} // Set true only if connecting a Stripe Platform account
/>
{/* Example Dashboard (only show if integrated) */}
{integrations.stripe_standard_account?.is_connected && (
<>
<h2>Payments Overview</h2>
<PaymentsDashboard
// Example: Use 'connected' kind if viewing standard account payments
dashboardKind="connected"
// Pass holdable ID for the specific account being viewed
holdableIds={{ stripe_standard: currentHoldableId }}
/>
</>
)}
</div>
);
};
export default PaymentSettingsPage;
When calling the /api/v1/integrations
endpoint (or other APIs that might implicitly check integrations), you must pass the providers
parameter listing only the providers your application uses. Omitting this or including unused providers can lead to errors if the engine tries to load configurations or associations that don't exist for your setup.
// Correct: Fetching only Stripe Standard integration status
axios.get('/payments/api/v1/integrations', {
params: { providers: ["stripe_standard"] }
});
// Incorrect (if not using all providers): Might cause errors
// axios.get('/payments/api/v1/integrations');
payable
(what's being paid for), accountable
(the payment account, e.g., Stripe::Account
), and holdable
(owner of the integration, e.g., Organization
) link the engine to host app models dynamically based on configuration.Payments::Stripe
, Payments::Razorpay
, Integrations::Razorpay
.Stripe::Payments::CreateService
).NeetoPaymentsEngine::Callbacks
to customize behavior. See the Callbacks section./payments/api/v1/public/<provider>/webhooks
.Use a tool like ngrok
or tunnelto
to expose your local development server to the internet. The connect
subdomain is reserved within Neeto for receiving callbacks from third-party services. Refer to Using Tunnelto and Exposing Tunnelto Locally for more details.
# Example using tunnelto
tunnelto --subdomain connect --port <YOUR_RAILS_APP_PORT>
# This makes https://connect.tunnelto.dev forward to your local app
https://connect.tunnelto.dev/payments/api/v1/public/stripe/webhooks
(replace with your actual tunnel URL).config/stripe/webhooks.yml
in the engine for the required list (e.g., payment_intent.succeeded
, charge.refunded
, account.updated
).whsec_...
).STRIPE_WEBHOOK_SECRET
in your local .env.local
or development credentials.neeto_payments_engine:stripe:webhooks:subscribe
rake task is not recommended for development setup as it doesn't create the endpoint and might show outdated secrets. Manual setup provides better control and ensures you have the correct, current secret. Refer to the Official Stripe Webhook Documentation for more details.https://connect.tunnelto.dev/payments/api/v1/public/razorpay/webhooks
(replace with your tunnel URL).payment.authorized
, payment.captured
, payment.failed
, refund.processed
, refund.failed
).RAZORPAY_WEBHOOK_SECRET
in your local development environment.CONNECT_PUBLIC_KEY
, CONNECT_PRIVATE_KEY
) configured in your secrets.ConnectLinkService
creates a short-lived JWT containing user context when initiating the OAuth flow.JwtService
verifies the token signature using the public key upon callback.Host applications must implement specific methods within a NeetoPaymentsEngine::Callbacks
module (e.g., in app/lib/neeto_payments_engine/callbacks.rb
) to tailor the engine's behavior to their domain logic.
# app/lib/neeto_payments_engine/callbacks.rb
module NeetoPaymentsEngine
class Callbacks
# --- Mandatory Callbacks ---
# Check user permissions for general payment management access.
# @param user [User] The user attempting the action.
# @return [Boolean] True if the user has permission.
def self.can_manage_payments?(user)
user.has_role?(:admin) # Replace with your authorization logic
end
# ... other callbacks ...
end
end
# app/lib/neeto_payments_engine/callbacks.rb
module NeetoPaymentsEngine
class Callbacks
# --- Optional Callbacks (Implement as needed) ---
# Return the fee object applicable to the payable item.
# @param payable [Object] The host application's payable model instance (e.g., Invoice, Booking).
# @return [NeetoPaymentsEngine::Fee, nil] The Fee object or nil if no fee applies.
def self.get_fee(payable)
# Example: Find fee based on payable's attributes or associated product/service
payable.applicable_fee # Replace with your actual logic
end
# Check if a payment can be processed for this payable item.
# @param payable [Object] The host application's payable model instance.
# @return [Boolean] True if payment is allowed, false otherwise.
def self.payment_processable?(payable)
# Example: Check if the payable is in a state allowing payment (e.g., 'pending', 'unpaid')
payable.can_be_paid? # Replace with your actual logic
end
# Return an ActiveRecord::Relation scope for payable objects.
# Used by the engine, e.g., when filtering payments by associated payables.
# @param organization [Organization] The organization context.
# @param payable_type [String, nil] The stringified class name of the payable type (e.g., "Invoice"), or nil.
# @return [ActiveRecord::Relation] A scope of payable objects.
def self.get_payables(organization, payable_type = nil)
# Example: Return all Invoices for the organization if type matches
if payable_type == "Invoice"
organization.invoices
elsif payable_type == "Booking"
organization.bookings
else
# Return a sensible default or handle all relevant types
organization.invoices # Defaulting to invoices here, adjust as needed
end
end
# Validate if a discount code is applicable to a specific payable item.
# @param discount_code [DiscountCode] Host application's discount code model instance.
# @param payable [Object] The host application's payable model instance.
# @return [Boolean] True if the discount code is valid for the payable, false otherwise.
def self.valid_discount_code?(discount_code, payable)
# Example: Check code validity, usage limits, applicability to the payable's item/service
discount_code.is_valid_for?(payable) # Replace with your actual logic
end
# Determine if the transaction cookie (used by Stripe flow) should be deleted.
# Default logic (return true) is usually sufficient. Delete after success/refund.
# @param payment [NeetoPaymentsEngine::Payment] The payment object.
# @return [Boolean] True to delete the cookie, false to keep it.
def self.delete_transaction_cookie?(payment)
true
end
# Return the Stripe connected account associated with the payable's context.
# Crucial for marketplace payments where funds go to a connected account.
# @param payable [Object] The host application's payable model instance.
# @return [NeetoPaymentsEngine::Stripe::Account, nil] The connected account or nil.
def self.get_connected_account(payable)
# Example: Find the seller/vendor associated with the payable item
payable.seller&.stripe_connected_account # Replace with your actual logic
end
# Return the Stripe connected account that should receive the split amount for a given payable.
# Often the same logic as get_connected_account.
# @param payable [Object] The host application's payable model instance.
# @return [NeetoPaymentsEngine::Stripe::Account, nil] The destination account for the split.
def self.get_split_account(payable)
# Example: Find the recipient/beneficiary for the split payment
payable.recipient_user&.stripe_connected_account # Replace with your actual logic
end
# Return a hash of details for a payable item, used in CSV exports.
# @param payable [Object] The host application's payable model instance.
# @return [Hash] Key-value pairs to include in the export.
def self.get_exportable_details(payable)
{
# Example: Extract relevant fields from your payable model
payable_name: payable.try(:name) || "N/A",
invoice_number: payable.try(:invoice_number),
product_sku: payable.try(:product_sku)
}
end
# Return the primary custom hostname for an organization.
# Used for registering Stripe Payment Method Domains.
# @param organization [Organization] The organization context.
# @return [String, nil] The custom hostname or nil.
def self.custom_domain_hostname(organization)
# Example: Fetch from a dedicated custom domain model or setting
organization.custom_domain_setting&.hostname
end
# Determine if a payment related to this payable can be split.
# @param payable [Object] The host application's payable model instance.
# @return [Boolean] True if splits are allowed for this payable type/context.
def self.splittable?(payable)
# Example: Enable splits only for specific product types or services
payable.allows_splits? # Replace with your actual logic
end
# Determine if a split transfer should be initiated immediately or held.
# @param payable [Object] The host application's payable model instance.
# @return [Boolean] True to transfer immediately, false to hold (e.g., for review).
def self.transferable?(payable)
# Example: Hold transfers for items pending review or fulfillment
!payable.needs_manual_review? # Replace with your actual logic
end
# Return metadata about the payable to include in Stripe transfers.
# @param payable [Object] The host application's payable model instance.
# @return [Hash] Metadata hash (keys/values must be strings, limited length).
def self.get_payable_details(payable)
{
payable_id: payable.id.to_s,
payable_type: payable.class.name,
# Add other relevant identifiers (e.g., order_id, booking_ref)
order_reference: payable.try(:order_reference).to_s
}
end
# Check permissions specifically for managing the Stripe Platform account (if applicable).
# @param user [User] The user attempting the action.
# @return [Boolean] True if the user has permission.
def self.can_manage_stripe_platform_account?(user)
user.has_role?(:super_admin) # Replace with your authorization logic
end
# Check permissions specifically for managing Razorpay integrations.
# @param user [User] The user attempting the action.
# @return [Boolean] True if the user has permission.
def self.can_manage_razorpay_integration?(user)
user.has_role?(:admin) # Replace with your authorization logic
end
end
end
The engine exposes various models, services, jobs, and tasks for integration and extension:
Payment
, Fee
, Refund
, Split
, Payments::Split
, Stripe::Account
, Stripe::PlatformAccount
, Integration
, Customer
, PaymentMethod
, Payout
, WebhookEvent
, etc. Host apps primarily interact with these through ActiveRecord associations defined on their own models.Stripe::Payments::CreateService
, Razorpay::Accounts::CreateService
, SplitTransfersFilterService
, ExportCsvService
). While mostly used internally, filter and export services might be invoked directly or indirectly via controllers.Stripe::WebhooksJob
, StripePlatform::CreatePaymentSplitsJob
, ExportCsvJob
, CreatePaymentMethodDomainJob
). These are queued and processed by Sidekiq.Amountable
, PaymentProcessable
, Taxable
, Stripe::Accountable
, Refundable
). Mostly for internal engine use.neeto_payments_engine:stripe:account:integrate
: Seeds a sample Stripe connected account. Development/Testing only.neeto_payments_engine:stripe:webhooks:subscribe
: Do not rely on this for setup. See Webhook Handling for manual setup instructions.The engine exposes several API endpoints under the configured mount path (default /payments
). Here are some key ones:
Method | Path | Description | Authentication |
---|---|---|---|
GET | /api/v1/integrations | List integrated payment provider accounts for the current context. | Host App Auth |
POST | /api/v1/exports | Initiate a CSV export for dashboards (Payments, Refunds, Splits). | Host App Auth |
GET | /api/v1/payments | List payments with filtering and pagination. | Host App Auth |
GET | /api/v1/refunds | List refunds with filtering and pagination. | Host App Auth |
PUT | /api/v1/recurring_payments/:id | Update the status of an admin-managed recurring payment. | Host App Auth |
GET | /api/v1/split_transfers | List split transfers (Stripe Platform) with filtering. | Host App Auth |
GET | /api/v1/split_transfers/:id | Show details of a specific split transfer. | Host App Auth |
POST | /api/v1/split_transfers/bulk_update | Cancel or resume multiple pending/cancelled split transfers. | Host App Auth |
GET | /api/v1/fees/:fee_id/taxes | List taxes configured for a specific fee. | Host App Auth |
POST | /api/v1/fees/:fee_id/taxes | Create a new tax for a fee. | Host App Auth |
PUT | /api/v1/fees/:fee_id/taxes/:id | Update an existing tax for a fee. | Host App Auth |
DELETE | /api/v1/fees/:fee_id/taxes/:id | Delete a tax from a fee. | Host App Auth |
GET | /api/v1/fees/:fee_id/recurring_setting | Get recurring payment settings for a fee. | Host App Auth |
PUT | /api/v1/fees/:fee_id/recurring_setting | Update recurring payment settings for a fee. | Host App Auth |
GET | /api/v1/stripe/countries | List countries supported by Stripe for account creation. | Host App Auth |
POST | /api/v1/stripe/accounts | Initiate Stripe connected account creation/onboarding. | Host App Auth |
GET | /api/v1/stripe/accounts/:id/creation_status | Check the status of an async account creation job. | Host App Auth |
DELETE | /api/v1/stripe/accounts/:id | Disconnect a Stripe connected account. | Host App Auth |
GET | /api/v1/stripe_platform/account | Get details of the configured Stripe Platform account. | Host App Auth |
POST | /api/v1/stripe_platform/account | Create/Connect the Stripe Platform account. | Host App Auth |
PUT | /api/v1/stripe_platform/account | Update Stripe Platform account settings. | Host App Auth |
DELETE | /api/v1/stripe_platform/account | Disconnect the Stripe Platform account. | Host App Auth |
GET | /api/v1/stripe_platform/payouts | List payouts made from the Stripe Platform account. | Host App Auth |
GET | /api/v1/razorpay/holdables/:holdable_id/account | Get Razorpay account details for a specific holdable (Deprecated style). | Host App Auth |
GET | /api/v1/upi/vpas | List configured UPI VPAs for the organization. | Host App Auth |
POST | /api/v1/upi/vpas | Add a new UPI VPA. | Host App Auth |
DELETE | /api/v1/upi/vpas/:id | Delete a UPI VPA. | Host App Auth |
PUT | /api/v1/upi/payments/:id | Confirm/update a manual UPI payment (Admin action). | Host App Auth |
GET | /api/v1/integrations/connect/stripe | Start Stripe Connect OAuth flow. | Host App Auth |
GET | /api/v1/integrations/connect/razorpay | Start Razorpay OAuth flow. | Host App Auth |
POST | /api/v1/public/stripe/transactions | Create a Stripe Payment Intent (Public endpoint). | Open |
POST | /api/v1/public/razorpay/payments | Create a Razorpay Order (Public endpoint). | Open |
POST | /api/v1/public/upi/payments | Initiate a manual UPI payment (Public endpoint). | Open |
GET | /api/v1/public/recurring_payments/:payable_id | Get status of a customer-managed recurring payment. | Open |
PUT | /api/v1/public/recurring_payments/:payable_id | Allow customer to cancel their recurring payment. | Open |
GET | /api/v1/public/stripe/oauth/authorize | Stripe OAuth authorization step (Redirect handled by engine). | JWT |
GET | /api/v1/public/stripe/oauth/callback | Stripe OAuth callback processing endpoint. | JWT |
GET | /api/v1/public/razorpay/oauth/authorize | Razorpay OAuth authorization step (Redirect handled by engine). | JWT |
GET | /api/v1/public/razorpay/oauth/callback | Razorpay OAuth callback processing endpoint. | JWT |
POST | /api/v1/public/stripe/webhooks | Stripe webhook receiver endpoint. | Stripe Sig. |
POST | /api/v1/public/razorpay/webhooks | Razorpay webhook receiver endpoint. | Razorpay Sig. |
POST | /api/v1/public/stripe_platform/webhooks | Stripe Platform webhook receiver endpoint. | Stripe Sig. |
Note: "Host App Auth" means the endpoint relies on the host application's authentication (e.g., authenticate_user!
). "JWT" means authentication relies on the JWT generated during the OAuth flow. "Open" means no authentication. "Stripe Sig." / "Razorpay Sig." means verification is done via webhook signatures.
If your host application uses neeto-org-incineration-engine
, you need to integrate NeetoPaymentsEngine
models correctly. The NeetoPaymentsEngine::Fee
model often requires special handling as it might be associated with host application models (feeable
).
Initial Setup: When first adding neeto-payments-engine
, add NeetoPaymentsEngine::Fee
to the SKIPPED_MODELS
list in your host application's IncinerableConcern
implementation (e.g., in app/models/concerns/incinerable_concern.rb
). This prevents incineration errors before associations are correctly set up especially while running the whole test suite in host app.
# In your host app's IncinerableConcern
# TODO: Incinerate Fee based on Entity later
SKIPPED_MODELS = ["NeetoActivitiesEngine::Activity", "NeetoPaymentsEngine::Fee"]
Define Associations Later(optional): Add the has_one :fee, as: :feeable
association to your host application models that should have fees (e.g., Product
, ServicePlan
).
Update Incineration Logic(optional): Once associations are defined, remove "NeetoPaymentsEngine::Fee"
from SKIPPED_MODELS
. You must then update your IncinerableConcern.associated_models
method to include logic that correctly finds and targets NeetoPaymentsEngine::Fee
records associated with the organization being incinerated, likely through the feeable
association.
# In your host app's IncinerableConcern
def self.associated_models(org_id)
# Add logic for NeetoPaymentsEngine::Fee
"NeetoPaymentsEngine::Fee": {
# Example: Find fees associated with Products belonging to the org
joins: "JOIN products ON products.id = neeto_payments_engine_fees.feeable_id AND neeto_payments_engine_fees.feeable_type = 'Product'",
where: ["products.organization_id = ?", org_id]
# Adjust join and where clause based on your actual feeable models
},
# Add logic for other NeetoPaymentsEngine models if needed,
# though many are handled via cascading deletes or direct Org association.
# Refer to the engine's IncinerableConcern for models it handles itself.
# "NeetoPaymentsEngine::Payment": { ... }
# rest of the code
end
Consult the engine's own NeetoPaymentsEngine::IncinerableConcern
(app/models/concerns/neeto_payments_engine/incinerable_concern.rb
) for the list of models it defines and how it targets them for deletion, to avoid duplication and ensure comprehensive cleanup.
Please be aware of the following deprecations and use the recommended alternatives:
Configuration holdable_class
:
NeetoPaymentsEngine.holdable_class = "ClassName"
configuration.NeetoPaymentsEngine.providers_holdable_class = { provider: "ClassName", ... }
to configure holdable models per provider. This provides more flexibility.Holdable-Scoped APIs:
/api/v1/<provider>/holdables/:holdable_id/...
are deprecated./api/v1/integrations
(passing the providers
param)./api/v1/<provider>/accounts/...
./api/v1/<provider>/payouts/...
.Charge
Model/Concept:
Charge
.NeetoPaymentsEngine::Payment
(and its STI subclasses like Payments::Stripe
). Use Payment
in associations and logic. The concept of Fee
(NeetoPaymentsEngine::Fee
) represents the configuration of what to charge, while Payment
represents the actual transaction.To run the engine locally integrated with a host application, ensure the following processes are running:
bundle exec rails s
# If using Vite
yarn dev
bundle exec sidekiq -e development -C config/sidekiq.yml
connect
subdomain for OAuth callbacks.
# Using tunnelto (replace <YOUR_APP_PORT> with your Rails server port, e.g., 3000)
tunnelto --subdomain connect --port <YOUR_APP_PORT>
Note: The connect
subdomain is conventionally used across Neeto applications for receiving callbacks from third-party services like Stripe/Razorpay OAuth.The Procfile.dev.PAYMENTS
might look like this:
web: bundle exec rails s
vite: yarn dev
worker: bundle exec sidekiq -e development -C config/sidekiq.yml
# Tunnel needs to be run in a separate terminal
currency_format_with_symbol
This helper method converts a currency code (like "INR" or "USD") into its corresponding symbol and returns the formatted amount with the symbol. Example $10.00.
Usage in host application:
In general modules (e.g., Services or View helpers)
module ApplicationHelper
include ::NeetoPaymentsEngine::CurrencyFormatHelper
def formatted_amount
currency_format_with_symbol(payment&.amount, payment&.currency)
end
end
In mailers
class Packages::PurchaseConfirmedMailer < ApplicationMailer
helper ::NeetoPaymentsEngine::CurrencyFormatHelper
def customer_email
end
end
<%= "#{currency_format_with_symbol(@purchase.payment&.amount,@purchase.payment&.currency)}" %>
test/dummy
app within the engine's repository for isolated testing.NeetoPaymentsEngine::TestHelpers
(includes HttpRequestHelpers
) for stubbing API calls to Stripe/Razorpay (test/helpers/http_request_helpers/
).4242 4242 4242 4242
4000 0000 0000 0002
12/30
)123
)12345
)log/development.log
) for detailed output from the engine's LogActivityHelper
.CONNECT_PUBLIC_KEY
) to verify the signature and inspect the payload (check exp
claim for expiry).providers_holdable_class
is Mandatory: Forgetting to configure this in the initializer will lead to errors when the engine tries to find associated accounts.providers
in API Calls: When calling /api/v1/integrations
, always pass the providers
param listing only the providers you actually use in your host app (e.g., params: { providers: ["stripe_standard"] }
). Failing to do so might cause errors if the engine tries to load an unconfigured provider (like UPI).bundle exec rails neeto_payments_engine:install:migrations
before db:migrate
when setting up or upgrading. We also need to run this rake task and migration after we run ./bin/setup
in the host app.For instructions on building and releasing the @bigbinary/neeto-payments-frontend
NPM package and the neeto-payments-engine
Ruby gem, please refer to the internal guide: Building and Releasing Packages.
FAQs
To manage payments across the neeto products.
The npm package @bigbinary/neeto-payments-frontend receives a total of 446 weekly downloads. As such, @bigbinary/neeto-payments-frontend popularity was classified as not popular.
We found that @bigbinary/neeto-payments-frontend demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Browserslist-rs now uses static data to reduce binary size by over 1MB, improving memory use and performance for Rust-based frontend tools.
Research
Security News
Eight new malicious Firefox extensions impersonate games, steal OAuth tokens, hijack sessions, and exploit browser permissions to spy on users.
Security News
The official Go SDK for the Model Context Protocol is in development, with a stable, production-ready release expected by August 2025.