├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src ├── config.ts ├── index.ts └── utils │ └── stripe.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .env 4 | .vscode 5 | .editorconfig 6 | .prettierrc 7 | dist 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stripe Plan Change 2 | 3 | > Automatically migrate your Stripe customers from one plan to another with the Stripe API. 4 | 5 | When it comes to increasing your pricing with Stripe, they don't allow you to edit an existing plan if it's already got active subscriptions on it. This means you have to manually go through each customer and move them to a new, higer priced plan - one by one. 6 | 7 | This script does it for you automatically. It uses the Stripe API to iterate every customer, and migrate them from one plan to another. It also keeps important things like the quantity & interval the same. 8 | 9 | **Please note:** 10 | 11 | This is a simple script that I've used to migrate customers. It may need to be changed to your use case, and depending on yours it may or may not work. Please test thoroughly before using, I take no responsibility for any issues that may happen. 12 | 13 | * use at your own risk, would suggest testing with a dev stripe_sk first before trying in live mode 14 | * before doing a live run, you can toggle `updateSubscriptions` to false to make sure it looks correct 15 | 16 | ### 1. Install Dependencies 17 | ```sh 18 | npm install 19 | ``` 20 | 21 | ### 2. Add a .env file 22 | In the base of the project add a .env file with your STRIPE_SK (can be live or dev key): 23 | 24 | ```env 25 | STRIPE_SK=your_stripe_sk 26 | ``` 27 | 28 | ### 3. Add config 29 | 30 | Add old plan ids (that we'll be migrating from), and new plan ids (migrate to) into the config file. 31 | 32 | ```js 33 | { 34 | // emails of customers to not change 35 | ignoreEmails: [], 36 | 37 | // old price ids that will be getting changed 38 | oldPriceIds: [], 39 | 40 | // the new prices we'll be going on to, test is for test moda, live is for live customers 41 | prices: { 42 | test: { 43 | year: '', 44 | month: '', 45 | }, 46 | live: { 47 | year: '', 48 | month: '', 49 | }, 50 | }, 51 | 52 | // change this to false if you want to do a test run as it won't actually update anything 53 | updateSubscriptions: true, 54 | } 55 | ``` 56 | 57 | ### 4. Run 58 | ```sh 59 | npm run dev 60 | ``` 61 | 62 | 63 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "priceincrease", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "priceincrease", 8 | "dependencies": { 9 | "dotenv": "16.0.1", 10 | "stripe": "^13.6.0" 11 | }, 12 | "devDependencies": { 13 | "@types/node": "20.2.5", 14 | "ts-node": "10.9.1", 15 | "typescript": "5.1.3" 16 | } 17 | }, 18 | "node_modules/@cspotcode/source-map-support": { 19 | "version": "0.8.1", 20 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 21 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 22 | "dev": true, 23 | "dependencies": { 24 | "@jridgewell/trace-mapping": "0.3.9" 25 | }, 26 | "engines": { 27 | "node": ">=12" 28 | } 29 | }, 30 | "node_modules/@jridgewell/resolve-uri": { 31 | "version": "3.1.1", 32 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 33 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", 34 | "dev": true, 35 | "engines": { 36 | "node": ">=6.0.0" 37 | } 38 | }, 39 | "node_modules/@jridgewell/sourcemap-codec": { 40 | "version": "1.4.15", 41 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 42 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 43 | "dev": true 44 | }, 45 | "node_modules/@jridgewell/trace-mapping": { 46 | "version": "0.3.9", 47 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 48 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 49 | "dev": true, 50 | "dependencies": { 51 | "@jridgewell/resolve-uri": "^3.0.3", 52 | "@jridgewell/sourcemap-codec": "^1.4.10" 53 | } 54 | }, 55 | "node_modules/@tsconfig/node10": { 56 | "version": "1.0.9", 57 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 58 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 59 | "dev": true 60 | }, 61 | "node_modules/@tsconfig/node12": { 62 | "version": "1.0.11", 63 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 64 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 65 | "dev": true 66 | }, 67 | "node_modules/@tsconfig/node14": { 68 | "version": "1.0.3", 69 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 70 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 71 | "dev": true 72 | }, 73 | "node_modules/@tsconfig/node16": { 74 | "version": "1.0.4", 75 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 76 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", 77 | "dev": true 78 | }, 79 | "node_modules/@types/node": { 80 | "version": "20.2.5", 81 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", 82 | "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==" 83 | }, 84 | "node_modules/acorn": { 85 | "version": "8.8.2", 86 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", 87 | "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", 88 | "dev": true, 89 | "bin": { 90 | "acorn": "bin/acorn" 91 | }, 92 | "engines": { 93 | "node": ">=0.4.0" 94 | } 95 | }, 96 | "node_modules/acorn-walk": { 97 | "version": "8.2.0", 98 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 99 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 100 | "dev": true, 101 | "engines": { 102 | "node": ">=0.4.0" 103 | } 104 | }, 105 | "node_modules/arg": { 106 | "version": "4.1.3", 107 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 108 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 109 | "dev": true 110 | }, 111 | "node_modules/call-bind": { 112 | "version": "1.0.2", 113 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 114 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 115 | "dependencies": { 116 | "function-bind": "^1.1.1", 117 | "get-intrinsic": "^1.0.2" 118 | }, 119 | "funding": { 120 | "url": "https://github.com/sponsors/ljharb" 121 | } 122 | }, 123 | "node_modules/create-require": { 124 | "version": "1.1.1", 125 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 126 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 127 | "dev": true 128 | }, 129 | "node_modules/diff": { 130 | "version": "4.0.2", 131 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 132 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 133 | "dev": true, 134 | "engines": { 135 | "node": ">=0.3.1" 136 | } 137 | }, 138 | "node_modules/dotenv": { 139 | "version": "16.0.1", 140 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", 141 | "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==", 142 | "engines": { 143 | "node": ">=12" 144 | } 145 | }, 146 | "node_modules/function-bind": { 147 | "version": "1.1.1", 148 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 149 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 150 | }, 151 | "node_modules/get-intrinsic": { 152 | "version": "1.2.1", 153 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", 154 | "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", 155 | "dependencies": { 156 | "function-bind": "^1.1.1", 157 | "has": "^1.0.3", 158 | "has-proto": "^1.0.1", 159 | "has-symbols": "^1.0.3" 160 | }, 161 | "funding": { 162 | "url": "https://github.com/sponsors/ljharb" 163 | } 164 | }, 165 | "node_modules/has": { 166 | "version": "1.0.3", 167 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 168 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 169 | "dependencies": { 170 | "function-bind": "^1.1.1" 171 | }, 172 | "engines": { 173 | "node": ">= 0.4.0" 174 | } 175 | }, 176 | "node_modules/has-proto": { 177 | "version": "1.0.1", 178 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", 179 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", 180 | "engines": { 181 | "node": ">= 0.4" 182 | }, 183 | "funding": { 184 | "url": "https://github.com/sponsors/ljharb" 185 | } 186 | }, 187 | "node_modules/has-symbols": { 188 | "version": "1.0.3", 189 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 190 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 191 | "engines": { 192 | "node": ">= 0.4" 193 | }, 194 | "funding": { 195 | "url": "https://github.com/sponsors/ljharb" 196 | } 197 | }, 198 | "node_modules/make-error": { 199 | "version": "1.3.6", 200 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 201 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 202 | "dev": true 203 | }, 204 | "node_modules/object-inspect": { 205 | "version": "1.12.3", 206 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 207 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", 208 | "funding": { 209 | "url": "https://github.com/sponsors/ljharb" 210 | } 211 | }, 212 | "node_modules/qs": { 213 | "version": "6.11.2", 214 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", 215 | "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", 216 | "dependencies": { 217 | "side-channel": "^1.0.4" 218 | }, 219 | "engines": { 220 | "node": ">=0.6" 221 | }, 222 | "funding": { 223 | "url": "https://github.com/sponsors/ljharb" 224 | } 225 | }, 226 | "node_modules/side-channel": { 227 | "version": "1.0.4", 228 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 229 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 230 | "dependencies": { 231 | "call-bind": "^1.0.0", 232 | "get-intrinsic": "^1.0.2", 233 | "object-inspect": "^1.9.0" 234 | }, 235 | "funding": { 236 | "url": "https://github.com/sponsors/ljharb" 237 | } 238 | }, 239 | "node_modules/stripe": { 240 | "version": "13.6.0", 241 | "resolved": "https://registry.npmjs.org/stripe/-/stripe-13.6.0.tgz", 242 | "integrity": "sha512-0VSzva12onR75i708y2CB+UCeQKfPeLRkOCaGZYzHXiXiubB0C3pLKq+MPNfnajzfOeO6EVBita6rEAFGj0ZGA==", 243 | "dependencies": { 244 | "@types/node": ">=8.1.0", 245 | "qs": "^6.11.0" 246 | }, 247 | "engines": { 248 | "node": ">=12.*" 249 | } 250 | }, 251 | "node_modules/ts-node": { 252 | "version": "10.9.1", 253 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", 254 | "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", 255 | "dev": true, 256 | "dependencies": { 257 | "@cspotcode/source-map-support": "^0.8.0", 258 | "@tsconfig/node10": "^1.0.7", 259 | "@tsconfig/node12": "^1.0.7", 260 | "@tsconfig/node14": "^1.0.0", 261 | "@tsconfig/node16": "^1.0.2", 262 | "acorn": "^8.4.1", 263 | "acorn-walk": "^8.1.1", 264 | "arg": "^4.1.0", 265 | "create-require": "^1.1.0", 266 | "diff": "^4.0.1", 267 | "make-error": "^1.1.1", 268 | "v8-compile-cache-lib": "^3.0.1", 269 | "yn": "3.1.1" 270 | }, 271 | "bin": { 272 | "ts-node": "dist/bin.js", 273 | "ts-node-cwd": "dist/bin-cwd.js", 274 | "ts-node-esm": "dist/bin-esm.js", 275 | "ts-node-script": "dist/bin-script.js", 276 | "ts-node-transpile-only": "dist/bin-transpile.js", 277 | "ts-script": "dist/bin-script-deprecated.js" 278 | }, 279 | "peerDependencies": { 280 | "@swc/core": ">=1.2.50", 281 | "@swc/wasm": ">=1.2.50", 282 | "@types/node": "*", 283 | "typescript": ">=2.7" 284 | }, 285 | "peerDependenciesMeta": { 286 | "@swc/core": { 287 | "optional": true 288 | }, 289 | "@swc/wasm": { 290 | "optional": true 291 | } 292 | } 293 | }, 294 | "node_modules/typescript": { 295 | "version": "5.1.3", 296 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", 297 | "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", 298 | "dev": true, 299 | "bin": { 300 | "tsc": "bin/tsc", 301 | "tsserver": "bin/tsserver" 302 | }, 303 | "engines": { 304 | "node": ">=14.17" 305 | } 306 | }, 307 | "node_modules/v8-compile-cache-lib": { 308 | "version": "3.0.1", 309 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 310 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 311 | "dev": true 312 | }, 313 | "node_modules/yn": { 314 | "version": "3.1.1", 315 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 316 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 317 | "dev": true, 318 | "engines": { 319 | "node": ">=6" 320 | } 321 | } 322 | }, 323 | "dependencies": { 324 | "@cspotcode/source-map-support": { 325 | "version": "0.8.1", 326 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 327 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 328 | "dev": true, 329 | "requires": { 330 | "@jridgewell/trace-mapping": "0.3.9" 331 | } 332 | }, 333 | "@jridgewell/resolve-uri": { 334 | "version": "3.1.1", 335 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 336 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", 337 | "dev": true 338 | }, 339 | "@jridgewell/sourcemap-codec": { 340 | "version": "1.4.15", 341 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 342 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 343 | "dev": true 344 | }, 345 | "@jridgewell/trace-mapping": { 346 | "version": "0.3.9", 347 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 348 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 349 | "dev": true, 350 | "requires": { 351 | "@jridgewell/resolve-uri": "^3.0.3", 352 | "@jridgewell/sourcemap-codec": "^1.4.10" 353 | } 354 | }, 355 | "@tsconfig/node10": { 356 | "version": "1.0.9", 357 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 358 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 359 | "dev": true 360 | }, 361 | "@tsconfig/node12": { 362 | "version": "1.0.11", 363 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 364 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 365 | "dev": true 366 | }, 367 | "@tsconfig/node14": { 368 | "version": "1.0.3", 369 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 370 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 371 | "dev": true 372 | }, 373 | "@tsconfig/node16": { 374 | "version": "1.0.4", 375 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 376 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", 377 | "dev": true 378 | }, 379 | "@types/node": { 380 | "version": "20.2.5", 381 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz", 382 | "integrity": "sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==" 383 | }, 384 | "acorn": { 385 | "version": "8.8.2", 386 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", 387 | "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", 388 | "dev": true 389 | }, 390 | "acorn-walk": { 391 | "version": "8.2.0", 392 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", 393 | "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", 394 | "dev": true 395 | }, 396 | "arg": { 397 | "version": "4.1.3", 398 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 399 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 400 | "dev": true 401 | }, 402 | "call-bind": { 403 | "version": "1.0.2", 404 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 405 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 406 | "requires": { 407 | "function-bind": "^1.1.1", 408 | "get-intrinsic": "^1.0.2" 409 | } 410 | }, 411 | "create-require": { 412 | "version": "1.1.1", 413 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 414 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 415 | "dev": true 416 | }, 417 | "diff": { 418 | "version": "4.0.2", 419 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 420 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 421 | "dev": true 422 | }, 423 | "dotenv": { 424 | "version": "16.0.1", 425 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", 426 | "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==" 427 | }, 428 | "function-bind": { 429 | "version": "1.1.1", 430 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 431 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 432 | }, 433 | "get-intrinsic": { 434 | "version": "1.2.1", 435 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", 436 | "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", 437 | "requires": { 438 | "function-bind": "^1.1.1", 439 | "has": "^1.0.3", 440 | "has-proto": "^1.0.1", 441 | "has-symbols": "^1.0.3" 442 | } 443 | }, 444 | "has": { 445 | "version": "1.0.3", 446 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 447 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 448 | "requires": { 449 | "function-bind": "^1.1.1" 450 | } 451 | }, 452 | "has-proto": { 453 | "version": "1.0.1", 454 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", 455 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" 456 | }, 457 | "has-symbols": { 458 | "version": "1.0.3", 459 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 460 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 461 | }, 462 | "make-error": { 463 | "version": "1.3.6", 464 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 465 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 466 | "dev": true 467 | }, 468 | "object-inspect": { 469 | "version": "1.12.3", 470 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 471 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" 472 | }, 473 | "qs": { 474 | "version": "6.11.2", 475 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", 476 | "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", 477 | "requires": { 478 | "side-channel": "^1.0.4" 479 | } 480 | }, 481 | "side-channel": { 482 | "version": "1.0.4", 483 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 484 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 485 | "requires": { 486 | "call-bind": "^1.0.0", 487 | "get-intrinsic": "^1.0.2", 488 | "object-inspect": "^1.9.0" 489 | } 490 | }, 491 | "stripe": { 492 | "version": "13.6.0", 493 | "resolved": "https://registry.npmjs.org/stripe/-/stripe-13.6.0.tgz", 494 | "integrity": "sha512-0VSzva12onR75i708y2CB+UCeQKfPeLRkOCaGZYzHXiXiubB0C3pLKq+MPNfnajzfOeO6EVBita6rEAFGj0ZGA==", 495 | "requires": { 496 | "@types/node": ">=8.1.0", 497 | "qs": "^6.11.0" 498 | } 499 | }, 500 | "ts-node": { 501 | "version": "10.9.1", 502 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", 503 | "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", 504 | "dev": true, 505 | "requires": { 506 | "@cspotcode/source-map-support": "^0.8.0", 507 | "@tsconfig/node10": "^1.0.7", 508 | "@tsconfig/node12": "^1.0.7", 509 | "@tsconfig/node14": "^1.0.0", 510 | "@tsconfig/node16": "^1.0.2", 511 | "acorn": "^8.4.1", 512 | "acorn-walk": "^8.1.1", 513 | "arg": "^4.1.0", 514 | "create-require": "^1.1.0", 515 | "diff": "^4.0.1", 516 | "make-error": "^1.1.1", 517 | "v8-compile-cache-lib": "^3.0.1", 518 | "yn": "3.1.1" 519 | } 520 | }, 521 | "typescript": { 522 | "version": "5.1.3", 523 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", 524 | "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", 525 | "dev": true 526 | }, 527 | "v8-compile-cache-lib": { 528 | "version": "3.0.1", 529 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 530 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 531 | "dev": true 532 | }, 533 | "yn": { 534 | "version": "3.1.1", 535 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 536 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 537 | "dev": true 538 | } 539 | } 540 | } 541 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "priceincrease", 3 | "scripts": { 4 | "dev": "ts-node src/index.ts" 5 | }, 6 | "dependencies": { 7 | "dotenv": "16.0.1", 8 | "stripe": "^13.6.0" 9 | }, 10 | "overrides": {}, 11 | "devDependencies": { 12 | "@types/node": "20.2.5", 13 | "ts-node": "10.9.1", 14 | "typescript": "5.1.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | // emails of customers to not change 3 | ignoreEmails: [], 4 | 5 | // old price ids that will be getting changed 6 | oldPriceIds: [], 7 | 8 | // the new prices we'll be going on to 9 | prices: { 10 | test: { 11 | year: '', 12 | month: '', 13 | }, 14 | live: { 15 | year: '', 16 | month: '', 17 | }, 18 | }, 19 | 20 | // change this to false if you want to do a test run as it won't actually update anything 21 | updateSubscriptions: true, 22 | } 23 | 24 | export default config 25 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import { changePlans } from './utils/stripe' 3 | 4 | // start changing plans 5 | changePlans() 6 | -------------------------------------------------------------------------------- /src/utils/stripe.ts: -------------------------------------------------------------------------------- 1 | import Stripe from 'stripe' 2 | import config from '../config' 3 | 4 | // @ts-ignore 5 | const stripe = new Stripe(process.env.STRIPE_SK, { apiVersion: '2019-11-05' }) 6 | 7 | // simple counters 8 | let processed = 0 9 | 10 | // set config 11 | const { prices, oldPriceIds, ignoreEmails, updateSubscriptions } = config 12 | 13 | export async function changePlans() { 14 | if (!process.env.STRIPE_SK) { 15 | console.log('No secret key found. Add a .env file with your STRIPE_SK.') 16 | return 17 | } 18 | 19 | if (!oldPriceIds.length) { 20 | console.log('No old price ids found. Add a price ids of the plans you want to change.') 21 | return 22 | } 23 | 24 | // iterate all subscriptions 25 | for await (const subscription of stripe.subscriptions.list({ limit: 1 })) { 26 | // get the subsriptions customer object 27 | const customer = await stripe.customers.retrieve(String(subscription.customer)) 28 | 29 | // change plans 30 | await processPlanChange(subscription, customer) 31 | } 32 | } 33 | 34 | async function processPlanChange(subscription: Stripe.Subscription, customer: Stripe.Customer | Stripe.DeletedCustomer) { 35 | // make sure customer is active 36 | if (customer.deleted === true) { 37 | console.log('Customer is deleted:', customer.id) 38 | return 39 | } 40 | 41 | // make sure customer not on emails to skip 42 | const email = customer.email 43 | if (ignoreEmails.includes(email)) { 44 | console.log('Customer is on custom plan:', email) 45 | return 46 | } 47 | 48 | // check if they don't have new prices 49 | const index = subscription.items.data.findIndex((v: Stripe.SubscriptionItem) => oldPriceIds.includes(v.price.id)) 50 | if (index < 0) { 51 | console.log('No item found with price id:', subscription.id) 52 | return 53 | } 54 | 55 | const item = subscription.items.data[index] 56 | const interval = item.price.recurring.interval 57 | const quantity = item.quantity 58 | const mode = subscription.livemode ? 'live' : 'test' 59 | 60 | console.log(`[${mode}] ${email} is on a ${interval} plan with ${quantity} quantity.\n`) 61 | 62 | // create the new item from the existing one 63 | const newItem: Stripe.SubscriptionUpdateParams.Item = { 64 | id: item.id, 65 | plan: prices[mode][interval], 66 | quantity, 67 | } 68 | 69 | console.log(`[${mode}] ${email} Changing item to:`, newItem) 70 | 71 | // update the subscription 72 | if (updateSubscriptions) { 73 | await stripe.subscriptions.update(subscription.id, { items: [newItem], proration_behavior: 'none' }) 74 | } 75 | 76 | // increase processed counter 77 | processed += 1 78 | 79 | // log shit 80 | console.log('\nProcessed subscriptions:', processed, '\n') 81 | 82 | await new Promise((resolve) => setTimeout(resolve, 1000)) 83 | } 84 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "esModuleInterop": true, 5 | "outDir": "dist", 6 | "skipLibCheck": true, 7 | "module": "CommonJS" 8 | }, 9 | "exclude": ["node_modules"] 10 | } 11 | --------------------------------------------------------------------------------