@bigbinary/neeto-payments-frontend 3.3.22 → 3.3.24

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/README.md CHANGED
@@ -1,295 +1,1127 @@
1
1
  # neeto-payments-nano
2
- The `neeto-payments-nano` manages payments across neeto products. The nano exports the `@bigbinary/neeto-payments-frontend` NPM package and `neeto-payments-engine` Rails engine for development.
3
-
4
- # Contents
5
- 1. [Development with Host Application](#development-with-host-application)
6
- - [Engine](#engine)
7
- - [Installation](#installation)
8
- - [Usage](#usage)
9
- - [Frontend package](#frontend-package)
10
- - [Installation](#installation-1)
11
- - [Instructions for development](#instructions-for-development)
12
- - [Components](#components)
13
- - [Hooks](#hooks)
14
- - [Functions](#functions)
15
- 2. [Instructions for Publishing](#instructions-for-publishing)
16
-
17
- # Development with Host Application
18
- ## Engine
19
- The engine is used to manage payments across neeto products.
20
-
21
- ### Installation
22
- 1. Add this line to your application's Gemfile:
23
- ```ruby
24
- source "NEETO_GEM_SERVER_URL" do
25
- # ..existing gems
26
2
 
27
- gem 'neeto-payments-engine'
28
- end
3
+ 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.
4
+
5
+ This engine enables host applications to:
6
+
7
+ - Integrate with multiple payment providers (Stripe, Razorpay, UPI).
8
+ - Process payments.
9
+ - Handle payment splits for marketplace scenarios (Stripe).
10
+ - Manage refunds.
11
+ - Store and reuse payment methods securely (optional).
12
+ - Configure fee structures and apply taxes/discounts.
13
+ - Provide administrative dashboards for monitoring transactions, refunds, payouts, and accounts.
14
+
15
+ # Table of Contents
16
+
17
+ - [Installation (Backend Engine)](#installation-backend-engine)
18
+ - [1. Add the Gem](#1-add-the-gem)
19
+ - [2. Install Gems](#2-install-gems)
20
+ - [3. Install Migrations](#3-install-migrations)
21
+ - [4. Run Migrations](#4-run-migrations)
22
+ - [5. Mount the Engine](#5-mount-the-engine)
23
+ - [Configuration (Backend Engine)](#configuration-backend-engine)
24
+ - [1. Initializer](#1-initializer)
25
+ - [2. Model Associations](#2-model-associations)
26
+ - [3. Secrets and Credentials](#3-secrets-and-credentials)
27
+ - [4. Stripe Connect Signup](#4-stripe-connect-signup)
28
+ - [5. OAuth Callback URI Registration](#5-oauth-callback-uri-registration)
29
+ - [Frontend Integration](#frontend-integration)
30
+ - [1. Install Frontend Package](#1-install-frontend-package)
31
+ - [2. Install Peer Dependencies](#2-install-peer-dependencies)
32
+ - [3. Components](#3-components)
33
+ - [4. Hooks](#4-hooks)
34
+ - [5. Constants](#5-constants)
35
+ - [6. API Calls - Specify Providers](#4-api-calls---specify-providers)
36
+ - [Core Concepts](#core-concepts)
37
+ - [Webhook Handling](#webhook-handling)
38
+ - [Development Environment Setup](#development-environment-setup)
39
+ - [1. Tunneling](#1-tunneling)
40
+ - [2. Stripe Webhook Setup (Manual)](#2-stripe-webhook-setup-manual)
41
+ - [3. Razorpay Webhook Setup (Manual)](#3-razorpay-webhook-setup-manual)
42
+ - [Authentication (JWT for OAuth)](#authentication-jwt-for-oauth)
43
+ - [Callbacks](#callbacks)
44
+ - [Mandatory Callbacks](#mandatory-callbacks)
45
+ - [Optional Callbacks (Implement as needed)](#optional-callbacks-implement-as-needed)
46
+ - [Exposed Entities](#exposed-entities)
47
+ - [API Endpoints](#api-endpoints)
48
+ - [Incineration Concern](#incineration-concern)
49
+ - [Deprecated Patterns](#deprecated-patterns)
50
+ - [Development Environment Setup](#development-environment-setup-1)
51
+ - [Helper methods](#helper-methods)
52
+ - [Testing & Debugging](#testing--debugging)
53
+ - [Gotchas & Tips](#gotchas--tips)
54
+ - [Publishing](#publishing)
55
+
56
+ ## Installation (Backend Engine)
57
+
58
+ Follow these steps to integrate the `neeto-payments-engine` into your host Rails application:
59
+
60
+ ### 1. Add the Gem
61
+ Add the gem to your application's `Gemfile`:
62
+ ```ruby
63
+ # Gemfile
64
+ source "NEETO_GEM_SERVER_URL" do
65
+ gem "neeto-payments-engine"
66
+ end
67
+ ```
68
+
69
+ ### 2. Install Gems
70
+ Run bundler to install the gem and its dependencies:
71
+ ```bash
72
+ bundle install
73
+ ```
74
+
75
+ ### 3. Install Migrations
76
+ Copy the engine's database migrations into your host application. **This step is crucial.**
77
+ ```bash
78
+ bundle exec rails neeto_payments_engine:install:migrations
79
+ ```
80
+
81
+ ### 4. Run Migrations
82
+ Apply the migrations to your database:
83
+ ```bash
84
+ bundle exec rails db:migrate
85
+ ```
86
+
87
+ ### 5. Mount the Engine
88
+ Add the engine's routes to your application's `config/routes.rb`:
89
+ ```ruby
90
+ # config/routes.rb
91
+ mount NeetoPaymentsEngine::Engine, at: "/payments" # Or your preferred mount point
92
+ ```
93
+ This makes the engine's API endpoints available, by default under `/payments`.
94
+
95
+ 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.
96
+
97
+ ## Configuration (Backend Engine)
98
+
99
+ ### 1. Initializer
100
+ Create an initializer file `config/initializers/neeto_payments_engine.rb` to configure the engine:
101
+
102
+ ```ruby
103
+ # config/initializers/neeto_payments_engine.rb
104
+ NeetoPaymentsEngine.is_card_preserving_enabled = false # Set to true to enable saving payment methods
105
+
106
+ # IMPORTANT: Configure which model holds the payment integration for each provider.
107
+ # Replace "Organization" or "User" with your actual model names.
108
+ # This configuration is MANDATORY.
109
+ NeetoPaymentsEngine.providers_holdable_class = {
110
+ stripe_standard: "Organization", # e.g., Organization or User that owns the Stripe Standard account
111
+ stripe_platform: "Organization", # Typically Organization that owns the Stripe Platform account
112
+ razorpay: "Organization", # e.g., Organization or User that owns the Razorpay account
113
+ upi: "Organization" # Typically Organization that owns the UPI VPA list
114
+ }
115
+ ```
116
+ - **`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`.
117
+
118
+ ### 2. Model Associations
119
+ 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:
120
+
121
+ ```ruby
122
+ # app/models/organization.rb (Example if Organization is a holdable)
123
+ class Organization < ApplicationRecord
124
+ # If Organization holds Stripe Standard Connect account(s)
125
+ has_one :stripe_connected_account, -> { where(payment_provider: :stripe_standard) }, class_name: "::NeetoPaymentsEngine::Stripe::Account", as: :holdable, dependent: :destroy
126
+ # OR if one Org can hold many standard accounts (less common)
127
+ # has_many :stripe_connected_accounts, -> { where(payment_provider: :stripe_standard) }, class_name: "::NeetoPaymentsEngine::Stripe::Account", as: :holdable, dependent: :destroy
128
+
129
+ # We have to add this association no matter what because
130
+ # it's internally required by the engine. Change the parent association
131
+ # to Organization/User/Whatever depending on your host app.
132
+ has_one :stripe_platform_account, class_name: "::NeetoPaymentsEngine::Stripe::PlatformAccount", inverse_of: :organization, dependent: :destroy
133
+
134
+ # If Organization holds the Razorpay account
135
+ has_one :razorpay_integration, -> { where(payment_provider: :razorpay) }, class_name: "::NeetoPaymentsEngine::Integration", as: :holdable, dependent: :destroy
136
+
137
+ # If Organization holds the UPI VPAs list
138
+ has_one :upi_integration, -> { where(payment_provider: :eupi) }, class_name: "::NeetoPaymentsEngine::Integration", as: :holdable, dependent: :destroy
139
+
140
+ # General association (optional, can be useful)
141
+ has_many :payment_integrations, class_name: "::NeetoPaymentsEngine::Integration", as: :holdable, dependent: :destroy
142
+
143
+ # If Organization itself can have fees associated
144
+ has_one :fee, class_name: "::NeetoPaymentsEngine::Fee", as: :feeable, dependent: :destroy
145
+ has_one :split, class_name: "::NeetoPaymentsEngine::Split", dependent: :destroy # If Org defines platform split %
146
+ end
147
+
148
+ # app/models/user.rb (Example if User is a holdable for Stripe Standard)
149
+ class User < ApplicationRecord
150
+ has_one :stripe_connected_account, -> { where(payment_provider: :stripe_standard) }, class_name: "::NeetoPaymentsEngine::Stripe::Account", as: :holdable, dependent: :destroy
151
+ # Add other associations if User can hold other provider accounts
152
+ end
153
+ ```
154
+
155
+ 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:
156
+ ```ruby
157
+ # app/models/invoice.rb (Example Payable Model)
158
+ class Invoice < ApplicationRecord
159
+ # Association to the payment record for this invoice
160
+ has_one :payment, class_name: "::NeetoPaymentsEngine::Payment", as: :payable, dependent: :destroy
161
+
162
+ # Optional: If fees are directly configured on the payable item itself
163
+ has_one :fee, class_name: "::NeetoPaymentsEngine::Fee", as: :feeable, dependent: :destroy
164
+ end
165
+ ```
166
+
167
+ ### 3. Secrets and Credentials
168
+ Configure API keys and secrets securely using environment variables and secrets file.
169
+
170
+ **Essential ENV variables for `neeto-payments-engine`:**
171
+
172
+ * **`.env.development` (Commit this file):** Contains non-sensitive defaults, placeholders, or development-specific configurations.
173
+ ```dotenv
174
+ # .env.development
175
+
176
+ # Base URL of your host application (adjust port if needed)
177
+ APP_URL='http://app.lvh.me:<APP_PORT>/'
178
+
179
+ # --- Stripe ---
180
+ # Base URL for OAuth callback (using tunnelto 'connect' subdomain for dev)
181
+ STRIPE_CALLBACK_BASE_URL="https://connect.tunnelto.dev" # Or your tunnel URL
182
+
183
+ # --- Razorpay (if used) ---
184
+ RAZORPAY_CLIENT_SECRET="rzp_test_..." # Test Key ID can often be committed
185
+ RAZORPAY_CLIENT_ID="..." # OAuth Client ID is usually safe
186
+ RAZORPAY_WEBHOOK_SECRET=".." # webhook secret
187
+ RAZORPAY_CALLBACK_BASE_URL="https://connect.tunnelto.dev" # Or your tunnel URL
29
188
  ```
30
- 2. And then execute:
31
- ```zsh
32
- bundle install
189
+
190
+ * **`.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`.
191
+
192
+ ```dotenv
193
+ # .env.local (DO NOT COMMIT THIS FILE)
194
+
195
+ # --- Stripe ---
196
+ STRIPE_SECRET_KEY="sk_test_..." # Your TEST Stripe Secret Key
197
+ STRIPE_WEBHOOK_SECRET="whsec_..." # Your TEST Stripe Webhook Signing Secret (from manual setup)
198
+ # Can find this from Stripe dashboard
199
+ STRIPE_PUBLISHABLE_KEY="pk_test_..."
200
+ # First sign up for Stripe Connect, then go to OAuth section of Stripe
201
+ # Connect to get the client id. You'd also have to register OAuth callback
202
+ # URI in that page.
203
+ STRIPE_CLIENT_ID="ca_..."
204
+
205
+ # --- Razorpay (if used) ---
206
+ RAZORPAY_KEY_SECRET="..." # Your TEST Razorpay Key Secret
207
+ RAZORPAY_WEBHOOK_SECRET="..." # Your TEST Razorpay Webhook Secret (you set this)
208
+ RAZORPAY_CLIENT_SECRET="..." # Your TEST Razorpay OAuth Client Secret
209
+
210
+ # --- JWT Keys ---
211
+ # Generate using: openssl genrsa -out private.pem 2048 && openssl rsa -in private.pem -pubout -out public.pem
212
+ # Copy the *entire* content including -----BEGIN...----- and -----END...----- lines.
213
+ CONNECT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
214
+ ...your private key content...
215
+ -----END RSA PRIVATE KEY-----"
216
+ CONNECT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
217
+ ...your public key content...
218
+ -----END PUBLIC KEY-----"
219
+
220
+ # --- Optional ---
221
+ # OPEN_EXCHANGE_RATE_API_KEY="your_actual_key" # Sensitive API key
33
222
  ```
34
- 3. Add this line to your application's `config/routes.rb` file:
35
- ```ruby
36
- mount NeetoPaymentsEngine::Engine, at: '/payments'
223
+
224
+ **Loading Secrets:** Ensure these secrets are loaded into `Rails.application.secrets`. An example `config/secrets.yml` structure:
225
+
226
+ ```yaml
227
+ # config/secrets.yml
228
+ default: &default
229
+ host: <%= ENV['APP_URL'] %>
230
+ stripe:
231
+ publishable_key: <%= ENV['STRIPE_PUBLISHABLE_KEY'] %>
232
+ secret_key: <%= ENV['STRIPE_SECRET_KEY'] %>
233
+ client_id: <%= ENV['STRIPE_CLIENT_ID'] %>
234
+ webhooks:
235
+ secret: <%= ENV['STRIPE_WEBHOOK_SECRET'] %>
236
+ razorpay:
237
+ key_id: <%= ENV["RAZORPAY_KEY_ID"] %>
238
+ key_secret: <%= ENV["RAZORPAY_KEY_SECRET"] %>
239
+ client_id: <%= ENV["RAZORPAY_CLIENT_ID"] %>
240
+ client_secret: <%= ENV["RAZORPAY_CLIENT_SECRET"] %>
241
+ oauth_callback_base_url: <%= ENV["RAZORPAY_CALLBACK_BASE_URL"] %>
242
+ webhook:
243
+ secret: <%= ENV["RAZORPAY_WEBHOOK_SECRET"] %>
244
+ jwt:
245
+ connect_private_key: <%= ENV['CONNECT_PRIVATE_KEY'].inspect %>
246
+ connect_public_key: <%= ENV['CONNECT_PUBLIC_KEY'].inspect %>
247
+
248
+ # Optional: If using Open Exchange Rates for currency conversion
249
+ open_exchange_rates:
250
+ api_key: <%= ENV["OPEN_EXCHANGE_RATE_API_KEY"] %>
251
+ # ... other secrets ...
252
+
253
+ development:
254
+ <<: *default
255
+ # Development specific overrides
256
+
257
+ production:
258
+ <<: *default
259
+ # Production specific overrides (use credentials!)
37
260
  ```
38
- 4. Configure the following environment variables.
39
- ```yaml
40
- APP_URL=#Application URL
41
- STRIPE_PUBLISHABLE_KEY='pk_test_fnFiGXjRkMXnLkueiNKJidGU00V83aRC0V'
42
- STRIPE_SECRET_KEY='sk_test_eyKnYYoSDcvojBAiFoDv3QJQ00z0yOlt89'
43
- ```
44
-
45
- NB: Replace `APP_URL` environment variable value with the host application's
46
- base URL without a trailing slash for example: `https://app.neetocal.net`.
47
- Stripe environment variables values are `test` mode keys.
48
- 5. Run the following command to subscribe Stripe webhooks events.
49
- ```shell
50
- bundle exec rails neeto_payments_engine:stripe:webhooks:subscribe
51
- ```
52
-
53
- - After successful subscription, the above command will return the list of
54
- events subscribed and the details of environment variable.
55
- - Sample output
56
- ```
57
- *** Successfully subscribed for webhooks ***
58
- Please add the following environment variable
59
- STRIPE_WEBHOOK_SECRET='whsec_06LHs3IOmLOgEzXDm3otErje8I4Q15ME'
60
- ==============================================
61
- Subscribed events: [
62
- "account.updated",
63
- "payment_intent.payment_failed",
64
- "payment_intent.processing",
65
- "payment_intent.succeeded",
66
- "charge.refund.updated",
67
- "charge.refunded"
68
- ]
69
- ```
70
- - Add the returned environment variable and its value.
71
- 6. Add the following secret values to `config/secrets.yml`.
72
- ```yaml
73
- host: <%= ENV['APP_URL'] || ENV['HEROKU_APP_URL'] %>
74
- stripe:
75
- publishable_key: <%= ENV['STRIPE_PUBLISHABLE_KEY'] %>
76
- secret_key: <%= ENV['STRIPE_SECRET_KEY'] %>
77
- webhooks:
78
- secret: <%= ENV['STRIPE_WEBHOOK_SECRET'] %>
79
- ```
80
- 7. Add the following line to application's
81
- `config/initializer/neeto_payments_engine.rb` file. Replace the
82
- `holdable_class` value with a model name where you want to associate your
83
- Stripe account in the host application.
84
- ```ruby
85
- NeetoPaymentsEngine.holdable_class = "Organization"
86
- ```
87
- 8. Add the following association to the model you defined as holdable_class in
88
- step 7.
89
- ```ruby
90
- has_one :stripe_integration, class_name: "::NeetoPaymentsEngine::Stripe::Account", foreign_key: :holdable_id
91
- ```
92
-
93
- 9. Add the following association to the chargeable model. The chargeable model
94
- is the model in the host application which needs to charge for its service.
95
- For example `Meeting` model in `neetoCal` where meeting service is a
96
- chargeable one.
97
- ```ruby
98
- has_one :charge, as: :chargeable, class_name: "::NeetoPaymentsEngine::Stripe::Charge", dependent: :destroy
99
- ```
100
- 10. Add the following association to the payable model.
101
- ```ruby
102
- has_one :payment, as: :payable, class_name: "::NeetoPaymentsEngine::Stripe::Transaction", dependent: :destroy
261
+
262
+ **Secrets Management:**
263
+ - **Development:** Use `.env.local` (which is typically gitignored) to store sensitive keys locally.
264
+ - **Staging/Production:** Use environment variables managed by your deployment platform (e.g., NeetoDeploy, Heroku Config Vars) or Rails encrypted credentials. **Do not commit secrets directly into your repository.**
265
+
266
+ ### 4. Stripe Connect Signup
267
+ 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.
268
+
269
+ ### 5. OAuth Callback URI Registration
270
+ Register the following callback URIs in your respective payment provider dashboards:
271
+ * **Stripe:** `https://<your_connect_subdomain_or_app_domain>/payments/api/v1/public/stripe/oauth/callback`
272
+ * `<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.
273
+ * **Razorpay:** `https://<your_razorpay_oauth_base_url>/payments/api/v1/public/razorpay/oauth/callback`
274
+ * `<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.
275
+
276
+ ## Frontend Integration
277
+
278
+ Integrate the React components provided by the `@bigbinary/neeto-payments-frontend` package.
279
+
280
+ ### 1. Install Frontend Package
281
+ ```bash
282
+ yarn add @bigbinary/neeto-payments-frontend
283
+ ```
284
+
285
+ ### 2. Install Peer Dependencies
286
+ 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:
287
+ ```bash
288
+ # DO NOT INSTALL THE VERSIONS MENTIONED BELOW AS IT MIGHT BE OUTDATED.
289
+ # ALWAYS PREFER INSTALLING THE LATEST VERSIONS.
290
+ # If something isn't working, then refer to these versions and see
291
+ # what's causing the breakage.
292
+ 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
293
+ ```
294
+ *Note: Carefully manage potential version conflicts with your host application.*
295
+
296
+ ### 3. Components
297
+ Import components into your React application as needed.
298
+
299
+ 1. `TaxesDashboard` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/main/app/javascript/src/components/TaxesDashboard/index.jsx))
300
+
301
+ **Props**
302
+ - `feeId`: The unique identifier for the tax fee.
303
+ - `breadcrumbs`: Data for rendering the header breadcrumbs.
304
+ - `paymentUrl` _(optional)_: The URL of the pricing page to redirect users if payments are not enabled.
305
+ - `headerSize` _(optional)_: Specifies the size of the header. Default size is `small`
306
+ - `noDataHelpText` _(optional)_: Help text displayed when there is no data available.
307
+ - `onTaxesChange` _(optional)_: Callback function triggered after performing actions in the taxes dashboard.
308
+ - `titleHelpPopoverProps`_(optional)_: Data for the header help popover.
309
+
310
+ **Usage**
311
+ ```jsx
312
+ import React from "react";
313
+ import { TaxesDashboard } from "@bigbinary/neeto-payments-frontend";
314
+
315
+ const App = () => {
316
+ return (
317
+ <TaxesDashboard
318
+ feeId={fee.id}
319
+ breadcrumbs={[{text: "Settings" ,link: routes.admin.form.settings}]}
320
+ />
321
+ );
322
+ };
103
323
  ```
104
- 11. Add the following queue to your sidekiq config file `config/sidekiq.yml`
105
- ```yaml
106
- queues:
107
- - payment # For neeto-payments-engine
324
+
325
+ 2. `PaymentsDashboard` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/main/app/javascript/src/components/PaymentsDashboard/index.jsx))
326
+
327
+ **Props**
328
+ - `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.
329
+ - `payableEntityColumns`: To specify the columns from the payable entity
330
+ which need to be additionally displayed. It is an optional prop that defaults to `[]` if not specified.
331
+ - `searchProps`: Allows specifying additional columns to be included in the search functionality. By default, only the `identifier` column from the payments table is searchable.
332
+ - `holdableIds`: Provide the holdable IDs for each payment provider when the holdable entity is not a `User` or `Organization` model.
333
+
334
+ **Usage**
335
+ ```jsx
336
+ import React from "react";
337
+ import { PaymentsDashboard } from "@bigbinary/neeto-payments-frontend";
338
+
339
+ const App = () => {
340
+
341
+ // neeto-filters-engine search prop syntax.
342
+ const SEARCH_PROPS = {
343
+ node: "bookings:payable.meeting.name",
344
+ model: "Meeting",
345
+ placeholder: "Search by identifier or meeting name",
346
+ };
347
+
348
+ // Only required if the payment provider holdable modal is not a User or Organization
349
+ const holdableIds = { stripeStandard: formId, razorpay: formId }
350
+
351
+ const payableEntityColumns = {
352
+ title: "Submission",
353
+ dataIndex: ["payable", "id"],
354
+ key: "submission",
355
+ ellipsis: true,
356
+ width: "300px",,
357
+ },
358
+
359
+ return (
360
+ <PaymentsDashboard
361
+ {...{ payableEntityColumns, holdableIds }}
362
+ searchProps={SEARCH_PROPS}
363
+ />
364
+ );
365
+ };
108
366
  ```
367
+ 3. `RefundsDashboard` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/main/app/javascript/src/components/RefundsDashboard/index.jsx))
109
368
 
110
- ### Usage
111
- You can learn more about setup and usage here:
112
- 1. [Services](/docs/engine/services.md)
113
- 2. [Stripe integration](/docs/engine/stripe_integrations.md)
114
- 2. [Razorpay integration](/docs/engine/razorpay_integrations.md)
115
- 3. [Stripe payment](/docs/engine/stripe_payment.md)
116
- 4. [Razorpay payment](/docs/engine/razorpay_payment.md)
117
-
118
- ## Frontend package
119
- ### Installation
120
- 1. Install the latest `neeto-payments-frontend` package using the below command:
121
- ```zsh
122
- yarn add @bigbinary/neeto-payments-frontend
123
- ```
124
- 2. `neeto-payments-frontend` has a few peer dependencies that are required for the proper functioning of the package. Install all the peer dependencies using the below command:
125
- ```zsh
126
- yarn add @bigbinary/neeto-commons-frontend@2.0.54 @bigbinary/neeto-filters-frontend@2.8.1 @bigbinary/neeto-icons@1.9.22 @bigbinary/neeto-molecules@1.0.9 @bigbinary/neetoui@4.4.10 @honeybadger-io/js@5.1.1 @honeybadger-io/react@5.1.3 axios@1.3.4 classnames@2.3.2 formik@2.2.9 js-logger@1.6.1 mixpanel-browser@2.45.0 ramda@0.28.0 react-helmet@6.1.0 react-query@3.39.3 react-router-dom@5.3.4 react-toastify@8.2.0 yup@1.0.2
127
- ```
128
-
129
- ### Instructions for development
130
- Check the [Frontend package development guide](https://neeto-engineering.neetokb.com/p/a-d34cb4b0) for step-by-step instructions to develop the frontend package.
131
-
132
- ### Components
133
- #### 1. `Dashboard` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/42ad8330442696b102d8555930162d75cf94931e/app/javascript/src/components/Dashboard/index.jsx))
134
-
135
- **Props**
136
- - `holdableId`: To specify the ID of the entity that holds the stripe
137
- account. This is an optional prop. If the value is not specified, the current
138
- organization ID is taken as the default value while querying for
139
- transactions.
140
- - `payableEntityColumns`: To specify the columns from the payable entity
141
- which need to be additionally displayed. It is an optional prop that defaults
142
- to `[]` if not specified.
143
- - `tabs`: To specify the tabs to be displayed in the payments dashboard. It
144
- is an optional parameter.
145
- - `headerProps`: To specify the props to be passed to the neetoUI `Header`
146
- component used in the `Dashboard`.
147
-
148
- **Configuration**
149
- - Refer to the [Dashboard](/docs/frontend/dashboard.md) section for detailed information on the available configurations for the `Dashboard` component.
150
-
151
- **Usage**
152
- ```jsx
153
- import React, { useState } from "react";
154
- import { Dashboard } from "@bigbinary/neeto-payments-frontend";
155
-
156
- const App = () => {
157
- const headerProps = {
158
- className: "w-80",
159
- // Your header properties
160
- };
369
+ **Props**
370
+ - `payableEntityColumns`: To specify the columns from the payable entity
371
+ which need to be additionally displayed. It is an optional prop that defaults to `[]` if not specified.
372
+ - `searchProps`: Allows specifying additional columns to be included in the search functionality. By default, only the `identifier` column from the refunds table is searchable.
161
373
 
162
- return (
163
- <Dashboard
164
- tabs={["all", "successful"]}
165
- headerProps={headerProps}
166
- />
167
- );
168
- };
374
+ **Usage**
375
+ ```jsx
376
+ import React from "react";
377
+ import { SplitTransfersDashboard } from "@bigbinary/neeto-payments-frontend";
378
+
379
+ const App = () => {
380
+
381
+ // neeto-filters-engine search prop syntax.
382
+ const SEARCH_PROPS = {
383
+ node: "bookings:payable.meeting.name",
384
+ model: "Meeting",
385
+ placeholder: "Search by identifier or meeting name",
386
+ };
387
+
388
+ const payableEntityColumns = {
389
+ title: "Submission",
390
+ dataIndex: ["payable", "id"],
391
+ key: "submission",
392
+ ellipsis: true,
393
+ width: "300px",,
394
+ },
395
+
396
+ return (
397
+ <SplitTransfersDashboard
398
+ {...{ payableEntityColumns }}
399
+ searchProps={SEARCH_PROPS}
400
+ />
401
+ );
402
+ };
403
+ ```
404
+
405
+ 4. `SplitTransfersDashboard` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/main/app/javascript/src/components/SplitTransfersDashboard/index.jsx))
406
+
407
+ **Props**
408
+ - `payableEntityColumns`: To specify the columns from the payable entity
409
+ which need to be additionally displayed. It is an optional prop that defaults to `[]` if not specified.
410
+ - `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.
411
+ **Usage**
412
+ ```jsx
413
+ import React from "react";
414
+ import { RefundsDashboard } from "@bigbinary/neeto-payments-frontend";
415
+
416
+ const App = () => {
417
+
418
+ // neeto-filters-engine search prop syntax.
419
+ const SEARCH_PROPS = {
420
+ node: "bookings:payable.meeting.name",
421
+ model: "Meeting",
422
+ placeholder: "Search by identifier or meeting name",
423
+ };
424
+
425
+ const payableEntityColumns = {
426
+ title: "Submission",
427
+ dataIndex: ["payable", "id"],
428
+ key: "submission",
429
+ ellipsis: true,
430
+ width: "300px",,
431
+ },
432
+
433
+ return (
434
+ <RefundsDashboard
435
+ {...{ payableEntityColumns }}
436
+ searchProps={SEARCH_PROPS}
437
+ />
438
+ );
439
+ };
440
+ ```
441
+
442
+
443
+ 5. `AccountsDashboard` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/main/app/javascript/src/components/AccountsDashboard/index.jsx))
444
+
445
+ **Usage**
446
+ ```jsx
447
+ import React from "react";
448
+ import { AccountsDashboard } from "@bigbinary/neeto-payments-frontend";
449
+
450
+ const App = () => {
451
+
452
+ return (<AccountsDashboard />);
453
+ };
454
+ ```
455
+
456
+ 6. `PayoutsDashboard` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/main/app/javascript/src/components/PayoutsDashboard/index.jsx))
457
+
458
+ **Props**
459
+ - `isPlatformEnabled`: Indicates whether platform integration is enabled.
460
+ - `payoutsPageRoute`: The route used to display detailed payout information.
461
+
462
+
463
+ **Usage**
464
+ ```jsx
465
+ import React from "react";
466
+ import { PayoutsDashboard } from "@bigbinary/neeto-payments-frontend";
467
+
468
+ const App = () => {
469
+
470
+ return (
471
+ <PayoutsDashboard
472
+ isPlatformEnabled={true}
473
+ payoutsPageRoute={routes.admin.payments.payouts.show}
474
+ />
475
+ );
476
+ };
477
+ ```
478
+ 7. `RazorpayPaymentButton` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/main/app/javascript/src/components/RazorpayPaymentButton.jsx))
479
+
480
+ **Props**
481
+ - `label`: The button label. Defaults to `Pay`.
482
+ - `payableId`: The ID of the payable entity.
483
+ - `discountCode`: The discount code to be applied, if any.
484
+ - `email`: The customer's email address.
485
+ - `name`: The customer's name.
486
+ - `theme`: The theme configuration object.
487
+ - `payableType`: The type of the payable entity.
488
+ - `onBeforePayment`: A callback function triggered before the payment is initiated.
489
+ - `onSuccessfulPayment`: A callback function triggered after a successful payment.
490
+ - `onFailedPayment`: A callback function triggered after a failed payment.
491
+
492
+ **Usage**
493
+ ```jsx
494
+ import React from "react";
495
+ import { RazorpayPaymentButton } from "@bigbinary/neeto-payments-frontend";
496
+
497
+ const App = () => {
498
+
499
+ return (
500
+ <RazorpayPaymentButton
501
+ email={customer.email}
502
+ name={customer.name}
503
+ discountCode={discountCode.code}
504
+ payableId={booking.id}
505
+ theme={meeting.theme}
506
+ />
507
+ );
508
+ };
509
+ ```
510
+
511
+ 8. `ConfirmUpiPaymentButton` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/main/app/javascript/src/components/ConfirmUpiPaymentButton/index.jsx))
512
+
513
+ **Props**
514
+ - `vpas`: A list of UPI IDs presented for the user to select during confirmation.
515
+ - `identifier`: The unique identifier associated with the payment.
516
+ - `paymentId`: The ID of the payment.
517
+ - `onSuccess`: A callback function triggered upon successful confirmation.
169
518
 
170
- export default App;
519
+ **Usage**
520
+
521
+ ```jsx
522
+ import React from "react";
523
+ import { ConfirmUpiPaymentButton } from "@bigbinary/neeto-payments-frontend";
524
+
525
+ const App = () => {
526
+
527
+ return (
528
+ <ConfirmUpiPaymentButton
529
+ identifier={payment.identifier}
530
+ payableId={payment.payableId}
531
+ paymentId={payment.paymentId}
532
+ vpas={meeting.fee.vpas}
533
+ />
534
+ );
535
+ };
536
+ ```
537
+
538
+ ### 4. Hooks
539
+
540
+ 1. `useStripePromise` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/main/app/javascript/src/hooks/useStripePromise.js))
541
+
542
+ This hook can used to provide the value for the `stripe` prop of the Stripe `Element` component.
543
+
544
+ **Usage**
545
+
546
+ ```jsx
547
+ import React from "react";
548
+ import { useStripePromise } from "@bigbinary/neeto-payments-frontend";
549
+
550
+ const App = () => {
551
+
552
+ const stripePromise = useStripePromise({
553
+ stripePlatformAccount // The integration object for the Stripe platform account. No value is required if the platform account integration is not configured.
554
+
555
+ stripeAccountIdentifier: paymentRecipient?.stripeConnectedAccount?.identifier, // Stripe Standard account integration identifier
556
+ });
557
+
558
+ return (
559
+ <Elements stripe={stripePromise}>
560
+ </Elements>
561
+ );
562
+ };
563
+ ```
564
+ 2. `useRazorpayPayment` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/main/app/javascript/src/hooks/useRazorpayPayment.js))
565
+
566
+ 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.
567
+
568
+
569
+ **Usage**
570
+
571
+ ```jsx
572
+ import React from "react";
573
+ import { useRazorpayPayment } from "@bigbinary/neeto-payments-frontend";
574
+
575
+ const App = () => {
576
+
577
+ const { makePayment } = useRazorpayPayment({
578
+ payableId, // The ID of the payable entity.
579
+ onBeforePayment, // A callback function triggered before the payment is initiated.
580
+ onSuccessfulPayment, // A callback function triggered after a successful payment.
581
+ onFailedPayment, // A callback function triggered after a failed payment.
582
+ customAmount, // The custom amount value to be used when a fee of type `range` or `variable` is configured.
583
+
584
+ });
585
+
586
+ };
587
+ ```
588
+
589
+ ### 5. constants
590
+
591
+ 1. CURRENCY_OPTIONS
592
+
593
+ A list of supported currencies in `{ value, label }` format. This can be used as options for the NeetoUI `Select` component.
594
+
595
+ ### 6. Others
171
596
  ```
597
+ // Example: Payment Settings Page
598
+ import React, { useState, useEffect } from 'react';
599
+ import axios from 'axios';
600
+ import { PaymentsDashboard, StripeConnect } from '@bigbinary/neeto-payments-frontend';
601
+ import { useQueryParams } from "@bigbinary/neeto-commons-frontend"; // Assuming you use this hook
602
+
603
+ const PaymentSettingsPage = () => {
604
+ const [integrations, setIntegrations] = useState({});
605
+ const [isLoading, setIsLoading] = useState(true);
606
+ const [isStripeConnectOpen, setIsStripeConnectOpen] = useState(false);
607
+ const { app: returnedApp, error: oauthError } = useQueryParams(); // For handling OAuth callbacks
608
+
609
+ useEffect(() => {
610
+ const fetchIntegrations = async () => {
611
+ setIsLoading(true);
612
+ try {
613
+ // IMPORTANT: Specify ONLY the providers your application actually uses.
614
+ const usedProviders = ["stripe_standard"]; // Example: only using Stripe Standard
615
+ const response = await axios.get('/payments/api/v1/integrations', {
616
+ params: { providers: usedProviders }
617
+ });
618
+ setIntegrations(response.data);
619
+ } catch (err) {
620
+ console.error("Failed to fetch integrations:", err);
621
+ // Handle error appropriately (e.g., show toast notification)
622
+ } finally {
623
+ setIsLoading(false);
624
+ }
625
+ };
626
+ fetchIntegrations();
172
627
 
173
- #### 2. `PaymentKindRestrictionAlert` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/42ad8330442696b102d8555930162d75cf94931e/app/javascript/src/components/Stripe/Connect/PaymentKindRestrictionAlert/index.jsx))
628
+ // Handle OAuth callback results
629
+ if (returnedApp === 'stripe' && oauthError) {
630
+ console.error("Stripe connection failed:", oauthError);
631
+ // Show error message to user
632
+ } else if (returnedApp === 'stripe') {
633
+ console.log("Stripe connection initiated successfully. Waiting for webhook verification.");
634
+ // Maybe show a success message, fetchIntegrations will update the state eventually
635
+ }
636
+ // Add similar checks for Razorpay if using OAuth
174
637
 
175
- **Props**
176
- - `isOpen`: To specify whether the Stripe connect restriction alert should be
177
- opened or closed.
178
- - `paymentKind`: To specify the type of Stripe payment kind for rendering
179
- related content to alert either as "standard" or "platform". If Stripe
180
- Standard is connected the alert shall forbid Stripe Platform connect and vice
181
- versa.
182
- - `onClose`: Handler function that is triggered when the disconnect button is
183
- clicked.
638
+ }, [returnedApp, oauthError]);
184
639
 
185
- **Usage**
186
- ```jsx
187
- import React, { useState } from "react";
188
- import { PaymentKindRestrictionAlert } from "@bigbinary/neeto-payments-frontend";
640
+ // Determine holdableId based on your app's logic (e.g., current organization)
641
+ // This might come from context, props, or another state hook
642
+ const currentHoldableId = integrations.stripe_standard_account?.holdable_id || 'your_org_or_user_id'; // Replace with actual logic
189
643
 
190
- const App = () => {
191
- const [isStripePlatformConnectedAlertOpen, setIsStripePlatformConnectedAlertOpen] = useState(false);
644
+ if (isLoading) {
645
+ return <div>Loading payment settings...</div>;
646
+ }
192
647
 
193
648
  return (
194
- <PaymentKindRestrictionAlert
195
- isOpen={isStripePlatformConnectedAlertOpen}
196
- paymentKind="standard"
197
- onClose={() => setIsStripePlatformConnectedAlertOpen(false)}
198
- />
649
+ <div>
650
+ <h1>Payment Settings</h1>
651
+
652
+ {/* Conditionally render based on integration status */}
653
+ {!integrations.stripe_standard_account?.is_connected && (
654
+ <button onClick={() => setIsStripeConnectOpen(true)}>
655
+ Connect Stripe Account
656
+ </button>
657
+ )}
658
+ {integrations.stripe_standard_account?.is_connected && (
659
+ <p>Stripe Account Connected: {integrations.stripe_standard_account.identifier}</p>
660
+ // Add disconnect button here
661
+ )}
662
+
663
+ {/* Stripe Connect Modal */}
664
+ <StripeConnect
665
+ isOpen={isStripeConnectOpen}
666
+ onClose={() => setIsStripeConnectOpen(false)}
667
+ holdableId={currentHoldableId} // Pass the ID of the Organization/User model instance
668
+ returnUrl={`${window.location.origin}/payment-settings`} // URL Stripe redirects back to (should match registered URI)
669
+ isPlatform={false} // Set true only if connecting a Stripe Platform account
670
+ />
671
+
672
+ {/* Example Dashboard (only show if integrated) */}
673
+ {integrations.stripe_standard_account?.is_connected && (
674
+ <>
675
+ <h2>Payments Overview</h2>
676
+ <PaymentsDashboard
677
+ // Example: Use 'connected' kind if viewing standard account payments
678
+ dashboardKind="connected"
679
+ // Pass holdable ID for the specific account being viewed
680
+ holdableIds={{ stripe_standard: currentHoldableId }}
681
+ />
682
+ </>
683
+ )}
684
+ </div>
199
685
  );
200
686
  };
201
687
 
202
- export default App;
688
+ export default PaymentSettingsPage;
203
689
  ```
204
690
 
205
- #### 4. `PayoutsDashboard` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/42ad8330442696b102d8555930162d75cf94931e/app/javascript/src/components/PayoutsDashboard/index.jsx))
691
+ ### 4. API Calls - Specify Providers
692
+ 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.
206
693
 
207
- **Props**
208
- - `payoutsPageRoute`: The route for the payouts page.
209
- - `isPlatformEnabled`: Indicates whether the platform is enabled or not.
694
+ ```javascript
695
+ // Correct: Fetching only Stripe Standard integration status
696
+ axios.get('/payments/api/v1/integrations', {
697
+ params: { providers: ["stripe_standard"] }
698
+ });
210
699
 
211
- **Usage**
212
- ```jsx
213
- import React from "react";
214
- import { PayoutsDashboard } from "@bigbinary/neeto-payments-frontend";
700
+ // Incorrect (if not using all providers): Might cause errors
701
+ // axios.get('/payments/api/v1/integrations');
702
+ ```
215
703
 
216
- const App = () => {
217
- return (
218
- <PayoutsDashboard
219
- isPlatformEnabled
220
- payoutsPageRoute={routes.admin.payment.stripePayouts.show}
221
- />
222
- );
223
- };
704
+ ## Core Concepts
224
705
 
225
- export default App;
706
+ - **Polymorphic Associations:** `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.
707
+ - **Single Table Inheritance (STI):** Used for provider-specific models like `Payments::Stripe`, `Payments::Razorpay`, `Integrations::Razorpay`.
708
+ - **Service Objects:** Encapsulate business logic (e.g., `Stripe::Payments::CreateService`).
709
+ - **Callbacks:** Host applications implement methods in `NeetoPaymentsEngine::Callbacks` to customize behavior. See the [Callbacks](#callbacks) section.
710
+ - **Webhooks:** Asynchronous events from providers update payment statuses. See [Webhook Handling](#webhook-handling).
711
+ - **JWT Authentication:** Secures OAuth callback flows using RSA keys.
712
+
713
+ ## Webhook Handling
714
+
715
+ - Webhooks are crucial for updating payment statuses asynchronously.
716
+ - Endpoints are available at `/payments/api/v1/public/<provider>/webhooks`.
717
+ - Signatures are verified using configured secrets.
718
+
719
+ ### Development Environment Setup
720
+
721
+ #### 1. Tunneling
722
+ 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](https://neeto-engineering.neetokb.com/articles/using-tunnelto) and [Exposing Tunnelto Locally](https://neeto-engineering.neetokb.com/articles/writing-payment-tests) for more details.
723
+ ```bash
724
+ # Example using tunnelto
725
+ tunnelto --subdomain connect --port <YOUR_RAILS_APP_PORT>
726
+ # This makes https://connect.tunnelto.dev forward to your local app
226
727
  ```
227
728
 
228
- #### 5. `TaxesDashboard` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/main/app/javascript/src/components/TaxesDashboard/index.jsx))
729
+ #### 2. Stripe Webhook Setup (Manual)
730
+ * Go to your **Stripe Test Dashboard** > Developers > Webhooks.
731
+ * Click "Add endpoint".
732
+ * Endpoint URL: `https://connect.tunnelto.dev/payments/api/v1/public/stripe/webhooks` (replace with your actual tunnel URL).
733
+ * Select events to listen to. Refer to `config/stripe/webhooks.yml` in the engine for the required list (e.g., `payment_intent.succeeded`, `charge.refunded`, `account.updated`).
734
+ * Click "Add endpoint".
735
+ * After creation, find the **Signing secret** (starts with `whsec_...`).
736
+ * **Copy this secret** and set it as the `STRIPE_WEBHOOK_SECRET` in your local `.env.local` or development credentials.
737
+ * **Note:** The `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](https://docs.stripe.com/webhooks) for more details.
229
738
 
230
- **Props**
231
- - `feeId`: The unique identifier for the tax fee.
232
- - `breadcrumbs`: Data for rendering the header breadcrumbs.
233
- - `paymentUrl` _(optional)_: The URL of the pricing page to redirect users if payments are not enabled.
234
- - `headerSize` _(optional)_: Specifies the size of the header. Default size is `small`
235
- - `noDataHelpText` _(optional)_: Help text displayed when there is no data available.
236
- - `onTaxesChange` _(optional)_: Callback function triggered after performing actions in the taxes dashboard.
237
- - `titleHelpPopoverProps`_(optional)_: Data for the header help popover.
739
+ #### 3. Razorpay Webhook Setup (Manual)
740
+ * Go to your **Razorpay Partners Dashboard**.
741
+ * Go to Applications
742
+ * Create a new application or open the existing application
743
+ * Add test webhook
744
+ * Webhook URL: `https://connect.tunnelto.dev/payments/api/v1/public/razorpay/webhooks` (replace with your tunnel URL).
745
+ * Set a **Secret** (create a strong random string).
746
+ * Select Active Events (e.g., `payment.authorized`, `payment.captured`, `payment.failed`, `refund.processed`, `refund.failed`).
747
+ * Save the webhook.
748
+ * **Copy the Secret you set** and configure it as `RAZORPAY_WEBHOOK_SECRET` in your local development environment.
238
749
 
239
- **Usage**
240
- ```jsx
241
- import React from "react";
242
- import { TaxesDashboard } from "@bigbinary/neeto-payments-frontend";
750
+ ## Authentication (JWT for OAuth)
243
751
 
244
- const App = () => {
245
- return (
246
- <TaxesDashboard
247
- feeId={fee.id}
248
- breadcrumbs={[{text: "Settings" ,link: routes.admin.form.settings}]}
249
- />
250
- );
251
- };
752
+ - Secures the OAuth callback flow for connecting Stripe/Razorpay accounts.
753
+ - Uses RSA public/private keys (`CONNECT_PUBLIC_KEY`, `CONNECT_PRIVATE_KEY`) configured in your secrets.
754
+ - The `ConnectLinkService` creates a short-lived JWT containing user context when initiating the OAuth flow.
755
+ - The `JwtService` verifies the token signature using the public key upon callback.
252
756
 
253
- export default App;
757
+ ## Callbacks
758
+
759
+ 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.
760
+
761
+ ### Mandatory Callbacks
762
+ ```ruby
763
+ # app/lib/neeto_payments_engine/callbacks.rb
764
+ module NeetoPaymentsEngine
765
+ class Callbacks
766
+ # --- Mandatory Callbacks ---
767
+
768
+ # Check user permissions for general payment management access.
769
+ # @param user [User] The user attempting the action.
770
+ # @return [Boolean] True if the user has permission.
771
+ def self.can_manage_payments?(user)
772
+ user.has_role?(:admin) # Replace with your authorization logic
773
+ end
774
+
775
+ # ... other callbacks ...
776
+ end
777
+ end
254
778
  ```
255
779
 
256
- ### Hooks
257
- #### 1. `useStripePromise` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/42ad8330442696b102d8555930162d75cf94931e/app/javascript/src/hooks/useStripePromise.js))
258
-
259
- - This hook is used to generate the `stripePromise` object to be passed to the
260
- stripe `<Elements>` context. The `useStripePromise` hook takes a stripe account
261
- identifier as input and returns a `Promise`.
262
- - Usage in [neetoForm](https://github.com/bigbinary/neeto-form-web/blob/f84ad909628b7ce3497cbb6f9a1da8631121a167/app/javascript/src/components/Form/Design/Preview/index.jsx#L39).
263
-
264
- > :memo: **Note:** `STRIPE_PUBLISHABLE_KEY` environment variable must be present
265
- > for this hook to work.
266
-
267
- **Usage**
268
- ```jsx
269
- const stripeAccountIdentifier = "acct_1Mtqtz2e5McIywz4";
270
- const stripePromise = useStripePromise(stripeAccountIdentifier);
271
- ...
272
- <Elements stripe={stripePromise}>
273
- <CardElement />
274
- </Elements>
780
+ ### Optional Callbacks (Implement as needed)
781
+ ```ruby
782
+ # app/lib/neeto_payments_engine/callbacks.rb
783
+ module NeetoPaymentsEngine
784
+ class Callbacks
785
+ # --- Optional Callbacks (Implement as needed) ---
786
+
787
+ # Return the fee object applicable to the payable item.
788
+ # @param payable [Object] The host application's payable model instance (e.g., Invoice, Booking).
789
+ # @return [NeetoPaymentsEngine::Fee, nil] The Fee object or nil if no fee applies.
790
+ def self.get_fee(payable)
791
+ # Example: Find fee based on payable's attributes or associated product/service
792
+ payable.applicable_fee # Replace with your actual logic
793
+ end
794
+
795
+ # Check if a payment can be processed for this payable item.
796
+ # @param payable [Object] The host application's payable model instance.
797
+ # @return [Boolean] True if payment is allowed, false otherwise.
798
+ def self.payment_processable?(payable)
799
+ # Example: Check if the payable is in a state allowing payment (e.g., 'pending', 'unpaid')
800
+ payable.can_be_paid? # Replace with your actual logic
801
+ end
802
+
803
+ # Return an ActiveRecord::Relation scope for payable objects.
804
+ # Used by the engine, e.g., when filtering payments by associated payables.
805
+ # @param organization [Organization] The organization context.
806
+ # @param payable_type [String, nil] The stringified class name of the payable type (e.g., "Invoice"), or nil.
807
+ # @return [ActiveRecord::Relation] A scope of payable objects.
808
+ def self.get_payables(organization, payable_type = nil)
809
+ # Example: Return all Invoices for the organization if type matches
810
+ if payable_type == "Invoice"
811
+ organization.invoices
812
+ elsif payable_type == "Booking"
813
+ organization.bookings
814
+ else
815
+ # Return a sensible default or handle all relevant types
816
+ organization.invoices # Defaulting to invoices here, adjust as needed
817
+ end
818
+ end
819
+
820
+ # Validate if a discount code is applicable to a specific payable item.
821
+ # @param discount_code [DiscountCode] Host application's discount code model instance.
822
+ # @param payable [Object] The host application's payable model instance.
823
+ # @return [Boolean] True if the discount code is valid for the payable, false otherwise.
824
+ def self.valid_discount_code?(discount_code, payable)
825
+ # Example: Check code validity, usage limits, applicability to the payable's item/service
826
+ discount_code.is_valid_for?(payable) # Replace with your actual logic
827
+ end
828
+
829
+ # Determine if the transaction cookie (used by Stripe flow) should be deleted.
830
+ # Default logic (return true) is usually sufficient. Delete after success/refund.
831
+ # @param payment [NeetoPaymentsEngine::Payment] The payment object.
832
+ # @return [Boolean] True to delete the cookie, false to keep it.
833
+ def self.delete_transaction_cookie?(payment)
834
+ true
835
+ end
836
+
837
+ # Return the Stripe connected account associated with the payable's context.
838
+ # Crucial for marketplace payments where funds go to a connected account.
839
+ # @param payable [Object] The host application's payable model instance.
840
+ # @return [NeetoPaymentsEngine::Stripe::Account, nil] The connected account or nil.
841
+ def self.get_connected_account(payable)
842
+ # Example: Find the seller/vendor associated with the payable item
843
+ payable.seller&.stripe_connected_account # Replace with your actual logic
844
+ end
845
+
846
+ # Return the Stripe connected account that should receive the split amount for a given payable.
847
+ # Often the same logic as get_connected_account.
848
+ # @param payable [Object] The host application's payable model instance.
849
+ # @return [NeetoPaymentsEngine::Stripe::Account, nil] The destination account for the split.
850
+ def self.get_split_account(payable)
851
+ # Example: Find the recipient/beneficiary for the split payment
852
+ payable.recipient_user&.stripe_connected_account # Replace with your actual logic
853
+ end
854
+
855
+ # Return a hash of details for a payable item, used in CSV exports.
856
+ # @param payable [Object] The host application's payable model instance.
857
+ # @return [Hash] Key-value pairs to include in the export.
858
+ def self.get_exportable_details(payable)
859
+ {
860
+ # Example: Extract relevant fields from your payable model
861
+ payable_name: payable.try(:name) || "N/A",
862
+ invoice_number: payable.try(:invoice_number),
863
+ product_sku: payable.try(:product_sku)
864
+ }
865
+ end
866
+
867
+ # Return the primary custom hostname for an organization.
868
+ # Used for registering Stripe Payment Method Domains.
869
+ # @param organization [Organization] The organization context.
870
+ # @return [String, nil] The custom hostname or nil.
871
+ def self.custom_domain_hostname(organization)
872
+ # Example: Fetch from a dedicated custom domain model or setting
873
+ organization.custom_domain_setting&.hostname
874
+ end
875
+
876
+ # Determine if a payment related to this payable can be split.
877
+ # @param payable [Object] The host application's payable model instance.
878
+ # @return [Boolean] True if splits are allowed for this payable type/context.
879
+ def self.splittable?(payable)
880
+ # Example: Enable splits only for specific product types or services
881
+ payable.allows_splits? # Replace with your actual logic
882
+ end
883
+
884
+ # Determine if a split transfer should be initiated immediately or held.
885
+ # @param payable [Object] The host application's payable model instance.
886
+ # @return [Boolean] True to transfer immediately, false to hold (e.g., for review).
887
+ def self.transferable?(payable)
888
+ # Example: Hold transfers for items pending review or fulfillment
889
+ !payable.needs_manual_review? # Replace with your actual logic
890
+ end
891
+
892
+ # Return metadata about the payable to include in Stripe transfers.
893
+ # @param payable [Object] The host application's payable model instance.
894
+ # @return [Hash] Metadata hash (keys/values must be strings, limited length).
895
+ def self.get_payable_details(payable)
896
+ {
897
+ payable_id: payable.id.to_s,
898
+ payable_type: payable.class.name,
899
+ # Add other relevant identifiers (e.g., order_id, booking_ref)
900
+ order_reference: payable.try(:order_reference).to_s
901
+ }
902
+ end
903
+
904
+ # Check permissions specifically for managing the Stripe Platform account (if applicable).
905
+ # @param user [User] The user attempting the action.
906
+ # @return [Boolean] True if the user has permission.
907
+ def self.can_manage_stripe_platform_account?(user)
908
+ user.has_role?(:super_admin) # Replace with your authorization logic
909
+ end
910
+
911
+ # Check permissions specifically for managing Razorpay integrations.
912
+ # @param user [User] The user attempting the action.
913
+ # @return [Boolean] True if the user has permission.
914
+ def self.can_manage_razorpay_integration?(user)
915
+ user.has_role?(:admin) # Replace with your authorization logic
916
+ end
917
+ end
918
+ end
275
919
  ```
276
920
 
277
- ### Functions
278
- #### 1. `buildStripeTransactionLink` ([source code](https://github.com/bigbinary/neeto-payments-nano/blob/42ad8330442696b102d8555930162d75cf94931e/app/javascript/src/utils/index.js#L3))
921
+ ## Exposed Entities
922
+
923
+ The engine exposes various models, services, jobs, and tasks for integration and extension:
924
+
925
+ - **Models:** `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.
926
+ - **Services:** Encapsulate core logic (e.g., `Stripe::Payments::CreateService`, `Razorpay::Accounts::CreateService`, `SplitTransfersFilterService`, `ExportCsvService`). While mostly used internally, filter and export services might be invoked directly or indirectly via controllers.
927
+ - **Jobs:** Handle background tasks (`Stripe::WebhooksJob`, `StripePlatform::CreatePaymentSplitsJob`, `ExportCsvJob`, `CreatePaymentMethodDomainJob`). These are queued and processed by Sidekiq.
928
+ - **Concerns:** Reusable modules (`Amountable`, `PaymentProcessable`, `Taxable`, `Stripe::Accountable`, `Refundable`). Mostly for internal engine use.
929
+ - **Rake Tasks:**
930
+ - `neeto_payments_engine:stripe:account:integrate`: Seeds a sample Stripe connected account. **Development/Testing only.**
931
+ - `neeto_payments_engine:stripe:webhooks:subscribe`: **Do not rely on this for setup.** See [Webhook Handling](#webhook-handling) for manual setup instructions.
932
+
933
+ ## API Endpoints
934
+
935
+ The engine exposes several API endpoints under the configured mount path (default `/payments`). Here are some key ones:
936
+
937
+ | Method | Path | Description | Authentication |
938
+ | :----- | :------------------------------------------------ | :----------------------------------------------------------------------- | :------------- |
939
+ | GET | `/api/v1/integrations` | List integrated payment provider accounts for the current context. | Host App Auth |
940
+ | POST | `/api/v1/exports` | Initiate a CSV export for dashboards (Payments, Refunds, Splits). | Host App Auth |
941
+ | GET | `/api/v1/payments` | List payments with filtering and pagination. | Host App Auth |
942
+ | GET | `/api/v1/refunds` | List refunds with filtering and pagination. | Host App Auth |
943
+ | PUT | `/api/v1/recurring_payments/:id` | Update the status of an admin-managed recurring payment. | Host App Auth |
944
+ | GET | `/api/v1/split_transfers` | List split transfers (Stripe Platform) with filtering. | Host App Auth |
945
+ | GET | `/api/v1/split_transfers/:id` | Show details of a specific split transfer. | Host App Auth |
946
+ | POST | `/api/v1/split_transfers/bulk_update` | Cancel or resume multiple pending/cancelled split transfers. | Host App Auth |
947
+ | GET | `/api/v1/fees/:fee_id/taxes` | List taxes configured for a specific fee. | Host App Auth |
948
+ | POST | `/api/v1/fees/:fee_id/taxes` | Create a new tax for a fee. | Host App Auth |
949
+ | PUT | `/api/v1/fees/:fee_id/taxes/:id` | Update an existing tax for a fee. | Host App Auth |
950
+ | DELETE | `/api/v1/fees/:fee_id/taxes/:id` | Delete a tax from a fee. | Host App Auth |
951
+ | GET | `/api/v1/fees/:fee_id/recurring_setting` | Get recurring payment settings for a fee. | Host App Auth |
952
+ | PUT | `/api/v1/fees/:fee_id/recurring_setting` | Update recurring payment settings for a fee. | Host App Auth |
953
+ | GET | `/api/v1/stripe/countries` | List countries supported by Stripe for account creation. | Host App Auth |
954
+ | POST | `/api/v1/stripe/accounts` | Initiate Stripe connected account creation/onboarding. | Host App Auth |
955
+ | GET | `/api/v1/stripe/accounts/:id/creation_status` | Check the status of an async account creation job. | Host App Auth |
956
+ | DELETE | `/api/v1/stripe/accounts/:id` | Disconnect a Stripe connected account. | Host App Auth |
957
+ | GET | `/api/v1/stripe_platform/account` | Get details of the configured Stripe Platform account. | Host App Auth |
958
+ | POST | `/api/v1/stripe_platform/account` | Create/Connect the Stripe Platform account. | Host App Auth |
959
+ | PUT | `/api/v1/stripe_platform/account` | Update Stripe Platform account settings. | Host App Auth |
960
+ | DELETE | `/api/v1/stripe_platform/account` | Disconnect the Stripe Platform account. | Host App Auth |
961
+ | GET | `/api/v1/stripe_platform/payouts` | List payouts made from the Stripe Platform account. | Host App Auth |
962
+ | GET | `/api/v1/razorpay/holdables/:holdable_id/account` | Get Razorpay account details for a specific holdable (Deprecated style). | Host App Auth |
963
+ | GET | `/api/v1/upi/vpas` | List configured UPI VPAs for the organization. | Host App Auth |
964
+ | POST | `/api/v1/upi/vpas` | Add a new UPI VPA. | Host App Auth |
965
+ | DELETE | `/api/v1/upi/vpas/:id` | Delete a UPI VPA. | Host App Auth |
966
+ | PUT | `/api/v1/upi/payments/:id` | Confirm/update a manual UPI payment (Admin action). | Host App Auth |
967
+ | GET | `/api/v1/integrations/connect/stripe` | Start Stripe Connect OAuth flow. | Host App Auth |
968
+ | GET | `/api/v1/integrations/connect/razorpay` | Start Razorpay OAuth flow. | Host App Auth |
969
+ | POST | `/api/v1/public/stripe/transactions` | Create a Stripe Payment Intent (Public endpoint). | Open |
970
+ | POST | `/api/v1/public/razorpay/payments` | Create a Razorpay Order (Public endpoint). | Open |
971
+ | POST | `/api/v1/public/upi/payments` | Initiate a manual UPI payment (Public endpoint). | Open |
972
+ | GET | `/api/v1/public/recurring_payments/:payable_id` | Get status of a customer-managed recurring payment. | Open |
973
+ | PUT | `/api/v1/public/recurring_payments/:payable_id` | Allow customer to cancel their recurring payment. | Open |
974
+ | GET | `/api/v1/public/stripe/oauth/authorize` | Stripe OAuth authorization step (Redirect handled by engine). | JWT |
975
+ | GET | `/api/v1/public/stripe/oauth/callback` | Stripe OAuth callback processing endpoint. | JWT |
976
+ | GET | `/api/v1/public/razorpay/oauth/authorize` | Razorpay OAuth authorization step (Redirect handled by engine). | JWT |
977
+ | GET | `/api/v1/public/razorpay/oauth/callback` | Razorpay OAuth callback processing endpoint. | JWT |
978
+ | POST | `/api/v1/public/stripe/webhooks` | Stripe webhook receiver endpoint. | Stripe Sig. |
979
+ | POST | `/api/v1/public/razorpay/webhooks` | Razorpay webhook receiver endpoint. | Razorpay Sig. |
980
+ | POST | `/api/v1/public/stripe_platform/webhooks` | Stripe Platform webhook receiver endpoint. | Stripe Sig. |
981
+
982
+ *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.*
983
+
984
+ ## Incineration Concern
985
+
986
+ 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`).
987
+
988
+ 1. **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.
989
+ ```ruby
990
+ # In your host app's IncinerableConcern
991
+ # TODO: Incinerate Fee based on Entity later
992
+ SKIPPED_MODELS = ["NeetoActivitiesEngine::Activity", "NeetoPaymentsEngine::Fee"]
993
+ ```
994
+ 2. **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`).
995
+ 3. **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.
996
+
997
+ ```ruby
998
+ # In your host app's IncinerableConcern
999
+ def self.associated_models(org_id)
1000
+ # Add logic for NeetoPaymentsEngine::Fee
1001
+ "NeetoPaymentsEngine::Fee": {
1002
+ # Example: Find fees associated with Products belonging to the org
1003
+ joins: "JOIN products ON products.id = neeto_payments_engine_fees.feeable_id AND neeto_payments_engine_fees.feeable_type = 'Product'",
1004
+ where: ["products.organization_id = ?", org_id]
1005
+ # Adjust join and where clause based on your actual feeable models
1006
+ },
1007
+ # Add logic for other NeetoPaymentsEngine models if needed,
1008
+ # though many are handled via cascading deletes or direct Org association.
1009
+ # Refer to the engine's IncinerableConcern for models it handles itself.
1010
+ # "NeetoPaymentsEngine::Payment": { ... }
1011
+
1012
+ # rest of the code
1013
+ end
1014
+ ```
1015
+ 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.
279
1016
 
280
- - This function is used to generate the stripe transaction link from payment identifier.
1017
+ ## Deprecated Patterns
281
1018
 
282
- **Parameters**
283
- 1. `identifier`: The payment identifier.
284
- 2. `isLive`: Specifying whether or not the environment is live. It defaults to `true`.
1019
+ Please be aware of the following deprecations and use the recommended alternatives:
1020
+
1021
+ 1. **Configuration `holdable_class`:**
1022
+ * **Deprecated:** The singular `NeetoPaymentsEngine.holdable_class = "ClassName"` configuration.
1023
+ * **Recommended:** Use the hash-based `NeetoPaymentsEngine.providers_holdable_class = { provider: "ClassName", ... }` to configure holdable models per provider. This provides more flexibility.
1024
+
1025
+ 2. **Holdable-Scoped APIs:**
1026
+ * **Deprecated:** API endpoints previously located under `/api/v1/<provider>/holdables/:holdable_id/...` are deprecated.
1027
+ * **Recommended:** Use the newer API structure:
1028
+ * For general integration status: `/api/v1/integrations` (passing the `providers` param).
1029
+ * For provider-specific account management: `/api/v1/<provider>/accounts/...`.
1030
+ * For provider-specific resources like payouts: `/api/v1/<provider>/payouts/...`.
1031
+
1032
+ 3. **`Charge` Model/Concept:**
1033
+ * **Deprecated:** Older versions might have referred to payment processing entities as `Charge`.
1034
+ * **Recommended:** The primary entity for payment processing is now `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.
1035
+
1036
+ ## Development Environment Setup
1037
+
1038
+ To run the engine locally integrated with a host application, ensure the following processes are running:
1039
+
1040
+ 1. **Rails Server:** Starts the main web application.
1041
+ ```bash
1042
+ bundle exec rails s
1043
+ ```
1044
+ 2. **Vite Dev Server:** Compiles and serves frontend assets.
1045
+ ```bash
1046
+ # If using Vite
1047
+ yarn dev
1048
+ ```
1049
+ 3. **Sidekiq Worker:** Processes background jobs (like webhook handling).
1050
+ ```bash
1051
+ bundle exec sidekiq -e development -C config/sidekiq.yml
1052
+ ```
1053
+ 4. **Tunneling Service (for Webhooks/OAuth):** Exposes your local server to the internet. Use the `connect` subdomain for OAuth callbacks.
1054
+ ```bash
1055
+ # Using tunnelto (replace <YOUR_APP_PORT> with your Rails server port, e.g., 3000)
1056
+ tunnelto --subdomain connect --port <YOUR_APP_PORT>
1057
+ ```
1058
+ *Note: The `connect` subdomain is conventionally used across Neeto applications for receiving callbacks from third-party services like Stripe/Razorpay OAuth.*
285
1059
 
286
- **Usage**
287
- ```jsx
288
- const transactionLink = buildStripeTransactionLink(
289
- "pi_3MqWX92fKivMPpZ11EPmOBBR",
290
- false
291
- );
1060
+ The `Procfile.dev.PAYMENTS` might look like this:
1061
+ ```yaml
1062
+ web: bundle exec rails s
1063
+ vite: yarn dev
1064
+ worker: bundle exec sidekiq -e development -C config/sidekiq.yml
1065
+ # Tunnel needs to be run in a separate terminal
292
1066
  ```
293
1067
 
294
- # Instructions for Publishing
295
- Consult the [building and releasing packages](https://neeto-engineering.neetokb.com/articles/building-and-releasing-packages) guide for details on how to publish.
1068
+ ## Helper methods
1069
+ - `currency_format_with_symbol`
1070
+ 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.
1071
+
1072
+ Usage in host application:
1073
+ - In general modules (e.g., Services or View helpers)
1074
+ ```ruby
1075
+ module ApplicationHelper
1076
+ include ::NeetoPaymentsEngine::CurrencyFormatHelper
1077
+
1078
+ def formatted_amount
1079
+ currency_format_with_symbol(payment&.amount, payment&.currency)
1080
+ end
1081
+ end
1082
+ ```
1083
+
1084
+ - In mailers
1085
+
1086
+ 1. Include the helper in your mailer:
1087
+ ```ruby
1088
+ class Packages::PurchaseConfirmedMailer < ApplicationMailer
1089
+ helper ::NeetoPaymentsEngine::CurrencyFormatHelper
1090
+
1091
+ def customer_email
1092
+ end
1093
+ end
1094
+ ```
1095
+ 2. Use the method in the mailer view:
1096
+ ```ruby
1097
+ <%= "#{currency_format_with_symbol(@purchase.payment&.amount,@purchase.payment&.currency)}" %>
1098
+ ```
1099
+
1100
+ ## Testing & Debugging
1101
+
1102
+ - **Dummy App:** Use the `test/dummy` app within the engine's repository for isolated testing.
1103
+ - **Test Helpers:** Utilize `NeetoPaymentsEngine::TestHelpers` (includes `HttpRequestHelpers`) for stubbing API calls to Stripe/Razorpay (`test/helpers/http_request_helpers/`).
1104
+ - **Stripe Test Cards:**
1105
+ * Valid Card: `4242 4242 4242 4242`
1106
+ * Declined Card: `4000 0000 0000 0002`
1107
+ * Expiry Date: Any future date (e.g., `12/30`)
1108
+ * CVC: Any 3 digits (e.g., `123`)
1109
+ * ZIP Code: Any valid ZIP (e.g., `12345`)
1110
+ * [More Stripe Test Cards](https://docs.stripe.com/testing)
1111
+ - **Razorpay Test Cards/UPI:** Refer to [Razorpay Testing Docs](https://razorpay.com/docs/payments/payments/test-card-details/).
1112
+ - **Logging:** Check Rails logs (`log/development.log`) for detailed output from the engine's `LogActivityHelper`.
1113
+ - **Provider Dashboards:** Use the Stripe and Razorpay **Test Mode** dashboards to view API logs, payment details, webhook attempts, and specific error messages.
1114
+ - **JWT Debugging:** Use tools like [jwt.io](https://jwt.io) to decode JWTs generated during OAuth flows. Paste the token and the **public key** (`CONNECT_PUBLIC_KEY`) to verify the signature and inspect the payload (check `exp` claim for expiry).
1115
+
1116
+ ## Gotchas & Tips
1117
+
1118
+ - **`providers_holdable_class` is Mandatory:** Forgetting to configure this in the initializer will lead to errors when the engine tries to find associated accounts.
1119
+ - **Specify `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).
1120
+ - **Stripe Connect Signup:** You *must* complete the Stripe Connect signup process in your Stripe account, even for platform-only usage.
1121
+ - **Webhook Secrets in Dev:** Manually created webhook endpoint secrets from Stripe/Razorpay dashboards are the source of truth for development, not necessarily what rake tasks might print.
1122
+ - **JWT Key Security:** Treat your JWT private key with the same security as your API secret keys.
1123
+ - **Migration Order:** Always run `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.
1124
+
1125
+ ## Publishing
1126
+
1127
+ 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](https://neeto-engineering.neetokb.com/articles/building-and-releasing-packages).