@ambushsoftworks/nestjs-payments-graphql 0.6.0-rc.1 → 0.6.0
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/CHANGELOG.md +16 -2
- package/README.md +126 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -56,7 +56,21 @@ prose for buried obligations. The marker was introduced in v0.4.0 and
|
|
|
56
56
|
> they settle. If you report on invoices closing, expect the lag to appear.
|
|
57
57
|
> 6. Services now reject non-integer and negative money. Anything that was
|
|
58
58
|
> silently storing fractional cents will start throwing.
|
|
59
|
-
> 7. **
|
|
59
|
+
> 7. **If you took Checkout payments before 0.3.1, back them up to the
|
|
60
|
+
> PaymentIntent id before upgrading, or refunds for them are silently
|
|
61
|
+
> skipped.** 0.3.1 fixed how new payments are keyed; it did not migrate
|
|
62
|
+
> existing rows. A pre-0.3.1 Checkout payment can be keyed on the Checkout
|
|
63
|
+
> Session id while refund webhooks quote the PaymentIntent id, and the lookup
|
|
64
|
+
> is an exact match with no fallback — so `RefundService` logs an error and
|
|
65
|
+
> skips, the customer's money goes back, and the invoice stays PAID. The
|
|
66
|
+
> README's "Backfilling Checkout payments taken before 0.3.1" has the SQL.
|
|
67
|
+
> Found by the jobsites team while upgrading from 0.2.0; our own answer to
|
|
68
|
+
> their question about this had said 0.3.1 covered it, which was true only
|
|
69
|
+
> for rows written after it.
|
|
70
|
+
> 8. **Subscribe to `charge.dispute.created` and `charge.dispute.closed`** — see
|
|
71
|
+
> the external-configuration section below. Build the list from
|
|
72
|
+
> `STRIPE_WEBHOOK_EVENTS` rather than prose, or set `stripe.verifyOnBoot`.
|
|
73
|
+
> 9. **The package no longer searches the provider for a customer to adopt.**
|
|
60
74
|
> If you lose a `PaymentCustomer` row while the provider customer survives,
|
|
61
75
|
> a new one is created and the client re-saves their card. In exchange,
|
|
62
76
|
> whether two divisions share a wallet is now entirely your repository's
|
|
@@ -252,7 +266,7 @@ prose for buried obligations. The marker was introduced in v0.4.0 and
|
|
|
252
266
|
schema still passes unchanged.
|
|
253
267
|
|
|
254
268
|
### Testing
|
|
255
|
-
- **+
|
|
269
|
+
- **+87 specs, 551 across 24 suites.** New: `payment-method.service.spec.ts`,
|
|
256
270
|
`recurring-invoice-scheduling.spec.ts`, `money-validation.spec.ts`, plus
|
|
257
271
|
saved-card coverage on the Stripe gateway and the catch-up storm, the
|
|
258
272
|
session-cancellation path, the auto-charge convergence race and the money
|
package/README.md
CHANGED
|
@@ -26,6 +26,8 @@ Production-grade payments module for NestJS with GraphQL support. Invoicing (inc
|
|
|
26
26
|
- [Email Notifications](#email-notifications)
|
|
27
27
|
- [Event Listeners](#event-listeners)
|
|
28
28
|
- [Transactions](#transactions)
|
|
29
|
+
- [Entry points](#entry-points)
|
|
30
|
+
- [Testing your integration](#testing-your-integration)
|
|
29
31
|
- [Services](#services)
|
|
30
32
|
- [Manual payments and confirmation](#manual-payments-and-confirmation)
|
|
31
33
|
- [Money and types](#money-and-types)
|
|
@@ -34,6 +36,7 @@ Production-grade payments module for NestJS with GraphQL support. Invoicing (inc
|
|
|
34
36
|
- [Error handling](#error-handling)
|
|
35
37
|
- [Operations](#operations)
|
|
36
38
|
- [Upgrading](#upgrading)
|
|
39
|
+
- [Backfilling Checkout payments taken before 0.3.1](#backfilling-checkout-payments-taken-before-031)
|
|
37
40
|
- [Configuration Reference](#configuration-reference)
|
|
38
41
|
- [Required Options](#required-options)
|
|
39
42
|
- [Optional Repositories](#optional-repositories)
|
|
@@ -239,6 +242,8 @@ The controller is registered at `POST /webhooks/stripe` and marked with `@Paymen
|
|
|
239
242
|
- Have guards check `Reflector.get(PAYMENTS_WEBHOOK, handler)` and bail out early, **or**
|
|
240
243
|
- The controller sets `isPublic` and `skipTenant` metadata via the `@PaymentsWebhook()` decorator; read them with `Reflector` and let the request through. `PAYMENTS_WEBHOOK` is the **metadata key** those are set under — not a path, so comparing it to `request.url` never matches.
|
|
241
244
|
|
|
245
|
+
`PaymentsWebhook` is exported, so you can apply the same three metadata keys to a route of your own if you proxy the webhook rather than exposing the package's controller directly. You do not need it otherwise — the controller already carries it.
|
|
246
|
+
|
|
242
247
|
See [Critical Integration Requirements](#critical-integration-requirements).
|
|
243
248
|
|
|
244
249
|
### 5. Build a resolver
|
|
@@ -248,8 +253,11 @@ import { Resolver, Mutation, Args } from '@nestjs/graphql';
|
|
|
248
253
|
import {
|
|
249
254
|
InvoiceService,
|
|
250
255
|
InvoiceModel,
|
|
251
|
-
CreateInvoiceInput,
|
|
252
256
|
} from '@ambushsoftworks/nestjs-payments-graphql';
|
|
257
|
+
// The @InputType classes live on the /graphql subpath. The plain names on the
|
|
258
|
+
// root are the *service* interfaces — what you pass to the service, which is a
|
|
259
|
+
// different shape (it carries `createdBy`, for one). See Upgrading.
|
|
260
|
+
import { CreateInvoiceInput } from '@ambushsoftworks/nestjs-payments-graphql/graphql';
|
|
253
261
|
|
|
254
262
|
@Resolver()
|
|
255
263
|
export class InvoiceResolver {
|
|
@@ -853,9 +861,17 @@ Utility helpers exported:
|
|
|
853
861
|
import {
|
|
854
862
|
computeNextInvoiceDate,
|
|
855
863
|
computeCycleCount,
|
|
864
|
+
computeNextInvoiceDateAfter,
|
|
856
865
|
} from '@ambushsoftworks/nestjs-payments-graphql';
|
|
857
866
|
```
|
|
858
867
|
|
|
868
|
+
`computeNextInvoiceDateAfter(anchor, unit, value, after)` is the one to use when
|
|
869
|
+
a schedule has fallen behind: it returns the first scheduled date strictly after
|
|
870
|
+
an instant, staying on the anchor's cadence. `computeCycleCount` rounds, which
|
|
871
|
+
is right for an exact cycle boundary and overshoots for an arbitrary one — a Jan
|
|
872
|
+
15 anchor evaluated on May 3 rounds to cycle 4 and returns June 15, stepping
|
|
873
|
+
over a May 15 that has not happened yet. Added in v0.6.0.
|
|
874
|
+
|
|
859
875
|
The consumer provides the scheduler (typically `@nestjs/schedule`) that calls `RecurringInvoiceService.processDueRecurringInvoices(now)` on a cron. See [Scheduled jobs](#scheduled-jobs).
|
|
860
876
|
|
|
861
877
|
### Saved Cards
|
|
@@ -944,6 +960,8 @@ const repositories = createPrismaPaymentRepositories(prisma, Prisma, {
|
|
|
944
960
|
|
|
945
961
|
Declare `@@unique([clientDetailsId, provider])` on `PaymentCustomer` to match, and read the hazard in the scope-key section before you do.
|
|
946
962
|
|
|
963
|
+
**Keep the `divisionId` column** even though you no longer key on it. The bundled adapter still writes it on create and `assertPaymentsSchema` still requires it, so dropping it because "we do not scope by division" fails at boot — or on the first card save if you skip the schema assertion. It stays useful as a record of which division first created the customer.
|
|
964
|
+
|
|
947
965
|
A custom gateway must implement `adoptSetupPaymentMethod` to support saved cards. It is optional on `RecurringPaymentGateway` so v0.2.x gateways still compile; without it the webhook logs a warning and the card stays unusable for auto-charge.
|
|
948
966
|
|
|
949
967
|
### E-Transfer
|
|
@@ -1033,6 +1051,63 @@ await transactionManager.runInTransaction(async (repos) => {
|
|
|
1033
1051
|
|
|
1034
1052
|
---
|
|
1035
1053
|
|
|
1054
|
+
## Entry points
|
|
1055
|
+
|
|
1056
|
+
The package publishes four. The root carries everything you need to call a
|
|
1057
|
+
service; the other three exist so a name means one thing.
|
|
1058
|
+
|
|
1059
|
+
| Import from | Carries |
|
|
1060
|
+
|---|---|
|
|
1061
|
+
| `@ambushsoftworks/nestjs-payments-graphql` | Services, service input interfaces, entity types, exceptions, `@ObjectType` models, DI tokens, `StripeGateway`, utilities |
|
|
1062
|
+
| `…/graphql` | The `@InputType` classes for your resolvers, and the `IsMetadata` validator |
|
|
1063
|
+
| `…/prisma` | The bundled Prisma adapter — repositories, `createPrismaPaymentRepositories`, `assertPaymentsSchema` |
|
|
1064
|
+
| `…/testing` | In-memory repositories for testing your integration without a database |
|
|
1065
|
+
|
|
1066
|
+
**Why `/graphql` is separate.** Six names collided: `CreateRefundInput`,
|
|
1067
|
+
`RecordManualPaymentInput`, `CreateInvoiceInput`, `CreatePaymentPlanInput`,
|
|
1068
|
+
`CreateRecurringInvoiceInput` and `UpdateRecurringTemplateInput` each named both
|
|
1069
|
+
a GraphQL `@InputType` and the service interface of the same purpose — and they
|
|
1070
|
+
are different shapes. The DTO has no `createdBy`; the service requires it. On
|
|
1071
|
+
the root the plain name is now the **service** interface, because that is what a
|
|
1072
|
+
resolver needs to call through. Moved in v0.6.0; see [Upgrading](#upgrading).
|
|
1073
|
+
|
|
1074
|
+
### Testing your integration
|
|
1075
|
+
|
|
1076
|
+
`/testing` ships the in-memory repositories the package's own suites use, so you
|
|
1077
|
+
can test resolvers and services without a database:
|
|
1078
|
+
|
|
1079
|
+
```typescript
|
|
1080
|
+
import { createInMemoryRepositories } from '@ambushsoftworks/nestjs-payments-graphql/testing';
|
|
1081
|
+
|
|
1082
|
+
const repos = createInMemoryRepositories(myInvoiceRepository, {
|
|
1083
|
+
// Mirror your own schema. 'division' (the default) matches the reference
|
|
1084
|
+
// schema's @@unique([divisionId, clientDetailsId, provider]); 'client'
|
|
1085
|
+
// matches @@unique([clientDetailsId, provider]).
|
|
1086
|
+
customerKey: 'division',
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
const moduleRef = await Test.createTestingModule({
|
|
1090
|
+
imports: [PaymentsModule.forRootAsync({ useFactory: () => ({ ...repos }) })],
|
|
1091
|
+
}).compile();
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
The keys match `PaymentsModuleOptions`, so the result spreads straight into your
|
|
1095
|
+
factory. `repos.store` is the raw data if you want to assert on stored rows, and
|
|
1096
|
+
`repos.openTransactions()` lets you assert no transaction is open at a gateway
|
|
1097
|
+
call.
|
|
1098
|
+
|
|
1099
|
+
**They enforce the unique constraints the reference schema declares**, because
|
|
1100
|
+
the package's recovery paths depend on them — `createPendingPayment`,
|
|
1101
|
+
`recordAutoChargePayment`, `createOrLinkCustomer` and the refund converger all
|
|
1102
|
+
catch `UniqueConstraintViolationException` and adopt the existing row. A fake
|
|
1103
|
+
without those constraints is more forgiving than any real database, and a test
|
|
1104
|
+
against it proves less than it appears to. That is also why `customerKey` exists:
|
|
1105
|
+
set it wrong and the double stops matching your schema.
|
|
1106
|
+
|
|
1107
|
+
**They do not model row locking.** `findByIdForUpdate` is a plain read. For
|
|
1108
|
+
anything that turns on locking — concurrent claims, `SKIP LOCKED`, the
|
|
1109
|
+
installment race — run against PostgreSQL.
|
|
1110
|
+
|
|
1036
1111
|
## Services
|
|
1037
1112
|
|
|
1038
1113
|
Every service is exported from the package root and injectable anywhere once the module is registered (it is `global: true`). Listed here so you can see the whole surface in one place; the feature sections above cover behaviour.
|
|
@@ -1041,7 +1116,7 @@ Every service is exported from the package root and injectable anywhere once the
|
|
|
1041
1116
|
|---|---|---|
|
|
1042
1117
|
| `InvoiceService` | Invoice lifecycle: create, send, line items, tax, void, overdue | `ensureSent` is the one to call before opening a payment session |
|
|
1043
1118
|
| `InvoiceNumberService` | Invoice number generation | Numbers are **global**, not per-division — see [Invoicing](#invoicing) |
|
|
1044
|
-
| `PaymentService` | Recording payments, webhook handling, reconciliation | `recordManualPayment`, `confirmPayment`, `rejectPayment` for the offline flow |
|
|
1119
|
+
| `PaymentService` | Recording payments, webhook handling, reconciliation | `recordManualPayment`, `confirmPayment`, `rejectPayment` for the offline flow; `cancelPendingPaymentsForInvoice` cancels live provider sessions and is called for you when an invoice is voided |
|
|
1045
1120
|
| `RefundService` | Full and partial refunds, refund webhooks | `createRefund`, `refundInvoice`, `findRefundsByPaymentId` |
|
|
1046
1121
|
| `PaymentConfigService` | Per-division `PaymentConfig` and active providers | `getConfig` returns `null` for an unconfigured division; `getDefaultProvider` throws |
|
|
1047
1122
|
| `PaymentPlanService` | Instalment plans and their invoices | `createPaymentPlan`, `invoiceInstallment`, `waiveInstallment`, `cancelPaymentPlan` |
|
|
@@ -1051,7 +1126,7 @@ Every service is exported from the package root and injectable anywhere once the
|
|
|
1051
1126
|
| `ETransferService` | E-transfer code and answer generation, instruction text | `getInstructions` is pure |
|
|
1052
1127
|
| `PaymentEmailService` | All outbound email | **Never throws**, and silently does nothing when no sender is configured |
|
|
1053
1128
|
| `PaymentJobsService` | The three scheduled jobs | See [Scheduled jobs](#scheduled-jobs) |
|
|
1054
|
-
| `GatewayRegistryService` | Gateway lookup and capability narrowing | `getRecurring`, `getInlineCapable`, `getPaymentMethodCapable` |
|
|
1129
|
+
| `GatewayRegistryService` | Gateway lookup and capability narrowing | `getRecurring`, `getInlineCapable` → `InlineCapableGateway`, `getPaymentMethodCapable` → `PaymentMethodCapableGateway`. Each returns the gateway with that capability's optional members narrowed to non-optional, or throws. |
|
|
1055
1130
|
|
|
1056
1131
|
### Manual payments and confirmation
|
|
1057
1132
|
|
|
@@ -1073,6 +1148,15 @@ Without that step the invoice never closes. If you record e-transfers and see in
|
|
|
1073
1148
|
|
|
1074
1149
|
**Every monetary value is an integer number of cents.** Since v0.6.0 the services reject anything else rather than storing it — a negative or fractional amount throws `InvalidInvoiceStateException`.
|
|
1075
1150
|
|
|
1151
|
+
Two helpers are exported for the same invariant. `assertMoneyAmount(amount, field)`
|
|
1152
|
+
throws `InvalidInvoiceStateException` unless the value is a whole, non-negative
|
|
1153
|
+
number of cents — the services call it at every money entry point, and you can
|
|
1154
|
+
use it at your own. `deriveInvoiceAmounts(total, ledgerAmountPaid)` returns the
|
|
1155
|
+
`{ amountPaid, amountDue, overpaidBy, clampedFromNegative }` an invoice should
|
|
1156
|
+
store: `amountPaid` records reality and never goes negative, `amountDue` is
|
|
1157
|
+
always derived from it, and an overpayment is reported rather than absorbed.
|
|
1158
|
+
Both added in v0.6.0.
|
|
1159
|
+
|
|
1076
1160
|
**Tax rates are `DecimalLike`, not `number`.** `Invoice.taxRate` and `InvoiceTaxComponent.rate` expose `.toNumber()` and `.toString()`, so a Prisma `Decimal` satisfies them directly. `invoice.taxRate * subtotal` gives `NaN`; use `invoice.taxRate.toNumber()`.
|
|
1077
1161
|
|
|
1078
1162
|
## Tenancy and scoping
|
|
@@ -1159,10 +1243,43 @@ The CHANGELOG carries the detail; this is the map. Every version's own section h
|
|
|
1159
1243
|
|
|
1160
1244
|
| From | Read | Out-of-repo work |
|
|
1161
1245
|
|---|---|---|
|
|
1162
|
-
| 0.2.x | 0.2.1, 0.3.0, 0.3.1, 0.4.0, 0.5.0, 0.6.0 | Subscribe to `payment_intent.succeeded
|
|
1163
|
-
| 0.3.x | 0.3.1, 0.4.0, 0.5.0, 0.6.0 |
|
|
1164
|
-
| 0.4.x | 0.4.0, 0.5.0, 0.6.0 | Widen tax-rate columns to `Decimal(6,5)`; add `@@unique([paymentId, providerRefundId])` to `Refund` |
|
|
1165
|
-
| 0.5.x | 0.6.0 | Add `PaymentCustomer.defaultPaymentMethodId` if you want saved cards |
|
|
1246
|
+
| 0.2.x | 0.2.1, 0.3.0, 0.3.1, 0.4.0, 0.5.0, 0.6.0 | Subscribe to `payment_intent.succeeded`, `refund.created`, `refund.updated`, `charge.dispute.created`, `charge.dispute.closed`; drop `charge.refunded`. **Backfill existing Checkout payments — see below.** |
|
|
1247
|
+
| 0.3.x | 0.3.1, 0.4.0, 0.5.0, 0.6.0 | Subscribe to `refund.created`, `refund.updated`, `charge.dispute.created`, `charge.dispute.closed`; drop `charge.refunded`. **Backfill if you took Checkout payments before 0.3.1.** |
|
|
1248
|
+
| 0.4.x | 0.4.0, 0.5.0, 0.6.0 | Subscribe to `charge.dispute.created`, `charge.dispute.closed`. Widen tax-rate columns to `Decimal(6,5)`; add `@@unique([paymentId, providerRefundId])` to `Refund` |
|
|
1249
|
+
| 0.5.x | 0.6.0 | Subscribe to `charge.dispute.created`, `charge.dispute.closed`. Add `PaymentCustomer.defaultPaymentMethodId` if you want saved cards |
|
|
1250
|
+
|
|
1251
|
+
**Do not transcribe that event list by hand.** `STRIPE_WEBHOOK_EVENTS` in `gateways/stripe/types` is the canonical set for the version you are on; build your preflight from it, or set `stripe.verifyOnBoot` and let the verifier tell you what is missing. The jobsites team followed the prose above when it was incomplete and the verifier caught the gap in seconds — which is the argument for not trusting prose here.
|
|
1252
|
+
|
|
1253
|
+
**Swap the refund events add-then-remove, not all at once.** 0.2.0 ignores `refund.created`/`refund.updated` and 0.6.0 ignores `charge.refunded`, so subscribing to all three across the deploy leaves no window where a refund arrives with nothing listening. Remove `charge.refunded` once the new version is live. (Discovered by the jobsites team doing exactly this.)
|
|
1254
|
+
|
|
1255
|
+
### Backfilling Checkout payments taken before 0.3.1
|
|
1256
|
+
|
|
1257
|
+
**If you took Checkout payments before 0.3.1, refunds for them will be silently skipped until you backfill.** This is the one upgrade step that loses money if you miss it.
|
|
1258
|
+
|
|
1259
|
+
Before 0.3.1 a Checkout payment's row could be keyed on the Checkout **Session** id (`cs_…`). Refund webhooks quote the **PaymentIntent** id (`pi_…`), and `RefundService` looks the payment up by an exact match with no fallback — so it logs an error, skips, and the customer's money goes back while the invoice stays `PAID`. 0.3.1 fixed the keying **for rows written from then on**; it does not migrate what you already have.
|
|
1260
|
+
|
|
1261
|
+
Find them:
|
|
1262
|
+
|
|
1263
|
+
```sql
|
|
1264
|
+
SELECT COUNT(*) FROM "Payment"
|
|
1265
|
+
WHERE provider = 'stripe'
|
|
1266
|
+
AND "providerPaymentId" LIKE 'cs_%'
|
|
1267
|
+
AND "providerData"->>'paymentIntentId' IS NOT NULL;
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
Backfill them:
|
|
1271
|
+
|
|
1272
|
+
```sql
|
|
1273
|
+
UPDATE "Payment"
|
|
1274
|
+
SET "providerPaymentId" = "providerData"->>'paymentIntentId'
|
|
1275
|
+
WHERE provider = 'stripe'
|
|
1276
|
+
AND "providerPaymentId" LIKE 'cs_%'
|
|
1277
|
+
AND "providerData"->>'paymentIntentId' IS NOT NULL;
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
Run it inside a transaction and check the count first. `Payment` carries `@@unique([provider, providerPaymentId])`, so the update fails rather than corrupting anything if a row already holds the target id — which happens when both success events were processed for one payment. Resolve those individually; the 0.3.1 notes describe how the package retires such a duplicate.
|
|
1281
|
+
|
|
1282
|
+
A row with a `cs_…` id and no `providerData.paymentIntentId` is a zero-amount session with no PaymentIntent at all. Leave it: there is nothing to refund.
|
|
1166
1283
|
|
|
1167
1284
|
Two database constraints are load-bearing wherever you are coming from, because the package's recovery paths depend on them: `Payment` needs `@@unique([provider, providerPaymentId])` and `Refund` needs `@@unique([paymentId, providerRefundId])`. `PaymentCustomer` needs **a** unique constraint for the same reason — `createOrLinkCustomer` adopts the winner of a concurrent create rather than failing, and without one there is no winner to adopt. Which columns is your choice: `[divisionId, clientDetailsId, provider]` for the reference schema's per-division customers, or `[clientDetailsId, provider]` for one wallet per client. See [Provider customers and the scope key](#provider-customers-and-the-scope-key).
|
|
1168
1285
|
|
|
@@ -1284,6 +1401,8 @@ import {
|
|
|
1284
1401
|
|
|
1285
1402
|
The package registers these names on module import. Consumers **must not** redefine or re-register them, or GraphQL will throw `"type already registered"`.
|
|
1286
1403
|
|
|
1404
|
+
The three `Paginated*` object types are built by the exported `Paginated(ItemModel)` mixin, which returns an `@ObjectType` with `items` and `totalCount`. Call it to build your own paginated wrapper over a package model; each call must produce a distinctly-named type, or GraphQL rejects the duplicate.
|
|
1405
|
+
|
|
1287
1406
|
**Object types:** `Invoice`, `InvoiceLineItem`, `InvoiceTaxComponent`, `InvoiceClient`, `Payment`, `Refund`, `PaymentPlan`, `PaymentPlanInstallment`, `PaymentConfig`, `PaymentProvider`, `PaymentMethodSetup`, `ETransferInstructions`, `OnlinePaymentSession`, `RecurringInvoice`, `RecurringInvoiceLineItem`, `RecurringInvoiceTaxComponent`, `PaginatedInvoices`, `PaginatedPaymentPlans`, `PaginatedRecurringInvoices`, `InlinePaymentIntent`, `StripePublicConfig`, `SavedPaymentMethod`
|
|
1288
1407
|
|
|
1289
1408
|
**Enums:** `InvoiceStatus`, `PaymentMethod`, `PaymentStatus`, `RefundStatus`, `PaymentPlanStatus`, `InstallmentStatus`, `RecurringIntervalUnit`, `RecurringInvoiceStatus`, `PaymentIntentStatus`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ambushsoftworks/nestjs-payments-graphql",
|
|
3
|
-
"version": "0.6.0
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "NestJS payments module with GraphQL support — invoicing, payment processing, recurring billing, and email notifications",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|