├── src ├── types │ ├── PaymobPayload.d.ts │ ├── PaymobCreatedPayment.d.ts │ ├── User.d.ts │ ├── Address.d.ts │ └── PaymobCallback.d.ts ├── routes │ └── paymobRouter.ts ├── services │ └── paymobService.service.ts ├── paymob │ ├── IPaymob.ts │ ├── CreditCardPaymobStrategy.ts │ ├── MobileWalletPaymobStrategy.ts │ └── PaymobStrategy.ts └── controllers │ └── paymobController.controller.ts ├── .env.samples ├── package.json ├── README.md └── .gitignore /src/types/PaymobPayload.d.ts: -------------------------------------------------------------------------------- 1 | type PaymobPayload = { 2 | paymentId: number; 3 | data: string 4 | } -------------------------------------------------------------------------------- /src/types/PaymobCreatedPayment.d.ts: -------------------------------------------------------------------------------- 1 | type PaymobCreatedPayment = { 2 | orderId: number; 3 | token: string; 4 | } -------------------------------------------------------------------------------- /.env.samples: -------------------------------------------------------------------------------- 1 | PAYMOB_API_KEY 2 | PAYMOB_FRAME_ID 3 | PAYMOB_CREDIT_CARD_INTEGRATION_ID 4 | PAYMOB_CREDIT_CARD_INTEGRATION_ID 5 | PAYMOB_MOBILE_WALLET_INTEGRATION_ID 6 | -------------------------------------------------------------------------------- /src/types/User.d.ts: -------------------------------------------------------------------------------- 1 | type User = { 2 | id: number; 3 | firstName: string; 4 | lastName: string; 5 | email?: string; 6 | phone: string; 7 | address?: Address; 8 | } -------------------------------------------------------------------------------- /src/types/Address.d.ts: -------------------------------------------------------------------------------- 1 | 2 | type Address = { 3 | apartment: string; 4 | floor: string; 5 | building: string; 6 | street: string; 7 | city: string; 8 | country: string; 9 | state: string; 10 | zip_code: string; 11 | } -------------------------------------------------------------------------------- /src/routes/paymobRouter.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import paymobController from "../controllers/paymobController.controller"; 3 | 4 | const router = Router(); 5 | 6 | router.route('/callback') 7 | .post(paymobController.paymobCallback) 8 | 9 | export default router; -------------------------------------------------------------------------------- /src/services/paymobService.service.ts: -------------------------------------------------------------------------------- 1 | class PaymobService { 2 | async handlePaymobCallback(orderId: number, success: boolean) { 3 | // Do something with the orderId and success to the transactioned product 4 | } 5 | } 6 | 7 | const paymobService = new PaymobService(); 8 | export default paymobService; -------------------------------------------------------------------------------- /src/types/PaymobCallback.d.ts: -------------------------------------------------------------------------------- 1 | type PaymobCallback = { 2 | obj: { 3 | order: { 4 | id: number 5 | } 6 | }, 7 | success: boolean 8 | // There are more fields, but we don't need them for my use case you can check for them here: https://docs.paymob.com/docs/transaction-webhooks 9 | } -------------------------------------------------------------------------------- /src/paymob/IPaymob.ts: -------------------------------------------------------------------------------- 1 | 2 | interface IPaymob { 3 | authenticate(): Promise; 4 | registerOrder(token: string, amount: number): Promise; 5 | generatePaymentKey(token: string, order_id: number, user: any, amount: number): Promise; 6 | createPayment(user: any, amount: number): Promise; 7 | getPaymentKey(user: any, amount: number): Promise; 8 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paymob-nodejs-integration", 3 | "version": "1.0.0", 4 | "description": "Simple paymob nodejs integration module", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@types/express": "^4.17.19", 13 | "typescript": "^5.2.2" 14 | }, 15 | "dependencies": { 16 | "axios": "^1.5.0", 17 | "express": "^4.18.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/controllers/paymobController.controller.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import paymobService from "../services/paymobService.service"; 3 | class PaymobController { 4 | public async paymobCallback(req: Request, res: Response, next: NextFunction) { 5 | const { obj, success }: PaymobCallback = req.body; 6 | const { id: orderId } = obj.order; 7 | await paymobService.handlePaymobCallback(orderId, success); 8 | } 9 | } 10 | 11 | const paymobController = new PaymobController(); 12 | export default paymobController; 13 | -------------------------------------------------------------------------------- /src/paymob/CreditCardPaymobStrategy.ts: -------------------------------------------------------------------------------- 1 | import PaymobStrategy from "./PaymobStrategy"; 2 | 3 | export default class CreditCardPaymobStrategy extends PaymobStrategy { 4 | integrationId: string; 5 | constructor() { 6 | super(); 7 | this.integrationId = process.env.PAYMOB_CREDIT_CARD_INTEGRATION_ID!; 8 | } 9 | async getPaymentKey(user: any, amount: number): Promise { 10 | let { orderId, token } = await this.createPayment(user, amount) 11 | return { 12 | paymentId: orderId, 13 | data: `https://accept.paymobsolutions.com/api/acceptance/iframes/${this.frameId}?payment_token=${token}` 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/paymob/MobileWalletPaymobStrategy.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import PaymobStrategy from "./PaymobStrategy"; 3 | 4 | export default class MobileWalletPaymobStrategy extends PaymobStrategy { 5 | integrationId: string; 6 | constructor() { 7 | super(); 8 | this.integrationId = process.env.PAYMOB_MOBILE_WALLET_INTEGRATION_ID! 9 | } 10 | async getPaymentKey(user: User, amount: number): Promise { 11 | let { orderId, token } = await this.createPayment(user, amount) 12 | let walletPayment = await this.identifyWalletPayment(token, user.phone); 13 | return { 14 | paymentId: orderId, 15 | data: walletPayment.iframe_redirection_url 16 | } 17 | } 18 | 19 | async identifyWalletPayment(token: string, phone: string) { 20 | try { 21 | let response = await axios.post(`${this.apiUrl}/acceptance/payments/pay`, { 22 | source: { 23 | identifier: phone, 24 | subtype: "WALLET" 25 | }, 26 | payment_token: token 27 | }) 28 | return response.data; 29 | } catch (error: any) { 30 | throw new Error(error); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # paymob-nodejs-integration 2 | 3 | This guide demonstrates a modular and efficient approach to using the PayMob library for seamless integration of PayMob's payment services into your project. 4 | 5 | ## Table of Contents 6 | 7 | - [Introduction](#introduction) 8 | - [Usage](#usage) 9 | - [Environment Variables](#environment-variables) 10 | - [Webhook Callback](#webhook-callback) 11 | - [Issues](#issues) 12 | 13 | ## Introduction 14 | 15 | This guide is intended to help you integrate PayMob's payment services into your project in a modular and efficient way. It is recommended that you read the [official documentation](https://docs.paymob.com/docs) before proceeding with this guide. 16 | 17 | ## Usage 18 | 19 | You can know more about the usage of the library by reading the [official documentation](https://docs.paymob.com/docs). 20 | This guide will focus on the integration of the library into your project, and how to use it in a modular way. 21 | 22 | ## Environment Variables 23 | 24 | The library uses environment variables to configure the library. You can set the environment variables in a `.env` file in the root directory of your project. The required environment variables can be found in `.env.samples` file. 25 | 26 | ```bash 27 | PAYMOB_API_KEY 28 | PAYMOB_FRAME_ID 29 | PAYMOB_CREDIT_CARD_INTEGRATION_ID 30 | PAYMOB_CREDIT_CARD_INTEGRATION_ID 31 | PAYMOB_MOBILE_WALLET_INTEGRATION_ID 32 | ``` 33 | 34 | ## Webhook Callback 35 | 36 | Paymob uses webhook callbacks to notify you of the status of the payment. You can set the webhook callback url in paymob dashboard. 37 | For testing purposes, you can use [ngrok](https://ngrok.com/) to create a tunnel to your localhost. You can then use the generated url as the webhook callback url as mentioned in the [official documentation](https://docs.paymob.com/docs/transaction-webhooks). 38 | 39 | You can check the webhook callback example in the router file `src/routes/paymobRouter.ts`. 40 | 41 | ## Issues 42 | 43 | If you encounter any issues while using the library, please open an issue in the [issues](https://github.com/Abanoub321/paymob-nodejs-integration/issues) section of the repository. 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /src/paymob/PaymobStrategy.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default abstract class PaymobStrategy implements IPaymob { 4 | public frameId: string; 5 | public apiUrl: string = "https://accept.paymobsolutions.com/api"; 6 | abstract integrationId: string; 7 | 8 | constructor() { 9 | this.frameId = process.env.PAYMOB_FRAME_ID!; 10 | } 11 | 12 | async authenticate(): Promise { 13 | try { 14 | let response = await axios.post(`${this.apiUrl}/auth/tokens`, { 15 | api_key: process.env.PAYMOB_API_KEY 16 | }) 17 | return response.data.token; 18 | } catch (error: any) { 19 | throw new Error(error); 20 | } 21 | } 22 | 23 | async registerOrder(token: string, amount: number): Promise { 24 | let response = await axios.post(`${this.apiUrl}/ecommerce/orders`, { 25 | auth_token: token, 26 | delivery_needed: false, 27 | amount_cents: amount * 100, 28 | currency: "EGP", 29 | items: [], 30 | }) 31 | return response.data.id; 32 | } 33 | async generatePaymentKey(token: string, order_id: number, user: User, amount: number): Promise { 34 | try { 35 | let response = await axios.post(`${this.apiUrl}/acceptance/payment_keys`, { 36 | auth_token: token, 37 | amount_cents: amount * 100, 38 | expiration: 3600, 39 | order_id, 40 | currency: "EGP", 41 | billing_data: { 42 | email: user.email || "NA", 43 | phone_number: user.phone, 44 | apartment: user.address ? user.address.apartment : "NA", 45 | floor: user.address ? user.address.floor : "NA", 46 | building: user.address ? user.address.building : "NA", 47 | street: user.address ? user.address.street : "NA", 48 | city: user.address ? user.address.city : "NA", 49 | country: user.address ? user.address.country : "NA", 50 | first_name: user.firstName, 51 | last_name: user.lastName, 52 | state: user.address ? user.address.state : "NA", 53 | zip_code: user.address ? user.address.zip_code : "NA", 54 | }, 55 | integration_id: this.integrationId, 56 | lock_order_when_paid: "false" 57 | }) 58 | return response.data.token; 59 | } catch (error: any) { 60 | throw new Error(error); 61 | } 62 | } 63 | async createPayment(user: User, amount: number): Promise { 64 | let token = await this.authenticate(); 65 | let orderId = await this.registerOrder(token, amount); 66 | let paymentToken = await this.generatePaymentKey(token, orderId, user, amount); 67 | return { 68 | orderId: orderId, 69 | token: paymentToken 70 | }; 71 | } 72 | 73 | abstract getPaymentKey(user: any, amount: number): Promise; 74 | } --------------------------------------------------------------------------------