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, but for payments.
This is not a payment platform - instead it’s a layer between your app and the payment platform (Stripe being the default).
Made by the team at Weld (www.weld.io), the #codefree web/app creation tool:
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
yarn test
For B2C apps, one Account has only one User. For B2B apps, there can be multiple Users on each Account.
- Accounts
- name
- reference (slug)
- 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)
DISABLE_JWT
: set to "true" if you don’t want JWT authentication.JWT_SECRET
: secret key for JSON Web Token authentication.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.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.
curl -X POST http://localhost:3034/api/accounts -H "Content-Type: application/json" -d '{ "name": "My Company" }'
curl -X PATCH http://localhost:3034/api/accounts/:accountReference -H "Content-Type: application/json" -d '{ "metadata.stripeCustomer": "XYZ" }'
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" }'
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" } }'
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" } }'
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: [
…
]
}
curl -X PUT http://localhost:3034/api/users/12345 -H "Content-Type: application/json" -d '{ "account": "my-company" }'
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." }'
curl -X POST http://localhost:3034/api/plans -H "Content-Type: application/json" -d '{ "name": "Standard package", "price": { "month": 9.99 }, "services": ["image-hosting"] }'
curl -X GET http://localhost:3034/api/plans
Options:
includeVAT
: true or false
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.
Partial update:
curl -X PUT http://localhost:3034/api/accounts/my-company/plans/:reference -H "Content-Type: application/json" -d '{ "services": ["video-hosting"] }'
curl -X DELETE http://localhost:3034/api/accounts/my-company/plans/:reference
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.
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" }'
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.
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
Built on Node.js, Express, MongoDB, mongoose-crudify.
# 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=...
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
.
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
}
}
}