├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── api │ ├── local │ │ └── route.ts │ └── online │ │ └── route.ts ├── favicon.ico ├── globals.css ├── layout.tsx ├── page.tsx └── success │ └── page.tsx ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public ├── next.svg └── vercel.svg ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Here’s the updated README with the correct `.env` keys and environment setup details: 2 | 3 | --- 4 | 5 | # Somali Local Payment System 6 | 7 | [](LICENSE) 8 | [](https://github.com/mchamoudadev/payments/graphs/contributors) 9 | [](https://github.com/mchamoudadev/payments/issues) 10 | 11 | A robust payment system designed to integrate Somali local payment methods such as **EVC**, **Zaad**, **Sahal**, and **Premier Bank Gateway** (powered by Mastercard). This project aims to simplify online transactions across Somalia using the most widely adopted local payment solutions. 12 | 13 | --- 14 | 15 | ## 🚀 Key Features 16 | 17 | - **Support for Somali Local Payment Methods**: Seamless integration with EVC, Zaad, Sahal, and Premier Bank Gateway (Mastercard). 18 | - **High Security**: Implements secure protocols to ensure safe and encrypted transactions. 19 | - **Real-time Payment Processing**: Instant verification and processing of payments for quick transaction completions. 20 | - **Customizable Integration**: Easy API setup to integrate with e-commerce platforms, websites, and mobile apps. 21 | - **Detailed Documentation**: Clear and concise instructions to help developers quickly set up and use the system. 22 | - **Scalable Infrastructure**: Handles large transaction volumes efficiently with minimal downtime. 23 | 24 | --- 25 | 26 | ## 🏗️ Installation 27 | 28 | 1. Clone the repository: 29 | 30 | ```bash 31 | git clone https://github.com/mchamoudadev/payments.git 32 | ``` 33 | 34 | 2. Navigate into the project directory: 35 | 36 | ```bash 37 | cd payments 38 | ``` 39 | 40 | 3. Install dependencies: 41 | 42 | ```bash 43 | npm install 44 | ``` 45 | 46 | 4. Configure environment variables: 47 | 48 | - Set up the following environment variables in your `.env` file: 49 | 50 | ```env 51 | MASTER_CARD_MERCHANT_ID=your_mastercard_merchant_id 52 | MASTER_CARD_API_PASSWORD=your_mastercard_api_password 53 | MERCHANT_U_ID=your_merchant_u_id 54 | MERCHANT_API_USER_ID=your_merchant_api_user_id 55 | MERCHANT_API_KEY=your_merchant_api_key 56 | ``` 57 | 58 | 5. Run the development server: 59 | 60 | ```bash 61 | npm start 62 | ``` 63 | 64 | --- 65 | 66 | ## 🌐 Supported Payment Methods 67 | 68 | - **EVC**: Mobile payment service widely used across Somalia. 69 | - **Zaad**: Popular mobile money platform from Telesom. 70 | - **Sahal**: Digital payment solution for easy transfers. 71 | - **Premier Bank Gateway**: Mastercard-powered payment gateway for secure transactions. 72 | 73 | --- 74 | 75 | ## 🤝 Contributing 76 | 77 | We welcome contributions to improve and expand this project! To contribute: 78 | 79 | 1. Fork the repository. 80 | 2. Create a new feature branch (`git checkout -b feature-branch`). 81 | 3. Commit your changes (`git commit -m 'Add new feature'`). 82 | 4. Push the branch (`git push origin feature-branch`). 83 | 5. Create a Pull Request. 84 | 85 | --- 86 | 87 | ## 📜 License 88 | 89 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information. 90 | 91 | --- 92 | 93 | ## ✨ Acknowledgements 94 | 95 | - **EVC** - For providing the mobile payment platform used by millions. 96 | - **Zaad** - For creating a reliable mobile money service. 97 | - **Sahal** - For enabling easy digital transactions across the country. 98 | - **Premier Bank** - For powering online transactions through Mastercard. 99 | 100 | --- 101 | -------------------------------------------------------------------------------- /app/api/local/route.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { NextRequest, NextResponse } from "next/server"; 3 | 4 | export async function POST(request : NextRequest) { 5 | 6 | 7 | const body = await request.json(); 8 | 9 | try { 10 | 11 | // if(body.operator === "ebir"){ 12 | 13 | // }else{ 14 | 15 | // } 16 | 17 | const paymentBody = { 18 | schemaVersion: "1.0", 19 | requestId: "10111331033", 20 | timestamp: Date.now(), 21 | channelName: "WEB", 22 | serviceName: "API_PURCHASE", 23 | serviceParams: { 24 | merchantUid: process.env.MERCHANT_U_ID, 25 | apiUserId: process.env.MERCHANT_API_USER_ID, 26 | apiKey: process.env.MERCHANT_API_KEY, 27 | paymentMethod: "mwallet_account", 28 | payerInfo: { 29 | accountNo: body.phone, 30 | }, 31 | transactionInfo: { 32 | referenceId: "12334", 33 | invoiceId: "7896504", 34 | amount: body.amount, 35 | // currency: "ETB", // ebir 36 | currency: "USD", 37 | description: "Product details", 38 | }, 39 | }, 40 | }; 41 | 42 | // ebir apiurl => https://payments.ebirr.com/asm 43 | const response = await axios.post(`https://api.waafipay.net/asm`,paymentBody) 44 | 45 | console.log("response", response.data); 46 | 47 | return NextResponse.json(response.data) 48 | 49 | } catch (error) { 50 | console.log("error at waafi", error); 51 | } 52 | } -------------------------------------------------------------------------------- /app/api/online/route.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { request } from "http"; 3 | import { NextRequest, NextResponse } from "next/server"; 4 | 5 | 6 | 7 | function generateUniqueId() { 8 | return 'order_'+ Date.now(); 9 | } 10 | 11 | 12 | export async function POST(request:NextRequest) { 13 | 14 | const body = await request.json(); 15 | 16 | 17 | try { 18 | 19 | const orderId = generateUniqueId(); 20 | 21 | const payload = { 22 | apiOperation : 'INITIATE_CHECKOUT', 23 | order: { 24 | amount: body.amount, 25 | currency: "USD", 26 | description: "Order Description", 27 | id: orderId 28 | }, 29 | interaction : { 30 | operation: "PURCHASE", 31 | returnUrl: body.returnUrl, 32 | } 33 | } 34 | 35 | // MASTER_CARD_MERCHANT_ID 36 | // MASTER_CARD_API_PASSWORD 37 | 38 | 39 | const authString = `merchant.${process.env.MASTER_CARD_MERCHANT_ID}:${process.env.MASTER_CARD_API_PASSWORD}`; 40 | 41 | const authHeader = `Basic ${Buffer.from(authString).toString("base64")}`; 42 | // https://test-gateway 43 | 44 | const response = await axios.post(`https://test-gateway.mastercard.com/api/rest/version/65/merchant/${process.env.MASTER_CARD_MERCHANT_ID}/session`, payload , { 45 | headers: { 46 | 'Authorization': authHeader, 47 | 'Content-Type': 'application/json' 48 | } 49 | }); 50 | 51 | 52 | const sessionData = response.data; 53 | 54 | console.log("session data", sessionData); 55 | 56 | // model 57 | 58 | // ordrId, session, sessionIdicator 59 | 60 | return NextResponse.json({session: {id: sessionData.session.id}}) 61 | 62 | } catch (error) { 63 | // @ts-ignore 64 | console.error('Error creating checkout session:', error.response ? error.response.data : error.message); 65 | return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); 66 | } 67 | } 68 | 69 | export async function GET(request: NextRequest){ 70 | 71 | 72 | 73 | try { 74 | const { resultIndicator } = req.query; 75 | 76 | // Find the order using the result indicator and orderId 77 | const order = await Orders.findOne({ resultIndicator }); 78 | 79 | if (!order) { 80 | return res.status(404).json({ error: 'Order not found' }); 81 | } 82 | 83 | if (order.masterCardStatus === 'CAPTURED' || order.status === 'Completed') { 84 | return res.json({ success: false, message: 'Payment already captured' }); 85 | } 86 | 87 | 88 | const authString = `merchant.${process.env.MASTER_CARD_MERCHANT_ID}:${process.env.MASTER_CARD_API_PASSWORD}`; 89 | const authHeader = `Basic ${Buffer.from(authString).toString('base64')}`; 90 | // Make a request to get the order info 91 | const response = await axios.get( 92 | `https://test-gateway.mastercard.com/api/rest/version/65/merchant/${process.env.MASTER_CARD_MERCHANT_ID}/order/${order.orderId}`, 93 | { 94 | headers: { 95 | 'Authorization': authHeader, 96 | 'Content-Type': 'application/json', 97 | }, 98 | } 99 | ); 100 | 101 | const orderInfo = response.data; 102 | 103 | if (orderInfo.status === 'CAPTURED') { 104 | order.masterCardStatus = 'CAPTURED'; 105 | order.phone = ""; 106 | order.status = 'Completed'; 107 | await order.save(); 108 | 109 | 110 | return res.json({ success: true, message: 'Payment captured successfully' }); 111 | } 112 | 113 | 114 | return res.json({ success: false, message: 'Payment not captured', orderInfo }); 115 | } catch (error) { 116 | console.error('Error confirming payment:', error.response ? error.response.data : error.message); 117 | res.status(500).json({ error: 'Internal Server Error' }); 118 | } 119 | } -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mchamoudadev/payments/4b4bb3217e6c078ed029602c43023b0b6ed036b5/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "Create Next App", 9 | description: "Generated by create next app", 10 | }; 11 | 12 | export default function RootLayout({ 13 | children, 14 | }: Readonly<{ 15 | children: React.ReactNode; 16 | }>) { 17 | return ( 18 | 19 |
{children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import axios from "axios"; 4 | import { FormEvent, useEffect, useState } from "react"; 5 | import toast, { Toaster } from 'react-hot-toast'; 6 | const localOptionsDefault = [ 7 | 8 | { 9 | operator: "zaad", 10 | shortNumber: "25263", 11 | isSelected: false, 12 | }, 13 | { 14 | operator: "evc", 15 | shortNumber: "252", 16 | isSelected: true, 17 | }, 18 | { 19 | operator: "sahal", 20 | shortNumber: "252", 21 | isSelected: false, 22 | }, 23 | ] 24 | 25 | 26 | export default function Home() { 27 | 28 | const [paymentMethod, setPaymentMethod] = useState('local'); 29 | 30 | const [localPaymentOptions, setLocalPaymentOptions] = useState(localOptionsDefault); 31 | 32 | const [phone, setPhone] = useState(''); 33 | const [amount, setAmount] = useState(''); 34 | const [loading, setLoading] = useState(false); 35 | const [sessionId, setSessionId] = useState(""); 36 | 37 | const [orderId, setOrderId] = useState('33321232'); // Set your order ID here 38 | 39 | 40 | const handlePaymentClickOption = (index: number) => { 41 | 42 | setLocalPaymentOptions(localPaymentOptions.map((option, i) => ({ 43 | ...option, 44 | isSelected: i === index 45 | }))) 46 | } 47 | 48 | const handleSubmit = async (e: FormEvent) => { 49 | 50 | e.preventDefault(); 51 | 52 | try { 53 | setLoading(true) 54 | const response = await axios.post('/api/local', { 55 | phone, 56 | amount, 57 | // operator: "zadd" 58 | }) 59 | 60 | toast.success(response.data.params.description); 61 | 62 | setLoading(false) 63 | } catch (error) { 64 | setLoading(false) 65 | console.log("error", error); 66 | // toast.error(response.data.params.description) 67 | } 68 | } 69 | 70 | useEffect(() => { 71 | 72 | const script = document.createElement("script"); 73 | script.src = "https://test-gateway.mastercard.com/static/checkout/checkout.min.js"; 74 | 75 | script.async = true; 76 | script.onload = () => console.log("master card script loaded"); 77 | document.body.appendChild(script); 78 | 79 | // clean up 80 | return () => { 81 | document.body.removeChild(script); 82 | } 83 | }, []); 84 | 85 | 86 | 87 | const configureCheckout = (sessionId: string) => { 88 | 89 | if (!window.Checkout) { 90 | console.log("checkout script not loaded"); 91 | return 92 | } 93 | 94 | 95 | window.Checkout.configure({ 96 | session: { 97 | id: sessionId, 98 | }, 99 | order: { 100 | description: "Order something", 101 | id: orderId 102 | }, 103 | interaction: { 104 | operation: 'PURCHASE', 105 | merchant: { 106 | name: "Dugsiiye", 107 | // address: { 108 | // line1: '123 Premier bank Street', 109 | // line2: 'StreetAddressLine2', 110 | // }, 111 | }, 112 | }, 113 | }) 114 | window.Checkout.showPaymentPage(); 115 | } 116 | 117 | 118 | 119 | const handleOnlinePayment = async () => { 120 | try { 121 | const response = await axios.post('/api/online', 122 | { 123 | amount: 20, 124 | returnUrl: 'http://localhost:3000/success', 125 | } 126 | ); 127 | 128 | const data = response.data; 129 | 130 | setSessionId(data.session.id); 131 | configureCheckout(data.session.id); 132 | 133 | } catch (error) { 134 | console.log("err", error) 135 | } 136 | } 137 | 138 | 139 | 140 | 141 | return ( 142 | <> 143 |