From 4a7003a53244d59ff2adbf7723008f281d3481ee Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Sat, 7 Jun 2025 15:01:38 +0330 Subject: [PATCH] chore: parsian --- .gitignore | 137 +++++++++++++++ database/seeder.runner.ts | 16 +- database/seeders/payment-gateway.seeder.ts | 11 ++ package.json | 2 + pnpm-lock.yaml | 152 +++++++++++++++- src/configs/parsian.config.ts | 22 +++ .../payments/DTO/deposit-wallet.dto.ts | 4 +- src/modules/payments/constants/index.ts | 6 + src/modules/payments/enums/gateway.enum.ts | 1 + .../payments/factories/payment.factory.ts | 7 +- .../payments/gateways/parsian.gateway.ts | 165 ++++++++++++++++++ src/modules/payments/interfaces/IPayment.ts | 45 +++++ src/modules/payments/payments.module.ts | 31 +++- src/modules/payments/types/gateway.type.ts | 2 +- 14 files changed, 582 insertions(+), 19 deletions(-) create mode 100644 src/configs/parsian.config.ts create mode 100644 src/modules/payments/gateways/parsian.gateway.ts diff --git a/.gitignore b/.gitignore index 4b56acf..2f1a7ba 100755 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /dist /node_modules /build +/IPGSW1Sale # Logs logs @@ -54,3 +55,139 @@ pids # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/database/seeder.runner.ts b/database/seeder.runner.ts index c6cf884..7e19ecb 100755 --- a/database/seeder.runner.ts +++ b/database/seeder.runner.ts @@ -1,11 +1,11 @@ import { Logger } from "@nestjs/common"; import { connectionSource } from "./connection"; -import { seedAdmin } from "./seeders/admin.seeder"; -import { seedCityAndProvince } from "./seeders/iran-city.seeder"; -import { seedNotifSettings } from "./seeders/notif-setting.seeder"; +// import { seedAdmin } from "./seeders/admin.seeder"; +// import { seedCityAndProvince } from "./seeders/iran-city.seeder"; +// import { seedNotifSettings } from "./seeders/notif-setting.seeder"; import { seedPaymentGateways } from "./seeders/payment-gateway.seeder"; -import { seedPermissionsAndRoles } from "./seeders/role.seeder"; +// import { seedPermissionsAndRoles } from "./seeders/role.seeder"; const logger = new Logger("Seeder"); @@ -13,11 +13,11 @@ export const runSeeder = async () => { await connectionSource.initialize(); logger.log("start seeding database"); // - await seedPermissionsAndRoles(connectionSource, logger); - await seedAdmin(connectionSource, logger); - await seedNotifSettings(connectionSource, logger); + // await seedPermissionsAndRoles(connectionSource, logger); + // await seedAdmin(connectionSource, logger); + // await seedNotifSettings(connectionSource, logger); await seedPaymentGateways(connectionSource, logger); - await seedCityAndProvince(connectionSource, logger); + // await seedCityAndProvince(connectionSource, logger); logger.log("seeding completed"); process.exit(0); diff --git a/database/seeders/payment-gateway.seeder.ts b/database/seeders/payment-gateway.seeder.ts index 10763ae..1c55079 100755 --- a/database/seeders/payment-gateway.seeder.ts +++ b/database/seeders/payment-gateway.seeder.ts @@ -11,6 +11,12 @@ const paymentGateways: Partial[] = [ logoUrl: "https://www.zarinpal.com/docs/logo/fa-dark.svg", description: "درگاه پرداخت زرین پال", }, + { + name: GatewayEnum.PARSIAN, + nameFa: "پارسیان", + logoUrl: "https://www.parsian.ir/docs/logo/fa-dark.svg", + description: "درگاه پرداخت پارسیان", + }, ]; export const seedPaymentGateways = async (dataSource: DataSource, logger: Logger) => { @@ -18,6 +24,11 @@ export const seedPaymentGateways = async (dataSource: DataSource, logger: Logger const paymentGatewayRepo = dataSource.getRepository(PaymentGateway); for (const paymentGateway of paymentGateways) { + const existingPg = await paymentGatewayRepo.findOne({ where: { name: paymentGateway.name } }); + if (existingPg) { + logger.log(`${paymentGateway.name} already exists`); + continue; + } const pg = paymentGatewayRepo.create(paymentGateway); await paymentGatewayRepo.save(pg); } diff --git a/package.json b/package.json index c08d04c..5e01c0a 100755 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "dotenv": "^16.5.0", "fastify": "^5.3.2", "handlebars": "^4.7.8", + "nestjs-soap": "^3.0.4", "nestjs-telegraf": "^2.8.1", "nodemailer": "^6.10.1", "passport-jwt": "^4.0.1", @@ -68,6 +69,7 @@ "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", "slugify": "^1.6.6", + "soap": "^1.1.12", "telegraf": "^4.16.3", "typeorm": "^0.3.22" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a9c9688..5761de8 100755 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,6 +101,9 @@ importers: handlebars: specifier: ^4.7.8 version: 4.7.8 + nestjs-soap: + specifier: ^3.0.4 + version: 3.0.4(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)) nestjs-telegraf: specifier: ^2.8.1 version: 2.8.1(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(telegraf@4.16.3)(typescript@5.8.3) @@ -122,6 +125,9 @@ importers: slugify: specifier: ^1.6.6 version: 1.6.6 + soap: + specifier: ^1.1.12 + version: 1.1.12 telegraf: specifier: ^4.16.3 version: 4.16.3 @@ -1939,6 +1945,14 @@ packages: '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 prom-client: ^15.0.0 + '@xmldom/is-dom-node@1.0.1': + resolution: {integrity: sha512-CJDxIgE5I0FH+ttq/Fxy6nRpxP70+e2O048EPe85J2use3XKdatVM7dDVvFNjQudd9B49NPoZ+8PG49zj4Er8Q==} + engines: {node: '>= 16'} + + '@xmldom/xmldom@0.8.10': + resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} + engines: {node: '>=10.0.0'} + '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -2147,9 +2161,15 @@ packages: resolution: {integrity: sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw==} engines: {node: '>= 10.0.0'} + axios-ntlm@1.4.4: + resolution: {integrity: sha512-kpCRdzMfL8gi0Z0o96P3QPAK4XuC8iciGgxGXe+PeQ4oyjI2LZN8WSOKbu0Y9Jo3T/A7pB81n6jYVPIpglEuRA==} + axios@1.8.4: resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} + axios@1.9.0: + resolution: {integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2578,6 +2598,15 @@ packages: supports-color: optional: true + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decimal.js@10.5.0: resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} @@ -2630,6 +2659,9 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + des.js@1.1.0: + resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==} + detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} @@ -2645,6 +2677,9 @@ packages: detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + dev-null@0.1.1: + resolution: {integrity: sha512-nMNZG0zfMgmdv8S5O0TM5cpwNbGKRGPCxVsr0SmA3NZZy9CYBbuNLL0PD3Acx9e5LIUgwONXtM9kM6RlawPxEQ==} + dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} @@ -3147,6 +3182,10 @@ packages: formidable@3.5.3: resolution: {integrity: sha512-pQEHGLZjLRyfLCe6r6n8IQGqHEceKfYR5tIf/iUDn5SabaitfVR/pIskxnyvSSl122J63rFY17i68hrfK0BVOA==} + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -3813,6 +3852,9 @@ packages: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} + js-md4@0.3.2: + resolution: {integrity: sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==} + js-stringify@1.0.2: resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==} @@ -4136,6 +4178,9 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + minimatch@10.0.1: resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} engines: {node: 20 || >=22} @@ -4307,6 +4352,12 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + nestjs-soap@3.0.4: + resolution: {integrity: sha512-/iPyBmHcQzJDSttQ5HeQ7ln9QDmxb27eKg+l8Jj0eO4sk/OmX9M4DUthEPjVZ3Gh11WVvzT5NuZIXEBSR/Im5A==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@nestjs/common': ^8.4.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + nestjs-telegraf@2.8.1: resolution: {integrity: sha512-7H3r6Yl4YNnJn/YSPcP26O2ESAKLJTgjKP6XuBkKw5OrYBXguGWmeUdNHYISCZTjKegHxfq8/9bjmfMrWrGIIA==} peerDependencies: @@ -4961,6 +5012,9 @@ packages: sax@1.2.1: resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==} + sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + schema-utils@3.3.0: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} @@ -5077,6 +5131,10 @@ packages: resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==} engines: {node: '>=8.0.0'} + soap@1.1.12: + resolution: {integrity: sha512-bfgy09VAA2Pnv8I/qDpMvPFPV//eIGr/DOeaLx3uexpMrI+gq8Xuvp3KlTZYMhKFwjwgcLd+YpabFbOv82R5gQ==} + engines: {node: '>=14.17.0'} + sonic-boom@4.2.0: resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} @@ -5631,6 +5689,10 @@ packages: webpack-cli: optional: true + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -5696,6 +5758,10 @@ packages: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + xml-crypto@6.1.2: + resolution: {integrity: sha512-leBOVQdVi8FvPJrMYoum7Ici9qyxfE4kVi+AkpUoYCSXaQF4IlBm1cneTK9oAxR61LpYxTx7lNcsnBIeRpGW2w==} + engines: {node: '>=16'} + xml2js@0.6.2: resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} engines: {node: '>=4.0.0'} @@ -5704,6 +5770,10 @@ packages: resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} engines: {node: '>=4.0'} + xpath@0.0.33: + resolution: {integrity: sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA==} + engines: {node: '>=0.6.0'} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -8172,6 +8242,10 @@ snapshots: '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) prom-client: 15.1.3 + '@xmldom/is-dom-node@1.0.1': {} + + '@xmldom/xmldom@0.8.10': {} + '@xtuc/ieee754@1.2.0': {} '@xtuc/long@4.2.2': {} @@ -8383,9 +8457,26 @@ snapshots: uuid: 8.0.0 xml2js: 0.6.2 + axios-ntlm@1.4.4(debug@4.4.1): + dependencies: + axios: 1.9.0(debug@4.4.1) + des.js: 1.1.0 + dev-null: 0.1.1 + js-md4: 0.3.2 + transitivePeerDependencies: + - debug + axios@1.8.4: dependencies: - follow-redirects: 1.15.9 + follow-redirects: 1.15.9(debug@4.4.1) + form-data: 4.0.2 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + axios@1.9.0(debug@4.4.1): + dependencies: + follow-redirects: 1.15.9(debug@4.4.1) form-data: 4.0.2 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -8897,6 +8988,10 @@ snapshots: dependencies: ms: 2.1.3 + debug@4.4.1: + dependencies: + ms: 2.1.3 + decimal.js@10.5.0: {} dedent@1.5.3: {} @@ -8934,6 +9029,11 @@ snapshots: dequal@2.0.3: {} + des.js@1.1.0: + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + detect-indent@6.1.0: optional: true @@ -8944,6 +9044,8 @@ snapshots: detect-node@2.1.0: optional: true + dev-null@0.1.1: {} + dezalgo@1.0.4: dependencies: asap: 2.0.6 @@ -9568,7 +9670,9 @@ snapshots: flatted@3.3.3: {} - follow-redirects@1.15.9: {} + follow-redirects@1.15.9(debug@4.4.1): + optionalDependencies: + debug: 4.4.1 for-each@0.3.5: dependencies: @@ -9609,6 +9713,12 @@ snapshots: dezalgo: 1.0.4 once: 1.4.0 + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.2.2 + dezalgo: 1.0.4 + once: 1.4.0 + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -10507,6 +10617,8 @@ snapshots: js-cookie@3.0.5: optional: true + js-md4@0.3.2: {} + js-stringify@1.0.2: optional: true @@ -10833,6 +10945,8 @@ snapshots: mimic-function@5.0.1: {} + minimalistic-assert@1.0.1: {} + minimatch@10.0.1: dependencies: brace-expansion: 2.0.1 @@ -11228,6 +11342,13 @@ snapshots: neo-async@2.6.2: {} + nestjs-soap@3.0.4(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)): + dependencies: + '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + soap: 1.1.12 + transitivePeerDependencies: + - supports-color + nestjs-telegraf@2.8.1(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.0.20(@nestjs/common@11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)(telegraf@4.16.3)(typescript@5.8.3): dependencies: '@nestjs/common': 11.0.20(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -11933,6 +12054,8 @@ snapshots: sax@1.2.1: {} + sax@1.4.1: {} + schema-utils@3.3.0: dependencies: '@types/json-schema': 7.0.15 @@ -12062,6 +12185,21 @@ snapshots: slugify@1.6.6: {} + soap@1.1.12: + dependencies: + axios: 1.9.0(debug@4.4.1) + axios-ntlm: 1.4.4(debug@4.4.1) + debug: 4.4.1 + formidable: 3.5.4 + get-stream: 6.0.1 + lodash: 4.17.21 + sax: 1.4.1 + strip-bom: 3.0.0 + whatwg-mimetype: 4.0.0 + xml-crypto: 6.1.2 + transitivePeerDependencies: + - supports-color + sonic-boom@4.2.0: dependencies: atomic-sleep: 1.0.0 @@ -12639,6 +12777,8 @@ snapshots: - esbuild - uglify-js + whatwg-mimetype@4.0.0: {} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -12741,6 +12881,12 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 3.0.7 + xml-crypto@6.1.2: + dependencies: + '@xmldom/is-dom-node': 1.0.1 + '@xmldom/xmldom': 0.8.10 + xpath: 0.0.33 + xml2js@0.6.2: dependencies: sax: 1.2.1 @@ -12748,6 +12894,8 @@ snapshots: xmlbuilder@11.0.1: {} + xpath@0.0.33: {} + xtend@4.0.2: {} y18n@5.0.8: {} diff --git a/src/configs/parsian.config.ts b/src/configs/parsian.config.ts new file mode 100644 index 0000000..0186f16 --- /dev/null +++ b/src/configs/parsian.config.ts @@ -0,0 +1,22 @@ +import { ConfigService } from "@nestjs/config"; + +export function parsianConfig() { + return { + inject: [ConfigService], + useFactory: (configService: ConfigService) => ({ + loginAccount: configService.getOrThrow("PARSIAN_LOGIN_ACCOUNT"), + callBackUrl: configService.getOrThrow("CALLBACK_URL"), + }), + }; +} + +export interface IParsianConfig { + loginAccount: string; + callBackUrl: string; +} + +export const ParsianConfiguration = { + Address: "https://pec.shaparak.ir/NewIPG/?token=", + WsdlInitial: "https://pec.shaparak.ir/NewIPGServices/Sale/SaleService.asmx?wsdl", + WsdlConfirm: "https://pec.shaparak.ir/NewIPGServices/Confirm/ConfirmService.asmx?wsdl", +}; diff --git a/src/modules/payments/DTO/deposit-wallet.dto.ts b/src/modules/payments/DTO/deposit-wallet.dto.ts index a6be0b5..b52c213 100755 --- a/src/modules/payments/DTO/deposit-wallet.dto.ts +++ b/src/modules/payments/DTO/deposit-wallet.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; -import { IsEnum, IsInt, IsNotEmpty, IsOptional, IsUUID, IsUrl, Min } from "class-validator"; +import { IsEnum, IsInt, IsNotEmpty, IsOptional, IsUUID, IsUrl } from "class-validator"; import { WalletMessage } from "../../../common/enums/message.enum"; import { TransferType } from "../enums/payment-type.enum"; @@ -7,7 +7,7 @@ import { TransferType } from "../enums/payment-type.enum"; export class DepositDto { @IsNotEmpty({ message: WalletMessage.AMOUNT_REQUIRED }) @IsInt({ message: WalletMessage.AMOUNT_MUST_BE_INTEGER }) - @Min(100_000, { message: WalletMessage.AMOUNT_MINIMUM }) + // @Min(100_000, { message: WalletMessage.AMOUNT_MINIMUM }) @ApiProperty({ description: "Amount to charge", example: 100_000 }) amount: number; } diff --git a/src/modules/payments/constants/index.ts b/src/modules/payments/constants/index.ts index 3c109c8..1933151 100755 --- a/src/modules/payments/constants/index.ts +++ b/src/modules/payments/constants/index.ts @@ -1,4 +1,10 @@ export const ZARINPAL_CONFIG = "ZARINPAL_CONFIG"; +export const PARSIAN_CONFIG = "PARSIAN_CONFIG"; + +// SOAP Client Names +export const PARSIAN_SALE_CLIENT = "PARSIAN_SALE_CLIENT"; +export const PARSIAN_CONFIRM_CLIENT = "PARSIAN_CONFIRM_CLIENT"; + //=============================================== export const PAYMENT = Object.freeze({ diff --git a/src/modules/payments/enums/gateway.enum.ts b/src/modules/payments/enums/gateway.enum.ts index ae9eb46..91104ac 100755 --- a/src/modules/payments/enums/gateway.enum.ts +++ b/src/modules/payments/enums/gateway.enum.ts @@ -1,3 +1,4 @@ export enum GatewayEnum { ZARINPAL = "zarinpal", + PARSIAN = "parsian", } diff --git a/src/modules/payments/factories/payment.factory.ts b/src/modules/payments/factories/payment.factory.ts index 34046af..900eb30 100755 --- a/src/modules/payments/factories/payment.factory.ts +++ b/src/modules/payments/factories/payment.factory.ts @@ -2,6 +2,7 @@ import { BadGatewayException, Injectable } from "@nestjs/common"; import { PaymentMessage } from "../../../common/enums/message.enum"; import { GatewayEnum } from "../enums/gateway.enum"; +import { ParsianGateway } from "../gateways/parsian.gateway"; import { ZarinpalGateway } from "../gateways/zarinpal.gateway"; import { IPaymentGateway, IPaymentGatewayFactory } from "../interfaces/IPayment"; import { GatewayType } from "../types/gateway.type"; @@ -10,13 +11,17 @@ import { GatewayType } from "../types/gateway.type"; export class PaymentGatewayFactory implements IPaymentGatewayFactory { private readonly gateways: Map; - constructor(private readonly zarinpalGateway: ZarinpalGateway) { + constructor( + private readonly zarinpalGateway: ZarinpalGateway, + private readonly parsianGateway: ParsianGateway, + ) { this.gateways = new Map(); this.initializeGateways(); } private initializeGateways(): void { this.gateways.set(GatewayEnum.ZARINPAL, this.zarinpalGateway); + this.gateways.set(GatewayEnum.PARSIAN, this.parsianGateway); } getPaymentGateway(provider: GatewayType): IPaymentGateway { diff --git a/src/modules/payments/gateways/parsian.gateway.ts b/src/modules/payments/gateways/parsian.gateway.ts new file mode 100644 index 0000000..913f2f0 --- /dev/null +++ b/src/modules/payments/gateways/parsian.gateway.ts @@ -0,0 +1,165 @@ +import { Inject, Injectable, InternalServerErrorException, Logger } from "@nestjs/common"; +import { Client } from "nestjs-soap"; + +import { PaymentMessage } from "../../../common/enums/message.enum"; +import { IParsianConfig, ParsianConfiguration } from "../../../configs/parsian.config"; +import { PARSIAN_CONFIG, PARSIAN_CONFIRM_CLIENT, PARSIAN_SALE_CLIENT } from "../constants"; +import { GatewayEnum } from "../enums/gateway.enum"; +import { + IPaymentGateway, + IPaymentVerifyParams, + IProcessPaymentParams, + ParsianCallbackData, + ParsianConfirmPaymentResponse, + ParsianSalePaymentResponse, +} from "../interfaces/IPayment"; + +@Injectable() +export class ParsianGateway implements IPaymentGateway { + private readonly logger = new Logger(ParsianGateway.name); + + constructor( + @Inject(PARSIAN_CONFIG) private readonly config: IParsianConfig, + @Inject(PARSIAN_SALE_CLIENT) private readonly saleClient: Client, + @Inject(PARSIAN_CONFIRM_CLIENT) private readonly confirmClient: Client, + ) { + // Log the initialization status + this.logger.log(`ParsianGateway initialized - Sale client: ${!!this.saleClient}, Confirm client: ${!!this.confirmClient}`); + } + + async processPayment(processParams: IProcessPaymentParams) { + try { + // Check if SOAP client is available + if (!this.saleClient) { + this.logger.error("SOAP sale client is not initialized"); + throw new InternalServerErrorException({ + message: "Payment service temporarily unavailable. Please try again later.", + code: "SOAP_CLIENT_NOT_INITIALIZED", + }); + } + + const requestData = { + requestData: { + LoginAccount: this.config.loginAccount, + OrderId: processParams.invoiceId || Date.now().toString(), + Amount: processParams.amount, + CallBackUrl: `${this.config.callBackUrl}/${GatewayEnum.PARSIAN}`, + Originator: processParams.mobile, + AdditionalData: processParams.description, + }, + }; + + this.logger.log(`Processing payment request:`, { + LoginAccount: this.config.loginAccount, + OrderId: requestData.requestData.OrderId, + Amount: processParams.amount, + CallBackUrl: `${this.config.callBackUrl}/${GatewayEnum.PARSIAN}`, + }); + + const result = (await this.saleClient.SalePaymentRequestAsync(requestData))[0] as ParsianSalePaymentResponse; + + if (!result || !result.SalePaymentRequestResult) { + this.logger.error("SalePaymentRequestResult not found in response"); + throw new InternalServerErrorException({ message: PaymentMessage.ERROR_IN_PROCESS_PAYMENT, result }); + } + + const saleResult = result.SalePaymentRequestResult; + + if (!saleResult.Token || Number(saleResult.Token) === 0) { + this.logger.error(`Parsian payment request failed: ${saleResult.Message}`); + throw new InternalServerErrorException({ message: PaymentMessage.ERROR_IN_PROCESS_PAYMENT, result }); + } + + this.logger.log(`Payment request successful - Token: ${saleResult.Token}`); + + return { + redirectUrl: ParsianConfiguration.Address + saleResult.Token, + message: saleResult.Message || "Payment initialized successfully", + reference: saleResult.Token, + }; + } catch (error) { + this.logger.error(`Payment processing error:`, error); + throw new InternalServerErrorException(PaymentMessage.ERROR_IN_PROCESS_PAYMENT); + } + } + + async verifyPayment(verifyParams: IPaymentVerifyParams) { + try { + // Check if SOAP client is available + if (!this.confirmClient) { + this.logger.error("SOAP confirm client is not initialized"); + throw new InternalServerErrorException({ + message: "Payment verification service temporarily unavailable. Please try again later.", + code: "SOAP_CLIENT_NOT_INITIALIZED", + }); + } + + // For Parsian, the verification should happen in the callback + // This method might be called with callback data + const callbackData = verifyParams as unknown as ParsianCallbackData; + + // Check if this is a successful callback + if (Number(callbackData.RRN) < 0 || Number(callbackData.status) !== 0) { + this.logger.warn(`Payment failed in callback - RRN: ${callbackData.RRN}, Status: ${callbackData.status}`); + return { + code: Number(callbackData.status) || -1, + message: "Payment failed", + ref_id: 0, + card_hash: "", + card_pan: "", + fee_type: "", + fee: 0, + }; + } + + // Confirm the payment with Parsian + const requestData = { + requestData: { + LoginAccount: this.config.loginAccount, + Token: callbackData.Token, + }, + }; + + this.logger.log(`Confirming payment:`, { + LoginAccount: this.config.loginAccount, + Token: callbackData.Token, + }); + + const result = (await this.confirmClient.ConfirmPaymentAsync(requestData)) as ParsianConfirmPaymentResponse; + + if (!result || !result.ConfirmPaymentResult) { + this.logger.error("ConfirmPaymentResult not found in response"); + throw new InternalServerErrorException(PaymentMessage.ERROR_IN_VERIFY_PAYMENT); + } + + const confirmResult = result.ConfirmPaymentResult; + + if (confirmResult.Status === 0) { + this.logger.log(`Payment confirmed successfully - RRN: ${confirmResult.RRN}`); + return { + code: 100, // Success code + message: confirmResult.Message || "Payment confirmed successfully", + ref_id: Number(confirmResult.RRN) || 0, + card_hash: confirmResult.CardNumberMasked || "", + card_pan: confirmResult.CardNumberMasked || "", + fee_type: "", + fee: 0, + }; + } else { + this.logger.warn(`Payment confirmation failed - Status: ${confirmResult.Status}, Message: ${confirmResult.Message}`); + return { + code: confirmResult.Status, + message: confirmResult.Message || "Payment confirmation failed", + ref_id: 0, + card_hash: confirmResult.CardNumberMasked || "", + card_pan: confirmResult.CardNumberMasked || "", + fee_type: "", + fee: 0, + }; + } + } catch (error) { + this.logger.error(`Payment verification error:`, error); + throw new InternalServerErrorException(PaymentMessage.ERROR_IN_VERIFY_PAYMENT); + } + } +} diff --git a/src/modules/payments/interfaces/IPayment.ts b/src/modules/payments/interfaces/IPayment.ts index 5646603..3474d1d 100755 --- a/src/modules/payments/interfaces/IPayment.ts +++ b/src/modules/payments/interfaces/IPayment.ts @@ -68,6 +68,7 @@ interface IZarinPalPGRequestData { interface ZarinpalError { code: number; message: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any validations?: any[]; } @@ -91,6 +92,50 @@ export interface ZarinPalPGVerifyData { errors: ZarinpalError[]; } +//--------------------- Parsian -------------------------------- + +export interface ParsianSalePaymentRequest { + LoginAccount: string; + OrderId: string; + Amount: number; + CallBackUrl: string; + Originator?: string; + AdditionalData?: string; +} + +export interface ParsianSalePaymentResponse { + SalePaymentRequestResult: { + Token: string; + Status: number; + Message: string; + }; +} + +export interface ParsianConfirmPaymentRequest { + LoginAccount: string; + Token: string; +} + +export interface ParsianConfirmPaymentResponse { + ConfirmPaymentResult: { + Status: number; + Token: string; + RRN: string; + Message: string; + CardNumberMasked: string; + }; +} + +export interface ParsianCallbackData { + Token: string; + OrderId: string; + TerminalNo: string; + RRN: string; + status: string; + Amount: string; + STraceNo: string; +} + export interface PaymentVerificationResult { status: PaymentStatus; payment: Payment; diff --git a/src/modules/payments/payments.module.ts b/src/modules/payments/payments.module.ts index 26bd049..a2f84c9 100755 --- a/src/modules/payments/payments.module.ts +++ b/src/modules/payments/payments.module.ts @@ -1,23 +1,26 @@ import { BullModule } from "@nestjs/bullmq"; import { Module } from "@nestjs/common"; import { TypeOrmModule } from "@nestjs/typeorm"; +import { SoapModule } from "nestjs-soap"; -import { PAYMENT, ZARINPAL_CONFIG } from "./constants"; +import { PARSIAN_CONFIG, PARSIAN_CONFIRM_CLIENT, PARSIAN_SALE_CLIENT, PAYMENT, ZARINPAL_CONFIG } from "./constants"; import { BankAccount } from "./entities/bank-account.entity"; +import { DepositRequest } from "./entities/deposit-request.entity"; +import { PaymentGateway } from "./entities/payment-gateway.entity"; import { Payment } from "./entities/payment.entity"; import { PaymentGatewayFactory } from "./factories/payment.factory"; +import { ParsianGateway } from "./gateways/parsian.gateway"; import { ZarinpalGateway } from "./gateways/zarinpal.gateway"; import { PaymentsController } from "./payments.controller"; import { PaymentsService } from "./providers/payments.service"; import { PaymentProcessor } from "./queue/payment.processor"; import { BankAccountsRepository } from "./repositories/bank-accounts.repository"; -import { PaymentsRepository } from "./repositories/payments.repository"; -import { zarinpalConfig } from "../../configs/zarinpal.config"; import { WalletsModule } from "../wallets/wallets.module"; -import { DepositRequest } from "./entities/deposit-request.entity"; -import { PaymentGateway } from "./entities/payment-gateway.entity"; import { DepositRequestsRepository } from "./repositories/deposit-requests.repository"; import { PaymentGatewaysRepository } from "./repositories/payment-gateway.repository"; +import { PaymentsRepository } from "./repositories/payments.repository"; +import { ParsianConfiguration, parsianConfig } from "../../configs/parsian.config"; +import { zarinpalConfig } from "../../configs/zarinpal.config"; import { NotificationModule } from "../notifications/notifications.module"; import { ReferralsModule } from "../referrals/referrals.module"; @@ -31,6 +34,18 @@ import { ReferralsModule } from "../referrals/referrals.module"; removeOnFail: false, }, }), + SoapModule.registerAsync({ + clientName: PARSIAN_SALE_CLIENT, + useFactory: () => ({ + uri: ParsianConfiguration.WsdlInitial, + }), + }), + SoapModule.registerAsync({ + clientName: PARSIAN_CONFIRM_CLIENT, + useFactory: () => ({ + uri: ParsianConfiguration.WsdlConfirm, + }), + }), WalletsModule, NotificationModule, ReferralsModule, @@ -40,6 +55,7 @@ import { ReferralsModule } from "../referrals/referrals.module"; PaymentsService, PaymentGatewayFactory, ZarinpalGateway, + ParsianGateway, PaymentsRepository, BankAccountsRepository, PaymentGatewaysRepository, @@ -50,6 +66,11 @@ import { ReferralsModule } from "../referrals/referrals.module"; useFactory: zarinpalConfig().useFactory, inject: zarinpalConfig().inject, }, + { + provide: PARSIAN_CONFIG, + useFactory: parsianConfig().useFactory, + inject: parsianConfig().inject, + }, ], exports: [PaymentsService], }) diff --git a/src/modules/payments/types/gateway.type.ts b/src/modules/payments/types/gateway.type.ts index 856119c..909ac9f 100755 --- a/src/modules/payments/types/gateway.type.ts +++ b/src/modules/payments/types/gateway.type.ts @@ -1 +1 @@ -export type GatewayType = "zarinpal"; +export type GatewayType = "zarinpal" | "parsian";