@reevit/react 0.6.0 → 0.8.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
@@ -293,6 +293,20 @@ function MpesaPayment() {
293
293
  | Monnify | NG | Card, Bank Transfer, USSD |
294
294
  | M-Pesa | KE, TZ | Mobile Money (STK Push) |
295
295
 
296
+ ## Release Notes
297
+
298
+ ### v0.7.0
299
+
300
+ - Redesigned checkout UI with premium visual polish
301
+ - New typography system: Grato Classic for body, ABC Repro Mono for amounts
302
+ - Layered shadow system for natural depth
303
+ - Replaced emoji icons with inline SVG icons (consistent cross-platform rendering)
304
+ - New loading animation (three-dot pulse), success glow effect, and countdown bar
305
+ - Improved dark mode contrast and surfaces
306
+ - Mobile bottom-sheet pattern with touch-friendly targets (44px+)
307
+ - Smoother animations with cubic-bezier easing curves
308
+ - Removed external Google Fonts dependency (zero network requests for fonts)
309
+
296
310
  ## License
297
311
 
298
312
  MIT © [Reevit](https://reevit.io)
package/dist/index.d.mts CHANGED
@@ -183,6 +183,8 @@ interface PaymentIntent {
183
183
  recommendedPsp: 'paystack' | 'hubtel' | 'flutterwave' | 'monnify' | 'mpesa' | 'stripe';
184
184
  /** Available payment methods for this intent */
185
185
  availableMethods: PaymentMethod[];
186
+ /** Provider transaction reference returned by Reevit */
187
+ providerRefId?: string;
186
188
  /** Reference provided or generated */
187
189
  reference?: string;
188
190
  /** Organization ID (from Reevit backend, required for webhook routing) */
@@ -361,6 +363,7 @@ declare function openHubtelPopup(config: {
361
363
  amount: number;
362
364
  clientReference?: string;
363
365
  callbackUrl?: string;
366
+ apiBaseUrl?: string;
364
367
  customerPhoneNumber?: string;
365
368
  basicAuth?: string;
366
369
  preferredMethod?: PaymentMethod;
@@ -623,6 +626,7 @@ interface PaymentIntentResponse {
623
626
  org_id?: string;
624
627
  connection_id: string;
625
628
  provider: string;
629
+ provider_ref_id?: string;
626
630
  status: string;
627
631
  client_secret: string;
628
632
  psp_public_key?: string;
package/dist/index.d.ts CHANGED
@@ -183,6 +183,8 @@ interface PaymentIntent {
183
183
  recommendedPsp: 'paystack' | 'hubtel' | 'flutterwave' | 'monnify' | 'mpesa' | 'stripe';
184
184
  /** Available payment methods for this intent */
185
185
  availableMethods: PaymentMethod[];
186
+ /** Provider transaction reference returned by Reevit */
187
+ providerRefId?: string;
186
188
  /** Reference provided or generated */
187
189
  reference?: string;
188
190
  /** Organization ID (from Reevit backend, required for webhook routing) */
@@ -361,6 +363,7 @@ declare function openHubtelPopup(config: {
361
363
  amount: number;
362
364
  clientReference?: string;
363
365
  callbackUrl?: string;
366
+ apiBaseUrl?: string;
364
367
  customerPhoneNumber?: string;
365
368
  basicAuth?: string;
366
369
  preferredMethod?: PaymentMethod;
@@ -623,6 +626,7 @@ interface PaymentIntentResponse {
623
626
  org_id?: string;
624
627
  connection_id: string;
625
628
  provider: string;
629
+ provider_ref_id?: string;
626
630
  status: string;
627
631
  client_secret: string;
628
632
  psp_public_key?: string;
package/dist/index.js CHANGED
@@ -515,8 +515,9 @@ function mapToPaymentIntent(response, config) {
515
515
  status: response.status,
516
516
  recommendedPsp: mapProviderToPsp(response.provider),
517
517
  availableMethods: config.paymentMethods || ["card", "mobile_money"],
518
- reference: response.reference || response.id,
519
- // Use backend reference or fallback to ID
518
+ providerRefId: response.provider_ref_id,
519
+ reference: response.reference || response.provider_ref_id || response.id,
520
+ // Use backend reference or fallback to provider ref then ID
520
521
  orgId: response.org_id,
521
522
  connectionId: response.connection_id,
522
523
  provider: response.provider,
@@ -817,30 +818,47 @@ function detectCountryFromCurrency(currency) {
817
818
  };
818
819
  return currencyToCountry[currency.toUpperCase()] || "GH";
819
820
  }
821
+ var MethodIcons = {
822
+ card: () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
823
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "4", width: "22", height: "16", rx: "3" }),
824
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "1", y1: "10", x2: "23", y2: "10" }),
825
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "5", y1: "15", x2: "9", y2: "15" })
826
+ ] }),
827
+ mobile_money: () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
828
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", y: "2", width: "14", height: "20", rx: "3" }),
829
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "18", x2: "12", y2: "18.01", strokeWidth: "2", strokeLinecap: "round" })
830
+ ] }),
831
+ bank_transfer: () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
832
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 21h18" }),
833
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 10h18" }),
834
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 3l9 7H3l9-7z" }),
835
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 10v8" }),
836
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 10v8" }),
837
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 10v8" }),
838
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 10v8" })
839
+ ] }),
840
+ apple_pay: () => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.53 4.09zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" }) }),
841
+ google_pay: () => /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12.24 10.285V14.4h6.806c-.275 1.765-2.056 5.174-6.806 5.174-4.095 0-7.439-3.389-7.439-7.574s3.345-7.574 7.439-7.574c2.33 0 3.891.989 4.785 1.849l3.254-3.138C18.189 1.186 15.479 0 12.24 0c-6.635 0-12 5.365-12 12s5.365 12 12 12c6.926 0 11.52-4.869 11.52-11.726 0-.788-.085-1.39-.189-1.989H12.24z", fill: "currentColor" }) })
842
+ };
820
843
  var methodConfig = {
821
844
  card: {
822
845
  label: "Card",
823
- icon: "\u{1F4B3}",
824
846
  description: "Pay with Visa, Mastercard, or other cards"
825
847
  },
826
848
  mobile_money: {
827
849
  label: "Mobile Money",
828
- icon: "\u{1F4F1}",
829
850
  description: "MTN, Telecel, AirtelTigo Money"
830
851
  },
831
852
  bank_transfer: {
832
853
  label: "Bank Transfer",
833
- icon: "\u{1F3E6}",
834
854
  description: "Pay directly from your bank account"
835
855
  },
836
856
  apple_pay: {
837
857
  label: "Apple Pay",
838
- icon: "\u{1F34E}",
839
858
  description: "Pay with Apple Pay"
840
859
  },
841
860
  google_pay: {
842
861
  label: "Google Pay",
843
- icon: "\u{1F916}",
844
862
  description: "Pay with Google Pay"
845
863
  }
846
864
  };
@@ -890,7 +908,6 @@ function PaymentMethodSelector({
890
908
  ),
891
909
  style: selectedTheme?.backgroundColor ? { backgroundColor: selectedTheme.backgroundColor } : void 0,
892
910
  children: methods.map((method, index) => {
893
- const config = methodConfig[method];
894
911
  const isSelected = selectedMethod === method;
895
912
  const methodLabel = getMethodLabel(method);
896
913
  const methodDescription = getMethodDescription(method);
@@ -921,7 +938,7 @@ function PaymentMethodSelector({
921
938
  className: "reevit-method-option__logo-img"
922
939
  },
923
940
  i
924
- )) }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "reevit-method-option__icon", children: config.icon }) }),
941
+ )) }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "reevit-method-option__icon", children: MethodIcons[method]() }) }),
925
942
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "reevit-method-option__content", children: [
926
943
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "reevit-method-option__label", style: selectedTheme?.textColor ? { color: selectedTheme.textColor } : void 0, children: methodLabel }),
927
944
  !isGrid && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "reevit-method-option__description", style: selectedTheme?.descriptionColor ? { color: selectedTheme.descriptionColor } : void 0, children: methodDescription })
@@ -1321,6 +1338,38 @@ function PaystackBridge({
1321
1338
  /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Connecting to Paystack..." })
1322
1339
  ] }) });
1323
1340
  }
1341
+ var DEFAULT_REEVIT_API_BASE_URL = "https://api.reevit.io";
1342
+ function getHubtelCallbackURL(apiBaseUrl) {
1343
+ return `${apiBaseUrl || DEFAULT_REEVIT_API_BASE_URL}/v1/webhooks/incoming/hubtel`;
1344
+ }
1345
+ function parseHubtelCallbackPayload(input) {
1346
+ if (!input || typeof input !== "object") {
1347
+ return {};
1348
+ }
1349
+ const raw = input;
1350
+ const nested = raw.data;
1351
+ if (typeof nested === "string") {
1352
+ try {
1353
+ const parsed = JSON.parse(nested);
1354
+ if (parsed && typeof parsed === "object") {
1355
+ return { ...raw, ...parsed };
1356
+ }
1357
+ } catch {
1358
+ }
1359
+ } else if (nested && typeof nested === "object") {
1360
+ return { ...raw, ...nested };
1361
+ }
1362
+ return raw;
1363
+ }
1364
+ function readHubtelField(payload, keys) {
1365
+ for (const key of keys) {
1366
+ const value = payload[key];
1367
+ if (typeof value === "string" && value.trim().length > 0) {
1368
+ return value.trim();
1369
+ }
1370
+ }
1371
+ return "";
1372
+ }
1324
1373
  function HubtelBridge({
1325
1374
  paymentId,
1326
1375
  publicKey,
@@ -1397,12 +1446,12 @@ function HubtelBridge({
1397
1446
  purchaseDescription: description,
1398
1447
  customerPhoneNumber: phone || "",
1399
1448
  ...email ? { customerEmail: email } : {},
1400
- clientReference: reference || `hubtel_${Date.now()}`,
1449
+ clientReference: reference || paymentId || `hubtel_${Date.now()}`,
1401
1450
  ...methodPreference ? { paymentMethod: methodPreference } : {}
1402
1451
  };
1403
1452
  const config = {
1404
1453
  branding: "enabled",
1405
- callbackUrl: callbackUrl || window.location.href,
1454
+ callbackUrl: callbackUrl || getHubtelCallbackURL(apiBaseUrl),
1406
1455
  merchantAccount: typeof resolvedMerchantAccount === "string" ? parseInt(resolvedMerchantAccount, 10) : resolvedMerchantAccount,
1407
1456
  basicAuth: authValue || "",
1408
1457
  ...methodPreference ? { paymentMethod: methodPreference } : {}
@@ -1413,24 +1462,41 @@ function HubtelBridge({
1413
1462
  callBacks: {
1414
1463
  onInit: () => console.log("Hubtel checkout initialized"),
1415
1464
  onPaymentSuccess: (data) => {
1465
+ const payload = parseHubtelCallbackPayload(data);
1466
+ const transactionReference = readHubtelField(payload, [
1467
+ "transactionId",
1468
+ "transaction_id",
1469
+ "transactionReference",
1470
+ "paymentReference",
1471
+ "checkoutId"
1472
+ ]);
1473
+ const clientReference = readHubtelField(payload, ["clientReference", "client_reference"]);
1416
1474
  const result = {
1417
- paymentId: data.transactionId || reference || "",
1418
- reference: data.clientReference || reference || "",
1475
+ paymentId,
1476
+ reference: clientReference || reference || paymentId,
1419
1477
  amount,
1420
1478
  currency: currency || "GHS",
1421
1479
  paymentMethod: preferredMethod || "mobile_money",
1422
1480
  psp: "hubtel",
1423
- pspReference: data.transactionId || "",
1424
- status: "success"
1481
+ pspReference: transactionReference || paymentId,
1482
+ status: "success",
1483
+ metadata: {
1484
+ hubtel_payload: payload,
1485
+ hubtel_raw: data
1486
+ }
1425
1487
  };
1426
1488
  onSuccess(result);
1427
1489
  checkout.closePopUp();
1428
1490
  },
1429
1491
  onPaymentFailure: (data) => {
1492
+ const payload = parseHubtelCallbackPayload(data);
1430
1493
  const error = {
1431
1494
  code: "PAYMENT_FAILED",
1432
- message: data.message || "Payment failed",
1433
- recoverable: true
1495
+ message: readHubtelField(payload, ["message", "error", "reason"]) || "Payment failed",
1496
+ recoverable: true,
1497
+ details: {
1498
+ hubtel_payload: payload
1499
+ }
1434
1500
  };
1435
1501
  onError(error);
1436
1502
  },
@@ -1455,6 +1521,8 @@ function HubtelBridge({
1455
1521
  phone,
1456
1522
  description,
1457
1523
  callbackUrl,
1524
+ paymentId,
1525
+ apiBaseUrl,
1458
1526
  authValue,
1459
1527
  isLoading,
1460
1528
  preferredMethod,
@@ -1487,7 +1555,7 @@ function openHubtelPopup(config) {
1487
1555
  };
1488
1556
  const checkoutConfig = {
1489
1557
  branding: "enabled",
1490
- callbackUrl: config.callbackUrl || window.location.href,
1558
+ callbackUrl: config.callbackUrl || getHubtelCallbackURL(config.apiBaseUrl),
1491
1559
  merchantAccount: typeof config.merchantAccount === "string" ? parseInt(config.merchantAccount, 10) : config.merchantAccount,
1492
1560
  basicAuth: config.basicAuth || "",
1493
1561
  ...methodPreference ? { paymentMethod: methodPreference } : {}
@@ -1497,11 +1565,11 @@ function openHubtelPopup(config) {
1497
1565
  config: checkoutConfig,
1498
1566
  callBacks: {
1499
1567
  onPaymentSuccess: (data) => {
1500
- config.onSuccess?.(data);
1568
+ config.onSuccess?.(parseHubtelCallbackPayload(data));
1501
1569
  checkout.closePopUp();
1502
1570
  },
1503
1571
  onPaymentFailure: (data) => {
1504
- config.onError?.(data);
1572
+ config.onError?.(parseHubtelCallbackPayload(data));
1505
1573
  },
1506
1574
  onClose: () => {
1507
1575
  config.onClose?.();
@@ -2481,7 +2549,11 @@ function ReevitCheckout({
2481
2549
  const renderContent = () => {
2482
2550
  if (status === "loading" || status === "processing") {
2483
2551
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "reevit-loading reevit-animate-fade-in", children: [
2484
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "reevit-spinner" }),
2552
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "reevit-dot-pulse", children: [
2553
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "reevit-dot-pulse__dot" }),
2554
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "reevit-dot-pulse__dot" }),
2555
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "reevit-dot-pulse__dot" })
2556
+ ] }),
2485
2557
  /* @__PURE__ */ jsxRuntime.jsx("p", { children: status === "loading" ? "Preparing checkout..." : "Processing payment..." })
2486
2558
  ] });
2487
2559
  }
@@ -2497,12 +2569,22 @@ function ReevitCheckout({
2497
2569
  "Reference: ",
2498
2570
  result.reference
2499
2571
  ] }),
2500
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "reevit-success__redirect", children: "Redirecting in a moment..." })
2572
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "reevit-success__redirect", children: "Redirecting in a moment..." }),
2573
+ /* @__PURE__ */ jsxRuntime.jsx(
2574
+ "div",
2575
+ {
2576
+ className: "reevit-success__countdown",
2577
+ style: { animationDuration: `${successDelayMs}ms` }
2578
+ }
2579
+ )
2501
2580
  ] });
2502
2581
  }
2503
2582
  if (status === "failed" && error && !error.recoverable) {
2504
2583
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "reevit-error reevit-animate-fade-in", children: [
2505
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "reevit-error__icon", children: "\u2715" }),
2584
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "reevit-error__icon", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
2585
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
2586
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
2587
+ ] }) }),
2506
2588
  /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Payment Failed" }),
2507
2589
  /* @__PURE__ */ jsxRuntime.jsx("p", { children: error.message }),
2508
2590
  /* @__PURE__ */ jsxRuntime.jsx("button", { className: "reevit-btn reevit-btn--primary", onClick: handleBack, children: "Try Again" })
@@ -2547,10 +2629,11 @@ function ReevitCheckout({
2547
2629
  merchantAccount: paymentIntent?.pspCredentials?.merchantAccount || "",
2548
2630
  amount: paymentIntent?.amount ?? amount,
2549
2631
  currency: paymentIntent?.currency ?? currency,
2550
- reference: paymentIntent?.reference || reference,
2632
+ reference: paymentIntent?.providerRefId || paymentIntent?.reference || reference,
2551
2633
  email,
2552
2634
  phone: momoData?.phone || phone,
2553
2635
  description: `Payment ${paymentIntent?.reference || reference || ""}`,
2636
+ callbackUrl: `${apiBaseUrl || "https://api.reevit.io"}/v1/webhooks/incoming/hubtel`,
2554
2637
  hubtelSessionToken: paymentIntent?.id ? paymentIntent.id : void 0,
2555
2638
  clientSecret: paymentIntent?.clientSecret,
2556
2639
  apiBaseUrl,
@@ -2624,7 +2707,11 @@ function ReevitCheckout({
2624
2707
  );
2625
2708
  default:
2626
2709
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "reevit-error", children: [
2627
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "reevit-error__icon", children: "\u26A0\uFE0F" }),
2710
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "reevit-error__icon", children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2711
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" }),
2712
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "9", x2: "12", y2: "13" }),
2713
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
2714
+ ] }) }),
2628
2715
  /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Provider Not Supported" }),
2629
2716
  /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
2630
2717
  "Provider (",