https://github.com/weld-io/subscription-service
REST API service managing subscription pricing, services, consumables, VAT, etc. Built in Node.js.
https://github.com/weld-io/subscription-service
payments pricing subscriptions
Last synced: 4 months ago
JSON representation
REST API service managing subscription pricing, services, consumables, VAT, etc. Built in Node.js.
- Host: GitHub
- URL: https://github.com/weld-io/subscription-service
- Owner: weld-io
- License: mit
- Created: 2017-06-24T11:29:20.000Z (almost 9 years ago)
- Default Branch: master
- Last Pushed: 2022-12-10T20:29:47.000Z (over 3 years ago)
- Last Synced: 2024-04-14T10:53:32.942Z (about 2 years ago)
- Topics: payments, pricing, subscriptions
- Language: JavaScript
- Size: 782 KB
- Stars: 13
- Watchers: 8
- Forks: 9
- Open Issues: 18
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# Subscription Service
**Subscription Service** is a REST API service managing subscription pricing, services, consumables, VAT, etc. Built in Node.js.
It allows you to be more agile with your product offering, pricing, discounts, VAT, etc. Like [Segment](https://segment.com), but for payments.
This is **not a payment platform** - instead it’s a layer between your app and the payment platform ([Stripe](https://www.stripe.com) being the default).
----------
Made by the team at **Weld** ([www.weld.io](https://www.weld.io?utm_source=github-subscription-service)), the #codefree web/app creation tool:
[](https://www.weld.io?utm_source=github-subscription-service)
## How to Run
Set JWT auth secret:
export JWT_SECRET=...
or no JWT:
export DISABLE_JWT=true
Optional:
export STRIPE_SECRET_KEY=...
export WEBHOOK_RENEW_SUBSCRIPTION=...
Then, just start with:
yarn dev # development, you can also use DISABLE_JWT=true yarn dev
or
yarn start # production
Server will default to **http://localhost:3034**
## How to Test
yarn test
## Entities
For B2C apps, one Account has only one User.
For B2B apps, there can be multiple Users on each Account.
- **Accounts**
- name
- reference (slug)
- email
- company
- name
- vatNumber
- countryCode
- discountCoupon
- metadata (free form data)
- stripeCustomer
- subscriptions (array of Subscriptions)
- **Users** (on an Account)
- reference (e.g. user ID in another app)
- account (reference to Account)
- consumables
- projects: 2
- metadata (free form data)
- **Plans**
- name
- reference (slug)
- description
- featureDescriptions (string array)
- tags (string array)
- position (order in a list)
- isAvailable: true/false
- allowMultiple: true/false (false = disable all other subscriptions unless they are allowMultiple=true)
- services (array of Services)
- price
- month
- year
- once
- vatIncluded
- consumables: { projects: 10 }
- trialDays: 30
- metadata (free form data)
- **Subscriptions** (an Account subscribes to one or more Plans)
- plan (reference to Plan)
- reference (e.g. domains, User can’t have multiple subscriptions with same Reference)
- dateExpires
- metadata (free form data)
- stripeSubscription
- **Services** (e.g. access to a feature, included in Plan)
- name
- reference (slug)
- description
- metadata (free form data)
- **Consumables** (e.g. projects, users - limited by Plan, consumed by Users not Accounts)
## Environment variables
* `DISABLE_JWT`: set to "true" if you don’t want JWT authentication.
* `JWT_SECRET`: secret key for [JSON Web Token authentication](https://jwt.io).
* `CACHE_PROVIDER`: defaults to 'fastly'. Add new Cache Providers in folder `/app/cacheProviders`.
* `PAYMENT_PROVIDER`: defaults to 'stripe'. Add new Payment Providers in folder `/app/paymentProviders`.
* `STRIPE_SECRET_KEY`: secret key from [Stripe dashboard](https://dashboard.stripe.com/account/apikeys).
* `VAT_PERCENT`: defaults to "20" (%), as in if the price incl. VAT is $10, VAT is $2.
* `WEBHOOK_RENEW_SUBSCRIPTION`: a complete URL that will receive a POST request whenever a subscription is renewed.
## API
### Accounts
#### Create new account
curl -X POST http://localhost:3034/api/accounts -H "Content-Type: application/json" -d '{ "name": "My Company" }'
#### Update existing account
curl -X PATCH http://localhost:3034/api/accounts/:accountReference -H "Content-Type: application/json" -d '{ "metadata.stripeCustomer": "XYZ" }'
### Users
#### Create new user
Note: `reference` is where you use your main permanent user ID, e.g. from another app.
curl -X POST http://localhost:3034/api/users -H "Content-Type: application/json" -d '{ "reference": "userId1", "account": "my-company" }'
#### Create new user and account
curl -X POST http://localhost:3034/api/users -H "Content-Type: application/json" -d '{ "reference": "userId2", "account": { "name": "My Company 2", "email": "invoices@mycompany2.com" } }'
#### Create new user and account and subscription
Note: currently creates a subscription without payment - don't use.
curl -X POST http://localhost:3034/api/users -H "Content-Type: application/json" -d '{ "reference": "userId2", "account": { "name": "My Company 2", "email": "invoices@mycompany2.com" }, "subscription": { "plan": "trial", "dateExpires": "2018-04-01" } }'
#### Get user
curl -X GET http://localhost:3034/api/users/:reference
Returns:
{
reference: xxx,
account: { reference: ... },
activePlan: {
reference: 'b2b_small',
dateExpires: '2017-12-31',
},
plans: ['b2b_small'],
services: {
remove_watermark: {
name: 'Remove watermark'
}
},
consumables: {
projects: {
max: 10,
current: 8,
remaining: 2
}
},
subscriptions: [
…
]
}
#### Update user
curl -X PUT http://localhost:3034/api/users/12345 -H "Content-Type: application/json" -d '{ "account": "my-company" }'
### Services
#### Create new service
curl -X POST http://localhost:3034/api/services -H "Content-Type: application/json" -d '{ "name": "Image hosting", "description": "Store unlimited images in our cloud service." }'
### Plans
#### Create new plan
curl -X POST http://localhost:3034/api/plans -H "Content-Type: application/json" -d '{ "name": "Standard package", "price": { "month": 9.99 }, "services": ["image-hosting"] }'
#### List plans
curl -X GET http://localhost:3034/api/plans
Options:
- `includeVAT`: true or false
#### Get plan info
curl -X GET http://localhost:3034/api/plans/:reference
Returns:
{
reference: 'standard-package',
name: 'Standard Package',
price: {
month: 149,
year: 1490,
once: 150000,
vatIncluded: true
},
vat: {
month: 15,
year: 149,
once: 15000,
},
services: {
remove_watermark: {
name: 'Remove watermark'
}
},
consumables: {
projects: {
max: 10,
per: 'user'***,
},
users: {
max: 2,
per: 'account',
}
},
}
***Support consumables over time? E.g 10 projects/month.
#### Update plan
Partial update:
curl -X PUT http://localhost:3034/api/accounts/my-company/plans/:reference -H "Content-Type: application/json" -d '{ "services": ["video-hosting"] }'
#### Delete plan
curl -X DELETE http://localhost:3034/api/accounts/my-company/plans/:reference
### Subscriptions
#### Start subscription
By Account:
curl -X POST http://localhost:3034/api/accounts/:accountReference/subscriptions -H "Content-Type: application/json" -d '{ "plan": "standard-package", "billing": "year", "paymentMethod": "pm_123" }'
or by User:
curl -X POST http://localhost:3034/api/users/:userReference/subscriptions -H "Content-Type: application/json" -d '{ "plan": "standard-package" }'
Notes:
- `billing` defaults to "month".
- Use `?ignorePaymentProvider=true` to not activate Stripe.
#### Update subscription
Change plan, billing cycle, etc.
By Account:
curl -X PUT http://localhost:3034/api/accounts/:accountReference/subscriptions/:id -H "Content-Type: application/json" -d '{ "plan": "enterprise" }'
or by User:
curl -X PUT http://localhost:3034/api/users/:userReference/subscriptions/:id -H "Content-Type: application/json" -d '{ "plan": "enterprise" }'
#### Renew subscription
E.g. called from Stripe webhook:
curl -X POST http://localhost:3034/api/subscriptions/renew
See “Stripe example webhook” below.
Will send a POST to URL specified in `WEBHOOK_RENEW_SUBSCRIPTION` if successful.
#### Stop subscription
Note: when you stop a subscription, it’s not deleted but a `dateStopped` is set and the subscription won’t be listed in Account/User.subscriptions.
curl -X DELETE http://localhost:3034/api/accounts/:accountReference/subscriptions/:id
or:
curl -X DELETE http://localhost:3034/api/users/:userReference/subscriptions/:id
Stop all subscriptions:
curl -X DELETE http://localhost:3034/api/accounts/:accountReference/subscriptions
or:
curl -X DELETE http://localhost:3034/api/users/:userReference/subscriptions
## Implementation
Built on Node.js, Express, MongoDB, [mongoose-crudify](https://github.com/ryo718/mongoose-crudify).
## Deploying on Heroku
# Set up and configure app
heroku create MYAPPNAME
heroku addons:add mongolab
heroku config:set JWT_SECRET=...
heroku config:set STRIPE_SECRET_KEY=...
heroku config:set WEBHOOK_RENEW_SUBSCRIPTION=...
## Payment providers (including Stripe)
Stripe is currently the only payment provider supported, but the idea is that it should be easy to add more payment provider integrations to `/app/paymentProviders` and change the `PAYMENT_PROVIDER` environment variable.
* Stripe plan **ID**’s (“This will identify this plan in the API”) are currently derived from Plan reference + billing period, e.g. `basicplan_month`.
* Stripe customer ID is saved in Accounts `metadata.stripeCustomer`.
* Stripe subscription ID is saved in Subscriptions `metadata.stripeSubscription`.
### Stripe example webhook
See https://stripe.com/docs/api#invoice_object
Smaller version with only the fields needed for Subscription Service:
{
"type": "invoice.payment_succeeded",
"data": {
"object": {
"customer": "cus_BpmeYvqhVnkuY9",
"subscription": "sub_9PpDkJFsxRMEg5",
"lines": {
"data": [
{
"plan": {
"interval": "year",
"interval_count": 1
}
}
]
}
}
}
}
Complete:
{
"type": "invoice.payment_succeeded",
"data": {
"object": {
"id": "in_196zZUCjkwdpPaFTm8GgPYth",
"object": "invoice",
"amount_due": 11880,
"application_fee": null,
"attempt_count": 1,
"attempted": true,
"billing": "charge_automatically",
"charge": "ch_196zZUCjkwdpPaFTCG1T4BZX",
"closed": true,
"currency": "sek",
"customer": "cus_BpmeYvqhVnkuY9",
"date": 1477043056,
"description": null,
"discount": null,
"ending_balance": 0,
"forgiven": false,
"lines": {
"data": [
{
"id": "sub_BpmewCPihjSysf",
"object": "line_item",
"amount": 2000,
"currency": "sek",
"description": null,
"discountable": true,
"livemode": true,
"metadata": {
},
"period": {
"start": 1514221509,
"end": 1516899909
},
"plan": {
"id": "professional_yearly_10",
"object": "plan",
"amount": 9504,
"created": 1473759356,
"currency": "sek",
"interval": "year",
"interval_count": 1,
"livemode": false,
"metadata": {
},
"name": "Weld professional website (yearly)",
"statement_descriptor": null,
"trial_period_days": null
},
"proration": false,
"quantity": 1,
"subscription": null,
"subscription_item": "si_BpmejWAFkv1rLv",
"type": "subscription"
}
],
"has_more": false,
"object": "list",
"url": "/v1/invoices/in_196zZUCjkwdpPaFTm8GgPYth/lines"
},
"livemode": false,
"metadata": {
},
"next_payment_attempt": null,
"paid": true,
"period_end": 1477043056,
"period_start": 1477043056,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": null,
"subscription": "sub_9PpDkJFsxRMEg5",
"subtotal": 9504,
"tax": 2376,
"tax_percent": 25.0,
"total": 11880,
"webhooks_delivered_at": 1477043069
}
}
}