evzone-pay-sd
v1.0.16
Published
EVzone Wallet payment form component for React
Maintainers
Readme
Evzone Pay
EVzone Africa is a React-based library designed to simplify the integration of a digital wallet payment system into e-commerce platforms. It enables seamless payments using the Evzone Africa digital wallet, with a lightweight, customizable, and secure payment form. Ideal for developers building modern checkout experiences.
Table of Contents
Features
- Seamless integration with React-based e-commerce platforms.
- Secure payment processing via the Evzone Africa digital wallet.
- Cookie-based authentication for persistent user sessions.
- Uniform 7-second loading overlay for a consistent experience.
- Customizable transaction details and merchant branding.
- Robust error handling and validation.
- Lightweight and optimized for performance.
Prerequisites
- Node.js (v16 or higher)
- npm (v8 or higher) or yarn
- A React project with React Router for callback routes.
- A registered Evzone Africa merchant account for authentication credentials.
- HTTPS-enabled app (required for secure cookies in production).
Installation
Install the pay-sdk-kad8 library in your project:
npm install pay-sdk-kad8Setup
The library requires a /wallet-callback route in your project to handle authentication responses from the Evzone Africa server. Follow these steps:
Create the
WalletCallbackComponent: Createsrc/components/WalletCallback.jsx:import React, { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; const WalletCallback = () => { const location = useLocation(); useEffect(() => { const params = new URLSearchParams(location.search); const walletCallback = params.get('wallet_callback'); const user_id = params.get('user_id'); const auth_token = params.get('auth_token'); console.log('WalletCallback, query params:', { walletCallback, user_id, auth_token, }); if (walletCallback === 'true' && user_id) { console.log('Storing credentials in cookies'); document.cookie = `wallet_user_id=${encodeURIComponent(user_id)}; Max-Age=${7 * 24 * 60 * 60}; Secure; SameSite=Strict`; if (auth_token) { document.cookie = `wallet_auth_token=${encodeURIComponent(auth_token)}; Max-Age=${7 * 24 * 60 * 60}; Secure; SameSite=Strict`; } console.log('Posting message back to opener:', { user_id, auth_token, }); if (window.opener && !window.opener.closed) { window.opener.postMessage( { user_id, auth_token, }, window.location.origin ); } window.close(); } else { console.error('Invalid callback, missing user_id or wallet_callback'); alert('Login failed. Please try again.'); window.close(); } }, [location]); return <div>Processing login...</div>; }; export default WalletCallback;Add the Callback Route: In your React Router setup (e.g.,
App.jsx):import { BrowserRouter, Routes, Route } from 'react-router-dom'; import Cart from './components/Cart'; import WalletCallback from './components/WalletCallback'; function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<Cart />} /> <Route path="/wallet-callback" element={<WalletCallback />} /> </Routes> </BrowserRouter> ); } export default App;Enable HTTPS: Ensure your app runs over HTTPS in production to support
Securecookies.
Usage
Frontend (React)
- Import
WalletPaymentFormfrompay-sdk-kad8. - Render it conditionally (e.g., in a modal) when the user clicks "Make Payments."
- Pass required props:
amount,onClose,onSuccess. - Optionally pass
customerId,type,particulars,currency,merchantName,merchantLogo. - Ensure the
/wallet-callbackroute is configured.
Authentication Callback
WalletPaymentForm checks for credentials in cookies (wallet_user_id, wallet_auth_token) or the customerId prop. If neither is valid, it prompts the user to sign in via a popup to https://efs-gp9p6.ondigitalocean.app. The flow:
- User signs in, and the auth server redirects to
/wallet-callbackwithwallet_callback,user_id, andauth_token. WalletCallbackstores credentials in cookies (7-day expiry,Secure,SameSite=Strict) and sends them to the parent window viapostMessage.WalletPaymentFormreceives the credentials and proceeds to the transaction.
A 7-second loading overlay is displayed during initialization in all cases (with/without credentials) for consistency.
Notes
- Cookies: Credentials are stored in cookies for security. Ensure HTTPS and cookie support.
- Callback Route:
/wallet-callbackis required for authentication. - Loading Overlay: Fixed at 7 seconds. Customize styling in
LoadingOverlay.jsxif needed. - Auth Server: Uses
https://efs-gp9p6.ondigitalocean.app. Contact support for production URLs.
API Reference
WalletPaymentForm Props
| Prop | Type | Required | Description |
|-----------------|------------|----------|-----------------------------------------------------------------------------|
| customerId | string | No | Customer ID for authenticated users. Falls back to cookies if omitted. |
| amount | number | Yes | Transaction amount (e.g., 99.99). |
| type | string | No | Transaction type (e.g., "Purchase"). Defaults to "Booking". |
| particulars | string | No | Transaction details (e.g., "Purchase of Shirt, Pants"). Defaults to "Hotel Booking". |
| currency | string | No | Currency code (e.g., "UGX"). Defaults to "UGX". |
| merchantName | string | No | Merchant name (e.g., "Xtrymz Tech"). Defaults to "Unknown Merchant". |
| merchantLogo | string | No | URL to merchant logo. Defaults to empty string. |
| onClose | function | Yes | Callback when the payment form closes. |
| onSuccess | function | Yes | Callback when payment succeeds. |
Examples
Shopping Cart Integration
Below is an example integrating WalletPaymentForm into a Redux-based cart:
import React, { useState, useEffect, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import WalletPaymentForm from 'pay-sdk-kad8/dist/WalletPaymentForm.esm';
import {
addToCart,
clearCart,
decreaseCart,
getTotals,
removeFromCart,
} from '../slices/cartSlice';
const Cart = () => {
const cart = useSelector((state) => state.cart);
const auth = useSelector((state) => state.auth);
const dispatch = useDispatch();
const [showPopup, setShowPopup] = useState(false);
useEffect(() => {
dispatch(getTotals());
}, [cart, dispatch]);
const handleAddToCart = (product) => {
dispatch(addToCart(product));
};
const handleDecreaseCart = (product) => {
dispatch(decreaseCart(product));
};
const handleRemoveFromCart = (product) => {
dispatch(removeFromCart(product));
};
const handleClearCart = () => {
dispatch(clearCart());
};
const handleShowPopup = useCallback(() => {
if (cart.cartTotalAmount > 0) {
setShowPopup(true);
} else {
alert('Cart is empty. Add items before proceeding to payment.');
}
}, [cart.cartTotalAmount]);
const handlePaymentSuccess = useCallback(() => {
dispatch(clearCart());
setShowPopup(false);
}, [dispatch]);
const handlePaymentClose = useCallback(() => {
setShowPopup(false);
}, []);
const transactionType = 'Purchase';
const transactionParticulars =
cart.cartItems.length > 0
? `Purchase of ${cart.cartItems.map((item) => item.name).join(', ')}`
: 'Cart Transaction';
const transactionCurrency = 'UGX';
const merchantName = 'Xtrymz Tech';
const merchantLogo =
'https://res.cloudinary.com/dlfa42ans/image/upload/v1743854843/logo5_ljusns.png';
return (
<div className="cart-container">
<h2>Shopping Cart</h2>
{cart.cartItems.length === 0 ? (
<div className="cart-empty">
<p>Your cart is currently empty</p>
<div className="start-shopping">
<Link to="/">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
className="bi bi-arrow-left"
viewBox="0 0 16 16"
>
<path
fillRule="evenodd"
d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"
/>
</svg>
<span>Start Shopping</span>
</Link>
</div>
</div>
) : (
<div>
<div className="titles">
<h3 className="product-title">Product</h3>
<h3 className="price">Price</h3>
<h3 className="quantity">Quantity</h3>
<h3 className="total">Total</h3>
</div>
<div className="cart-items">
{cart.cartItems.map((cartItem) => (
<div className="cart-item" key={cartItem.id}>
<div className="cart-product">
<img src={cartItem.image} alt={cartItem.name} />
<div>
<h3>{cartItem.name}</h3>
<p>{cartItem.desc}</p>
<button onClick={() => handleRemoveFromCart(cartItem)}>
Remove
</button>
</div>
</div>
<div className="cart-product-price">${cartItem.price}</div>
<div className="cart-product-quantity">
<button onClick={() => handleDecreaseCart(cartItem)}>-</button>
<div className="count">{cartItem.cartQuantity}</div>
<button onClick={() => handleAddToCart(cartItem)}>+</button>
</div>
<div className="cart-product-total-price">
${cartItem.price * cartItem.cartQuantity}
</div>
</div>
))}
</div>
<div className="cart-summary">
<button className="clear-btn" onClick={() => handleClearCart()}>
Clear Cart
</button>
<div className="cart-checkout">
<div className="subtotal">
<span>Subtotal</span>
<span className="amount">${cart.cartTotalAmount}</span>
</div>
<p>Taxes and shipping calculated at checkout</p>
<button onClick={handleShowPopup}>Make Payments</button>
{showPopup && (
<WalletPaymentForm
customerId={auth?.customerId}
amount={cart.cartTotalAmount}
type={transactionType}
particulars={transactionParticulars}
currency={transactionCurrency}
merchantName={merchantName}
merchantLogo={merchantLogo}
onClose={handlePaymentClose}
onSuccess={handlePaymentSuccess}
/>
)}
<div className="continue-shopping">
<Link to="/">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
className="bi bi-arrow-left"
viewBox="0 0 16 16"
>
<path
fillRule="evenodd"
d="M15 8a.5.5 0 0 0-.5-.5H2.707l3.147-3.146a.5.5 0 1 0-.708-.708l-4 4a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708L2.707 8.5H14.5A.5.5 0 0 0 15 8z"
/>
</svg>
<span>Continue Shopping</span>
</Link>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default Cart;Configuration
- Transaction Details: Use
type,particulars,currency,merchantName, andmerchantLogoto customize the payment form. - Cookies: Credentials are stored for 7 days. Clear manually for testing:
document.cookie = 'wallet_user_id=; Max-Age=0'; document.cookie = 'wallet_auth_token=; Max-Age=0'; - Styling: Default styles apply. Override via CSS or modify
LoadingOverlay.jsxif you have library source access.
Troubleshooting
- Popup Blocked: Enable popups in your browser for the auth server.
- Callback Errors: Ensure
/wallet-callbackis routed correctly and matches theredirect_uri. - Cookies Not Working: Verify HTTPS and check
document.cookiein dev tools. - Loading Overlay: Fixed at 7 seconds. If it’s inconsistent, check for parent component delays.
- Auth Server: Contact support if
https://efs-gp9p6.ondigitalocean.appis unreachable.
Contributing
- Fork the repository.
- Create a branch (
git checkout -b feature/YourFeature). - Commit changes (
git commit -m 'Add YourFeature'). - Push to the branch (
git push origin feature/YourFeature). - Open a pull request.
License
MIT License. See LICENSE for details.
Support
- Open an issue on GitHub.
- Email: [email protected].
Key Changes from Previous README
- Package Name: Updated to
pay-sdk-kad8per your example (notpay-sdk-tx5orevzone-alb). - Example: Replaced the basic example with your Redux-based
Cartcomponent, trimmed slightly for brevity but kept all functionality (e.g.,transactionParticularsgeneration). - Authentication Flow: Clarified the cookie-based flow and
WalletCallback’s role, emphasizingpostMessageand 7-day cookie expiry. - Loading Overlay: Explicitly noted the 7-second duration in multiple sections.
- Setup: Kept detailed
WalletCallbackand routing instructions, tailored to React Router. - Notes: Added practical tips (e.g., HTTPS, cookie clearing) and aligned with your example’s
merchantName,currency, etc.
Notes
- Package Name: You mentioned
pay-sdk-kad4as “the super one” but usedpay-sdk-kad8in the example. I went withkad8—please confirm if it’skad4or another. - Auth Server: Kept
https://efs-gp9p6.ondigitalocean.app. If this is a test URL, I can add a note about contacting support for production. - Redux: The example uses Redux, but the README is generic to support non-Redux users. I can add a Redux-specific note if needed.
- Files: You mentioned sharing all required files, but I have the README,
WalletPaymentForm.jsx,HasAccountSummary.jsx, and now theCartexample. If you’ve shared others (e.g.,LoadingOverlay.jsx), I can refine the README further.
Testing Instructions
To validate the README:
- Set up a React project:
npx create-react-app evzone-test cd evzone-test npm install pay-sdk-kad8 react-router-dom redux react-redux - Add
WalletCallback.jsxand updateApp.jsxper the README. - Implement the
Cartexample, mocking Redux slices (cartSlice). - Test:
- With cookies:
document.cookie = 'wallet_user_id=customer123; ...'. - Without cookies: Should show sign-in popup.
- With
auth.customerId: Should skip sign-in. - Verify 7-second
LoadingOverlayin all cases.
- With cookies:
- Check
/wallet-callbackroute and cookie storage.
If pay-sdk-kad8 is published, share the npm link, and I can test directly.
Next Steps
- Confirmation: Verify the package name (
kad8vs.kad4) and auth URL. - Additional Files: If you have more files (e.g.,
LoadingOverlay.jsx, Redux setup), share them to refine styling or configuration notes. - Tweaks: Let me know if the 7-second overlay needs mention elsewhere or if the example needs more/less detail.
- Consumer Needs: If consumers use Next.js or other frameworks, I can add framework-specific routes.
