add payment service

This commit is contained in:
Emily
2025-03-26 15:30:22 +01:00
parent 94a28b31d3
commit 1f9ef5d18c
13 changed files with 1542 additions and 0 deletions

View File

@@ -14,6 +14,9 @@
"consumer:shared": "ts-node scripts/consumer/shared.ts",
"consumer:deploy": "ts-node scripts/consumer/deploy.ts",
"payments:shared": "ts-node scripts/payments/shared.ts",
"payments:deploy": "ts-node scripts/payments/deploy.ts",
"email:deploy": "ts-node scripts/email/deploy.ts"
},
"keywords": [],

9
payments/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
node_modules
static
ecosystem.config.cjs
ecosystem.config.js
dist
start_dev.js
package-lock.json
build_all.bat
src/shared

28
payments/package.json Normal file
View File

@@ -0,0 +1,28 @@
{
"name": "payments",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"dev": "node scripts/start_dev.js",
"dev_prod": "node scripts/start_dev_prod.js",
"compile": "tsc",
"build": "npm run compile",
"workspace:shared": "ts-node ../scripts/payments/shared.ts",
"workspace:deploy": "ts-node ../scripts/payments/deploy.ts"
},
"keywords": [],
"author": "Emily",
"license": "MIT",
"dependencies": {
"@types/express": "^5.0.1",
"cors": "^2.8.5",
"express": "^4.21.2",
"mongoose": "^8.13.0",
"stripe": "^17.7.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/cors": "^2.8.17"
}
}

901
payments/pnpm-lock.yaml generated Normal file
View File

@@ -0,0 +1,901 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
'@types/express':
specifier: ^5.0.1
version: 5.0.1
cors:
specifier: ^2.8.5
version: 2.8.5
express:
specifier: ^4.21.2
version: 4.21.2
mongoose:
specifier: ^8.13.0
version: 8.13.0
stripe:
specifier: ^17.7.0
version: 17.7.0
zod:
specifier: ^3.24.2
version: 3.24.2
devDependencies:
'@types/cors':
specifier: ^2.8.17
version: 2.8.17
packages:
'@mongodb-js/saslprep@1.2.0':
resolution: {integrity: sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==}
'@types/body-parser@1.19.5':
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
'@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
'@types/cors@2.8.17':
resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
'@types/express-serve-static-core@5.0.6':
resolution: {integrity: sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==}
'@types/express@5.0.1':
resolution: {integrity: sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==}
'@types/http-errors@2.0.4':
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
'@types/mime@1.3.5':
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
'@types/node@22.13.13':
resolution: {integrity: sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==}
'@types/qs@6.9.18':
resolution: {integrity: sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==}
'@types/range-parser@1.2.7':
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
'@types/send@0.17.4':
resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
'@types/serve-static@1.15.7':
resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
'@types/webidl-conversions@7.0.3':
resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==}
'@types/whatwg-url@11.0.5':
resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==}
accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
array-flatten@1.1.1:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
body-parser@1.20.3:
resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
bson@6.10.3:
resolution: {integrity: sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==}
engines: {node: '>=16.20.1'}
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
call-bound@1.0.4:
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
engines: {node: '>= 0.4'}
content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
cookie-signature@1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
cookie@0.7.1:
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
engines: {node: '>= 0.6'}
cors@2.8.5:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
debug@4.4.0:
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
destroy@1.2.0:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
encodeurl@1.0.2:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
encodeurl@2.0.0:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
es-errors@1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
etag@1.8.1:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
express@4.21.2:
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
engines: {node: '>= 0.10.0'}
finalhandler@1.3.1:
resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
engines: {node: '>= 0.8'}
forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
http-errors@2.0.0:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
kareem@2.6.3:
resolution: {integrity: sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==}
engines: {node: '>=12.0.0'}
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
memory-pager@1.5.0:
resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
merge-descriptors@1.0.3:
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
methods@1.1.2:
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
engines: {node: '>= 0.6'}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
hasBin: true
mongodb-connection-string-url@3.0.2:
resolution: {integrity: sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==}
mongodb@6.15.0:
resolution: {integrity: sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==}
engines: {node: '>=16.20.1'}
peerDependencies:
'@aws-sdk/credential-providers': ^3.188.0
'@mongodb-js/zstd': ^1.1.0 || ^2.0.0
gcp-metadata: ^5.2.0
kerberos: ^2.0.1
mongodb-client-encryption: '>=6.0.0 <7'
snappy: ^7.2.2
socks: ^2.7.1
peerDependenciesMeta:
'@aws-sdk/credential-providers':
optional: true
'@mongodb-js/zstd':
optional: true
gcp-metadata:
optional: true
kerberos:
optional: true
mongodb-client-encryption:
optional: true
snappy:
optional: true
socks:
optional: true
mongoose@8.13.0:
resolution: {integrity: sha512-e/iYV1mPeOkg+SWAMHzt3t42/EZyER3OB1H2pjP9C3vQ+Qb5DMeV9Kb+YCUycKgScA3fbwL7dKG4EpinGlg21g==}
engines: {node: '>=16.20.1'}
mpath@0.9.0:
resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==}
engines: {node: '>=4.0.0'}
mquery@5.0.0:
resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==}
engines: {node: '>=14.0.0'}
ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
object-inspect@1.13.4:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
path-to-regexp@0.1.12:
resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
qs@6.13.0:
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
engines: {node: '>=0.6'}
qs@6.14.0:
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
engines: {node: '>=0.6'}
range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
raw-body@2.5.2:
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
engines: {node: '>= 0.8'}
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
send@0.19.0:
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
engines: {node: '>= 0.8.0'}
serve-static@1.16.2:
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
engines: {node: '>= 0.8.0'}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
side-channel-list@1.0.0:
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
engines: {node: '>= 0.4'}
side-channel-map@1.0.1:
resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
engines: {node: '>= 0.4'}
side-channel-weakmap@1.0.2:
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
engines: {node: '>= 0.4'}
side-channel@1.1.0:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
sift@17.1.3:
resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==}
sparse-bitfield@3.0.3:
resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
stripe@17.7.0:
resolution: {integrity: sha512-aT2BU9KkizY9SATf14WhhYVv2uOapBWX0OFWF4xvcj1mPaNotlSc2CsxpS4DS46ZueSppmCF5BX1sNYBtwBvfw==}
engines: {node: '>=12.*'}
toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
tr46@5.1.0:
resolution: {integrity: sha512-IUWnUK7ADYR5Sl1fZlO1INDUhVhatWl7BtJWsIhwJ0UAK7ilzzIa8uIqOO/aYVWHZPJkKbEL+362wrzoeRF7bw==}
engines: {node: '>=18'}
type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
undici-types@6.20.0:
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
webidl-conversions@7.0.0:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
whatwg-url@14.2.0:
resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
engines: {node: '>=18'}
zod@3.24.2:
resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==}
snapshots:
'@mongodb-js/saslprep@1.2.0':
dependencies:
sparse-bitfield: 3.0.3
'@types/body-parser@1.19.5':
dependencies:
'@types/connect': 3.4.38
'@types/node': 22.13.13
'@types/connect@3.4.38':
dependencies:
'@types/node': 22.13.13
'@types/cors@2.8.17':
dependencies:
'@types/node': 22.13.13
'@types/express-serve-static-core@5.0.6':
dependencies:
'@types/node': 22.13.13
'@types/qs': 6.9.18
'@types/range-parser': 1.2.7
'@types/send': 0.17.4
'@types/express@5.0.1':
dependencies:
'@types/body-parser': 1.19.5
'@types/express-serve-static-core': 5.0.6
'@types/serve-static': 1.15.7
'@types/http-errors@2.0.4': {}
'@types/mime@1.3.5': {}
'@types/node@22.13.13':
dependencies:
undici-types: 6.20.0
'@types/qs@6.9.18': {}
'@types/range-parser@1.2.7': {}
'@types/send@0.17.4':
dependencies:
'@types/mime': 1.3.5
'@types/node': 22.13.13
'@types/serve-static@1.15.7':
dependencies:
'@types/http-errors': 2.0.4
'@types/node': 22.13.13
'@types/send': 0.17.4
'@types/webidl-conversions@7.0.3': {}
'@types/whatwg-url@11.0.5':
dependencies:
'@types/webidl-conversions': 7.0.3
accepts@1.3.8:
dependencies:
mime-types: 2.1.35
negotiator: 0.6.3
array-flatten@1.1.1: {}
body-parser@1.20.3:
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
http-errors: 2.0.0
iconv-lite: 0.4.24
on-finished: 2.4.1
qs: 6.13.0
raw-body: 2.5.2
type-is: 1.6.18
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
bson@6.10.3: {}
bytes@3.1.2: {}
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
function-bind: 1.1.2
call-bound@1.0.4:
dependencies:
call-bind-apply-helpers: 1.0.2
get-intrinsic: 1.3.0
content-disposition@0.5.4:
dependencies:
safe-buffer: 5.2.1
content-type@1.0.5: {}
cookie-signature@1.0.6: {}
cookie@0.7.1: {}
cors@2.8.5:
dependencies:
object-assign: 4.1.1
vary: 1.1.2
debug@2.6.9:
dependencies:
ms: 2.0.0
debug@4.4.0:
dependencies:
ms: 2.1.3
depd@2.0.0: {}
destroy@1.2.0: {}
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
es-errors: 1.3.0
gopd: 1.2.0
ee-first@1.1.1: {}
encodeurl@1.0.2: {}
encodeurl@2.0.0: {}
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
escape-html@1.0.3: {}
etag@1.8.1: {}
express@4.21.2:
dependencies:
accepts: 1.3.8
array-flatten: 1.1.1
body-parser: 1.20.3
content-disposition: 0.5.4
content-type: 1.0.5
cookie: 0.7.1
cookie-signature: 1.0.6
debug: 2.6.9
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 1.3.1
fresh: 0.5.2
http-errors: 2.0.0
merge-descriptors: 1.0.3
methods: 1.1.2
on-finished: 2.4.1
parseurl: 1.3.3
path-to-regexp: 0.1.12
proxy-addr: 2.0.7
qs: 6.13.0
range-parser: 1.2.1
safe-buffer: 5.2.1
send: 0.19.0
serve-static: 1.16.2
setprototypeof: 1.2.0
statuses: 2.0.1
type-is: 1.6.18
utils-merge: 1.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
finalhandler@1.3.1:
dependencies:
debug: 2.6.9
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
parseurl: 1.3.3
statuses: 2.0.1
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
forwarded@0.2.0: {}
fresh@0.5.2: {}
function-bind@1.1.2: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
es-define-property: 1.0.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
function-bind: 1.1.2
get-proto: 1.0.1
gopd: 1.2.0
has-symbols: 1.1.0
hasown: 2.0.2
math-intrinsics: 1.1.0
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
gopd@1.2.0: {}
has-symbols@1.1.0: {}
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
http-errors@2.0.0:
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.1
toidentifier: 1.0.1
iconv-lite@0.4.24:
dependencies:
safer-buffer: 2.1.2
inherits@2.0.4: {}
ipaddr.js@1.9.1: {}
kareem@2.6.3: {}
math-intrinsics@1.1.0: {}
media-typer@0.3.0: {}
memory-pager@1.5.0: {}
merge-descriptors@1.0.3: {}
methods@1.1.2: {}
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
mime@1.6.0: {}
mongodb-connection-string-url@3.0.2:
dependencies:
'@types/whatwg-url': 11.0.5
whatwg-url: 14.2.0
mongodb@6.15.0:
dependencies:
'@mongodb-js/saslprep': 1.2.0
bson: 6.10.3
mongodb-connection-string-url: 3.0.2
mongoose@8.13.0:
dependencies:
bson: 6.10.3
kareem: 2.6.3
mongodb: 6.15.0
mpath: 0.9.0
mquery: 5.0.0
ms: 2.1.3
sift: 17.1.3
transitivePeerDependencies:
- '@aws-sdk/credential-providers'
- '@mongodb-js/zstd'
- gcp-metadata
- kerberos
- mongodb-client-encryption
- snappy
- socks
- supports-color
mpath@0.9.0: {}
mquery@5.0.0:
dependencies:
debug: 4.4.0
transitivePeerDependencies:
- supports-color
ms@2.0.0: {}
ms@2.1.3: {}
negotiator@0.6.3: {}
object-assign@4.1.1: {}
object-inspect@1.13.4: {}
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
parseurl@1.3.3: {}
path-to-regexp@0.1.12: {}
proxy-addr@2.0.7:
dependencies:
forwarded: 0.2.0
ipaddr.js: 1.9.1
punycode@2.3.1: {}
qs@6.13.0:
dependencies:
side-channel: 1.1.0
qs@6.14.0:
dependencies:
side-channel: 1.1.0
range-parser@1.2.1: {}
raw-body@2.5.2:
dependencies:
bytes: 3.1.2
http-errors: 2.0.0
iconv-lite: 0.4.24
unpipe: 1.0.0
safe-buffer@5.2.1: {}
safer-buffer@2.1.2: {}
send@0.19.0:
dependencies:
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
encodeurl: 1.0.2
escape-html: 1.0.3
etag: 1.8.1
fresh: 0.5.2
http-errors: 2.0.0
mime: 1.6.0
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
statuses: 2.0.1
transitivePeerDependencies:
- supports-color
serve-static@1.16.2:
dependencies:
encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
send: 0.19.0
transitivePeerDependencies:
- supports-color
setprototypeof@1.2.0: {}
side-channel-list@1.0.0:
dependencies:
es-errors: 1.3.0
object-inspect: 1.13.4
side-channel-map@1.0.1:
dependencies:
call-bound: 1.0.4
es-errors: 1.3.0
get-intrinsic: 1.3.0
object-inspect: 1.13.4
side-channel-weakmap@1.0.2:
dependencies:
call-bound: 1.0.4
es-errors: 1.3.0
get-intrinsic: 1.3.0
object-inspect: 1.13.4
side-channel-map: 1.0.1
side-channel@1.1.0:
dependencies:
es-errors: 1.3.0
object-inspect: 1.13.4
side-channel-list: 1.0.0
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
sift@17.1.3: {}
sparse-bitfield@3.0.3:
dependencies:
memory-pager: 1.5.0
statuses@2.0.1: {}
stripe@17.7.0:
dependencies:
'@types/node': 22.13.13
qs: 6.14.0
toidentifier@1.0.1: {}
tr46@5.1.0:
dependencies:
punycode: 2.3.1
type-is@1.6.18:
dependencies:
media-typer: 0.3.0
mime-types: 2.1.35
undici-types@6.20.0: {}
unpipe@1.0.0: {}
utils-merge@1.0.1: {}
vary@1.1.2: {}
webidl-conversions@7.0.0: {}
whatwg-url@14.2.0:
dependencies:
tr46: 5.1.0
webidl-conversions: 7.0.0
zod@3.24.2: {}

6
payments/src/Utils.ts Normal file
View File

@@ -0,0 +1,6 @@
import type { Response } from "express";
export function sendJson(res: Response, status: number, data: Record<string, any>): void {
res.status(status).json(data);
}

0
payments/src/index.ts Normal file
View File

View File

@@ -0,0 +1,43 @@
import { json, Router } from 'express';
import z from 'zod';
import { getPlanFromId } from '../shared/data/PLANS';
import StripeService from '../services/StripeService';
import { sendJson } from '../Utils';
import { ProjectModel } from '../shared/schema/project/ProjectSchema';
export const paymentRouter = Router();
export const ZBodyCreatePayment = z.object({
pid: z.string(),
plan_id: z.number()
})
paymentRouter.post('/create', json(), async (req, res) => {
try {
const createPaymentData = ZBodyCreatePayment.parse(req.body);
const plan = getPlanFromId(createPaymentData.plan_id);
if (!plan) return sendJson(res, 400, { error: 'plan not found' });
const project = await ProjectModel.findById(createPaymentData.pid);
if (!project) return sendJson(res, 400, { error: 'project not found' });
if (!project.customer_id) return sendJson(res, 400, { error: 'project have no customer_id' });
const price = StripeService.testMode ? plan.PRICE_TEST : plan.PRICE;
const checkout = await StripeService.createPayment(
price,
'https://dashboard.litlyx.com/payment_ok',
createPaymentData.pid,
project.customer_id
);
if (!checkout) return sendJson(res, 400, { error: 'cannot create payment' });
return sendJson(res, 200, { url: checkout.url });
} catch (ex) {
res.status(500).json({ error: ex.message });
}
});

View File

@@ -0,0 +1,19 @@
import { json, Router } from 'express';
export const webhookRouter = Router();
webhookRouter.get('/', json(), async (req, res) => {
try {
const signature = req.header('stripe-signature');
if (!signature) {
console.error('No signature on the webhook')
}
} catch (ex) {
res.status(500).json({ error: ex.message });
}
});

View File

@@ -0,0 +1,209 @@
import Stripe from "stripe";
class StripeService {
private stripe?: Stripe;
private privateKey?: string;
private webhookSecret?: string;
public testMode?: boolean;
init(privateKey: string, webhookSecret: string, testMode: boolean = false) {
this.privateKey = privateKey;
this.webhookSecret = webhookSecret;
this.stripe = new Stripe(this.privateKey);
this.testMode = testMode;
}
parseWebhook(body: any, sig: string) {
if (!this.stripe) throw Error('Stripe not initialized');
if (!this.webhookSecret) {
console.error('Stripe not initialized')
return;
}
return this.stripe.webhooks.constructEvent(body, sig, this.webhookSecret);
}
async createOnetimePayment(price: string, success_url: string, pid: string, customer?: string) {
if (!this.stripe) throw Error('Stripe not initialized');
const checkout = await this.stripe.checkout.sessions.create({
allow_promotion_codes: true,
payment_method_types: ['card'],
invoice_creation: {
enabled: true,
},
line_items: [
{ price, quantity: 1 }
],
payment_intent_data: {
metadata: {
pid, price
}
},
customer,
success_url,
mode: 'payment'
});
return checkout;
}
async createPayment(price: string, success_url: string, pid: string, customer: string) {
if (!this.stripe) throw Error('Stripe not initialized');
const checkout = await this.stripe.checkout.sessions.create({
allow_promotion_codes: true,
payment_method_types: ['card'],
line_items: [
{ price, quantity: 1 }
],
subscription_data: {
metadata: { pid },
},
customer,
success_url,
mode: 'subscription'
});
return checkout;
}
async getPriceData(priceId: string) {
if (!this.stripe) throw Error('Stripe not initialized');
const priceData = await this.stripe.prices.retrieve(priceId);
return priceData;
}
async deleteSubscription(subscriptionId: string) {
if (!this.stripe) throw Error('Stripe not initialized');
const subscription = await this.stripe.subscriptions.cancel(subscriptionId);
return subscription;
}
async getSubscription(subscriptionId: string) {
if (!this.stripe) throw Error('Stripe not initialized');
const subscription = await this.stripe.subscriptions.retrieve(subscriptionId);
return subscription;
}
async getAllSubscriptions(customer_id: string) {
if (!this.stripe) throw Error('Stripe not initialized');
const subscriptions = await this.stripe.subscriptions.list({ customer: customer_id });
return subscriptions;
}
async getInvoices(customer_id: string) {
if (!this.stripe) throw Error('Stripe not initialized');
const invoices = await this.stripe?.invoices.list({ customer: customer_id });
return invoices;
}
async getCustomer(customer_id: string) {
if (!this.stripe) throw Error('Stripe not initialized');
const customer = await this.stripe.customers.retrieve(customer_id, { expand: [] })
return customer;
}
async createCustomer(email: string) {
if (!this.stripe) throw Error('Stripe not initialized');
const customer = await this.stripe.customers.create({ email });
return customer;
}
async setCustomerInfo(customer_id: string, address: { line1: string, line2: string, city: string, country: string, postal_code: string, state: string }) {
if (!this.stripe) throw Error('Stripe not initialized');
const customer = await this.stripe.customers.update(customer_id, {
address: {
line1: address.line1,
line2: address.line2,
city: address.city,
country: address.country,
postal_code: address.postal_code,
state: address.state
}
})
return customer.id;
}
async deleteCustomer(customer_id: string) {
if (!this.stripe) throw Error('Stripe not initialized');
const { deleted } = await this.stripe.customers.del(customer_id);
return deleted;
}
// async createStripeCode(plan: PREMIUM_TAG) {
// if (!this.stripe) throw Error('Stripe not initialized');
// const INCUBATION_COUPON = 'sDD7Weh3';
// if (plan === 'INCUBATION') {
// await this.stripe.promotionCodes.create({
// coupon: INCUBATION_COUPON,
// active: true,
// code: 'TESTCACCA1',
// max_redemptions: 1,
// })
// return true;
// }
// return false;
// }
// async createSubscription(customer_id: string, planId: number) {
// if (this.disabledMode) return;
// if (!this.stripe) throw Error('Stripe not initialized');
// const PLAN = getPlanFromId(planId);
// if (!PLAN) throw Error('Plan not found');
// const subscription = await this.stripe.subscriptions.create({
// customer: customer_id,
// items: [
// { price: this.testMode ? PLAN.PRICE_TEST : PLAN.PRICE, quantity: 1 }
// ],
// });
// return subscription;
// }
// async createOneTimeSubscriptionDummy(customer_id: string, planId: number) {
// if (this.disabledMode) return;
// if (!this.stripe) throw Error('Stripe not initialized');
// const PLAN = getPlanFromId(planId);
// if (!PLAN) throw Error('Plan not found');
// const subscription = await this.stripe.subscriptions.create({
// customer: customer_id,
// items: [
// { price: this.testMode ? PLAN.PRICE_TEST : PLAN.PRICE, quantity: 1 }
// ],
// });
// return subscription;
// }
// async createFreeSubscription(customer_id: string) {
// if (this.disabledMode) return;
// if (!this.stripe) throw Error('Stripe not initialized');
// const FREE_PLAN = getPlanFromTag('FREE');
// const subscription = await this.stripe.subscriptions.create({
// customer: customer_id,
// items: [
// { price: this.testMode ? FREE_PLAN.PRICE_TEST : FREE_PLAN.PRICE, quantity: 1 }
// ]
// });
// return subscription;
// }
}
const instance = new StripeService();
export default instance;

13
payments/tsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "NodeNext",
"target": "ESNext",
"outDir": "dist"
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,87 @@
// import fs from 'fs-extra';
// import path from 'path';
// import child from 'child_process';
// import { createZip } from '../helpers/zip-helper';
// import { DeployHelper } from '../helpers/deploy-helper';
// import { DATABASE_CONNECTION_STRING_PRODUCTION, DATABASE_CONNECTION_STRING_TESTMODE, REMOTE_HOST_TESTMODE } from '../.config';
// const TMP_PATH = path.join(__dirname, '../../tmp');
// const LOCAL_PATH = path.join(__dirname, '../../consumer');
// const REMOTE_PATH = '/home/litlyx/consumer';
// const ZIP_NAME = 'consumer.zip';
// const MODE = DeployHelper.getMode();
// const SKIP_BUILD = DeployHelper.getArgAt(0) == '--no-build';
// console.log('Deploying consumer in mode:', MODE);
// setTimeout(() => { main(); }, 3000);
// async function main() {
// if (fs.existsSync(TMP_PATH)) fs.rmSync(TMP_PATH, { force: true, recursive: true });
// fs.ensureDirSync(TMP_PATH);
// if (!SKIP_BUILD) {
// console.log('Building');
// child.execSync(`cd ${LOCAL_PATH} && pnpm run build`);
// }
// console.log('Creting zip file');
// const archive = createZip(TMP_PATH + '/' + ZIP_NAME);
// archive.directory(LOCAL_PATH + '/dist', '/dist');
// if (MODE === 'testmode') {
// const ecosystemContent = fs.readFileSync(LOCAL_PATH + '/ecosystem.config.js', 'utf8');
// const REDIS_URL = ecosystemContent.match(/REDIS_URL: ["'](.*?)["']/)[1];
// const devContent = ecosystemContent
// .replace(REDIS_URL, `redis://${REMOTE_HOST_TESTMODE}`)
// .replace(DATABASE_CONNECTION_STRING_PRODUCTION, `redis://${DATABASE_CONNECTION_STRING_TESTMODE}`);
// archive.append(Buffer.from(devContent), { name: '/ecosystem.config.js' });
// } else {
// archive.file(LOCAL_PATH + '/ecosystem.config.js', { name: '/ecosystem.config.js' })
// }
// archive.file(LOCAL_PATH + '/package.json', { name: '/package.json' });
// archive.file(LOCAL_PATH + '/pnpm-lock.yaml', { name: '/pnpm-lock.yaml' });
// await archive.finalize();
// await DeployHelper.connect();
// const { scp, ssh } = DeployHelper.instances();
// console.log('Creating remote structure');
// console.log('Check existing');
// const remoteExist = await scp.exists(REMOTE_PATH);
// console.log('Exist', remoteExist);
// if (remoteExist) {
// console.log('Deleting');
// await DeployHelper.execute(`rm -r ${REMOTE_PATH}`);
// }
// console.log('Creating folder');
// await scp.mkdir(REMOTE_PATH);
// console.log('Uploading zip file');
// await scp.uploadFile(TMP_PATH + '/' + ZIP_NAME, REMOTE_PATH + '/' + ZIP_NAME);
// scp.close();
// console.log('Cleaning local');
// fs.rmSync(TMP_PATH + '/' + ZIP_NAME, { force: true, recursive: true });
// console.log('Extracting remote');
// await DeployHelper.execute(`cd ${REMOTE_PATH} && unzip ${ZIP_NAME} && rm -r ${ZIP_NAME}`);
// console.log('Installing remote');
// await DeployHelper.execute(`cd ${REMOTE_PATH} && /root/.nvm/versions/node/v21.2.0/bin/pnpm i`);
// console.log('Executing remote');
// await DeployHelper.execute(`cd ${REMOTE_PATH} && /root/.nvm/versions/node/v21.2.0/bin/pm2 start ecosystem.config.js`);
// ssh.dispose();
// }

View File

@@ -0,0 +1,23 @@
import { SharedHelper } from "../helpers/shared-helper";
import path from "node:path";
const helper = new SharedHelper(path.join(__dirname, '../../payments/src/shared'))
helper.clear();
helper.create('utils');
helper.copy('utils/requireEnv.ts');
helper.create('services');
helper.copy('services/DatabaseService.ts');
helper.copy('services/EmailService.ts');
helper.create('schema');
helper.copy('schema/UserSchema.ts');
helper.create('schema/project');
helper.copy('schema/project/ProjectSchema.ts');
helper.create('data');
helper.copy('data/PLANS.ts');

201
shared_global/data/PLANS.ts Normal file
View File

@@ -0,0 +1,201 @@
export type PREMIUM_TAG = typeof PREMIUM_TAGS[number];
export const PREMIUM_TAGS = [
'FREE',
'PLAN_1',
'PLAN_2',
'CUSTOM_1',
'INCUBATION',
'ACCELERATION',
'GROWTH',
'EXPANSION',
'SCALING',
'UNICORN',
'LIFETIME_GROWTH_ONETIME',
'GROWTH_DUMMY',
'APPSUMO_INCUBATION',
'APPSUMO_ACCELERATION',
'APPSUMO_GROWTH',
'APPSUMO_UNICORN'
] as const;
export type PREMIUM_DATA = {
COUNT_LIMIT: number,
AI_MESSAGE_LIMIT: number,
PRICE: string,
PRICE_TEST: string,
ID: number,
COST: number,
TAG: PREMIUM_TAG
}
export const PREMIUM_PLAN: Record<PREMIUM_TAG, PREMIUM_DATA> = {
FREE: {
ID: 0,
COUNT_LIMIT: 5_000,
AI_MESSAGE_LIMIT: 10,
PRICE: 'price_1POKCMB2lPUiVs9VLe3QjIHl',
PRICE_TEST: 'price_1PNbHYB2lPUiVs9VZP32xglF',
COST: 0,
TAG: 'FREE'
},
PLAN_1: {
ID: 1,
COUNT_LIMIT: 150_000,
AI_MESSAGE_LIMIT: 100,
PRICE: 'price_1POKCOB2lPUiVs9VC13s2rQw',
PRICE_TEST: 'price_1PNZjVB2lPUiVs9VrsTbJL04',
COST: 0,
TAG: 'PLAN_1'
},
PLAN_2: {
ID: 2,
COUNT_LIMIT: 500_000,
AI_MESSAGE_LIMIT: 5_000,
PRICE: 'price_1POKCKB2lPUiVs9Vol8XOmhW',
PRICE_TEST: 'price_1POK34B2lPUiVs9VIROb0IIV',
COST: 0,
TAG: 'PLAN_2'
},
CUSTOM_1: {
ID: 1001,
COUNT_LIMIT: 10_000_000,
AI_MESSAGE_LIMIT: 100_000,
PRICE: 'price_1POKZyB2lPUiVs9VMAY6jXTV',
PRICE_TEST: '',
COST: 0,
TAG: 'CUSTOM_1'
},
INCUBATION: {
ID: 101,
COUNT_LIMIT: 50_000,
AI_MESSAGE_LIMIT: 30,
PRICE: 'price_1PdsyzB2lPUiVs9V4J246Jw0',
PRICE_TEST: '',
COST: 499,
TAG: 'INCUBATION'
},
ACCELERATION: {
ID: 102,
COUNT_LIMIT: 150_000,
AI_MESSAGE_LIMIT: 100,
PRICE: 'price_1Pdt5bB2lPUiVs9VhkuCouEt',
PRICE_TEST: '',
COST: 999,
TAG: 'ACCELERATION'
},
GROWTH: {
ID: 103,
COUNT_LIMIT: 500_000,
AI_MESSAGE_LIMIT: 3_000,
PRICE: 'price_1PdszrB2lPUiVs9VIdkT3thv',
PRICE_TEST: '',
COST: 2999,
TAG: 'GROWTH'
},
EXPANSION: {
ID: 104,
COUNT_LIMIT: 1_000_000,
AI_MESSAGE_LIMIT: 5_000,
PRICE: 'price_1Pdt0xB2lPUiVs9V0Rdt80Fe',
PRICE_TEST: '',
COST: 5999,
TAG: 'EXPANSION'
},
SCALING: {
ID: 105,
COUNT_LIMIT: 2_500_000,
AI_MESSAGE_LIMIT: 10_000,
PRICE: 'price_1Pdt1UB2lPUiVs9VUmxntSwZ',
PRICE_TEST: '',
COST: 9999,
TAG: 'SCALING'
},
UNICORN: {
ID: 106,
COUNT_LIMIT: 5_000_000,
AI_MESSAGE_LIMIT: 20_000,
PRICE: 'price_1Pdt2LB2lPUiVs9VGBFAIG9G',
PRICE_TEST: '',
COST: 14999,
TAG: 'UNICORN'
},
LIFETIME_GROWTH_ONETIME: {
ID: 2001,
COUNT_LIMIT: 500_000,
AI_MESSAGE_LIMIT: 3_000,
PRICE: 'price_1PvewGB2lPUiVs9VLheJC8s1',
PRICE_TEST: 'price_1Pvf7LB2lPUiVs9VMFNyzpim',
COST: 239900,
TAG: 'LIFETIME_GROWTH_ONETIME'
},
GROWTH_DUMMY: {
ID: 5001,
COUNT_LIMIT: 500_000,
AI_MESSAGE_LIMIT: 3_000,
PRICE: 'price_1PvgoRB2lPUiVs9VC51YBT7J',
PRICE_TEST: 'price_1PvgRTB2lPUiVs9V3kFSNC3G',
COST: 0,
TAG: 'GROWTH_DUMMY'
},
APPSUMO_INCUBATION: {
ID: 6001,
COUNT_LIMIT: 50_000,
AI_MESSAGE_LIMIT: 30,
PRICE: 'price_1QIXwbB2lPUiVs9VKSsoksaU',
PRICE_TEST: '',
COST: 0,
TAG: 'APPSUMO_INCUBATION'
},
APPSUMO_ACCELERATION: {
ID: 6002,
COUNT_LIMIT: 150_000,
AI_MESSAGE_LIMIT: 100,
PRICE: 'price_1QIXxRB2lPUiVs9VrjaVRoOl',
PRICE_TEST: '',
COST: 0,
TAG: 'APPSUMO_ACCELERATION'
},
APPSUMO_GROWTH: {
ID: 6003,
COUNT_LIMIT: 500_000,
AI_MESSAGE_LIMIT: 3_000,
PRICE: 'price_1QIXy8B2lPUiVs9VQBOUPAoE',
PRICE_TEST: '',
COST: 0,
TAG: 'APPSUMO_GROWTH'
},
APPSUMO_UNICORN: {
ID: 6006,
COUNT_LIMIT: 5_000_000,
AI_MESSAGE_LIMIT: 20_000,
PRICE: 'price_1Qls1lB2lPUiVs9VI6ej8hwE',
PRICE_TEST: '',
COST: 0,
TAG: 'APPSUMO_UNICORN'
}
}
export function getPlanFromTag(tag: PREMIUM_TAG) {
return PREMIUM_PLAN[tag];
}
export function getPlanFromId(id: number) {
for (const tag of PREMIUM_TAGS) {
const plan = getPlanFromTag(tag);
if (plan.ID === id) return plan;
}
}
export function getPlanFromPrice(price: string, testMode: boolean) {
for (const tag of PREMIUM_TAGS) {
const plan = getPlanFromTag(tag);
if (testMode) {
if (plan.PRICE_TEST === price) return plan;
} else {
if (plan.PRICE === price) return plan;
}
}
}