Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define operations and usage scenarios clearly #25

Closed
fangpenlin opened this issue Aug 6, 2013 · 13 comments
Closed

Define operations and usage scenarios clearly #25

fangpenlin opened this issue Aug 6, 2013 · 13 comments

Comments

@fangpenlin
Copy link
Contributor

@mahmoudimus

As mentioned section 4 in #24, I think we need some definitions about what kind of operations this service should provide and what kind of scenarios to use them. I think one of reasons I have no idea how to contribute on this project is that I cannot find documents about this in the project. What is a plan, what is subscription and so on.

I see you have wrote

import billy

# create the goat plan to bill every 3 months.

billy.Plan.create(
    amount=100,
    description='the goat - silver',
    trial_period=None,
    frequency=billy.Frequency.MONTHLY,
    interval=3
)

in readme file. I feel that could be helpful as it provides a scenario how it would work. But still lack some important details (who to charge, what kind of method should be used with balanced API? what is the trial period here?). I need your input. Could you envision more about how this service should be used?

@mjallday
Copy link
Contributor

mjallday commented Aug 6, 2013

I agree that we need to document scenarios, it will help define the API.

One customer in #15 has already described their scenario. Internally I know we have a few scenarios that we'd like to cover, let's document them!

@matin
Copy link
Member

matin commented Aug 7, 2013

Could we use the same schema and scenarios format as https://github.com/matin/balanced-api/tree/revision1 ?

@fangpenlin
Copy link
Contributor Author

The scenarios I mean here are basically the requirements of this system. I prefer to do it in a more human readable way to express it. I just want to create some clear direction and goals of this system, with big pictures thing can be done easily and correctly.

I think I just write down everything came to my mind and clean them up later. I am writing these also for making sure my understanding to the system is correct. If youguys see anything wrong please correct me.

Big picture

This system should be the backend for recurring payments. Most of them are subscription type service, for instance, hosting service is one kind of subscription services. Customers can choose different hosting plans and subscript to them. The subscription fee can be debited from either customer's bank account or credit card periodically.

Although I think the major cases there would be charging customers periodically, still, as we are running a payment gateway for marketplace, there might be some recurring payout case, such as Gittip. However, I think I should focus on the common cases and try to reach more special case later. A more long term goal could be to provide some more complex recurring payment processing.

There will be two major ways customer of balanced can employ this system, one is to host this system by themselves, the other is to use the hosted by us.

This service is provided in RESTful API form mainly, maybe we can create a dashboard for it later.

Entity definitions

Company

As this system should be used by different users while it is hosted by us, it should provide multi-user service rather than serving single user solely. So, the Company here is mainly for muitl-user accessing. There should be an API key for authenticating user, and should be a balanced API key being associated (for accessing payment processing API).

In short, the user to billy system

Customer

Customers are basically who purchase service or got payed out from the Company.

Company <--- 1 to N ---> Customer

Plan

Plan is a recurring payment schedule, such as hosting plan. It should have amount, frequency and those relative fields for profiling what kind of plan it is, how it should be processed. Maybe we should split it into ChargePlan and PayoutPlan as what we are doing now, or we can share the same table if they have too many common fields.

Company <--- 1 to N ---> Plan

Subscription

Subscription here is an association from Customer to Plan. This usually means a Customer subscribe a certain service from the Company, like what we said, might be hosting plan or something like that.

Plan <--- 1 to N ---> Subscription
Customer <--- 1 to N ---> Subscription

Transaction

Transaction is a record for charging or paying out result (could fail) of a Subscription.

Subscription <--- 1 to N ---> Transaction

Invoice

Invoice is a record with success charge or payout result details, it is associated with Transaction.

Transaction <--- 1 to 1 ---> Invoice

The ERD diagram

billy_erd

Usage scenario

Let's envision some possible usage cases here.

Say, John is running a web hosting service, he wants to use Balanced for processing the payment. He has 3 plans,

  • Small size, $5 USD / mo
  • Middle size, $10 USD / mo
  • Big size, $15 USD / mo

At very first, he will need to register a company in the system to get an API key for accessing billy, he also need to give his balanced API key to let billy do the recurring processing for him.

import billy
api_key = billy.create_company(
    balanced_key='1234567890abcdef',
    name="John's Awesome Hosting Plans",
)

With the api key for billy, he can then create the plans

billy.configure(api_key)
plan1_id = billy.create_plan(
    name='Small',
    description='Small hosting plan, 5GB storage, 1024MB, $5 USD / mo',
    amount=5,
    frequency=billy.FREQ_MONTHLY,
    user_data='hd=5GB,mem=1024MB',
    type=billy.PLAN_TYPE_CHARGE,
)
plan2_id = billy.create_plan(
    name='Middle',
    description='Middle hosting plan, 10GB storage, 2048MB, $10 USD / mo',
    amount=10,
    frequency=billy.FREQ_MONTHLY,
    user_data='hd=10GB,mem=2048MB',
    type=billy.PLAN_TYPE_CHARGE,
)
plan3_id = billy.create_plan(
    name='Big',
    description='Big hosting plan, 15GB storage, 4096MB, $15 USD / mo',
    amount=15,
    frequency=billy.FREQ_MONTHLY,
    user_data='hd=15GB,mem=4096MB',
    type=billy.PLAN_TYPE_CHARGE,
)

Great, he got 3 plans now. Then, Tom wants to purchase his middle size hosting service, so here the code goes

customer_id = billy.create_customer(
    name='Tom',
    payment_uri='/v1/credit_card/55667788abcdefg',
    email='[email protected]'
)

billy.subscribe(
    customer_id=customer_id,
    plan_id=plan2_id,
)

Okay, here we are, for now, billy will charge Tom's bank account (or credit card) 10 USD every month.

Cancel subscription

Daniel is also a customer of John's hosting server, he subscribed Small plan for 3 months, and he want to cancel his hosting plan in the middle of 4th month. John's hosting policy is to refund user prorate in middle of subscription period, so here the code goes

billy.cancel_subscription(
    sub_id='SUBabcdefgh1234',
    prorated_refund=True,
)

In that way, billy will mark this subscription as canceled, also, it will generate a prorated refund transaction back to Daniel's account.

If we want to just cancel the subscription, but no refund is issue, then we can call it like this

billy.cancel_subscription(
    sub_id='SUBabcdefgh1234',
)

the prorated_refund is False by default.

Discount

Tom is a happy user so far, he decided to purchase another hosting plan. As he is the first customer to John's hosting, John wants to give him some discount for his new plan. So, here the code goes

customer = billy.get_customer_by_external_id(' customer id in their own database goes here')

billy.subscribe(
    customer_id=customer.id,
    plan_id=plan2_id,
    discount=0.3
)

As the the price of middle plan is $10 USD per month, 0.3 discount means 30% price off, so he will be changed for $7 USD per month instead of $10 USD.

Start subscription at a specific date time

Say, Linda have a violin course for students to attend. She would like to have all the recurring payments be processed at 1st of month (easier to manage). Here she can use started_at

billy.subscribe(
    customer_id=customer.id,
    plan_id=voilin_course_id,
    started_at=datetime.datetime(2013, 9, 1)
)

By doing this, the very first transaction will only be processed at 2013/Sep/1st, and transactions will be scheduled at 1st for all following months.

Multiple interval period

Michael has some apartments for rental. He'd like to collect his fee for every 6 months, so here he can write

plan_id = billy.create_plan(
    name='Small apartment rental',
    amount=2000 * 6,
    frequency=billy.FREQ_MONTHLY,
    type=billy.PLAN_TYPE_CHARGE,
    interval=6,
)

In this case, his customer will be charged every 6 months.

@fangpenlin
Copy link
Contributor Author

@matin @mjallday @mahmoudimus

Is this direction looking fine for you guys?

@matthewfl
Copy link

@victorlin I don't know what interface you are going to build for the billy api, but in the next revision of the balanced api we are going to support jsonapi. We are already building tooling around running scenarios of this form. It is currently set up to validate all requests and responses against jsonschema

@mjallday
Copy link
Contributor

@matthewfl i believe this is the internal interface, not the external API interface.

@victorlin that's a great base. do you think we need to define edge cases at this level such as

  1. what happens if you cancel a subscription half way through a month?
  2. what happens if you change subscription plan half way through a month?
  3. can i apply discounts for select customers or would i define this by adding a custom plan?

@fangpenlin
Copy link
Contributor Author

@matthewfl

These are just the way I think how user will use them. They are not final API design, just a rough way to express how it should work. I need to make sure this direction (big picture) is correct before I get started on this, otherwise that would be a trail and error. We can always write these human readable scenario into JSON for running tests later.

@mjallday

Yeah, that would be helpful, I see PayPal recurring payment has refund option, some edge cases like that would be nice to have. I can make better design decision with those scenarios.

@mahmoudimus
Copy link
Contributor

@victorlin - you definitely have the model down correctly. When do you want to work on the scenarios? Ping me on Google chat or IRC channel

@fangpenlin
Copy link
Contributor Author

@mjallday

I have already updated the scenario, see if this meet the requirement.

However, I cannot understand your second scenario,

what happens if you change subscription plan half way through a month?

Could you clarify this?

@mjallday
Copy link
Contributor

Let's say you have a monthly recurring plan.

Tom, our customer, subscribes to the medium plan, he gets billed for the first month. Half way through that month he decides he needs another plan and changes his subscription.

Ideally the change would go into action instantly but the price of the plans is different. Do we refund half (2/4 weeks) of the first plan and then charge half (2/4 weeks) of the second plan in this case?

It sounds like the prorated_refund param will satisfy this requirement.

customer_id = billy.create_customer(
    name='Tom',
    payment_uri='/v1/credit_card/55667788abcdefg',
    email='[email protected]'
)

billy.subscribe(
    customer_id=customer_id,
    plan_id=plan2_id,
)

# wait 2 weeks ...

billy.cancel_subscription(
    customer_id=customer_id,
    plan_id=plan2_id,
    prorated_refund=True,
)

billy.subscribe(
    customer_id=customer_id,
    plan_id=plan1_id,
)

@fangpenlin
Copy link
Contributor Author

@mjallday Yeah, that could do. I prefer to provide minimum and complete interface, i.e, you can do anything you like to do (you should be able to do) with minimum interfaces. But I will also provide sugar if that can ease some work and reduce error chance.

@fangpenlin fangpenlin mentioned this issue Aug 19, 2013
@fangpenlin
Copy link
Contributor Author

To make it more flexible, I made some changes to the usage and the models.

1. Add an amount field to replace discount field

As I think sometimes we may want to overwrite the amount of plan in subscription. And discount is somehow a way to overwrite the amount from plan. So I decide to replace it with amount field for overwriting the one in plan.

So, the way we do discount 0.3 will be something like this

plan_id = billy.create_plan(
    amount=10,
    frequency=billy.FREQ_MONTHLY,
    type=billy.PLAN_TYPE_CHARGE,
)
billy.subscribe(
    customer_id=customer.id,
    plan_id=plan_id,
    amount=7
)

This could also be helpful for some marketplaces such as Gittips. They need to let user chose arbitrary amount to subscribe. If we only provide discount field, they have to create many plans just for different amount. With this new approach, they can create one weekly plan, subscribe all customer to this plan with arbitrary amount. May look like this

plan_id = billy.create_plan(
    name='Weekly donation',
    amount=1,
    frequency=billy.FREQ_WEEKLY,
    type=billy.PLAN_TYPE_CHARGE,
)
billy.subscribe(
    customer_id='USER_1',
    plan_id=plan_id,
    amount=20  # donate 20 weekly
)
billy.subscribe(
    customer_id='USER_2',
    plan_id=plan_id,
    amount=50  # donate 50 weekly
)
billy.subscribe(
    customer_id='USER_3',
    plan_id=plan_id,
    amount=3  # donate 3 weekly
)
# ...

2. Move payment_uri from customer to subscription.

As I see one customer can be associated with many funding instruments, could be credit cards and bank accounts. To let user decides which one to charge/payout, I move the payment_uri field from customer to subscription. In this way, user can subscribe to any funding instrument they want. It looks like this

plan_id = billy.create_plan(
    amount=10,
    frequency=billy.FREQ_MONTHLY,
    type=billy.PLAN_TYPE_CHARGE,
)
billy.subscribe(
    customer_id=customer.id,
    plan_id=plan_id,
    payment_uri='/v1/cards/black_card
)

I also make the payment_uri an optional argument. So, it is possible to subscribe an existing customer in balanced which already has some funding instruments associated with it. Like this

customer_id = billy.create_customer(
    external_id='AC1jqOF9TocQXGIXjuMVrpMu',
)
billy.subscribe(
    customer_id=customer.id,
    plan_id=plan_id,
)

@victorlin TODO: update scenarios above with new approach

@mjallday
Copy link
Contributor

@whit537 do the proposed changes mentioned in @victorlin's last message help accommodate Gittip's use-case outlined in #15?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants