@pandait.tech/payment-nuvei 0.2.0 → 0.3.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 +37 -18
- package/dist/handlers/index.cjs +84 -26
- package/dist/handlers/index.cjs.map +1 -1
- package/dist/handlers/index.d.cts +5 -4
- package/dist/handlers/index.d.ts +5 -4
- package/dist/handlers/index.js +84 -26
- package/dist/handlers/index.js.map +1 -1
- package/dist/ui/styles.css +2 -0
- package/package.json +6 -2
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"`)
|
|
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.
|
|
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
|
package/dist/handlers/index.cjs
CHANGED
|
@@ -922,7 +922,43 @@ function createWebhookHandler(deps) {
|
|
|
922
922
|
}
|
|
923
923
|
|
|
924
924
|
// src/handlers/3ds-callback.ts
|
|
925
|
+
function pickCres(src) {
|
|
926
|
+
for (const key of Object.keys(src)) {
|
|
927
|
+
const k = key.toLowerCase();
|
|
928
|
+
if (k === "cres" || k === "cres_value" || k === "cresvalue" || k === "value" || k === "param.value" || k === "paramvalue") {
|
|
929
|
+
const v = src[key];
|
|
930
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return src.cres || src.CRes || src.CRES || src.value || "";
|
|
934
|
+
}
|
|
935
|
+
function transStatusFromCres(cres, logger) {
|
|
936
|
+
try {
|
|
937
|
+
const base64 = cres.replace(/-/g, "+").replace(/_/g, "/");
|
|
938
|
+
const pad = base64.length % 4;
|
|
939
|
+
const padded = pad ? base64 + "=".repeat(4 - pad) : base64;
|
|
940
|
+
const decoded = JSON.parse(
|
|
941
|
+
Buffer.from(padded, "base64").toString("utf-8")
|
|
942
|
+
);
|
|
943
|
+
if (decoded && typeof decoded.transStatus === "string") {
|
|
944
|
+
return decoded.transStatus;
|
|
945
|
+
}
|
|
946
|
+
} catch (e) {
|
|
947
|
+
logger.warn(
|
|
948
|
+
"[3ds-callback] Could not decode CRES payload:",
|
|
949
|
+
e instanceof Error ? e.message : e
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
function paramsToObject(params) {
|
|
955
|
+
const obj = {};
|
|
956
|
+
for (const [k, v] of params.entries()) obj[k] = v;
|
|
957
|
+
return obj;
|
|
958
|
+
}
|
|
925
959
|
function buildCompletionPage(orderId, transStatus) {
|
|
960
|
+
const safeOrderId = String(orderId).replace(/[^a-zA-Z0-9_-]/g, "");
|
|
961
|
+
const safeTransStatus = String(transStatus).replace(/[^a-zA-Z0-9]/g, "");
|
|
926
962
|
const html = `<!DOCTYPE html>
|
|
927
963
|
<html><head><meta charset="utf-8"><title>3DS Verification</title></head>
|
|
928
964
|
<body>
|
|
@@ -930,8 +966,8 @@ function buildCompletionPage(orderId, transStatus) {
|
|
|
930
966
|
(function() {
|
|
931
967
|
var message = {
|
|
932
968
|
type: "3DS_COMPLETE",
|
|
933
|
-
orderId: "${
|
|
934
|
-
transStatus: "${
|
|
969
|
+
orderId: "${safeOrderId}",
|
|
970
|
+
transStatus: "${safeTransStatus}"
|
|
935
971
|
};
|
|
936
972
|
try {
|
|
937
973
|
if (window.parent && window.parent !== window) {
|
|
@@ -954,46 +990,68 @@ Verificando autenticación...
|
|
|
954
990
|
function create3dsCallbackHandler(deps) {
|
|
955
991
|
const logger = deps.logger ?? console;
|
|
956
992
|
const { db } = deps.firebase;
|
|
957
|
-
async function
|
|
958
|
-
if (orderId
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
993
|
+
async function persist(orderId, cres, transStatus) {
|
|
994
|
+
if (!orderId) return;
|
|
995
|
+
try {
|
|
996
|
+
const ref = db.collection("orders").doc(orderId);
|
|
997
|
+
const snap = await ref.get();
|
|
998
|
+
const status = snap.data()?.status;
|
|
999
|
+
if (!snap.exists || status !== "3ds-pending") {
|
|
1000
|
+
logger.warn(
|
|
1001
|
+
"[3ds-callback] rejected: order not in 3ds-pending state",
|
|
1002
|
+
{ orderId, status }
|
|
1003
|
+
);
|
|
1004
|
+
return;
|
|
966
1005
|
}
|
|
1006
|
+
const update = { updatedAt: /* @__PURE__ */ new Date() };
|
|
1007
|
+
if (cres) update.threeDSCres = cres;
|
|
1008
|
+
if (transStatus) update.threeDSTransStatus = transStatus;
|
|
1009
|
+
await ref.update(update);
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
logger.error("[3ds-callback] failed to store cres on order:", err);
|
|
967
1012
|
}
|
|
968
1013
|
}
|
|
1014
|
+
function resolveTransStatus(cres, fallback) {
|
|
1015
|
+
if (cres) {
|
|
1016
|
+
const fromCres = transStatusFromCres(cres, logger);
|
|
1017
|
+
if (fromCres) return fromCres;
|
|
1018
|
+
}
|
|
1019
|
+
return fallback;
|
|
1020
|
+
}
|
|
969
1021
|
const POST = async function POST2(request) {
|
|
970
|
-
const
|
|
971
|
-
const orderId = searchParams.get("orderId") || "";
|
|
1022
|
+
const orderId = new URL(request.url).searchParams.get("orderId") || "";
|
|
972
1023
|
let transStatus = "U";
|
|
973
1024
|
let cres = "";
|
|
974
1025
|
try {
|
|
975
1026
|
const contentType = request.headers.get("content-type") || "";
|
|
1027
|
+
const raw = await request.text();
|
|
976
1028
|
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
cres = params.get("cres") || params.get("CRes") || "";
|
|
1029
|
+
const obj = paramsToObject(new URLSearchParams(raw));
|
|
1030
|
+
transStatus = obj.transStatus || obj.TransStatus || "U";
|
|
1031
|
+
cres = pickCres(obj);
|
|
981
1032
|
} else {
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1033
|
+
try {
|
|
1034
|
+
const body = JSON.parse(raw);
|
|
1035
|
+
transStatus = body.transStatus || body.TransStatus || "U";
|
|
1036
|
+
cres = pickCres(body);
|
|
1037
|
+
} catch {
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
if (!cres && raw) {
|
|
1041
|
+
cres = pickCres(paramsToObject(new URLSearchParams(raw)));
|
|
985
1042
|
}
|
|
986
1043
|
} catch {
|
|
987
1044
|
}
|
|
988
|
-
|
|
1045
|
+
transStatus = resolveTransStatus(cres, transStatus);
|
|
1046
|
+
await persist(orderId, cres, transStatus);
|
|
989
1047
|
return buildCompletionPage(orderId, transStatus);
|
|
990
1048
|
};
|
|
991
1049
|
const GET = async function GET2(request) {
|
|
992
|
-
const
|
|
993
|
-
const orderId =
|
|
994
|
-
const
|
|
995
|
-
const
|
|
996
|
-
await
|
|
1050
|
+
const params = new URL(request.url).searchParams;
|
|
1051
|
+
const orderId = params.get("orderId") || "";
|
|
1052
|
+
const cres = params.get("cres") || params.get("CRes") || params.get("value") || "";
|
|
1053
|
+
const transStatus = resolveTransStatus(cres, params.get("transStatus") || "Y");
|
|
1054
|
+
await persist(orderId, cres, transStatus);
|
|
997
1055
|
return buildCompletionPage(orderId, transStatus);
|
|
998
1056
|
};
|
|
999
1057
|
return { POST, GET };
|