@pandait.tech/payment-nuvei 0.2.1 → 0.4.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/README.md CHANGED
@@ -141,9 +141,44 @@ A production-validated implementation of both (`threeDSCallback` + `nuveiProxy`,
141
141
 
142
142
  ## UI components — usage
143
143
 
144
- All `/ui` exports are client components (`"use client"`) and expect a Tailwind setup. The components consume CSS variables for theming so the same components work for every white-label client with their own branding.
144
+ All `/ui` exports are client components (`"use client"`). They consume CSS variables for theming so the same components work for every white-label client with their own branding.
145
145
 
146
- ### 1. Define CSS variables in your global stylesheet
146
+ ### 1. Load the styles
147
+
148
+ You have two options. **Option A is recommended** — it works without Tailwind, on any Tailwind version, and doesn't require configuring your build to scan this package.
149
+
150
+ **Option A — import the prebuilt stylesheet (recommended, standalone):**
151
+
152
+ ```ts
153
+ // once, e.g. in app/layout.tsx or your global entry
154
+ import "@pandait.tech/payment-nuvei/ui/styles.css";
155
+ ```
156
+
157
+ This ships the exact utility classes the components use (no Tailwind Preflight/reset — it won't touch your page's base styles). You still define the brand tokens (step 2) for theming.
158
+
159
+ **Option B — let your own Tailwind build the classes (Tailwind v4 users):**
160
+
161
+ Skip the CSS import and instead point Tailwind at the package so it generates the classes itself. Tailwind **v4** (`@source` in your CSS):
162
+
163
+ ```css
164
+ @import "tailwindcss";
165
+ @source "../node_modules/@pandait.tech/payment-nuvei/dist/**/*.{js,cjs}";
166
+ ```
167
+
168
+ Tailwind **v3** (`content` in `tailwind.config`):
169
+
170
+ ```js
171
+ export default {
172
+ content: [
173
+ "./app/**/*.{ts,tsx}",
174
+ "./node_modules/@pandait.tech/payment-nuvei/dist/**/*.{js,cjs}",
175
+ ],
176
+ };
177
+ ```
178
+
179
+ > If components render unstyled, you forgot Option A's import or Option B's `@source`/`content` entry. Option A removes that failure mode entirely.
180
+
181
+ ### 2. Define CSS variables in your global stylesheet
147
182
 
148
183
  Add this to your `app/globals.css` (Next.js) or equivalent, in `:root`:
149
184
 
@@ -177,22 +212,6 @@ Add this to your `app/globals.css` (Next.js) or equivalent, in `:root`:
177
212
  }
178
213
  ```
179
214
 
180
- ### 2. Add the package to Tailwind's content config
181
-
182
- Tailwind needs to detect the arbitrary-value utility classes (e.g. `bg-[var(--color-primary)]`) inside the package's compiled output:
183
-
184
- ```js
185
- // tailwind.config.ts
186
- export default {
187
- content: [
188
- "./app/**/*.{ts,tsx}",
189
- "./src/**/*.{ts,tsx}",
190
- "./node_modules/@pandait.tech/payment-nuvei/dist/**/*.{js,cjs}",
191
- ],
192
- // ...
193
- };
194
- ```
195
-
196
215
  ### 3. Use the components
197
216
 
198
217
  ```tsx
@@ -235,6 +254,39 @@ The components POST/GET to fixed paths because the package's handler factories a
235
254
 
236
255
  The consumer must wire those routes — each `route.ts` is one or two lines re-exporting the factory result.
237
256
 
257
+ `CardVisual` **degrades gracefully** if no BIN endpoint is served: it just renders without bank-specific colors. The endpoint is optional and overridable via the `binDatabaseEndpoint` prop (default `/api/bins`).
258
+
259
+ ## Firestore data contract
260
+
261
+ The handlers read/write a single `orders` collection (plus `promotions` for coupon usage). Your Firestore schema must match these names, statuses, and fields:
262
+
263
+ **`orders/{orderId}`** — the document the charge/webhook/3DS/refund handlers operate on. `dev_reference` sent to Nuvei **is** the `orderId`.
264
+
265
+ | Field | Written by | Notes |
266
+ |---|---|---|
267
+ | `status` | all | lifecycle: `pending` → `3ds-pending` → `paid` \| `cancelled` \| `refunded`. Final statuses (`paid`/`delivered`/`shipped`) are never downgraded by the webhook. |
268
+ | `userId` | consumer (at create) | must match the `__session` cookie uid; handlers authorize against it. |
269
+ | `userEmail` | consumer | used for confirmation emails. |
270
+ | `items`, `subtotal`, `vat`, `total`, `discount` | consumer | echoed into emails. |
271
+ | `promotionId`, `couponCode` | consumer | if set, webhook/charge increment `promotions/{id}.currentUses` and write a `promotions/{id}/usages/{auto}` doc atomically. |
272
+ | `paymentTransactionId`, `authorizationCode`, `nuveiTransactionId` | charge/webhook | Nuvei refs; `paymentTransactionId` is required for refunds. |
273
+ | `threeDSCres`, `threeDSTransStatus` | 3DS callback / webhook | set during the challenge; cleared on completion. |
274
+ | `verifyCalledAt` | webhook | guards the BY_CRES verify path (single-fire). |
275
+ | `emailPending`, `missingAuthCodeFlagged` | charge/webhook | pending-email reconciliation when auth_code arrives late. |
276
+ | `deleteCardAfterPayment`, `paymentToken` | charge | "don't save card" opt-out, honored on 3DS/webhook completion. |
277
+ | `shippingAddress.fullName` | consumer | used as the email customer name. |
278
+ | `refundedAt` | refund | set on successful refund. |
279
+
280
+ **`promotions/{promotionId}`**: `currentUses` (incremented). **`promotions/{promotionId}/usages/{auto}`**: `{ userId, orderId, discountApplied, usedAt }`.
281
+
282
+ > Business-specific side effects (course enrollment, fulfillment, paymentLink usage) live in your `onPaymentSucceeded` callback — the package never assumes those collections. **`onPaymentSucceeded` fires once, on the transition into `paid`** (idempotent across Nuvei webhook retries), but you should still make it internally idempotent as defense-in-depth.
283
+
284
+ ## API surface & stability
285
+
286
+ Public, supported entry points: the package root (SDK), `./handlers`, `./adapters`, `./payment-links`, `./ui`, and `./ui/styles.css`. Everything else (e.g. internal `http.ts`) is private and may change without notice.
287
+
288
+ Pre-1.0 the package follows semver with the caveat that **minor versions may make small breaking changes to handler `*HandlerDeps` interfaces** as the design settles. From **1.0.0** onward the public API is frozen under semver. See `CHANGELOG.md`.
289
+
238
290
  ## Migration from `pauhenriques-website`
239
291
 
240
292
  See per-file `Source: pauhenriques-website/...` headers throughout the package. The refactor preserves behavior with three intentional fixes vs. the original (documented in commit `4b0cb06` — coupon-reuse in webhook BY_CRES + card-delete on 3DS flows).
@@ -900,7 +900,8 @@ function createWebhookHandler(deps) {
900
900
  logger.log(
901
901
  `Webhook: Order ${orderId} \u2192 ${newStatus} (status_detail: ${transaction.status_detail})`
902
902
  );
903
- if (newStatus === "paid" && deps.onPaymentSucceeded) {
903
+ const transitionedToPaid = newStatus === "paid" && !finalStatuses.includes(currentStatus);
904
+ if (transitionedToPaid && deps.onPaymentSucceeded) {
904
905
  try {
905
906
  await deps.onPaymentSucceeded({ ...orderData, id: orderId });
906
907
  } catch (err) {