{"_id":"@carselona/service-kit","_rev":"3-af3cf2305244eb14805a310982f692af","name":"@carselona/service-kit","dist-tags":{"latest":"1.1.1"},"versions":{"1.0.0":{"name":"@carselona/service-kit","version":"1.0.0","keywords":["carselona","payments","easebuzz","service-kit"],"author":{"name":"Shubham Singh"},"license":"MIT","_id":"@carselona/service-kit@1.0.0","maintainers":[{"name":"carselona","email":"shubh.singh.it@gmail.com"}],"dist":{"shasum":"1a7cf6c9878bdecbe4140511c5e223912dde7fe8","tarball":"https://registry.npmjs.org/@carselona/service-kit/-/service-kit-1.0.0.tgz","fileCount":30,"integrity":"sha512-JqoDEHIBx3YYKPJ1plxqFZ8Vsvvr/7Of+ecTUWHWyKgMLgvX3A3zrJTi2KmdZIpiRieaUby9sTusO98ehPUHHg==","signatures":[{"sig":"MEYCIQDD126I9S15MxHMurwdPylPPO1fj4ihaaT8wK2BUXfvsgIhAKBHb8SEjbCAt8dHuMMOBANMuQjpW934a9z8b7M/+tc6","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":69898},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=18"},"scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc -p tsconfig.json","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"carselona","email":"shubh.singh.it@gmail.com"},"_npmVersion":"10.9.2","description":"Reusable service utilities for Carselona projects (Easebuzz payments and more).","directories":{},"_nodeVersion":"23.11.0","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.4.0","@types/node":"^20.11.0"},"_npmOperationalInternal":{"tmp":"tmp/service-kit_1.0.0_1781081249948_0.15205004747925877","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@carselona/service-kit","version":"1.0.1","keywords":["carselona","payments","easebuzz","service-kit"],"author":{"name":"Shubham Singh"},"license":"MIT","_id":"@carselona/service-kit@1.0.1","maintainers":[{"name":"carselona","email":"shubh.singh.it@gmail.com"}],"dist":{"shasum":"1099831a243a6ef94631f68cc7380e5fa22af0b1","tarball":"https://registry.npmjs.org/@carselona/service-kit/-/service-kit-1.0.1.tgz","fileCount":30,"integrity":"sha512-jKAXLhrm3olbHvQ3T55blvFOnsywcGBLUOwlTdssrGnQTaUD5i8QFrKEjUbWCnQyZ/h4vjU+p5/u4GCBkxfY/A==","signatures":[{"sig":"MEUCIFUEAOQriZpwhRlRFzJfx8ByFTVmJ7t6mVwPxBxhVCuQAiEA/uTfitzcZIh08w4WzbBgf9RSNGtQ58f/g6WreW9pRvM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":71093},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=18"},"scripts":{"test":"echo \"Error: no test specified\" && exit 1","build":"tsc -p tsconfig.json","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"carselona","email":"shubh.singh.it@gmail.com"},"_npmVersion":"10.9.2","description":"Reusable service utilities for Carselona projects (Easebuzz payments and more).","directories":{},"_nodeVersion":"23.11.0","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.4.0","@types/node":"^20.11.0"},"_npmOperationalInternal":{"tmp":"tmp/service-kit_1.0.1_1781084347837_0.7508171259728569","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@carselona/service-kit","version":"1.1.1","description":"Reusable service utilities for Carselona projects (Easebuzz payments and more).","main":"dist/index.js","types":"dist/index.d.ts","scripts":{"build":"tsc -p tsconfig.json","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build","test":"echo \"Error: no test specified\" && exit 1"},"engines":{"node":">=18"},"keywords":["carselona","payments","easebuzz","service-kit"],"author":{"name":"Shubham Singh"},"license":"MIT","devDependencies":{"@types/node":"^20.11.0","typescript":"^5.4.0"},"_id":"@carselona/service-kit@1.1.1","_nodeVersion":"23.11.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-jJdFq+5k6sk9/LVfgm498ydRTK8k5a+hmvfk0HjAXvFllo4VW/I3b8qkadWKxfo8y1DghYPMwLkLthr+ziQeWQ==","shasum":"a5e51b6014152c18a72aefa46e9d967181e0b2c4","tarball":"https://registry.npmjs.org/@carselona/service-kit/-/service-kit-1.1.1.tgz","fileCount":30,"unpackedSize":90131,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCmbU4YHlHNuuBvtFh54mnrScTDFtN9ox4zcUchluZFuAIgPbhri1mKlg+GotdSFiBvtSp7CU9mdBRS7fL1XsSLH9o="}]},"_npmUser":{"name":"carselona","email":"shubh.singh.it@gmail.com"},"directories":{},"maintainers":[{"name":"carselona","email":"shubh.singh.it@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/service-kit_1.1.1_1781087839055_0.8271348423572378"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-10T08:47:29.819Z","modified":"2026-06-10T10:37:19.283Z","1.0.0":"2026-06-10T08:47:30.097Z","1.0.1":"2026-06-10T09:39:07.967Z","1.1.1":"2026-06-10T10:37:19.190Z"},"author":{"name":"Shubham Singh"},"license":"MIT","keywords":["carselona","payments","easebuzz","service-kit"],"description":"Reusable service utilities for Carselona projects (Easebuzz payments and more).","maintainers":[{"name":"carselona","email":"shubh.singh.it@gmail.com"}],"readme":"# @carselona/service-kit\n\nReusable service utilities for Carselona projects. Designed to be installed as a\npackage and shared across services — no project-specific globals, env files, ORM\nmodels, or HTTP wrappers are required. Everything is injected through config.\n\n## Install\n\n```bash\nnpm install @carselona/service-kit\n```\n\nRequires Node 18+ (uses the built-in `fetch` and `crypto`). Zero runtime\ndependencies.\n\n## PaymentUtils (Easebuzz)\n\n```ts\nimport { PaymentUtils } from \"@carselona/service-kit\";\n\nconst payments = new PaymentUtils({\n  credentials: {\n    key: process.env.EASEBUZZ_KEY!,\n    salt: process.env.EASEBUZZ_SALT!,\n  },\n  // Required only for hosted-checkout `initiatePayment`:\n  successUrl: \"https://api.example.com/payments/easebuzz/success\",\n  failureUrl: \"https://api.example.com/payments/easebuzz/failure\",\n  // Optional: persist transactions in YOUR datastore however you like.\n  onTransaction: (record) => db.paymentTransactions.create(record),\n});\n```\n\n### Why config injection?\n\nThe original `PaymentUtils` in the admin API was bound to that project's\n`getEnvironmentVariables()`, Sequelize `Model`, `Helper`, and `ApiRequest`. To be\ninstallable elsewhere, this version takes credentials/URLs as constructor config,\nuses Node built-ins for hashing and HTTP, and replaces the hard-coded DB writes\nwith an optional `onTransaction` hook. Subscription-specific logic\n(`capturePaymentBreakups`) lives with the consuming app, not the package.\n\n### Methods\n\n| Method | Purpose |\n| --- | --- |\n| `initiatePayment(params)` | Start a hosted-checkout payment; returns the gateway access token. |\n| `createPaymentLink(params)` | Create a shareable EasyCollect link → `{ success, shortUrl, txnId }`. |\n| `initiateRefund({ txnId, refundAmount })` | Refund a completed transaction (resolves `easepayid` automatically). |\n| `getRefundStatus(txnId)` | Look up refund status. |\n| `debitRequestNotify({ amount, autopayToken })` | Send autopay pre-debit notification. |\n| `debitRequestInitiate({ amount, autopayToken })` | Notify + execute an autopay debit. |\n| `getTransactionDetail(txnId)` | Fetch a single transaction's detail. |\n| `getTransactionDetailByDate({ start, end, next? })` | Fetch transactions in a date range (paginated). |\n| `createHash(params)` | Compute the Easebuzz `key\\|…\\|salt` SHA-512 — use it to verify webhook/redirect callbacks. |\n\nFailures throw `PaymentError` (with a `statusCode`), except `createPaymentLink`,\nwhich resolves to `{ success: false }` to mirror the original behavior.\n\n## SubscriptionPaymentUtils (payment breakups)\n\n`capturePaymentBreakups` maps the component amounts of a subscription payment\n(paid, coins, offer, fine, adjusted, super-discount, total) into breakup rows and\na subscription-month update. The package owns that **mapping**; you own the\n**tables**. Implement a small store adapter with your models:\n\n```ts\nimport { SubscriptionPaymentUtils } from \"@carselona/service-kit\";\n\nconst subscriptionPayments = new SubscriptionPaymentUtils({\n  store: {\n    async getSubscription(id) {\n      const s = await Model.SubscriptionMonth.findOne({ where: { id } });\n      if (!s) return null;\n      return { id: s.id, transactionId: s.transactionid, paymentMode: s.payment_mode };\n    },\n    async saveBreakups({ subscriptionId, rows, subscriptionUpdate }) {\n      await Model.sequelize.transaction(async (t) => {\n        await Model.SubscriptionPaymentBreakup.destroy({\n          where: { subscription_id: subscriptionId }, transaction: t,\n        });\n        await Model.SubscriptionPaymentBreakup.bulkCreate(rows, { transaction: t });\n        await Model.SubscriptionMonth.update(subscriptionUpdate, {\n          where: { id: subscriptionId }, transaction: t,\n        });\n      });\n    },\n  },\n  // optional: log-and-swallow like the original admin API; omit to throw instead\n  onError: (err, { subscriptionId }) => logger.error({ subscriptionId, err }),\n});\n\nawait subscriptionPayments.capturePaymentBreakups(subscriptionId, {\n  PAID: 1000, COINS: 50, COINS_VALUE: 50, OFFER: 100, OFFER_QUANTITY: 2,\n  OFFER_UNIT: \"days\", ADJUSTED: 20, FINE: 30, SUPER_DISCOUNT: 40,\n  SUPER_DISCOUNT_PERCENTAGE: 10, TOTAL_SUBSCRIPTION_AMOUNT: 1240,\n});\n```\n\n- `getSubscription` → `null` makes capture fail with a `PaymentError(404)`.\n- `saveBreakups` should do delete + bulk-insert + update in **one transaction** so\n  the capture is atomic (replacing any prior breakups for that subscription).\n\n### Reading breakups\n\n`getPaymentBreakupsBySubscriptionId` computes the breakups for you — **all** the\nmath lives in the library (package pricing, offer discount, coins valuation,\nfines, and the coupon / month-discount / previous-subscription adjustment\nbranching). You only supply a `breakupProvider` whose methods fetch raw records\nand gateway amounts — no business logic on your side:\n\n```ts\nconst subscriptionPayments = new SubscriptionPaymentUtils({\n  breakupProvider: {\n    async getSubscription(id) {\n      const s = await Model.SubscriptionMonth.findOne({\n        where: { id }, include: [{ model: Model.CtVehicle, as: \"vehicle\" }],\n      });\n      if (!s) return null;\n      return {\n        id: s.id, packageId: s.packageid, frequencyId: s.frequencyid, months: s.months,\n        ctOfferId: s.ct_offer_id, customerId: s.customerid,\n        walletTransactionId: s.wallet_transaction_id, fineAmount: s.fine_amount,\n        discountFor: s.discountfor, discountPrice: s.discountprice,\n        settledForSubscriptionId: s.settled_for_subscription_id,\n        superDiscountPercentage: s.super_discount_percentage,\n        paidAmount: s.paid_amount, paymentMode: s.payment_mode, transactionId: s.transactionid,\n        vehicle: s.vehicle && {\n          vehicleType: s.vehicle.vehicle_type, vehicleCategory: s.vehicle.vehicle_category,\n        },\n      };\n    },\n    async getPackagePrice({ packageId, frequencyId, vehicleType, vehicleCategory }) {\n      const p = await Model.PackageFrequencyPrice.findOne({\n        where: { packageid: packageId, frequencyid: frequencyId,\n                 vehicletype: vehicleType, categoryid: vehicleCategory },\n      });\n      return p?.total_price || 0;\n    },\n    async getOfferDiscount({ ctOfferId, totalPackagePrice, customerId }) {\n      const { offerDiscount, offer } =\n        await Helper.getDiscountAmountWithOffer(ctOfferId, totalPackagePrice, customerId);\n      return { offerDiscount, offer }; // offer: { amount, type }\n    },\n    async getWalletTransaction(walletTransactionId) {\n      const w = await Model.WalletTransaction.findOne({ where: { id: walletTransactionId } });\n      return w && { coins: w.coins, oneCoinValue: w.one_coin_value };\n    },\n    async getFineOrExtra(settledForSubscriptionId) {\n      const r = await Helper.getfineOrExtra(settledForSubscriptionId);\n      return r && { discountPrice: r.discountprice };\n    },\n    async getGatewayPaidAmount({ paymentMode, transactionId }) {\n      if (paymentMode === \"easebuzz\") {\n        const txn = await PaymentUtils.getEasebuzzTransactionDetail({ txnID: transactionId });\n        return txn?.amount ?? 0;\n      }\n      if (paymentMode === \"razorpay\") {\n        const p = await razorpayInstance.payments.fetch(transactionId);\n        return (p?.amount ?? 0) / 100;\n      }\n      return 0;\n    },\n  },\n});\n\nconst breakups =\n  await subscriptionPayments.getPaymentBreakupsBySubscriptionId(subscriptionId);\n```\n\n- Missing subscription ⇒ `PaymentError(404)`; missing vehicle ⇒\n  `PaymentError(\"Subscription vehicle not found\", 404)` — same as the original.\n- `store` and `breakupProvider` are independent: provide whichever the methods\n  you call need (or both).\n- Need just the mapping? Use the pure helper:\n  `buildPaymentBreakups(subscriptionRef, breakups)` → `{ rows, subscriptionUpdate }`,\n  or `subscriptionPayments.buildBreakups(id, breakups)` to resolve via the store\n  without persisting.\n\n### Targeting the test gateway\n\nOverride any subset of endpoints:\n\n```ts\nnew PaymentUtils({\n  credentials: { key, salt },\n  endpoints: {\n    initiateLink: \"https://testpay.easebuzz.in/payment/initiateLink\",\n  },\n});\n```\n\n## Build\n\n```bash\nnpm run build   # tsc → dist/ (JS + .d.ts)\n```\n","readmeFilename":"README.md"}