diff --git a/package-lock.json b/package-lock.json index bf1f958..11f6a59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -414,9 +414,9 @@ } }, "node_modules/@codegenie/serverless-express": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@codegenie/serverless-express/-/serverless-express-4.14.1.tgz", - "integrity": "sha512-B90/1OmA9mf9bEJnplLj7FGf+N2v2ikB68c/9W9uXmCa4ep/V00ymCiivwGLyeuzQRW33tcj4+KxZ2utfmu39Q==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@codegenie/serverless-express/-/serverless-express-4.15.0.tgz", + "integrity": "sha512-adnKbnW1Tg5LAe0lcbyoRchu8G6+gLwP1rvgwfHvTbCwvBQNfhsgnzq4cKkLn7ZKn2sa4JZNis/Gn/2jWBWa4A==", "dev": true, "engines": { "node": ">=12" @@ -1526,17 +1526,17 @@ } }, "node_modules/@loopback/boot": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/boot/-/boot-7.0.4.tgz", - "integrity": "sha512-LWF2EOMu2U5uoJGGx6eA0fbvztWSmlmjaCag+xVbB2yAohW6QMwVXpjlk0WauGGlwmGwKfKSbGUScdFKl2VeEQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@loopback/boot/-/boot-7.0.5.tgz", + "integrity": "sha512-8uMRdFDOe1QGU5K1p9Yvlk9dsYvZAcN1b+3dqsX4yFZDCStBF6LhFe4lvFN2GEZlXKTNwz1EX6XCG2rEJyvWNQ==", "dependencies": { - "@loopback/model-api-builder": "^6.0.4", - "@loopback/repository": "^7.0.4", - "@loopback/service-proxy": "^7.0.4", + "@loopback/model-api-builder": "^6.0.5", + "@loopback/repository": "^7.0.5", + "@loopback/service-proxy": "^7.0.5", "@types/debug": "^4.1.12", "@types/glob": "^8.1.0", "debug": "^4.3.5", - "glob": "^10.4.2", + "glob": "^10.4.5", "tslib": "^2.6.3" }, "engines": { @@ -1547,23 +1547,23 @@ } }, "node_modules/@loopback/build": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/@loopback/build/-/build-11.0.4.tgz", - "integrity": "sha512-Ilm7GabuRqinsyZDh/rLJc0PeY0X7gbFZ3LZnuwfok0ywrFA38DnP6/PU24A+2XkypufJOm0MaWVgiHJRGWu2w==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@loopback/build/-/build-11.0.5.tgz", + "integrity": "sha512-gCjJEuSrU46ZWYiBgibi77Xu6TNxHn+neTetLQ6fcmw0aGSyb65kNeu+jCKafaj1/NiVjxsI2M9H9yOBw0OaaQ==", "dev": true, "dependencies": { - "@loopback/eslint-config": "^15.0.3", + "@loopback/eslint-config": "^15.0.4", "@types/mocha": "^10.0.7", "@types/node": "^16.18.101", "cross-spawn": "^7.0.3", "debug": "^4.3.5", "eslint": "^8.57.0", "fs-extra": "^11.2.0", - "glob": "^10.4.2", + "glob": "^10.4.5", "lodash": "^4.17.21", "mocha": "^10.6.0", "nyc": "^17.0.0", - "prettier": "^3.2.5", + "prettier": "^3.3.2", "rimraf": "^5.0.7", "source-map-support": "^0.5.21", "typescript": "~5.2.2" @@ -1582,9 +1582,9 @@ } }, "node_modules/@loopback/build/node_modules/@types/node": { - "version": "16.18.104", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.104.tgz", - "integrity": "sha512-OF3keVCbfPlkzxnnDBUZJn1RiCJzKeadjiW0xTEb0G1SUJ5gDVb3qnzZr2T4uIFvsbKJbXy1v2DN7e2zaEY7jQ==", + "version": "16.18.105", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.105.tgz", + "integrity": "sha512-w2d0Z9yMk07uH3+Cx0N8lqFyi3yjXZxlbYappPj+AsOlT02OyxyiuNoNHdGt6EuiSm8Wtgp2YV7vWg+GMFrvFA==", "dev": true }, "node_modules/@loopback/build/node_modules/brace-expansion": { @@ -1910,11 +1910,11 @@ } }, "node_modules/@loopback/context": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/context/-/context-7.0.4.tgz", - "integrity": "sha512-NWtXJ2mH3Akj+Qlj4hPd4RZXyIkF4vS0DOZZXsHorPFAmvMdNi32SOXjUm/Je59k5xHBMQEW53tey8P+9iJXHQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@loopback/context/-/context-7.0.5.tgz", + "integrity": "sha512-+2jFBrDT+5LLfbFUno1Bun7VqiOOZQKF6mkpylbpsspSVhXigsNrcYwmktg/11sVlrZXcrbsVh2gbr3WinFOLg==", "dependencies": { - "@loopback/metadata": "^7.0.4", + "@loopback/metadata": "^7.0.5", "@types/debug": "^4.1.12", "debug": "^4.3.5", "hyperid": "^3.2.0", @@ -1927,11 +1927,11 @@ } }, "node_modules/@loopback/core": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@loopback/core/-/core-6.1.1.tgz", - "integrity": "sha512-gphIDW8sT1+0f6QqPc1h+P3l92oaMfMd1VK8DHtyCZwrbBGJkbuZC2BgzGOQyPXhgoLbIVO07shjmjgygaV2sg==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@loopback/core/-/core-6.1.2.tgz", + "integrity": "sha512-P1V4IGEM/f9fnQ81AfiRsZKYYndzb19KJ4VRpiZq3agzo229GOCi+um3f2CB2ak3xNmFV9Mgl9CmzSlFSW3w8w==", "dependencies": { - "@loopback/context": "^7.0.4", + "@loopback/context": "^7.0.5", "debug": "^4.3.5", "tslib": "^2.6.3" }, @@ -1940,13 +1940,13 @@ } }, "node_modules/@loopback/eslint-config": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@loopback/eslint-config/-/eslint-config-15.0.3.tgz", - "integrity": "sha512-Rv/o0qtEXMTIhLU2IFY9Mgp2eKDv732bG3UuVPXYROrM5P3AJkCO6tuonCDDrm6w4KTAL2xoJ9V+Fzcb8opxog==", + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/@loopback/eslint-config/-/eslint-config-15.0.4.tgz", + "integrity": "sha512-ZmCd/2qoSQrKzvM4pBmSJJ1KAecVvFFimGQjZgafmxe7J5Jfb1bamLoDYMcMI1DmKCRxd77nweX1PJHC4AmGtg==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "^7.10.0", - "@typescript-eslint/parser": "^7.10.0", + "@typescript-eslint/eslint-plugin": "^7.16.0", + "@typescript-eslint/parser": "^7.16.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-eslint-plugin": "^5.5.1", "eslint-plugin-mocha": "^10.4.3" @@ -1959,11 +1959,11 @@ } }, "node_modules/@loopback/express": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/express/-/express-7.0.4.tgz", - "integrity": "sha512-JraWV1WD5FHX3/8HEMfu7BC7GcS5kK/o4NwV0fuNiiv9sD+4mnWy7mgo+w3TbyfDSxq5BKspPWSoGMsUXYnwAw==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@loopback/express/-/express-7.0.5.tgz", + "integrity": "sha512-yHy7mAOFtsHFFZneujhknAHmQoTPaucJI4kM2kyqVVN/25wbpfuMfmmFb5niRllbbnqj4ujLrYLRG33n+xAP/Q==", "dependencies": { - "@loopback/http-server": "^6.0.4", + "@loopback/http-server": "^6.0.5", "@types/body-parser": "^1.19.5", "@types/express": "^4.17.21", "@types/express-serve-static-core": "^4.17.37", @@ -1984,9 +1984,9 @@ } }, "node_modules/@loopback/filter": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@loopback/filter/-/filter-5.0.4.tgz", - "integrity": "sha512-16SdyFe+mh8b6JIgbnS1NIiop+6vhh62BQ0hOBfRVnffCf1NEE3mrERNsbMNou9eMx/OOIBMfhoAoFwfSfyJdw==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@loopback/filter/-/filter-5.0.5.tgz", + "integrity": "sha512-0mEwNWkNHQvao1C6BOkj0eXkklZ8SwM4TYz+0WNWxVdE/3C9EjAQOzvzrkEgECf992abQylfZGHAxjWOuo1PVw==", "dependencies": { "tslib": "^2.6.3" }, @@ -1995,9 +1995,9 @@ } }, "node_modules/@loopback/http-server": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@loopback/http-server/-/http-server-6.0.4.tgz", - "integrity": "sha512-e5QyUeX4ime9mbnPOO8la7AccRCJ9Iyw/+HSRDoFV0ufOCZFwVkzu8NLM/NJPyFTPcMe+jTddvgH0+KcmBYCwA==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@loopback/http-server/-/http-server-6.0.5.tgz", + "integrity": "sha512-AQz0AyfTIiICpZejQ0Ph5UBuRvOtvc02MoY+TyAY2OTqFHocUXzq26mPyrnkjWQycQO78X0Jkxr+QwogCjyHGA==", "dependencies": { "debug": "^4.3.5", "stoppable": "^1.1.0", @@ -2008,9 +2008,9 @@ } }, "node_modules/@loopback/logging": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/@loopback/logging/-/logging-0.12.4.tgz", - "integrity": "sha512-+30PxdR2F8V9mrUxTD7eHjhV46LgAyVbZfv4+C9Sjrpa81+e2V/uXBai8o6Ir1pJsQo6fm97EohLttYV3LrNUA==", + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@loopback/logging/-/logging-0.12.5.tgz", + "integrity": "sha512-yhUvD/LWkbWCmDO1oe+6PtPzaPVsr0m2q0hK6OAPBu15L5L7zHbORzl1/pERrE8i0IN6XWndwNYRUx+EKTlHAQ==", "dev": true, "dependencies": { "fluent-logger": "^3.4.1", @@ -2028,9 +2028,9 @@ } }, "node_modules/@loopback/metadata": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/metadata/-/metadata-7.0.4.tgz", - "integrity": "sha512-PMR9FYnWbx9FcnF/oy5ukY852l2xl7eUU1WMOVeiYU66vpXdCXb1bOoTZjMOAuqZcSlQUjqwk15cZzydU/ITxw==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@loopback/metadata/-/metadata-7.0.5.tgz", + "integrity": "sha512-Likq4nLftj58mPrkgtlSEGS78FPOsFMFYpqc67WMw/ISFzfjSJhCJr9KNOoZZS8DapPk55+35gh4MEUgydYRGA==", "dependencies": { "debug": "^4.3.5", "lodash": "^4.17.21", @@ -2042,9 +2042,9 @@ } }, "node_modules/@loopback/model-api-builder": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@loopback/model-api-builder/-/model-api-builder-6.0.4.tgz", - "integrity": "sha512-Or3vS/q76FPZ3voLkvLTP0j3kewa+gM4A3sf0wzGVFEe2/mq2Kkvx2e5hMe/siKEe9VfythQ2SSvf4uJz0zFhg==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@loopback/model-api-builder/-/model-api-builder-6.0.5.tgz", + "integrity": "sha512-lI0iQdaiGWMLlQiGdktQ+J1UVVutmpmafvksppHeuSjODgeWkDpM6DCxpBPCdXjXAxSjn7mlkrajSzMOg5CqmQ==", "dependencies": { "tslib": "^2.6.3" }, @@ -2057,11 +2057,11 @@ } }, "node_modules/@loopback/openapi-v3": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/@loopback/openapi-v3/-/openapi-v3-10.0.4.tgz", - "integrity": "sha512-ZZSKd7pMNDP7BgvHcWUeAV3DqTA9g89C6Ra4wCmCHnpvQdxoqoNWI10wupRFKFgbLr/x9YK/Pf9CnwN+WZQIMQ==", + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/@loopback/openapi-v3/-/openapi-v3-10.0.5.tgz", + "integrity": "sha512-dLGcu0dyMfA4GRCy3PwPzSkYDUkzGC+5gQRGR4gqUA6QdbU/nUwr/8oR5nYjlXsGpP8y+RGvecAMkCt1UAzpmg==", "dependencies": { - "@loopback/repository-json-schema": "^8.0.4", + "@loopback/repository-json-schema": "^8.0.5", "debug": "^4.3.5", "http-status": "^1.7.4", "json-merge-patch": "^1.0.2", @@ -2077,11 +2077,11 @@ } }, "node_modules/@loopback/repository": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/repository/-/repository-7.0.4.tgz", - "integrity": "sha512-SP9i/38N/S3p8eBhrWSv2or+hRXFkZ6o5bFFr2QvuZtMEFP6OUR1bTUfSlElWGddOAUOpwh7COnn5evezwRhdQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@loopback/repository/-/repository-7.0.5.tgz", + "integrity": "sha512-TKlrRu/6NEQ0/zjOrCOWC6ZmFoZ8My9uqJL3vHAYsQ3cffv5hXDshlUq2NJLkMLkaIzhIwvflMLsO3TfyJo1qw==", "dependencies": { - "@loopback/filter": "^5.0.4", + "@loopback/filter": "^5.0.5", "@types/debug": "^4.1.12", "debug": "^4.3.5", "lodash": "^4.17.21", @@ -2096,9 +2096,9 @@ } }, "node_modules/@loopback/repository-json-schema": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@loopback/repository-json-schema/-/repository-json-schema-8.0.4.tgz", - "integrity": "sha512-WUWEQIw66rsw6EHVcsel7Bty/MV93E0HUvn8dcSHexKhoGv/9oae1EYUsaThkjb4hnG2iYZt4ZNDL4SyMJ6aXg==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@loopback/repository-json-schema/-/repository-json-schema-8.0.5.tgz", + "integrity": "sha512-iLFfLltQrUeasUaYHQRXcCHpVOeBd1xnzfwbuJXMtKxnexU4tiO5i50nXXdlyryIXlDyJAaqx8ItLJ5VKNa0lw==", "dependencies": { "@types/json-schema": "^7.0.15", "debug": "^4.3.5", @@ -2113,13 +2113,13 @@ } }, "node_modules/@loopback/rest": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@loopback/rest/-/rest-14.0.4.tgz", - "integrity": "sha512-1oQBJ1J+i7i4NsA9WzhGZJ2R9CK/aK2VTbQb6wlIgeSg5t65uSqUYUYgcY/Gt1wOJudZQ6l0CQJKhK1sdZj7+A==", + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/@loopback/rest/-/rest-14.0.5.tgz", + "integrity": "sha512-x9YdU5eeojOK/dfdtyTgth6MgSb0JBK7w/WaCEVWWZEo6+x1Xonv0Zv8DE0tU1P/wjMTWIC6587nDjFcQq95cg==", "dependencies": { - "@loopback/express": "^7.0.4", - "@loopback/http-server": "^6.0.4", - "@loopback/openapi-v3": "^10.0.4", + "@loopback/express": "^7.0.5", + "@loopback/http-server": "^6.0.5", + "@loopback/openapi-v3": "^10.0.5", "@openapi-contrib/openapi-schema-to-json-schema": "^5.1.0", "@types/body-parser": "^1.19.5", "@types/cors": "^2.8.17", @@ -2143,7 +2143,7 @@ "lodash": "^4.17.21", "on-finished": "^2.4.1", "path-to-regexp": "^6.2.2", - "qs": "^6.12.2", + "qs": "^6.12.3", "strong-error-handler": "^5.0.10", "tslib": "^2.6.3", "type-is": "^1.6.18", @@ -2157,9 +2157,9 @@ } }, "node_modules/@loopback/rest-explorer": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/rest-explorer/-/rest-explorer-7.0.4.tgz", - "integrity": "sha512-qz9gdjVIQ7YzH48iDBaL/lW4B40faR+Peg6AnJE0yetLG6DBi2Sm4yjzSFU7IGz4PbPG5wt1lDxdrutWK632lA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@loopback/rest-explorer/-/rest-explorer-7.0.5.tgz", + "integrity": "sha512-As+GsbWoZdOH4gX2JRgiA79H/0nBmm1hfYhsQKMEL8cYaghGJbDa/7uwOMnIz/3S8VXToaFY/GXaRuTJ6HtkpQ==", "dependencies": { "ejs": "^3.1.10", "swagger-ui-dist": "5.17.14", @@ -2213,9 +2213,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/@loopback/service-proxy": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/service-proxy/-/service-proxy-7.0.4.tgz", - "integrity": "sha512-Nw1S71eqNszN874AwmoRLQcT7aocD7E6aomB0Nff5jTTDONkZpIE+Zancyn0EtVp58DTVa2K+x9sPfiaL1XwYQ==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@loopback/service-proxy/-/service-proxy-7.0.5.tgz", + "integrity": "sha512-vMJSBdGANxdpvJ3l4bpsl7grJ+LIXPMbmr+ru6XfujDw4YoU/sWa/oAJp1fip+cncSGV552rbBZDXAoJBhOmfw==", "dependencies": { "loopback-datasource-juggler": "^5.0.11", "tslib": "^2.6.3" @@ -2228,9 +2228,9 @@ } }, "node_modules/@loopback/testlab": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/testlab/-/testlab-7.0.4.tgz", - "integrity": "sha512-8ZuiyMpdSuIHyA3Hxe+XyH9vEEndKTr093uPsSwevmks+oInCwMB9Y7Op5t2Ef9SEU+p8iSpfW8ikf2cp8VJ0g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@loopback/testlab/-/testlab-7.0.5.tgz", + "integrity": "sha512-YF8sKq/BwRptmj0TLwOATP6VELXlhs+dzE8EsK8xKgVCGAFf2VOz1VnwC4cjOPW0uMmRApJQIKF1cyM3zFMpVQ==", "dev": true, "dependencies": { "@hapi/shot": "^6.0.1", @@ -3006,18 +3006,13 @@ } }, "node_modules/@openapi-contrib/openapi-schema-to-json-schema/node_modules/@types/node": { - "version": "20.14.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.14.tgz", - "integrity": "sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==", + "version": "20.16.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz", + "integrity": "sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, - "node_modules/@openapi-contrib/openapi-schema-to-json-schema/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -3672,9 +3667,9 @@ } }, "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "dev": true }, "node_modules/@sourceloop/core": { @@ -3831,9 +3826,9 @@ } }, "node_modules/@types/aws-lambda": { - "version": "8.10.142", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.142.tgz", - "integrity": "sha512-wy2y/2hQKrS6myOS++koXg3N1Hg+LLyPjaggCFajczSHZPqBnOMuT2sdH3kiASrmdBYyM3pmjyz5SoWraRllCQ==", + "version": "8.10.143", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.143.tgz", + "integrity": "sha512-u5vzlcR14ge/4pMTTMDQr3MF0wEe38B2F9o84uC4F43vN5DGTy63npRrB6jQhyt+C0lGv4ZfiRcRkqJoZuPnmg==", "dev": true }, "node_modules/@types/body-parser": { @@ -3998,11 +3993,11 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "node_modules/@types/node": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", - "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", + "version": "22.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.4.1.tgz", + "integrity": "sha512-1tbpb9325+gPnKK0dMm+/LMriX0vKxf6RnB0SZUqfyVkQ4fMgUSySqhxE/y8Jvs4NyF1yHzTfG9KlnkIODxPKg==", "dependencies": { - "undici-types": "~6.13.0" + "undici-types": "~6.19.2" } }, "node_modules/@types/normalize-package-data": { @@ -4417,12 +4412,11 @@ "dev": true }, "node_modules/accept-language": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.18.tgz", - "integrity": "sha512-sUofgqBPzgfcF20sPoBYGQ1IhQLt2LSkxTnlQSuLF3n5gPEqd5AimbvOvHEi0T1kLMiGVqPWzI5a9OteBRth3A==", + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.20.tgz", + "integrity": "sha512-xklPzRma4aoDEPk0ZfMjeuxB2FP4JBYlAR25OFUqCoOYDjYo6wGwAs49SnTN/MoB5VpnNX9tENfZ+vEIFmHQMQ==", "dependencies": { - "bcp47": "^1.1.2", - "stable": "^0.1.6" + "bcp47": "^1.1.2" } }, "node_modules/accepts": { @@ -4789,9 +4783,9 @@ } }, "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" }, "node_modules/asynckit": { "version": "0.4.0", @@ -4844,15 +4838,15 @@ } }, "node_modules/aws4": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", - "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", + "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==", "peer": true }, "node_modules/axios": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", - "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -4957,11 +4951,6 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/bl/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, "node_modules/bl/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -5478,9 +5467,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001649", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001649.tgz", - "integrity": "sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "dev": true, "funding": [ { @@ -5620,6 +5609,18 @@ "node": "*" } }, + "node_modules/chargebee": { + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/chargebee/-/chargebee-2.40.0.tgz", + "integrity": "sha512-U+ZtTdN+2mz58s6GIpTrQfRZI+8HeSx05RFUK63NY+rafbcyE7t5bxqQj+nKVQAiO3mSIRF4LYukPJZ9XjFVGg==", + "dependencies": { + "q": ">=1.0.1", + "safer-buffer": "2.1.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -7071,6 +7072,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-equal/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -7409,9 +7415,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz", - "integrity": "sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==", + "version": "1.5.12", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.12.tgz", + "integrity": "sha512-tIhPkdlEoCL1Y+PToq3zRNehUaKp3wBX/sr7aclAWdIWjvqAe/Im/H0SiCM4c1Q8BLPHCdoJTol+ZblflydehA==", "dev": true }, "node_modules/emoji-regex": { @@ -7617,6 +7623,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, "node_modules/es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", @@ -8567,9 +8578,9 @@ } }, "node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -8867,12 +8878,6 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/get-pkg-repo/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, "node_modules/get-pkg-repo/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -9665,9 +9670,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" @@ -10463,9 +10468,9 @@ } }, "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/isexe": { "version": "2.0.0", @@ -11629,6 +11634,26 @@ "node": ">=8" } }, + "node_modules/local-billing": { + "name": "@local/billing", + "version": "0.0.1", + "resolved": "file:services/subscription-service/local-billing-0.0.1.tgz", + "integrity": "sha512-EWVOKh5WvjNdwLkt+U58DzclSlMgAvanW8dyLCg+4vSRz5eee3dV8XGSt9py87GVryUTJJ5hSUR/DemHHrNJkg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@loopback/rest": "^14.0.0", + "@loopback/rest-explorer": "^7.0.0", + "chargebee": "^2.38.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@loopback/core": "^6.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -11827,9 +11852,9 @@ } }, "node_modules/loopback-connector-postgresql": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/loopback-connector-postgresql/-/loopback-connector-postgresql-7.1.3.tgz", - "integrity": "sha512-MaRNOLbjEDz3VGqP32CGI0DdtvJc/f2OOCYeXkizPCFhFElOxL81qPlN4h9RMVoFuLF7AYdxgGQhev6sOv2psw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/loopback-connector-postgresql/-/loopback-connector-postgresql-7.1.4.tgz", + "integrity": "sha512-nLBUgHWZUw5PQCW+cygRjkbf8WtJUjx8QhzZlKNJk90dsdYd16UqUUBtxXiHBJhcsQLi8MtU7+kGEi2TxSYC3g==", "dependencies": { "async": "^3.2.0", "bluebird": "^3.4.6", @@ -11845,13 +11870,13 @@ } }, "node_modules/loopback-connector-postgresql/node_modules/loopback-connector": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-6.1.7.tgz", - "integrity": "sha512-Mc5EkBjOfiCu2d+xlS094oIT8BaT50IohJeOCGB55Gj8iM6EWZOthhEtID0IUHq+7v+/okkf6OhvqILybIIyMw==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-6.1.8.tgz", + "integrity": "sha512-boN+gK1fzxTgJA8ourX8F1vZWPyVWzN00a1i5z+PHMjSh6OZCjjzOric8PwKA2t9sgJxOa7xggFszVjsmTFOuw==", "dependencies": { "async": "^3.2.5", "bluebird": "^3.7.2", - "debug": "^4.3.5", + "debug": "^4.3.6", "msgpack5": "^4.5.1", "strong-globalize": "^6.0.6", "uuid": "^10.0.0" @@ -11907,20 +11932,20 @@ } }, "node_modules/loopback-datasource-juggler": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/loopback-datasource-juggler/-/loopback-datasource-juggler-5.0.11.tgz", - "integrity": "sha512-qPgJA4VbrsclgesjPMkuzCpa+emuVTrmgY89zO7zmWvLnBzCPkIISxhoCAIo6Z3n6VSXkgzNKcstj7lwgbqXhw==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/loopback-datasource-juggler/-/loopback-datasource-juggler-5.0.12.tgz", + "integrity": "sha512-a4c3DO8Y0dP9EGTgvLYqKDjFzgSp5o4+1XrypsMqtLE+XBWG534aWG+6cIPyS1cXDljPDO87NZo6O3F2vs9qAQ==", "dependencies": { "async": "^3.2.5", "change-case": "^4.1.2", - "debug": "^4.3.5", + "debug": "^4.3.6", "depd": "^2.0.0", "inflection": "^3.0.0", "lodash": "^4.17.21", "loopback-connector": "^6.1.7", "minimatch": "^9.0.5", "nanoid": "^3.3.7", - "qs": "^6.12.2", + "qs": "^6.13.0", "strong-globalize": "^6.0.6", "traverse": "^0.6.9", "uuid": "^10.0.0" @@ -11938,13 +11963,13 @@ } }, "node_modules/loopback-datasource-juggler/node_modules/loopback-connector": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-6.1.7.tgz", - "integrity": "sha512-Mc5EkBjOfiCu2d+xlS094oIT8BaT50IohJeOCGB55Gj8iM6EWZOthhEtID0IUHq+7v+/okkf6OhvqILybIIyMw==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-6.1.8.tgz", + "integrity": "sha512-boN+gK1fzxTgJA8ourX8F1vZWPyVWzN00a1i5z+PHMjSh6OZCjjzOric8PwKA2t9sgJxOa7xggFszVjsmTFOuw==", "dependencies": { "async": "^3.2.5", "bluebird": "^3.7.2", - "debug": "^4.3.5", + "debug": "^4.3.6", "msgpack5": "^4.5.1", "strong-globalize": "^6.0.6", "uuid": "^10.0.0" @@ -12766,9 +12791,9 @@ } }, "node_modules/mocha": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.0.tgz", - "integrity": "sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==", + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", "dev": true, "dependencies": { "ansi-colors": "^4.1.3", @@ -13101,12 +13126,6 @@ "msgpack": "bin/msgpack" } }, - "node_modules/msgpack-lite/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, "node_modules/msgpack5": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.5.1.tgz", @@ -13118,11 +13137,6 @@ "safe-buffer": "^5.1.2" } }, - "node_modules/msgpack5/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, "node_modules/msgpack5/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -15937,9 +15951,9 @@ "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==" }, "node_modules/postman-request": { - "version": "2.88.1-postman.37", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.37.tgz", - "integrity": "sha512-TpHeMnvO5xvlYCYp8QntLR1Fq0hohWGOLbf9RBqO5JTMdPWZpGBbR8xs11tHsZRVMDXWFg4m960ItkcDxiaWSA==", + "version": "2.88.1-postman.39", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.39.tgz", + "integrity": "sha512-rsncxxDlbn1YpygXSgJqbJzIjGlHFcZjbYDzeBPTQHMDfLuSTzZz735JHV8i1+lOROuJ7MjNap4eaSD3UijHzQ==", "peer": true, "dependencies": { "@postman/form-data": "~3.1.1", @@ -17018,6 +17032,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -17910,12 +17929,6 @@ "node": ">=8" } }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" - }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -19148,9 +19161,9 @@ } }, "node_modules/uglify-js": { - "version": "3.19.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.1.tgz", - "integrity": "sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A==", + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.2.tgz", + "integrity": "sha512-S8KA6DDI47nQXJSi2ctQ629YzwOVs+bQML6DAtvy0wgNdpi+0ySpQK0g2pxBq2xfF2z3YCscu7NNA8nXT9PlIQ==", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -19201,9 +19214,9 @@ } }, "node_modules/undici-types": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", - "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/unicode-properties": { "version": "1.4.1", @@ -19561,9 +19574,9 @@ } }, "node_modules/winston": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.1.tgz", - "integrity": "sha512-SvZit7VFNvXRzbqGHsv5KSmgbEYR5EiQfDAL9gxYkRqa934Hnk++zze0wANKtMHcy/gI4W/3xmSDwlhf865WGw==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.14.2.tgz", + "integrity": "sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==", "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", @@ -20028,108 +20041,19 @@ } }, "services/orchestrator-service/node_modules/@types/node": { - "version": "18.19.43", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.43.tgz", - "integrity": "sha512-Mw/YlgXnyJdEwLoFv2dpuJaDFriX+Pc+0qOBJ57jC1H6cDxIj2xc5yUrdtArDVG0m+KV6622a4p2tenEqB3C/g==", + "version": "18.19.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.45.tgz", + "integrity": "sha512-VZxPKNNhjKmaC1SUYowuXSRSMGyQGmQjvvA1xE4QZ0xce2kLtEhPDS+kqpCPBZYgqblCLQ2DAjSzmgCM5auvhA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, - "services/orchestrator-service/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "services/orchestrator-service/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "services/orchestrator-service/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "services/orchestrator-service/node_modules/nodemon": { - "version": "3.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "services/orchestrator-service/node_modules/semver": { - "version": "7.6.3", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "services/orchestrator-service/node_modules/simple-update-notifier": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "services/orchestrator-service/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "services/orchestrator-service/node_modules/typescript": { "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20153,7 +20077,7 @@ "@loopback/context": "^7.0.2", "@loopback/core": "^6.0.2", "@loopback/openapi-v3": "^10.0.2", - "@loopback/repository": "^7.0.2", + "@loopback/repository": "^7.0.4", "@loopback/rest": "^14.0.2", "@loopback/rest-explorer": "^7.0.2", "@loopback/service-proxy": "^7.0.2", @@ -20170,6 +20094,7 @@ "@types/jsonwebtoken": "^9.0.5", "dotenv": "^16.0.3", "dotenv-extended": "^2.9.0", + "local-billing": "file:local-billing-0.0.1.tgz", "loopback-connector-postgresql": "^7.1.1", "loopback4-authentication": "^12.0.2", "loopback4-authorization": "^7.0.2", @@ -20195,9 +20120,10 @@ } }, "services/subscription-service/node_modules/@types/node": { - "version": "18.19.31", + "version": "18.19.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.45.tgz", + "integrity": "sha512-VZxPKNNhjKmaC1SUYowuXSRSMGyQGmQjvvA1xE4QZ0xce2kLtEhPDS+kqpCPBZYgqblCLQ2DAjSzmgCM5auvhA==", "dev": true, - "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } @@ -20278,9 +20204,10 @@ } }, "services/tenant-management-service/node_modules/@types/node": { - "version": "18.19.31", + "version": "18.19.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.45.tgz", + "integrity": "sha512-VZxPKNNhjKmaC1SUYowuXSRSMGyQGmQjvvA1xE4QZ0xce2kLtEhPDS+kqpCPBZYgqblCLQ2DAjSzmgCM5auvhA==", "dev": true, - "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } diff --git a/services/subscription-service/migrations/pg/migrations/20240209122448-add-customer-table.js b/services/subscription-service/migrations/pg/migrations/20240209122448-add-customer-table.js new file mode 100644 index 0000000..17c1fd9 --- /dev/null +++ b/services/subscription-service/migrations/pg/migrations/20240209122448-add-customer-table.js @@ -0,0 +1,59 @@ +'use strict'; + +let dbm; +let type; +let seed; +let fs = require('fs'); +let path = require('path'); +let Promise; + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + */ +exports.setup = function (options, seedLink) { + dbm = options.dbmigrate; + type = dbm.dataType; + seed = seedLink; + Promise = options.Promise; +}; + +exports.up = function (db) { + let filePath = path.join( + __dirname, + 'sqls', + '20240209122448-add-customer-table-up.sql', + ); + return new Promise(function (resolve, reject) { + fs.readFile(filePath, {encoding: 'utf-8'}, function (err, data) { + if (err) return reject(err); + console.log('received data: ' + data); + + resolve(data); + }); + }).then(function (data) { + return db.runSql(data); + }); +}; + +exports.down = function (db) { + let filePath = path.join( + __dirname, + 'sqls', + '20240209122448-add-customer-table-down.sql', + ); + return new Promise(function (resolve, reject) { + fs.readFile(filePath, {encoding: 'utf-8'}, function (err, data) { + if (err) return reject(err); + console.log('received data: ' + data); + + resolve(data); + }); + }).then(function (data) { + return db.runSql(data); + }); +}; + +exports._meta = { + version: 1, +}; diff --git a/services/subscription-service/migrations/pg/migrations/sqls/20240205055601-init-up.sql b/services/subscription-service/migrations/pg/migrations/sqls/20240205055601-init-up.sql index f5da7f9..4c2261f 100644 --- a/services/subscription-service/migrations/pg/migrations/sqls/20240205055601-init-up.sql +++ b/services/subscription-service/migrations/pg/migrations/sqls/20240205055601-init-up.sql @@ -21,6 +21,8 @@ CREATE TABLE plans ( CONSTRAINT pk_plans_id PRIMARY KEY ( id ) ); + + CREATE TABLE subscriptions ( id uuid DEFAULT (md5(((random())::text || (clock_timestamp())::text)))::uuid NOT NULL , created_on timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL , diff --git a/services/subscription-service/migrations/pg/migrations/sqls/20240209122448-add-customer-table-down.sql b/services/subscription-service/migrations/pg/migrations/sqls/20240209122448-add-customer-table-down.sql new file mode 100644 index 0000000..66e21ae --- /dev/null +++ b/services/subscription-service/migrations/pg/migrations/sqls/20240209122448-add-customer-table-down.sql @@ -0,0 +1,2 @@ +drop table main.billing_customer; +drop table main.invoice; \ No newline at end of file diff --git a/services/subscription-service/migrations/pg/migrations/sqls/20240209122448-add-customer-table-up.sql b/services/subscription-service/migrations/pg/migrations/sqls/20240209122448-add-customer-table-up.sql new file mode 100644 index 0000000..35e942d --- /dev/null +++ b/services/subscription-service/migrations/pg/migrations/sqls/20240209122448-add-customer-table-up.sql @@ -0,0 +1,41 @@ + +CREATE TABLE main.billing_customer ( + id uuid DEFAULT (md5(((random())::text || (clock_timestamp())::text)))::uuid NOT NULL, + tenant_id varchar(255) NOT NULL, + customer_id varchar(255) NOT NULL, + payment_source_id varchar(255), + created_on timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL, + modified_on timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted boolean DEFAULT false NOT NULL, + deleted_on timestamptz, + deleted_by uuid, + created_by uuid NOT NULL, + modified_by uuid, + CONSTRAINT pk_billing_customer_id PRIMARY KEY (id), + CONSTRAINT uq_billing_customer_customer_id UNIQUE (customer_id) +); + + + +CREATE TABLE main.invoice ( + id UUID DEFAULT (md5(((random())::text || (clock_timestamp())::text)))::uuid NOT NULL, + invoice_id VARCHAR(255) NOT NULL, + invoice_status BOOLEAN, + billing_customer_id uuid NOT NULL, + -- subscription_id uuid, + created_on TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + modified_on TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted BOOLEAN DEFAULT false NOT NULL, + deleted_on TIMESTAMPTZ, + deleted_by UUID, + created_by UUID NOT NULL, + modified_by UUID, + CONSTRAINT pk_invoice_id PRIMARY KEY (id), + CONSTRAINT fk_invoice_customer FOREIGN KEY (billing_customer_id) REFERENCES main.billing_customer(id) + + -- CONSTRAINT fk_invoice_subscription FOREIGN KEY (subscription_id) REFERENCES main.subscriptions(id) -- Add this constraint +); + +ALTER TABLE main.subscriptions +ADD COLUMN invoice_id uuid NOT NULL, +ADD CONSTRAINT fk_subscriptions_invoice FOREIGN KEY (invoice_id) REFERENCES main.invoice(id); \ No newline at end of file diff --git a/services/subscription-service/package.json b/services/subscription-service/package.json index a9d1822..2cf4a56 100644 --- a/services/subscription-service/package.json +++ b/services/subscription-service/package.json @@ -67,7 +67,7 @@ "@loopback/context": "^7.0.2", "@loopback/core": "^6.0.2", "@loopback/openapi-v3": "^10.0.2", - "@loopback/repository": "^7.0.2", + "@loopback/repository": "^7.0.4", "@loopback/rest": "^14.0.2", "@loopback/rest-explorer": "^7.0.2", "@loopback/service-proxy": "^7.0.2", @@ -84,6 +84,7 @@ "@types/jsonwebtoken": "^9.0.5", "dotenv": "^16.0.3", "dotenv-extended": "^2.9.0", + "local-billing": "file:local-billing-0.0.1.tgz", "loopback-connector-postgresql": "^7.1.1", "loopback4-authentication": "^12.0.2", "loopback4-authorization": "^7.0.2", diff --git a/services/subscription-service/src/component.ts b/services/subscription-service/src/component.ts index 9455848..93b82cd 100644 --- a/services/subscription-service/src/component.ts +++ b/services/subscription-service/src/component.ts @@ -2,68 +2,27 @@ // // This software is released under the MIT License. // https://opensource.org/licenses/MIT -import { - Binding, - Component, - ControllerClass, - CoreBindings, - inject, - ProviderMap, - ServiceOrProviderClass, -} from '@loopback/core'; -import {Class, Model, Repository} from '@loopback/repository'; -import {RestApplication} from '@loopback/rest'; -import { - BearerVerifierBindings, - BearerVerifierComponent, - BearerVerifierConfig, - BearerVerifierType, - CoreComponent, - SECURITY_SCHEME_SPEC, - ServiceSequence, -} from '@sourceloop/core'; -import {AuthenticationComponent} from 'loopback4-authentication'; -import { - AuthorizationBindings, - AuthorizationComponent, -} from 'loopback4-authorization'; -import {SubscriptionServiceBindings} from './keys'; -import {ISubscriptionServiceConfig} from './types'; -import { - BillingCycleRepository, - CurrencyRepository, - PlanRepository, - PlanSizesRepository, - ResourceRepository, - ServiceRepository, - SubscriptionRepository, -} from './repositories'; -import { - BillinCycleController, - CurrencyController, - HomePageController, - PingController, - PlanController, - PlanFeaturesController, - PlanSizesController, - PlanSubscriptionController, - ResourceController, - ServiceController, - SubscriptionController, -} from './controllers'; -import { - BillingCycle, - Currency, - Plan, - PlanSizes, - Resource, - Service, - Subscription, -} from './models'; -import { - FeatureToggleBindings, - FeatureToggleServiceComponent, -} from '@sourceloop/feature-toggle-service'; + +import { inject, Binding } from "@loopback/context"; +import { Component, CoreBindings, ProviderMap, ServiceOrProviderClass, ControllerClass } from "@loopback/core"; +import { Class, Repository, Model } from "@loopback/repository"; +import { RestApplication } from "@loopback/rest"; +import { CoreComponent, SECURITY_SCHEME_SPEC, ServiceSequence, BearerVerifierBindings, BearerVerifierType, BearerVerifierConfig, BearerVerifierComponent } from "@sourceloop/core"; +import { FeatureToggleBindings, FeatureToggleServiceComponent } from "@sourceloop/feature-toggle-service"; +import { BillingComponent } from "local-billing"; +import { AuthenticationComponent } from "loopback4-authentication"; +import { AuthorizationBindings, AuthorizationComponent } from "loopback4-authorization"; +import { BillinCycleController, HomePageController, PingController, CurrencyController, PlanController, ResourceController, ServiceController, SubscriptionController, PlanSubscriptionController, PlanSizesController, PlanFeaturesController } from "./controllers"; +import { SubscriptionServiceBindings, SYSTEM_USER, WEBHOOK_VERIFIER } from "./keys"; +import { BillingCycle, Currency, Plan, Resource, BillingCustomer, Invoice, Service, Subscription, PlanSizes } from "./models"; +import { BillingCycleRepository, CurrencyRepository, PlanRepository, ResourceRepository, ServiceRepository, SubscriptionRepository, PlanSizesRepository, BillingCustomerRepository, InvoiceRepository } from "./repositories"; +import { ISubscriptionServiceConfig } from "./types"; +import { WebhookVerifierProvider } from "./interceptors/webhook-verifier.interceptor"; +import { SystemUserProvider } from "./providers"; +import { BillingCustomerController } from "./controllers/billing-customer.controller"; +import { BillingInvoiceController } from "./controllers/billing-invoice.controller"; +import { BillingPaymentSourceController } from "./controllers/billing-payment-source.controller"; +import { WebhookController } from "./controllers/webhook.controller"; export class SubscriptionServiceComponent implements Component { constructor( @@ -82,6 +41,7 @@ export class SubscriptionServiceComponent implements Component { .bind(FeatureToggleBindings.Config) .to({bindControllers: true, useCustomSequence: true}); this.application.component(FeatureToggleServiceComponent); + this.application.component(BillingComponent); this.application.api({ openapi: '3.0.0', @@ -108,7 +68,8 @@ export class SubscriptionServiceComponent implements Component { ResourceRepository, ServiceRepository, SubscriptionRepository, - PlanSizesRepository, + PlanSizesRepository,BillingCustomerRepository, + InvoiceRepository, ]; this.models = [ @@ -116,10 +77,17 @@ export class SubscriptionServiceComponent implements Component { Currency, Plan, Resource, + BillingCustomer, + Invoice, Service, Subscription, PlanSizes, ]; + this.bindings = [ + Binding.bind(WEBHOOK_VERIFIER).toProvider(WebhookVerifierProvider), + + Binding.bind(SYSTEM_USER).toProvider(SystemUserProvider), + ]; this.controllers = [ BillinCycleController, @@ -133,6 +101,10 @@ export class SubscriptionServiceComponent implements Component { PlanSubscriptionController, PlanSizesController, PlanFeaturesController, + BillingCustomerController, + BillingInvoiceController, + BillingPaymentSourceController, + WebhookController, ]; } diff --git a/services/subscription-service/src/controllers/billing-customer.controller.ts b/services/subscription-service/src/controllers/billing-customer.controller.ts new file mode 100644 index 0000000..ce5a430 --- /dev/null +++ b/services/subscription-service/src/controllers/billing-customer.controller.ts @@ -0,0 +1,233 @@ +import { + Count, + CountSchema, + Filter, + FilterExcludingWhere, + repository, + Where, +} from '@loopback/repository'; +import { + post, + param, + get, + getModelSchemaRef, + patch, + put, + del, + requestBody, +} from '@loopback/rest'; +import {AddressDto, BillingCycle} from '../models'; +import {BillingCycleRepository, InvoiceRepository} from '../repositories'; +import {authorize} from 'loopback4-authorization'; +import {authenticate, STRATEGY} from 'loopback4-authentication'; +import {PermissionKey} from '../permissions'; +import {OPERATION_SECURITY_SPEC, STATUS_CODE} from '@sourceloop/core'; +import {BillingCustomerRepository} from '../repositories/billing-customer.repository'; +import {Getter, inject} from '@loopback/core'; +import { + BillingComponentBindings, + IChargeBeeService, + IService, +} from 'local-billing'; +import {CustomerDto} from '../models/dto/customer-dto.model'; +import {InvoiceDto} from '../models/dto/invoice-dto.model'; +import {PaymentSourceDto} from '../models/dto/payment-dto.model'; +import {BillingCustomer} from '../models/billing-customer.model'; + +const basePath = '/billing-customer'; +export class BillingCustomerController { + constructor( + @repository(BillingCustomerRepository) + public billingCustomerRepository: BillingCustomerRepository, + @repository(InvoiceRepository) + public invoiceRepository: InvoiceRepository, + @inject(BillingComponentBindings.BillingProvider) + private readonly billingProvider: IService, + ) {} + + @authorize({ + permissions: [PermissionKey.CreateBillingCustomer], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @post(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'BillingCustomer model instance', + content: { + 'application/json': { + schema: getModelSchemaRef(CustomerDto, { + title: 'NewBillingCustomer', + }), + }, + }, + }, + }, + }) + async create( + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(CustomerDto, { + title: 'NewCustomer', + exclude: ['id'], + }), + }, + }, + }) + customerDto: Omit, + @param.header.string('tenantId') tenantId: string, + ): Promise { + const customer = await this.billingProvider.createCustomer(customerDto); + await this.billingCustomerRepository.create( + new BillingCustomer({ + tenantId, + customerId: customer.id, + }), + ); + return new CustomerDto({ + id:customer.id, + first_name: customer.first_name, + last_name: customer.last_name, + email: customer.email, + company: customer.company, + phone: customer.phone, + billing_address: new AddressDto({ + first_name: customer.billing_address?.first_name, + last_name: customer.billing_address?.last_name, + email: customer.billing_address?.email, + company: customer.billing_address?.company, + phone: customer.billing_address?.phone, + city: customer.billing_address?.city, + state: customer.billing_address?.state, + zip: customer.billing_address?.zip, + country: customer.billing_address?.country, + }), + }); + } + + @authorize({ + permissions: [PermissionKey.GetBillingCustomer], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @get(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'BillingCustomer model ', + content: {'application/json': {schema: getModelSchemaRef(CustomerDto)}}, + }, + }, + }) + async getCustomer( + @param.header.string('tenantId') tenantId: string, + ): Promise<{customerDetails:CustomerDto, + info:BillingCustomer + }> { + const customers = await this.billingCustomerRepository.find({ + where: { + tenantId: tenantId, + }, + include: [ + { + relation: 'invoices', // This includes related invoices + }, + ], + }); + if (customers.length == 0) { + throw new Error(' Customer with tenantId is not present'); + } + + const customer = await this.billingProvider.getCustomers( + customers[0].customerId, + ); + return {customerDetails:new CustomerDto({ + first_name: customer.first_name, + last_name: customer.last_name, + email: customer.email, + company: customer.company, + phone: customer.phone, + billing_address: new AddressDto({ + first_name: customer.billing_address?.first_name, + last_name: customer.billing_address?.last_name, + email: customer.billing_address?.email, + company: customer.billing_address?.company, + phone: customer.billing_address?.phone, + city: customer.billing_address?.city, + state: customer.billing_address?.state, + zip: customer.billing_address?.zip, + country: customer.billing_address?.country, + }), + }),info:customers[0]}; + } + + @authorize({ + permissions: [PermissionKey.UpdateBillingCustomer], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @patch(`${basePath}/{tenantId}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'BillingCustomer PATCH success', + }, + }, + }) + async updateById( + @param.path.string('tenantId') tenantId: string, + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(CustomerDto, {partial: true}), + }, + }, + }) + customerDto: Partial, + ): Promise { + const customers = await this.billingCustomerRepository.find({ + where: {tenantId: tenantId}, + }); + + if (customers.length == 0) { + throw new Error(' Customer with tenantId is not present'); + } + await this.billingProvider.updateCustomerById( + customers[0].customerId, + customerDto, + ); + } + + @authorize({ + permissions: [PermissionKey.DeleteBillingCustomer], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @del(`${basePath}/{tenantId}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'BillingCustomer DELETE success', + }, + }, + }) + async deleteById( + @param.path.string('tenantId') tenantId: string, + ): Promise { + const customer = await this.billingCustomerRepository.find({ + where: {tenantId: tenantId}, + }); + if (customer.length == 0) { + throw new Error(' Customer with tenantId is not present'); + } + await this.billingProvider.deleteCustomer(customer[0].customerId); + await this.invoiceRepository.deleteAll({billingCustomerId: customer[0].id}); + await this.billingCustomerRepository.deleteById(customer[0].id); + } +} diff --git a/services/subscription-service/src/controllers/billing-invoice.controller.ts b/services/subscription-service/src/controllers/billing-invoice.controller.ts new file mode 100644 index 0000000..a3467a1 --- /dev/null +++ b/services/subscription-service/src/controllers/billing-invoice.controller.ts @@ -0,0 +1,228 @@ +import {inject} from '@loopback/core'; +import {repository} from '@loopback/repository'; +import { + del, + get, + getModelSchemaRef, + param, + patch, + post, + requestBody, +} from '@loopback/rest'; +import {OPERATION_SECURITY_SPEC, STATUS_CODE} from '@sourceloop/core'; +import { + BillingComponentBindings, + IChargeBeeService, + IService, +} from 'local-billing'; +import {authenticate, STRATEGY} from 'loopback4-authentication'; +import {authorize} from 'loopback4-authorization'; +import {CustomerDto} from '../models/dto/customer-dto.model'; +import {InvoiceDto} from '../models/dto/invoice-dto.model'; +import {PaymentSourceDto} from '../models/dto/payment-dto.model'; +import {PermissionKey} from '../permissions'; +import {BillingCustomerRepository} from '../repositories/billing-customer.repository'; +import {InvoiceRepository} from '../repositories'; +import {AddressDto, ChargeDto, Invoice} from '../models'; + +const basePath = '/billing-invoice'; +export class BillingInvoiceController { + constructor( + @repository(BillingCustomerRepository) + public billingCustomerRepository: BillingCustomerRepository, + @repository(InvoiceRepository) + public invoiceRepository: InvoiceRepository, + @inject(BillingComponentBindings.BillingProvider) + private readonly billingProvider: IService, + ) {} + + @authorize({ + permissions: [PermissionKey.CreateBillingInvoice], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @post(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'invoice model instance', + content: {'application/json': {schema: getModelSchemaRef(InvoiceDto)}}, + }, + }, + }) + async create( + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(InvoiceDto, { + title: 'newInvoice', + exclude: ['id', 'status'], + }), + }, + }, + }) + invoiceDto: Omit, + ): Promise { + const customer = await this.billingCustomerRepository.find({ + where: {customerId: invoiceDto.customer_id}, + }); + + if (customer.length == 0) { + throw new Error(' Customer with tenantId is not present'); + } + const invoice = await this.billingProvider.createInvoice(invoiceDto); + const charges = invoice.charges?.map( + charge => + new ChargeDto({amount: charge.amount, description: charge.description}), + ); + + const invoiceInfo=await this.invoiceRepository.create({ + invoiceId: invoice.id, + invoiceStatus: invoice.status, + billingCustomerId: customer[0].id, + }); + return new InvoiceDto({ + id:invoiceInfo.id, // passed the id of invoice info created in our db, to setup relation between subscription and invoice + customer_id: invoice.customer_id, + charges: charges, + status: invoice.status, + shipping_address: new AddressDto({ + first_name: invoice.shipping_address?.first_name ?? '', + last_name: invoice.shipping_address?.last_name ?? '', + email: invoice.shipping_address?.email ?? '', + company: invoice.shipping_address?.company, + phone: invoice.shipping_address?.phone, + city: invoice.shipping_address?.city ?? '', + state: invoice.shipping_address?.state ?? '', + zip: invoice.shipping_address?.zip ?? '', + country: invoice.shipping_address?.country ?? '', + }), + options: invoice.options, + }); + } + + @authorize({ + permissions: [PermissionKey.GetBillingInvoice], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @get(`${basePath}/{invoiceId}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'get invoice', + content: {'application/json': {schema: getModelSchemaRef(InvoiceDto)}}, + }, + }, + }) + async getInvoice( + @param.path.string('invoiceId') invoiceId: string, + ): Promise { + const invoice = await this.billingProvider.retrieveInvoice(invoiceId); + const charges = invoice.charges?.map( + charge => + new ChargeDto({amount: charge.amount, description: charge.description}), + ); + return new InvoiceDto({ + customer_id: invoice.customer_id, + charges: charges, + status: invoice.status, + shipping_address: new AddressDto({ + first_name: invoice.shipping_address?.first_name ?? '', + last_name: invoice.shipping_address?.last_name ?? '', + email: invoice.shipping_address?.email ?? '', + company: invoice.shipping_address?.company, + phone: invoice.shipping_address?.phone, + city: invoice.shipping_address?.city ?? '', + state: invoice.shipping_address?.state ?? '', + zip: invoice.shipping_address?.zip ?? '', + country: invoice.shipping_address?.country ?? '', + }), + options: invoice.options, + }); + } + + @authorize({ + permissions: [PermissionKey.UpdateBillingInvoice], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @patch(`${basePath}/{invoiceId}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'Billing Invoice PATCH success', + }, + }, + }) + async updateById( + @param.path.string('invoiceId') invoiceId: string, + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(InvoiceDto, {partial: true}), + }, + }, + }) + invoiceDto: Partial, + ): Promise { + await this.billingProvider.updateInvoice(invoiceId, invoiceDto); + } + + @authorize({ + permissions: [PermissionKey.CreateBillingInvoice], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @post(`${basePath}/{invoiceId}/payments`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'invoice model instance', + }, + }, + }) + async applyPaymentForInvoice( + @param.path.string('invoiceId') invoiceId: string, + @param.header.string('paymentSourceId') paymentSourceId: string, + ): Promise { + await this.billingProvider.applyPaymentSourceForInvoice( + invoiceId, + paymentSourceId, + ); + } + + @authorize({ + permissions: [PermissionKey.DeleteBillingPaymentSource], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @del(`${basePath}/{invoiceId}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'Billing Invoice DELETE success', + }, + }, + }) + async deleteById( + @param.path.string('invoiceId') invoiceId: string, + ): Promise { + const invoice = await this.invoiceRepository.find({ + where: {invoiceId: invoiceId}, + }); + if (invoice.length == 0) + throw new Error(' invoice with invoiceId is not present'); + await this.billingProvider.deleteInvoice(invoiceId); + // await this.billingCustomerRepository.updateById(customer[0].id, { + // invoiceId: undefined, + // invoiceStatus: undefined, + // }); + await this.invoiceRepository.deleteById(invoice[0].id); + } +} diff --git a/services/subscription-service/src/controllers/billing-payment-source.controller.ts b/services/subscription-service/src/controllers/billing-payment-source.controller.ts new file mode 100644 index 0000000..353b118 --- /dev/null +++ b/services/subscription-service/src/controllers/billing-payment-source.controller.ts @@ -0,0 +1,144 @@ +import {inject} from '@loopback/core'; +import {repository} from '@loopback/repository'; +import { + del, + get, + getModelSchemaRef, + param, + post, + requestBody, +} from '@loopback/rest'; +import {OPERATION_SECURITY_SPEC, STATUS_CODE} from '@sourceloop/core'; +import { + BillingComponentBindings, + IChargeBeeService, + IService, +} from 'local-billing'; +import {authenticate, STRATEGY} from 'loopback4-authentication'; +import {authorize} from 'loopback4-authorization'; +import {CustomerDto} from '../models/dto/customer-dto.model'; +import {InvoiceDto} from '../models/dto/invoice-dto.model'; +import {PaymentSourceDto} from '../models/dto/payment-dto.model'; +import {PermissionKey} from '../permissions'; +import {BillingCustomerRepository} from '../repositories/billing-customer.repository'; +import {InvoiceRepository} from '../repositories'; + +const basePath = '/billing-payment-source'; +export class BillingPaymentSourceController { + constructor( + @repository(BillingCustomerRepository) + public billingCustomerRepository: BillingCustomerRepository, + @repository(InvoiceRepository) + public invoiceRepository: InvoiceRepository, + @inject(BillingComponentBindings.BillingProvider) + private readonly billingProvider: IService, + ) {} + + @authorize({ + permissions: [PermissionKey.CreateBillingPaymentSource], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @post(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'Payment model instance', + content: { + 'application/json': {schema: getModelSchemaRef(PaymentSourceDto)}, + }, + }, + }, + }) + async create( + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(PaymentSourceDto, { + title: 'NewPaymentSource', + exclude: ['id'], + }), + }, + }, + }) + paymentSourceDto: PaymentSourceDto, + ): Promise { + const customer = await this.billingCustomerRepository.find({ + where: {customerId: paymentSourceDto.customer_id}, + }); + + if (customer.length == 0) { + throw new Error(' Customer with tenantId is not present'); + } + const paymentSource = + await this.billingProvider.createPaymentSource(paymentSourceDto); + await this.billingCustomerRepository.updateById(customer[0].id, { + paymentSourceId: paymentSource.id, + }); + return new PaymentSourceDto({ + id: paymentSource.id, + customer_id: paymentSource.customer_id, + card: paymentSource.card, + }); + } + + @authorize({ + permissions: [PermissionKey.GetBillingPaymentSource], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @get(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'get payment source', + content: { + 'application/json': {schema: getModelSchemaRef(PaymentSourceDto)}, + }, + }, + }, + }) + async getPaymentSource( + @param.path.string('paymentSourceId') paymentSourceId: string, + ): Promise { + const paymentSource = + await this.billingProvider.retrievePaymentSource(paymentSourceId); + return new PaymentSourceDto({ + id: paymentSource.id, + customer_id: paymentSource.customer_id, + card: paymentSource.card, + }); + } + + @authorize({ + permissions: [PermissionKey.DeleteBillingPaymentSource], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @del(`${basePath}/{paymentSourceId}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'Billing Payment Source DELETE success', + }, + }, + }) + async deleteById( + @param.path.string('paymentSourceId') paymentSourceId: string, + ): Promise { + const customer = await this.billingCustomerRepository.find({ + where: {paymentSourceId: paymentSourceId}, + }); + + if (customer.length == 0) { + throw new Error(' Customer with tenantId is not present'); + } + await this.billingProvider.deletePaymentSource(paymentSourceId); + await this.billingCustomerRepository.updateById(customer[0].id, { + paymentSourceId: undefined, + }); + } +} diff --git a/services/subscription-service/src/controllers/index.ts b/services/subscription-service/src/controllers/index.ts index 7910a50..4ebd610 100644 --- a/services/subscription-service/src/controllers/index.ts +++ b/services/subscription-service/src/controllers/index.ts @@ -9,3 +9,4 @@ export * from './currency.controller'; export * from './plan-subscription.controller'; export * from './plan-sizes.controller'; export * from './plan-features.controller'; +export * from './subscription-invoice.controller'; diff --git a/services/subscription-service/src/controllers/subscription-invoice.controller.ts b/services/subscription-service/src/controllers/subscription-invoice.controller.ts new file mode 100644 index 0000000..a833dbe --- /dev/null +++ b/services/subscription-service/src/controllers/subscription-invoice.controller.ts @@ -0,0 +1,38 @@ +import { + repository, +} from '@loopback/repository'; +import { + param, + get, + getModelSchemaRef, +} from '@loopback/rest'; +import { + Subscription, + Invoice, +} from '../models'; +import {SubscriptionRepository} from '../repositories'; + +export class SubscriptionInvoiceController { + constructor( + @repository(SubscriptionRepository) + public subscriptionRepository: SubscriptionRepository, + ) { } + + @get('/subscriptions/{id}/invoice', { + responses: { + '200': { + description: 'Invoice belonging to Subscription', + content: { + 'application/json': { + schema: getModelSchemaRef(Invoice), + }, + }, + }, + }, + }) + async getInvoice( + @param.path.string('id') id: typeof Subscription.prototype.id, + ): Promise { + return this.subscriptionRepository.invoice(id); + } +} diff --git a/services/subscription-service/src/controllers/webhook.controller.ts b/services/subscription-service/src/controllers/webhook.controller.ts new file mode 100644 index 0000000..5994fd9 --- /dev/null +++ b/services/subscription-service/src/controllers/webhook.controller.ts @@ -0,0 +1,44 @@ +import {intercept} from '@loopback/core'; +import {repository} from '@loopback/repository'; +import {post, requestBody} from '@loopback/rest'; +import {authorize} from 'loopback4-authorization'; +import {WEBHOOK_VERIFIER} from '../keys'; +import {InvoiceRepository} from '../repositories'; +import {BillingCustomerRepository} from '../repositories/billing-customer.repository'; + +export class WebhookController { + constructor( + @repository(BillingCustomerRepository) + public billingCustomerRepository: BillingCustomerRepository, + @repository(InvoiceRepository) + public invoiceRepository: InvoiceRepository, + ) {} + + @authorize({ + permissions: ['*'], + }) + @intercept(WEBHOOK_VERIFIER) + @post('/webhooks/chargebee') + async handleWebhook(@requestBody() payload: any): Promise { + const event = payload.event_type; + const content = payload.content; + + switch (event) { + case 'payment_succeeded': + await this.handlePaymentSucceeded(content); + break; + // Handle other events here + default: + console.log(`Unhandled event type: ${event}`); + } + } + + private async handlePaymentSucceeded(content: any): Promise { + const invoice = await this.invoiceRepository.find({ + where: {invoiceId: content.invoice.id}, + }); + await this.invoiceRepository.updateById(invoice[0].id, { + invoiceStatus: content.invoice.status, + }); + } +} diff --git a/services/subscription-service/src/interceptors/index.ts b/services/subscription-service/src/interceptors/index.ts new file mode 100644 index 0000000..dc511f6 --- /dev/null +++ b/services/subscription-service/src/interceptors/index.ts @@ -0,0 +1 @@ +export * from './webhook-verifier.interceptor'; diff --git a/services/subscription-service/src/interceptors/webhook-verifier.interceptor.ts b/services/subscription-service/src/interceptors/webhook-verifier.interceptor.ts new file mode 100644 index 0000000..26a204e --- /dev/null +++ b/services/subscription-service/src/interceptors/webhook-verifier.interceptor.ts @@ -0,0 +1,51 @@ +import { + Interceptor, + InvocationContext, + Provider, + Setter, + ValueOrPromise, + inject, +} from '@loopback/core'; +import {HttpErrors, RequestContext} from '@loopback/rest'; +import {ILogger, LOGGER} from '@sourceloop/core'; +import {AuthenticationBindings, IAuthUser} from 'loopback4-authentication'; +import {SYSTEM_USER} from '../keys'; + +export class WebhookVerifierProvider implements Provider { + constructor( + @inject(LOGGER.LOGGER_INJECT) + private readonly logger: ILogger, + @inject.setter(AuthenticationBindings.CURRENT_USER) + private readonly setCurrentUser: Setter, + @inject(SYSTEM_USER) + private readonly systemUser: IAuthUser, + ) {} + + value() { + return this.intercept.bind(this); + } + + async intercept( + invocationCtx: InvocationContext, + next: () => ValueOrPromise, + ) { + const {request} = invocationCtx.parent as RequestContext; + const authHeader = request.headers['authorization']; + const username = process.env.WEBHOOK_USERNAME; + const password = process.env.WEBHOOK_PASSWORD; + const expectedAuthHeader = + 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'); + + try { + if (!authHeader || authHeader !== expectedAuthHeader) { + throw new HttpErrors.Unauthorized('Invalid authorization.'); + } + } catch (e) { + this.logger.error(e); + throw new HttpErrors.Unauthorized(); + } + + this.setCurrentUser(this.systemUser); + return next(); + } +} diff --git a/services/subscription-service/src/keys.ts b/services/subscription-service/src/keys.ts index 3801d39..9b3c9fb 100644 --- a/services/subscription-service/src/keys.ts +++ b/services/subscription-service/src/keys.ts @@ -1,8 +1,9 @@ -import {BindingKey} from '@loopback/core'; +import {BindingKey, Interceptor} from '@loopback/core'; import {ISubscriptionServiceConfig} from './types'; import {BINDING_PREFIX} from '@sourceloop/core'; import {VerifyFunction} from 'loopback4-authentication'; import {AnyObject} from '@loopback/repository'; +import {IAuthUser} from 'loopback4-authorization'; export namespace SubscriptionServiceBindings { export const Config = BindingKey.create( @@ -10,9 +11,20 @@ export namespace SubscriptionServiceBindings { ); } +export const WEBHOOK_VERIFIER = BindingKey.create( + 'sf.webhook.verifier', +); + export const LEAD_TOKEN_VERIFIER = BindingKey.create< VerifyFunction.BearerFn >('sf.user.lead.verifier'); /** * Binding key for the lead token verifier. */ + +/** + * Binding key for the system user. + */ +export const SYSTEM_USER = BindingKey.create( + 'sf.user.system', +); diff --git a/services/subscription-service/src/models/billing-customer.model.ts b/services/subscription-service/src/models/billing-customer.model.ts new file mode 100644 index 0000000..47bc964 --- /dev/null +++ b/services/subscription-service/src/models/billing-customer.model.ts @@ -0,0 +1,44 @@ +import {model, property, hasMany} from '@loopback/repository'; +import {UserModifiableEntity} from '@sourceloop/core'; +import {Invoice} from './invoice.model'; + +@model({ + name: 'billing_customer', + description: 'contacts belonging to a tenant', +}) +export class BillingCustomer extends UserModifiableEntity { + @property({ + type: 'string', + id: true, + generated: true, + }) + id?: string; + + @property({ + type: 'string', + name: 'tenant_id', + required: true, + }) + tenantId: string; // tenantId of customer + + @property({ + type: 'string', + name: 'customer_id', + required: true, + }) + customerId: string; // id of customer generated on third party billing module + + @property({ + type: 'string', + name: 'payment_source_id', + }) + paymentSourceId?: string; + + // Define the hasMany relation + @hasMany(() => Invoice, {keyTo: 'billingCustomerId'}) + invoices: Invoice[]; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/services/subscription-service/src/models/dto/address-dto.model.ts b/services/subscription-service/src/models/dto/address-dto.model.ts new file mode 100644 index 0000000..ca8706f --- /dev/null +++ b/services/subscription-service/src/models/dto/address-dto.model.ts @@ -0,0 +1,71 @@ +import {Entity, model, property} from '@loopback/repository'; +import {Options} from 'local-billing'; + +@model() +export class AddressDto extends Entity { + @property({ + type: 'string', + name: 'first_name', + }) + first_name: string; + + @property({ + type: 'string', + name: 'last_name', + }) + last_name: string; + @property({ + type: 'string', + required: true, + }) + email: string; + + @property({ + type: 'string', + }) + company?: string; + + @property({ + type: 'string', + }) + phone?: string; + + @property({ + type: 'string', + required: true, + }) + city: string; + + @property({ + type: 'string', + required: true, + }) + state: string; + + @property({ + type: 'string', + required: true, + }) + zip: string; + + @property({ + type: 'string', + required: true, + }) + country: string; + + @property({ + type: 'object', + }) + options?: Options; + + constructor(data?: Partial) { + super(data); + } +} + +export interface AddressRelations { + // describe navigational properties here +} + +export type AddressWithRelations = AddressDto & AddressRelations; diff --git a/services/subscription-service/src/models/dto/charge-dto.model.ts b/services/subscription-service/src/models/dto/charge-dto.model.ts new file mode 100644 index 0000000..87c4aaf --- /dev/null +++ b/services/subscription-service/src/models/dto/charge-dto.model.ts @@ -0,0 +1,26 @@ +import {Entity, model, property} from '@loopback/repository'; + +@model() +export class ChargeDto extends Entity { + @property({ + type: 'number', + required: true, + }) + amount: number; + + @property({ + type: 'string', + required: true, + }) + description: string; + + constructor(data?: Partial) { + super(data); + } +} + +export interface ChargeRelations { + // describe navigational properties here +} + +export type ChargeWithRelations = ChargeDto & ChargeRelations; diff --git a/services/subscription-service/src/models/dto/customer-dto.model.ts b/services/subscription-service/src/models/dto/customer-dto.model.ts new file mode 100644 index 0000000..4f70468 --- /dev/null +++ b/services/subscription-service/src/models/dto/customer-dto.model.ts @@ -0,0 +1,75 @@ +import {model, Model, property} from '@loopback/repository'; +import {AddressDto} from './address-dto.model'; +import {Options} from 'local-billing'; + +@model({ + name: 'customer_dto', +}) +export class CustomerDto extends Model { + @property({ + type: 'string', + name: 'id', + }) + id?: string; + + @property({ + type: 'string', + name: 'first_name', + }) + first_name: string; + + @property({ + type: 'string', + name: 'last_name', + }) + last_name: string; + + @property({ + type: 'string', + name: 'email', + }) + email: string; + + @property({ + type: 'string', + name: 'company', + }) + company: string; + + @property({ + type: 'string', + name: 'phone', + }) + phone: string; + + @property({ + type: AddressDto, + name: 'billing_address', + }) + billing_address: AddressDto; + + @property({ + type: 'object', + }) + options?: Options; + + constructor(data?: Partial) { + super(data); + } +} + +// this refers to the billing address and shipping address. +export interface IAddress { + firstName: string; + lastName: string; + email: string; + company?: string; + phone?: string; + line1?: string; + line2?: string; + line3?: string; + city: string; + state: string; + zip: string; + coutnry: string; +} diff --git a/services/subscription-service/src/models/dto/index.ts b/services/subscription-service/src/models/dto/index.ts new file mode 100644 index 0000000..3037b0e --- /dev/null +++ b/services/subscription-service/src/models/dto/index.ts @@ -0,0 +1,5 @@ +export * from './address-dto.model'; +export * from './charge-dto.model'; +export * from './customer-dto.model'; +export * from './invoice-dto.model'; +export * from './payment-dto.model'; diff --git a/services/subscription-service/src/models/dto/invoice-dto.model.ts b/services/subscription-service/src/models/dto/invoice-dto.model.ts new file mode 100644 index 0000000..f4885d1 --- /dev/null +++ b/services/subscription-service/src/models/dto/invoice-dto.model.ts @@ -0,0 +1,74 @@ +import {model, Model, property} from '@loopback/repository'; +import {Options} from 'local-billing'; +import {AddressDto} from './address-dto.model'; +import {ChargeDto} from './charge-dto.model'; + +@model({ + name: 'invoice_dto', +}) +export class InvoiceDto extends Model { + @property({ + type: 'string', + name: 'id', + }) + id?: string; + + @property({ + type: 'string', + name: 'customer_id', + }) + customer_id: string; + + @property({ + type: 'object', + }) + options?: Options; + + @property({ + type: AddressDto, + name: 'shipping_address', + }) + shipping_address: AddressDto; + + @property({ + type: 'array', + itemType: ChargeDto, + name: 'charges', + }) + charges: ChargeDto[]; + + // @property({ + // type: 'string', + // name: 'auto_collection', + // }) + // auto_collection?: AutoCollection; + + @property({ + type: 'string', + name: 'status', + }) + status?: string; + + // @property({ + // type: 'string', + // name:'subscription_id' + // }) + // subscriptionId?: string; + + // @property({ + // type: 'array', + // itemType: DiscountDto, + // name: 'discounts', + // }) + // discounts:DiscountDto[] + + constructor(data?: Partial) { + super(data); + } +} + +// This refers to the item to be added in invoice. +// export interface ICharge{ +// amount:number; +// description:string; +// } diff --git a/services/subscription-service/src/models/dto/payment-dto.model.ts b/services/subscription-service/src/models/dto/payment-dto.model.ts new file mode 100644 index 0000000..c9a6fc3 --- /dev/null +++ b/services/subscription-service/src/models/dto/payment-dto.model.ts @@ -0,0 +1,55 @@ +import {model, Model, property} from '@loopback/repository'; + +@model({ + name: 'payment_source_dto', +}) +export class PaymentSourceDto extends Model { + @property({ + type: 'string', + name: 'id', + }) + id?: string; + + @property({ + type: 'string', + name: 'customer_id', + }) + customer_id: string; + + @property({ + type: 'object', + name: 'card', + required: true, + jsonSchema: { + type: 'object', + properties: { + gateway_account_id: {type: 'string'}, + number: {type: 'string'}, + expiry_month: {type: 'number'}, + expiry_year: {type: 'number'}, + cvv: {type: 'number'}, + }, + required: [ + 'gateway_account_id', + 'number', + 'expiry_month', + 'expiry_year', + 'cvv', + ], + }, + }) + card: ICard; + + constructor(data?: Partial) { + super(data); + } +} + +// this refers to the card +export interface ICard { + gateway_account_id: string; + number: string; + expiry_month: number; + expiry_year: number; + cvv: string; +} diff --git a/services/subscription-service/src/models/index.ts b/services/subscription-service/src/models/index.ts index 0dc32c6..8753b60 100644 --- a/services/subscription-service/src/models/index.ts +++ b/services/subscription-service/src/models/index.ts @@ -5,3 +5,6 @@ export * from './resource.model'; export * from './billing-cycle.model'; export * from './currency.model'; export * from './plan-sizes.model'; +export * from './dto'; +export * from './billing-customer.model'; +export * from './invoice.model'; diff --git a/services/subscription-service/src/models/invoice.model.ts b/services/subscription-service/src/models/invoice.model.ts new file mode 100644 index 0000000..20f729b --- /dev/null +++ b/services/subscription-service/src/models/invoice.model.ts @@ -0,0 +1,46 @@ +import {model, property} from '@loopback/repository'; +import {UserModifiableEntity} from '@sourceloop/core'; + +@model({ + name: 'invoice', + description: 'invoice for a customer', +}) +export class Invoice extends UserModifiableEntity { + @property({ + type: 'string', + id: true, + generated: true, + }) + id?: string; + + @property({ + type: 'string', + name: 'invoice_id', + required: true, + }) + invoiceId: string; + + @property({ + type: 'boolean', + name: 'invoice_status', + description: 'payment or invoice status', + }) + invoiceStatus?: Boolean; + + @property({ + type: 'string', + name: 'billing_customer_id', + required: true, + }) + billingCustomerId: string; + + // @property({ + // type: 'string', + // name:'subscription_id' + // }) + // subscriptionId?: string; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/services/subscription-service/src/models/subscription.model.ts b/services/subscription-service/src/models/subscription.model.ts index 3b2f834..bd1efd1 100644 --- a/services/subscription-service/src/models/subscription.model.ts +++ b/services/subscription-service/src/models/subscription.model.ts @@ -3,6 +3,7 @@ import {SubscriptionStatus} from '../enums/subscription-status.enum'; import {numericEnumValues} from '../utils'; import {UserModifiableEntity} from '@sourceloop/core'; import {Plan} from './plan.model'; +import {Invoice} from './invoice.model'; @model({ name: 'subscriptions', @@ -53,6 +54,12 @@ export class Subscription extends UserModifiableEntity { }) planId: string; + @belongsTo(() => Invoice,undefined,{ + description: 'invoice id of the subscription', + name:"invoice_id" + }) + invoiceId: string; + constructor(data?: Partial) { super(data); } diff --git a/services/subscription-service/src/permissions.ts b/services/subscription-service/src/permissions.ts index d1b5c60..0f8749b 100644 --- a/services/subscription-service/src/permissions.ts +++ b/services/subscription-service/src/permissions.ts @@ -48,4 +48,19 @@ export const PermissionKey = { DeleteInvoice: '10214', ViewInvoice: '10215', CreateNotification: '2', + CreateBillingCustomer: '5321', + CreateBillingPaymentSource: '5322', + CreateBillingInvoice: '5323', + GetBillingCustomer: '5324', + GetBillingPaymentSource: '5325', + GetBillingInvoice: '5326', + UpdateBillingCustomer: '5327', + UpdateBillingPaymentSource: '5328', + UpdateBillingInvoice: '5329', + DeleteBillingCustomer: '5331', + DeleteBillingPaymentSource: '5332', + DeleteBillingInvoice: '5333', }; + + + diff --git a/services/subscription-service/src/providers/index.ts b/services/subscription-service/src/providers/index.ts index e69de29..e1a63a7 100644 --- a/services/subscription-service/src/providers/index.ts +++ b/services/subscription-service/src/providers/index.ts @@ -0,0 +1 @@ +export * from './system-user.provider'; diff --git a/services/subscription-service/src/providers/system-user.provider.ts b/services/subscription-service/src/providers/system-user.provider.ts new file mode 100644 index 0000000..763b77c --- /dev/null +++ b/services/subscription-service/src/providers/system-user.provider.ts @@ -0,0 +1,13 @@ +import {Provider, ValueOrPromise} from '@loopback/core'; +import {IAuthUser} from 'loopback4-authorization'; +import {AnyObject} from '@loopback/repository'; +export class SystemUserProvider implements Provider { + value(): ValueOrPromise { + return { + username: 'SYSTEM', + id: process.env.SYSTEM_USER_ID!, + userTenantId: process.env.SYSTEM_USER_ID!, + permissions: [], + }; + } +} diff --git a/services/subscription-service/src/repositories/billing-customer.repository.ts b/services/subscription-service/src/repositories/billing-customer.repository.ts new file mode 100644 index 0000000..8a7c558 --- /dev/null +++ b/services/subscription-service/src/repositories/billing-customer.repository.ts @@ -0,0 +1,44 @@ +import {inject, Getter} from '@loopback/core'; +import { + DefaultCrudRepository, + repository, + HasManyRepositoryFactory, + juggler, +} from '@loopback/repository'; +// import {SubServiceDataSource} from '../datasources'; +import {BillingCustomer, Invoice} from '../models'; +import {InvoiceRepository} from './invoice.repository'; +import {SubscriptionDbSourceName} from '../types'; +import { + DefaultUserModifyCrudRepository, + IAuthUserWithPermissions, +} from '@sourceloop/core'; +import {AuthenticationBindings} from 'loopback4-authentication'; + +export class BillingCustomerRepository extends DefaultUserModifyCrudRepository< + BillingCustomer, + typeof BillingCustomer.prototype.id, + {} +> { + public readonly invoices: HasManyRepositoryFactory< + Invoice, + typeof BillingCustomer.prototype.id + >; + + constructor( + @inject(`datasources.${SubscriptionDbSourceName}`) + dataSource: juggler.DataSource, + @repository.getter('InvoiceRepository') + protected invoiceRepositoryGetter: Getter, + + @inject.getter(AuthenticationBindings.CURRENT_USER) + public readonly getCurrentUser: Getter, + ) { + super(BillingCustomer, dataSource, getCurrentUser); + this.invoices = this.createHasManyRepositoryFactoryFor( + 'invoices', + invoiceRepositoryGetter, + ); + this.registerInclusionResolver('invoices', this.invoices.inclusionResolver); + } +} diff --git a/services/subscription-service/src/repositories/index.ts b/services/subscription-service/src/repositories/index.ts index 6a82b38..4379caa 100644 --- a/services/subscription-service/src/repositories/index.ts +++ b/services/subscription-service/src/repositories/index.ts @@ -5,3 +5,5 @@ export * from './subscription.repository'; export * from './billing-cycle.repository'; export * from './currency.repository'; export * from './plan-sizes.repository'; +export * from './billing-customer.repository'; +export * from './invoice.repository'; diff --git a/services/subscription-service/src/repositories/invoice.repository.ts b/services/subscription-service/src/repositories/invoice.repository.ts new file mode 100644 index 0000000..bb729ec --- /dev/null +++ b/services/subscription-service/src/repositories/invoice.repository.ts @@ -0,0 +1,25 @@ +import {Getter, inject} from '@loopback/core'; +import {DefaultCrudRepository, juggler} from '@loopback/repository'; +// import {SubServiceDataSource} from '../datasources'; +import {Invoice} from '../models'; +import {SubscriptionDbSourceName} from '../types'; +import { + DefaultUserModifyCrudRepository, + IAuthUserWithPermissions, +} from '@sourceloop/core'; +import {AuthenticationBindings} from 'loopback4-authentication'; + +export class InvoiceRepository extends DefaultUserModifyCrudRepository< + Invoice, + typeof Invoice.prototype.id, + {} +> { + constructor( + @inject(`datasources.${SubscriptionDbSourceName}`) + dataSource: juggler.DataSource, + @inject.getter(AuthenticationBindings.CURRENT_USER) + public readonly getCurrentUser: Getter, + ) { + super(Invoice, dataSource, getCurrentUser); + } +} diff --git a/services/subscription-service/src/repositories/subscription.repository.ts b/services/subscription-service/src/repositories/subscription.repository.ts index 8c57007..7511fb0 100644 --- a/services/subscription-service/src/repositories/subscription.repository.ts +++ b/services/subscription-service/src/repositories/subscription.repository.ts @@ -1,6 +1,6 @@ import {inject, Getter} from '@loopback/core'; import {repository, BelongsToAccessor, juggler} from '@loopback/repository'; -import {Subscription, SubscriptionRelations, Plan} from '../models'; +import {Subscription, SubscriptionRelations, Plan, Invoice} from '../models'; import {PlanRepository} from './plan.repository'; import {AuthenticationBindings} from 'loopback4-authentication'; import { @@ -8,6 +8,7 @@ import { IAuthUserWithPermissions, } from '@sourceloop/core'; import {SubscriptionDbSourceName} from '../types'; +import {InvoiceRepository} from './invoice.repository'; export class SubscriptionRepository extends DefaultUserModifyCrudRepository< Subscription, @@ -19,15 +20,19 @@ export class SubscriptionRepository extends DefaultUserModifyCrudRepository< typeof Subscription.prototype.id >; + public readonly invoice: BelongsToAccessor; + constructor( @inject(`datasources.${SubscriptionDbSourceName}`) dataSource: juggler.DataSource, @inject.getter(AuthenticationBindings.CURRENT_USER) public readonly getCurrentUser: Getter, @repository.getter('PlanRepository') - protected planRepositoryGetter: Getter, + protected planRepositoryGetter: Getter, @repository.getter('InvoiceRepository') protected invoiceRepositoryGetter: Getter, ) { super(Subscription, dataSource, getCurrentUser); + this.invoice = this.createBelongsToAccessorFor('invoice', invoiceRepositoryGetter,); + this.registerInclusionResolver('invoice', this.invoice.inclusionResolver); this.plan = this.createBelongsToAccessorFor('plan', planRepositoryGetter); this.registerInclusionResolver('plan', this.plan.inclusionResolver); } diff --git a/services/tenant-management-service/src/permissions.ts b/services/tenant-management-service/src/permissions.ts index 9da2602..4cf7dd9 100644 --- a/services/tenant-management-service/src/permissions.ts +++ b/services/tenant-management-service/src/permissions.ts @@ -28,4 +28,17 @@ export const PermissionKey = { CreateNotificationTemplate: '8001', UpdateNotificationTemplate: '8002', DeleteNotificationTemplate: '8003', + + CreateBillingCustomer: '5321', + CreateBillingPaymentSource: '5322', + CreateBillingInvoice: '5323', + GetBillingCustomer: '5324', + GetBillingPaymentSource: '5325', + GetBillingInvoice: '5326', + UpdateBillingCustomer: '5327', + UpdateBillingPaymentSource: '5328', + UpdateBillingInvoice: '5329', + DeleteBillingCustomer: '5331', + DeleteBillingPaymentSource: '5332', + DeleteBillingInvoice: '5333', }; diff --git a/services/tenant-management-service/src/services/lead-authenticator.service.ts b/services/tenant-management-service/src/services/lead-authenticator.service.ts index ee2c700..b99e649 100644 --- a/services/tenant-management-service/src/services/lead-authenticator.service.ts +++ b/services/tenant-management-service/src/services/lead-authenticator.service.ts @@ -43,6 +43,19 @@ export class LeadAuthenticator { PermissionKey.ViewPlan, PermissionKey.ViewSubscription, PermissionKey.CreateInvoice, + PermissionKey.CreateBillingCustomer, + PermissionKey.CreateBillingPaymentSource, + PermissionKey.CreateBillingInvoice, + PermissionKey.GetBillingCustomer, + PermissionKey.GetBillingPaymentSource, + PermissionKey.GetBillingInvoice, + PermissionKey.UpdateBillingCustomer, + PermissionKey.UpdateBillingPaymentSource, + PermissionKey.UpdateBillingInvoice, + PermissionKey.DeleteBillingCustomer, + PermissionKey.DeleteBillingPaymentSource, + PermissionKey.DeleteBillingInvoice, + ]); const randomKey = this.cryptoHelperService.generateRandomString( +process.env.LEAD_KEY_LENGTH!, diff --git a/services/tenant-management-service/src/services/onboarding.service.ts b/services/tenant-management-service/src/services/onboarding.service.ts index 2e11ea6..aa397aa 100644 --- a/services/tenant-management-service/src/services/onboarding.service.ts +++ b/services/tenant-management-service/src/services/onboarding.service.ts @@ -1,5 +1,5 @@ import {BindingScope, inject, injectable, service} from '@loopback/core'; -import {repository} from '@loopback/repository'; +import {relation, repository} from '@loopback/repository'; import {HttpErrors} from '@loopback/rest'; import {ILogger, LOGGER} from '@sourceloop/core'; import {Address, Contact, Lead, Tenant, TenantOnboardDTO} from '../models'; @@ -197,8 +197,17 @@ export class OnboardingService { }, {transaction}, ); + const res = await this.tenantRepository.findById(tenant.id, { + include: [ + {relation: 'contacts'}, + {relation: 'resources'}, + {relation: 'lead'}, + {relation: 'address'}, + ], + },{transaction}); + await transaction.commit(); - return tenant; + return res; } catch (error) { await transaction.rollback(); throw error;