balanced / billy Goto Github PK
View Code? Open in Web Editor NEWBilly - The open source recurring billing system, powered by Balanced.
License: Other
Billy - The open source recurring billing system, powered by Balanced.
License: Other
Check out https://github.com/balanced/omnibus-balanced/blob/master/config/software/rump.rb and https://github.com/balanced/omnibus-balanced/blob/master/config/projects/rump.rb as examples of packaging a Python application.
To test building the package you can clone the https://github.com/balanced-cookbooks/balanced-omnibus repository and run vagrant up billy
. This should create a .deb package we can use for deployment.
curl https://billing.balancedpayments.com/v1/plans \
-X POST \
-u 6w9KwCPCmCQJpEYgCCtjaPmbLNQSavv5sX4mCZ9Sf6pb: \
-d "plan_type=charge" \
-d "amount=5" \
-d "frequency=monthly"
{
"guid": "PL97ZvyeA4wzM3WUyEG8xwps",
"company_guid": "CPMM8C8Uhkt4pDeJ8oqJu8Nj",
"plan_type": "charge",
"interval": 1,
"amount": "5.00",
"frequency": "monthly",
"deleted": false,
"created_at": "2013-10-02T05:48:26.210843",
"updated_at": "2013-10-02T05:48:26.210843"
}
One of the things that we've seen with companies that have recurring billing or delayed invoicing is a higher than normal rate of Soft Failures when charging a card. A Soft Failure is a card declination where you can retry later and possibly have the card approved.
Here are a few examples:
How will this be handled by billy?
Often at Balanced, we need to make ad-hoc adjustments to invoices.
This can be done as part of an initial creation of an invoice or after an invoice has failed and is about to be resubmitted.
An adjustment can be for any arbitrary amount, both positive or negative and will have a string field associated with it describing the reason for the adjustment.
Can Billy handle this?
Subscription canceling and plan deleting are not implemented, yet.
I think when we delete a plan, all subscriptions to it should also be canceled.
The refunding (in processor and process_transactions script) is also not implemented. So, if we want to implement prorated_refund, we need to implement refunding first.
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?
I would like to ask a question about how can I deal with database transaction failure with Balanced API calls?
For example, here I am processing a single transaction generated from a subscription
# create a transaction record in local database
tid = create_transaction(
amount=100,
payment_uri='/v1/credit_card/xxx'
)
# try to debit with balanced
transaction = get_transaction(tid)
debit_id = balanced.debit(transaction.payment_uri)
# NOTICE: When following update fails, we have a big trouble,
# the charging may already done, but we have no idea (no record) about that
# the system will certainly charge the customer again later
# update transaction record, we made a success debit
update_transaction(
tid=tid,
status='done',
debit_id=debit_id,
)
This could work fine if everything goes well, however, when a crash or something bad happens during processing this, let's say, the final update_transaction
is not done successfully. In this case, there is no transaction finish record in the database, billy system will think this transaction is not finish yet, it will try to charge it again when the system goes back online.
The way PayPal deals with this is that user needs to create a profile before they do a transaction like this
# make sure we won't duplicate transaction
tx = get_transaction()
if tx is not None:
pp_tx = paypal.get_profile(tx.profile_id)
if pp_tx.status == 'Paid':
update_transaction(tx.id, status='done')
return
profile = paypal.create_profile(...)
create_transaction(profile_id=profile.id)
result = paypal.start_recurring_payment(...)
if result.status == 'Paid':
update_transaction(tx.id, status='done')
As you can see, we can store the profile id in database before we do a real transaction. We can then check before doing a transaction, to make sure it is not already done.
With balanced, it appears there is only one step to do a transaction. A possible approach to workaround this is to assign a billy generated transaction id to balanced transaction. And we can get previous transaction record back with this id before we try to do a transaction again.
The code would look like this
# make sure we won't duplicate transaction
tx = get_transaction()
if tx is not None:
bp_tx = balanced.get_transaction(tx.id)
if bp_tx.status == 'Paid':
update_transaction(tx.id, status='done')
return
tx = create_transaction(...)
balanced.debit(tx.payment_uri)
update_transaction(tx.id, status='done')
I am not very familiar with the API, I see there is a meta
field I can set some of my key-value data to it, but is there a way to retrieve a record by the ID or data I set previously? Or are there other ways to workaround this?
I just cloned the repo and tried to run the tests.
I needed to manually install the nose test runner and now I'm getting errors about the db and user not existing.
It would be great if I could run something like ./scripts/db-recreate-test
which would ensure those exist for me.
From balanced/balanced-api#453 by @jkwade
Customers should have the ability to download their invoice history at the end of the year.
Some marketplaces use Balanced mainly for next-day ACH.
If you're also performing card processing with Balanced, you will always carry an escrow from the card transactions. If you're only using Balanced to pay customers, they you have to fund the yourself. This means the escrow can run out, which will cause transactions to fail.
Balanced should add the ability to specify, in the dashboard, a minimum amount to maintain in escrow. If the escrow drops below that amount, Balanced will auto-debit to ensure there's no service interruption.
Build API layer on top of models using flask. Should be good man.
Setup Travis CI (https://travis-ci.org) for billy project
The way we process transactions periodically is like this
Yield transactions from subscriptions
Process transactions
We yield transactions for recurring charging/paying-out/refunding as transaction
records in the database. The initial status is INIT
, then we process them. If everything goes well, it should then transit into DONE
status. However, when something goes wrong, it will go into RETRYING
status, and we will try to process it again later.
Sometimes, the retrying transactions are just never going to be finished as something is wrong from very beginning, such as wrong balanced API given by user. In this case, it is only wasting time and resource to keep trying these transactions.
So, here I want to limit the maximum retry times for each transactions. After a specific retry times, a transaction shall transit into FAILED
status.
The whole state transition would look like this:
And a note here, as the branch is merged back, I want to keep the development traceable to everybody, so I will stop working on the master branch directly. I will try to create issues and corresponding branch and pull request. I will keep the master as stable as possible, so that everybody can work on it. Also, for pull requests, I think only the ones pass all unit tests and functional tests should be merged. I see youguys have pretty cool testing integration with Github for balanced-dashboard that indicates testing result for each commit. I don't know what it is, but I think we can do the same on billy.
Record listing is not implemented yet. For now, we only have transaction list for a company
/v1/transactions/
maybe it would be nice to have transaction listing for a subscription or for a plan and customer? Like
/v1/subscriptions/SUXXXXXX/transactions
/v1/plans/PLXXXXXX/transactions
/v1/customers/CUXXXXX/transactions
Maybe we should consider this with #40, see what kind of style we should use.
I am trying to work on billy project, however, I cannot get tests work. How can I run them correctly?
I tried nosetests
on the mater
branch, and basically everything failed
$ nosetests
EEEEEEEEEEEEEEEE
======================================================================
ERROR: Failure: InvalidRequestError (Table 'charge_subscription' is already defined for this MetaData instance. Specify 'extend_existing=True' to redefine options and columns on an existing Table object.)
----------------------------------------------------------------------
(... omit ...)
----------------------------------------------------------------------
Ran 16 tests in 0.012s
FAILED (errors=16)
The complete dump: https://gist.github.com/victorlin/6204313
It appears that something goes wrong with import issue and table declarations. So, I tried my import fixed branch, and I get other tons of error
EEEEEEEEEEEEEEE
======================================================================
ERROR: Failure: ImportError (cannot import name sample_customer_2)
----------------------------------------------------------------------
( ... omit ...)
----------------------------------------------------------------------
Ran 15 tests in 0.563s
FAILED (errors=15)
The complete dump: https://gist.github.com/victorlin/6204321
None of tests passed, did I miss something? How do you run tests usually?
I've been unable to find a way to specify the appears_on_statement_as for Billy recurring transactions. I would think this parameter would exist when you create a plan.
Does this functionality already exist? If not, this would be a valuable enhancement.
From a potential customer:
Before the ACH transfer, we need to verify account access. Like many sites, we are planning to do micro-deposits (ACH credit) for their account.
Can Balanced help us do micro-deposits to a customer's account?
It's not efficient for a customer to use Balanced for this if they won't also be using Balanced's ACH Debts functionality. Does it make sense or Billy to contain the logic for MDVs, so these customers can use Billy with another ACH provider?
This project works fine, so far so good. However, the barrier for letting people getting work on this project is a little bit high, namely, technical debt should be reduced. I would like to do some major refactorying. Before we go into implementation, some goals to achieve are listed here to remind us.
#1. Documentation
We should write some documents. Developer could understand how to use this project, what are some classes and module for. Some comments in code could be helpful. For example, when you are using a dirty hack, it's better to write why you are doing this, and what kind of side effect it may have. Something might look like this
# NOTICE: this is a dirty hack to use foo module
# as xxx feature is not supported in foo version xx
# it's better to find a more clean way to do this
foo._dirty_hacky_call()
Also, when you left something unfinished or not sure what to do, you can leave a # TODO comment on it:
# TODO: get rid of the brute force and improve performance
High level document should also be added to make people understand the project easily. Such as some how-to-use-it API call example.
Here we demonstrate how to create a daily basis payout:
api = billy.API(api_key='xxx')
api.daily_payout(...)
Something like that.
Document is important, but do not overkill.
#2. Isolate data model from the table definition
Data model and business logic should be extracted from tables and put in some isolated classes. Do not expose too much SQLALchemy details to the client side (the web framework views). In this way, it is much easier to test, also, when we don't want to use another web framework, it is much easier to adopt. Or, when we are switching to a NO-SQL database, the data model in middle also do help. We don't need to rewrite the whole client-end code, the data model interface should remains almost the same, we only need to rewrite under layer database querying stuff. Some visual explanation would looks like this.
The original design
[SQLAlchemy tables with business logic] <-------- [Flask client-end code]
the new approach
[SQLAlchemy tables] <-------- [Business logic] <-------- [Flask client-end code]
#3. Use SQLIte as the default database storage
To make it as easier to test and deploy as possible, we should use SQLite as the default database storage. The way we use SQL should also be as standard as possible (do not use XX database only feature). In this way, developers who like to get involved can jump in and start hacking quickly without installing any database server on their machine. The testing suites can also be ran without any extra setups. Users can also even use SQLite as the database in production environment when it is really easy to get started.
When SQLite based operation is well tested and proved. Surely SQLAlchemy can support almost any other SQL database without too much effort.
#4. Define operations and usage scenarios clearly
I think it is necessary to define what kind of operation this service should provide and what kind of scenario they will be used. With those descriptions, we can then implement those feature correctly and understand when to use them. For example, Niko wants to charge protection fee weekly from Tony (ah.. what a badass). Then he can create a plan
(or bill plan
more clearly?) like this:
import billy
# create the goat plan to bill every 3 months.
billy.Plan.create(
amount=100,
description='weekly protection fee',
trial_period=None,
frequency=billy.Frequency.WEEKLY,
interval=3
)
Otherwise, without clear blueprint in mind, people can hardly understand it.
Cannot recall some ideas passed my mind, will update this when they come back to me.
Balanced internally has built its own (very simple) metered billing system to instrument our customer's use of the API.
We collect some statistics on their usage and at 0:00 UTC every day, we generate an invoice for that business day and then settle it.
I want to move this logic out of Balanced and into Billy. Ergo, Billy should support metered billing support and the Balanced dashboard should use Billy to display the invoices for our customers.
The name billy
is already taken on PyPI.
Should we:
billie
?pybilly
?I had a glance on the test code, I think it needs some clean up. For example, import statements should not be put in the module level. In cases, some modules are missing, other tests can continue.
For example, this
import unittest
class TestSometing(unittest.TestCase):
def test_xxx(self):
import module1
from my_module import do_some_thing
do_some_thing()
# ...
is better than
import unittest
import module1
from my_module import do_some_thing
class TestSometing(unittest.TestCase):
def test_xxx(self):
do_some_thing()
# ...
For more guideline, please reference to this article:
http://docs.pylonsproject.org/en/latest/community/testing.html
It is Pyramid testing guideline, very neat practices to follow. Pyramid have 100% test coverage, these guys are proud of it.
I created a new virtualenv and installed billy by running python setup.py develop && pip install -r requirements.txt
Next, I tried to run initialize_billy_db development.ini
| ~/code/balanced/billy @ mjallday-2 (billy)(master)
| => initialize_billy_db development.ini
Traceback (most recent call last):
File "/Users/marshall/.virtualenvs/billy/bin/initialize_billy_db", line 9, in <module>
load_entry_point('billy==0.0.1', 'console_scripts', 'initialize_billy_db')()
File "/Users/marshall/code/balanced/billy/billy/scripts/initializedb.py", line 27, in main
settings = setup_database({}, **settings)
File "/Users/marshall/code/balanced/billy/billy/models/__init__.py", line 18, in setup_database
engine_from_config(settings, 'sqlalchemy.')
File "/Users/marshall/.virtualenvs/billy/lib/python2.7/site-packages/SQLAlchemy-0.8.2-py2.7-macosx-10.8-x86_64.egg/sqlalchemy/engine/__init__.py", line 351, in engine_from_config
return create_engine(url, **opts)
File "/Users/marshall/.virtualenvs/billy/lib/python2.7/site-packages/SQLAlchemy-0.8.2-py2.7-macosx-10.8-x86_64.egg/sqlalchemy/engine/__init__.py", line 332, in create_engine
return strategy.create(*args, **kwargs)
File "/Users/marshall/.virtualenvs/billy/lib/python2.7/site-packages/SQLAlchemy-0.8.2-py2.7-macosx-10.8-x86_64.egg/sqlalchemy/engine/strategies.py", line 64, in create
dbapi = dialect_cls.dbapi(**dbapi_args)
File "/Users/marshall/.virtualenvs/billy/lib/python2.7/site-packages/SQLAlchemy-0.8.2-py2.7-macosx-10.8-x86_64.egg/sqlalchemy/dialects/sqlite/pysqlite.py", line 295, in dbapi
raise e
ImportError: No module named pysqlite2
Running pip install pysqlite
fixes the issue but I'm not sure if the dependency is incorrect or if it's missing.
It is better to import project modules in a certain package. For example, code like this
from models.base import Base, RelativeDelta
from models.groups import Group
from utils.generic import uuid_factory
Should rewrite in
from billy.models.base import Base, RelativeDelta
from billy.models.groups import Group
from billy.utils.generic import uuid_factory
style. Otherwise, you cannot understand which one is from your own project, which are from other third-party packages. Also, it will be error prone in certain situation. If it is a relative import, for example
+ billy
+ models
- module_a.py
- module_b.py
- module_c.py
In module_a.py, you can import module_b.py and module_c.py like this
from . import module_b
from .. import module_c
But you should notice that, when you use a relative manner, the module should be imported in a relative manner as well to import correctly. You cannot execute
python module_a.py
and expect it works. It would work when it is
python -m billy.models.module_a
It is executed (or imported) in a relative manner, then it can find parent module then the relative ones.
So, in most cases, absolute import style is recommended.
Original posted here:
https://github.com/PoundPay/balanced/issues/300
As the design of Balanced API doesn't allow us to charge bank account cross marketplaces, explained in following diagram
Therefore, we will need a marketplace dedicated for invoicing, and have all marketplace owner customer and their funding instruments to be cloned to that marketplace like this.
The approach we will use is to emit events about adding funding instruments (and all necessary operations), then let the syncer subscribes to these events, and duplicate these records into our marketplace for invoicing.
As the naming external_id
implies, sounds it should be an ID, so user may misunderstand it, they may think it should be a GUID like CUXXXXXXX...
from balanced service. However, it actually accepts only a complete URI in /v1/customers/CUXXXXX
format. In that way, user may waste their time in trying it with invalid customer external id.
I am thinking renaming the external_id
to external_uri
, it would be more clear that it is an URI rather than an ID. Also, I think maybe we can call Balanced API before creating a customer record in Billy to make sure the given value is valid. By make failure happens as earlier as possible, users don't have to wait for a period to see what's wrong there from transactions error message.
I get the following output:
| => vagrant up
Failed to load the "vagrant-butcher" plugin. View logs for more details.
Bringing machine 'default' up with 'virtualbox' provider...
[default] Importing base box 'precise64'...
[default] Matching MAC address for NAT networking...
[default] Setting the name of the VM...
[default] Clearing any previously set forwarded ports...
[default] Creating shared folders metadata...
[default] Clearing any previously set network interfaces...
[default] Preparing network interfaces based on configuration...
[default] Forwarding ports...
[default] -- 22 => 2222 (adapter 1)
[default] Booting VM...
[default] Waiting for VM to boot. This can take a few minutes.
[default] VM booted and ready for use!
[default] Mounting shared folders...
[default] -- /vagrant
[default] -- /tmp/vagrant-chef-1/chef-solo-1/cookbooks
[default] Running provisioner: shell...
[default] Running: inline script
stdin: is not a tty
Successfully installed mixlib-cli-1.3.0
Successfully installed net-ssh-2.7.0
Successfully installed chef-11.6.0
3 gems installed
[default] Running provisioner: chef_solo...
Generating chef JSON and uploading...
Running chef-solo...
stdin: is not a tty
[2013-10-08T15:54:37+00:00] INFO: Forking chef instance to converge...
[2013-10-08T15:54:37+00:00] INFO: *** Chef 11.6.0 ***
[2013-10-08T15:54:37+00:00] INFO: Setting the run_list to ["recipe[billy]"] from JSON
[2013-10-08T15:54:37+00:00] INFO: Run List is [recipe[billy]]
[2013-10-08T15:54:37+00:00] INFO: Run List expands to [billy]
[2013-10-08T15:54:37+00:00] INFO: Starting Chef Run for precise64
[2013-10-08T15:54:37+00:00] INFO: Running start handlers
[2013-10-08T15:54:37+00:00] INFO: Start handlers complete.
[2013-10-08T15:54:38+00:00] INFO: ohai plugins will be at: /etc/chef/ohai_plugins
[2013-10-08T15:54:38+00:00] INFO: remote_directory[/etc/chef/ohai_plugins] created directory /etc/chef/ohai_plugins
[2013-10-08T15:54:38+00:00] INFO: remote_directory[/etc/chef/ohai_plugins] mode changed to 755
[2013-10-08T15:54:38+00:00] INFO: cookbook_file[/etc/chef/ohai_plugins/README] created file /etc/chef/ohai_plugins/README
[2013-10-08T15:54:38+00:00] INFO: cookbook_file[/etc/chef/ohai_plugins/README] updated file contents /etc/chef/ohai_plugins/README
[2013-10-08T15:54:38+00:00] INFO: cookbook_file[/etc/chef/ohai_plugins/README] mode changed to 644
[2013-10-08T15:54:38+00:00] INFO: ohai[custom_plugins] reloaded
[2013-10-08T15:54:43+00:00] INFO: execute[apt-get-update-build-essentials] ran successfully
[2013-10-08T15:55:05+00:00] WARN: Cloning resource attributes for service[nginx] from prior resource (CHEF-3694)
[2013-10-08T15:55:05+00:00] WARN: Current service[nginx]: /tmp/vagrant-chef-1/chef-solo-1/cookbooks/nginx/recipes/source.rb:114:in `from_file'
[2013-10-08T15:55:05+00:00] WARN: Cloning resource attributes for execute[apt-get update] from prior resource (CHEF-3694)
[2013-10-08T15:55:05+00:00] WARN: Previous execute[apt-get update]: /tmp/vagrant-chef-1/chef-solo-1/cookbooks/apt/recipes/default.rb:29:in `from_file'
[2013-10-08T15:55:05+00:00] WARN: Current execute[apt-get update]: /tmp/vagrant-chef-1/chef-solo-1/cookbooks/postgresql/recipes/ruby.rb:27:in `from_file'
[2013-10-08T15:55:09+00:00] INFO: execute[apt-get update] ran successfully
[2013-10-08T15:55:34+00:00] INFO: execute[apt-get-update] ran successfully
[2013-10-08T15:55:36+00:00] INFO: package[update-notifier-common] sending run action to execute[apt-get-update] (immediate)
[2013-10-08T15:55:41+00:00] INFO: execute[apt-get-update] ran successfully
[2013-10-08T15:55:41+00:00] INFO: directory[/var/cache/local] created directory /var/cache/local
[2013-10-08T15:55:41+00:00] INFO: directory[/var/cache/local] owner changed to 0
[2013-10-08T15:55:41+00:00] INFO: directory[/var/cache/local] group changed to 0
[2013-10-08T15:55:41+00:00] INFO: directory[/var/cache/local] mode changed to 755
[2013-10-08T15:55:41+00:00] INFO: directory[/var/cache/local/preseeding] created directory /var/cache/local/preseeding
[2013-10-08T15:55:41+00:00] INFO: directory[/var/cache/local/preseeding] owner changed to 0
[2013-10-08T15:55:41+00:00] INFO: directory[/var/cache/local/preseeding] group changed to 0
[2013-10-08T15:55:41+00:00] INFO: directory[/var/cache/local/preseeding] mode changed to 755
[2013-10-08T15:56:01+00:00] INFO: remote_file[/var/chef/cache/ez_setup.py] created file /var/chef/cache/ez_setup.py
[2013-10-08T15:56:02+00:00] INFO: remote_file[/var/chef/cache/ez_setup.py] updated file contents /var/chef/cache/ez_setup.py
[2013-10-08T15:56:02+00:00] INFO: remote_file[/var/chef/cache/ez_setup.py] mode changed to 644
[2013-10-08T15:56:02+00:00] INFO: remote_file[/var/chef/cache/get-pip.py] created file /var/chef/cache/get-pip.py
[2013-10-08T15:56:03+00:00] INFO: remote_file[/var/chef/cache/get-pip.py] updated file contents /var/chef/cache/get-pip.py
[2013-10-08T15:56:03+00:00] INFO: remote_file[/var/chef/cache/get-pip.py] mode changed to 644
[2013-10-08T15:56:03+00:00] INFO: execute[install-setuptools] ran successfully
[2013-10-08T15:56:05+00:00] INFO: execute[install-pip] ran successfully
[2013-10-08T15:56:05+00:00] INFO: Installing python_pip[virtualenv] version latest
[2013-10-08T15:56:06+00:00] INFO: user[www-data] altered
[2013-10-08T15:56:07+00:00] INFO: template[/etc/chef/ohai_plugins/nginx.rb] created file /etc/chef/ohai_plugins/nginx.rb
[2013-10-08T15:56:07+00:00] INFO: template[/etc/chef/ohai_plugins/nginx.rb] updated file contents /etc/chef/ohai_plugins/nginx.rb
[2013-10-08T15:56:07+00:00] INFO: template[/etc/chef/ohai_plugins/nginx.rb] owner changed to 0
[2013-10-08T15:56:07+00:00] INFO: template[/etc/chef/ohai_plugins/nginx.rb] group changed to 0
[2013-10-08T15:56:07+00:00] INFO: template[/etc/chef/ohai_plugins/nginx.rb] mode changed to 755
[2013-10-08T15:56:07+00:00] INFO: template[/etc/chef/ohai_plugins/nginx.rb] sending reload action to ohai[reload_nginx] (immediate)
[2013-10-08T15:56:07+00:00] INFO: ohai[reload_nginx] reloaded
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx] created directory /etc/nginx
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx] owner changed to 0
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx] group changed to 0
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx] mode changed to 755
[2013-10-08T15:56:07+00:00] INFO: directory[/var/log/nginx] created directory /var/log/nginx
[2013-10-08T15:56:07+00:00] INFO: directory[/var/log/nginx] owner changed to 33
[2013-10-08T15:56:07+00:00] INFO: directory[/var/log/nginx] mode changed to 755
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx/sites-available] created directory /etc/nginx/sites-available
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx/sites-available] owner changed to 0
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx/sites-available] group changed to 0
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx/sites-available] mode changed to 755
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx/sites-enabled] created directory /etc/nginx/sites-enabled
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx/sites-enabled] owner changed to 0
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx/sites-enabled] group changed to 0
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx/sites-enabled] mode changed to 755
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx/conf.d] created directory /etc/nginx/conf.d
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx/conf.d] owner changed to 0
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx/conf.d] group changed to 0
[2013-10-08T15:56:07+00:00] INFO: directory[/etc/nginx/conf.d] mode changed to 755
[2013-10-08T15:56:07+00:00] INFO: template[/usr/sbin/nxensite] created file /usr/sbin/nxensite
[2013-10-08T15:56:07+00:00] INFO: template[/usr/sbin/nxensite] updated file contents /usr/sbin/nxensite
[2013-10-08T15:56:07+00:00] INFO: template[/usr/sbin/nxensite] owner changed to 0
[2013-10-08T15:56:07+00:00] INFO: template[/usr/sbin/nxensite] group changed to 0
[2013-10-08T15:56:07+00:00] INFO: template[/usr/sbin/nxensite] mode changed to 755
[2013-10-08T15:56:07+00:00] INFO: template[/usr/sbin/nxdissite] created file /usr/sbin/nxdissite
[2013-10-08T15:56:07+00:00] INFO: template[/usr/sbin/nxdissite] updated file contents /usr/sbin/nxdissite
[2013-10-08T15:56:07+00:00] INFO: template[/usr/sbin/nxdissite] owner changed to 0
[2013-10-08T15:56:07+00:00] INFO: template[/usr/sbin/nxdissite] group changed to 0
[2013-10-08T15:56:07+00:00] INFO: template[/usr/sbin/nxdissite] mode changed to 755
[2013-10-08T15:56:10+00:00] INFO: remote_file[http://nginx.org/download/nginx-1.2.9.tar.gz] created file /var/chef/cache/nginx-1.2.9.tar.gz
[2013-10-08T15:56:10+00:00] INFO: remote_file[http://nginx.org/download/nginx-1.2.9.tar.gz] updated file contents /var/chef/cache/nginx-1.2.9.tar.gz
[2013-10-08T15:56:10+00:00] INFO: template[nginx.conf] created file /etc/nginx/nginx.conf
[2013-10-08T15:56:10+00:00] INFO: template[nginx.conf] updated file contents /etc/nginx/nginx.conf
[2013-10-08T15:56:10+00:00] INFO: template[nginx.conf] owner changed to 0
[2013-10-08T15:56:10+00:00] INFO: template[nginx.conf] group changed to 0
[2013-10-08T15:56:10+00:00] INFO: template[nginx.conf] mode changed to 644
[2013-10-08T15:56:10+00:00] INFO: template[/etc/nginx/sites-available/default] created file /etc/nginx/sites-available/default
[2013-10-08T15:56:10+00:00] INFO: template[/etc/nginx/sites-available/default] updated file contents /etc/nginx/sites-available/default
[2013-10-08T15:56:10+00:00] INFO: template[/etc/nginx/sites-available/default] owner changed to 0
[2013-10-08T15:56:10+00:00] INFO: template[/etc/nginx/sites-available/default] group changed to 0
[2013-10-08T15:56:10+00:00] INFO: template[/etc/nginx/sites-available/default] mode changed to 644
[2013-10-08T15:56:10+00:00] INFO: template[/etc/nginx/sites-available/default] not queuing delayed action reload on service[nginx] (delayed), as it's already been queued
[2013-10-08T15:56:11+00:00] INFO: execute[nxensite default] ran successfully
[2013-10-08T15:56:11+00:00] INFO: execute[nxensite default] not queuing delayed action reload on service[nginx] (delayed), as it's already been queued
[2013-10-08T15:56:11+00:00] INFO: cookbook_file[/etc/nginx/mime.types] created file /etc/nginx/mime.types
[2013-10-08T15:56:11+00:00] INFO: cookbook_file[/etc/nginx/mime.types] updated file contents /etc/nginx/mime.types
[2013-10-08T15:56:11+00:00] INFO: cookbook_file[/etc/nginx/mime.types] owner changed to 0
[2013-10-08T15:56:11+00:00] INFO: cookbook_file[/etc/nginx/mime.types] group changed to 0
[2013-10-08T15:56:11+00:00] INFO: cookbook_file[/etc/nginx/mime.types] mode changed to 644
[2013-10-08T15:56:11+00:00] INFO: cookbook_file[/etc/nginx/mime.types] not queuing delayed action reload on service[nginx] (delayed), as it's already been queued
[2013-10-08T15:56:34+00:00] INFO: bash[compile_nginx_source] ran successfully
[2013-10-08T15:56:34+00:00] WARN: Method 'template_location' of 'Chef::Provider::Template' is deprecated. It will be removed in Chef 12.
[2013-10-08T15:56:34+00:00] WARN: Please update your cookbooks accordingly. Accessed from:
[2013-10-08T15:56:34+00:00] WARN: /opt/vagrant_ruby/lib/ruby/gems/1.8/gems/chef-11.6.0/bin/../lib/chef/provider/package.rb:207:in `preseed_resource'
[2013-10-08T15:56:34+00:00] WARN: /opt/vagrant_ruby/lib/ruby/gems/1.8/gems/chef-11.6.0/bin/../lib/chef/provider/package.rb:182:in `get_preseed_file'
[2013-10-08T15:56:34+00:00] WARN: /opt/vagrant_ruby/lib/ruby/gems/1.8/gems/chef-11.6.0/bin/../lib/chef/provider/package.rb:73:in `action_install'
[2013-10-08T15:56:34+00:00] WARN: /opt/vagrant_ruby/lib/ruby/gems/1.8/gems/chef-11.6.0/bin/../lib/chef/provider.rb:114:in `send'
[2013-10-08T15:56:34+00:00] WARN: Method 'template_finder' of 'Chef::Provider::Template' is deprecated. It will be removed in Chef 12.
[2013-10-08T15:56:34+00:00] WARN: Please update your cookbooks accordingly. Accessed from:
[2013-10-08T15:56:34+00:00] WARN: /opt/vagrant_ruby/lib/ruby/gems/1.8/gems/chef-11.6.0/bin/../lib/chef/deprecation/provider/template.rb:42:in `template_location'
[2013-10-08T15:56:34+00:00] WARN: /opt/vagrant_ruby/lib/ruby/gems/1.8/gems/chef-11.6.0/bin/../lib/chef/deprecation/warnings.rb:30:in `template_location'
[2013-10-08T15:56:34+00:00] WARN: /opt/vagrant_ruby/lib/ruby/gems/1.8/gems/chef-11.6.0/bin/../lib/chef/provider/package.rb:207:in `preseed_resource'
[2013-10-08T15:56:34+00:00] WARN: /opt/vagrant_ruby/lib/ruby/gems/1.8/gems/chef-11.6.0/bin/../lib/chef/provider/package.rb:182:in `get_preseed_file'
[2013-10-08T15:56:34+00:00] INFO: cookbook_file[/var/chef/cache/preseed/runit/runit-2.1.1-6.2ubuntu2.seed] created file /var/chef/cache/preseed/runit/runit-2.1.1-6.2ubuntu2.seed
[2013-10-08T15:56:34+00:00] INFO: cookbook_file[/var/chef/cache/preseed/runit/runit-2.1.1-6.2ubuntu2.seed] updated file contents /var/chef/cache/preseed/runit/runit-2.1.1-6.2ubuntu2.seed
[2013-10-08T15:56:34+00:00] INFO: package[runit] pre-seeding package installation instructions
[2013-10-08T15:56:36+00:00] INFO: package[runit] sending nothing action to execute[start-runsvdir] (immediate)
[2013-10-08T15:56:36+00:00] INFO: package[runit] sending nothing action to execute[runit-hup-init] (immediate)
[2013-10-08T15:56:36+00:00] INFO: directory[/etc/sv/nginx] created directory /etc/sv/nginx
[2013-10-08T15:56:36+00:00] INFO: directory[/etc/sv/nginx] mode changed to 755
[2013-10-08T15:56:36+00:00] INFO: template[/etc/sv/nginx/run] created file /etc/sv/nginx/run
[2013-10-08T15:56:36+00:00] INFO: template[/etc/sv/nginx/run] updated file contents /etc/sv/nginx/run
[2013-10-08T15:56:36+00:00] INFO: template[/etc/sv/nginx/run] mode changed to 755
[2013-10-08T15:56:36+00:00] INFO: directory[/etc/sv/nginx/log] created directory /etc/sv/nginx/log
[2013-10-08T15:56:36+00:00] INFO: directory[/etc/sv/nginx/log] mode changed to 755
[2013-10-08T15:56:36+00:00] INFO: directory[/etc/sv/nginx/log/main] created directory /etc/sv/nginx/log/main
[2013-10-08T15:56:36+00:00] INFO: directory[/etc/sv/nginx/log/main] mode changed to 755
[2013-10-08T15:56:36+00:00] INFO: template[/etc/sv/nginx/log/run] created file /etc/sv/nginx/log/run
[2013-10-08T15:56:36+00:00] INFO: template[/etc/sv/nginx/log/run] updated file contents /etc/sv/nginx/log/run
[2013-10-08T15:56:36+00:00] INFO: template[/etc/sv/nginx/log/run] mode changed to 755
[2013-10-08T15:56:36+00:00] INFO: template[/etc/sv/nginx/log/config] created file /etc/sv/nginx/log/config
[2013-10-08T15:56:36+00:00] INFO: template[/etc/sv/nginx/log/config] updated file contents /etc/sv/nginx/log/config
[2013-10-08T15:56:36+00:00] INFO: template[/etc/sv/nginx/log/config] mode changed to 644
[2013-10-08T15:56:36+00:00] INFO: link[/etc/init.d/nginx] created
[2013-10-08T15:56:36+00:00] INFO: runit_service[nginx] configured
[2013-10-08T15:56:36+00:00] INFO: link[/etc/service/nginx] created
[2013-10-08T15:56:41+00:00] INFO: runit_service[nginx] enabled
[2013-10-08T15:56:42+00:00] INFO: directory[/etc/postgresql/9.1/main] created directory /etc/postgresql/9.1/main
[2013-10-08T15:56:42+00:00] INFO: directory[/etc/postgresql/9.1/main] mode changed to 777
[2013-10-08T15:56:42+00:00] INFO: directory[/var/log/postgresql] created directory /var/log/postgresql
[2013-10-08T15:56:42+00:00] INFO: directory[/var/log/postgresql] mode changed to 777
================================================================================
Error executing action `create` on resource 'template[/etc/postgresql/9.1/main/postgresql.conf]'
================================================================================
Chef::Exceptions::UserIDNotFound
--------------------------------
cannot determine user id for 'postgres', does the user exist on this system?
Resource Declaration:
---------------------
# In /tmp/vagrant-chef-1/chef-solo-1/cookbooks/postgresql/recipes/server.rb
66: template "#{node['postgresql']['dir']}/postgresql.conf" do
67: source "postgresql.conf.erb"
68: owner "postgres"
69: group "postgres"
70: mode 0600
71: notifies :restart, 'service[postgresql]', :immediately
72: end
73:
Compiled Resource:
------------------
# Declared in /tmp/vagrant-chef-1/chef-solo-1/cookbooks/postgresql/recipes/server.rb:66:in `from_file'
template("/etc/postgresql/9.1/main/postgresql.conf") do
owner "postgres"
action "create"
recipe_name "server"
retry_delay 2
cookbook_name :postgresql
backup 5
path "/etc/postgresql/9.1/main/postgresql.conf"
source "postgresql.conf.erb"
mode 384
retries 0
provider Chef::Provider::Template
group "postgres"
atomic_update true
end
[2013-10-08T15:56:42+00:00] INFO: Running queued delayed notifications before re-raising exception
[2013-10-08T15:56:42+00:00] INFO: template[nginx.conf] sending reload action to service[nginx] (delayed)
[2013-10-08T15:56:42+00:00] INFO: service[nginx] reloaded
[2013-10-08T15:56:42+00:00] INFO: bash[compile_nginx_source] sending restart action to service[nginx] (delayed)
[2013-10-08T15:56:44+00:00] INFO: service[nginx] restarted
[2013-10-08T15:56:44+00:00] ERROR: Running exception handlers
[2013-10-08T15:56:44+00:00] ERROR: Exception handlers complete
[2013-10-08T15:56:44+00:00] FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out
[2013-10-08T15:56:44+00:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)
Chef never successfully completed! Any errors should be visible in the
output above. Please fix your recipes so that they properly complete.
________________________________________________________________________________
| ~/code/balanced/billy @ mjallday-2 (master)
| =>
I started with a clean slate by running vagrant destroy && vagrant up
With some inputs from a customer and the scenario of how invoice is processed in balanced, I have a better understanding of what invoicing should be. For now, I am staring to describe usage scenarios here, improve the design gradually, and implement it when it looks good.
Some wanted features here:
Unlike what the most obvious usage scenario we mentioned in the previous development cycle, common hosting service providers can use billy to charge or payout a fixed amount to specific customers. However, not all of hosting service providers, or cloud service providers are charging customers in a fixed rate. Such as Amazon Web Service, the price is basically determined by the usage. In this case, we want our customer to be able to charge customer based on metered usage. Another good example is Balanced itself, customer are charged based on transaction count. This is great, we can eat our own dog food at very first.
As we need to grab usage data from the user side, I think there are two obvious ways to do
Still thinking pros and cons of these two approaches, however, maybe we can implement them all. But anyway, the data should contain
I envision the data format of a invoice may look like this
amount: 100
customer: customer_guid
title: Foobar Super Cloud Service October Invoice
items:
- name: Bandwidth usage
count: 10
unit: TB
amount: 20
- name: Instance usage
count: 200
unit: hours
amount: 80
notify_email:
- [email protected]
- [email protected]
On receiving these data from end of user, billy then generate corresponding invoice, send email to receivers and charge customers if it is necessary.
TODO:
"Frequency length" probably isn't the best term to use, but we sell our subscriptions in the following intervals:
Maybe add another optional property to Frequency(object)?
I just found this sweet recurring billing software called Billy. It looks totally awesome but I don't know how to use it. It sure would be cool if there was a tutorial that showed me how :)
@mahmoudimus @mjallday @msherry
As we know there are customers waiting for billy beta environment. I think we should setup one as soon as possible. Almost everything is ready to go.
We have a Chef repo for deploying it
https://github.com/victorlin/billy-chef
It is already running on my own EC2 instance mentioned at #44, but I don't think it is a good idea to run Balanced service on my own instance. I think we are using EC2 for deployment aren't we? If we are, we have two options
I think I can do the deployment and maintenance by myself, you can setup a account via AWS Identity and Access Management with minimum permission to run an EC2 instance, and rest of it is my DIY time.
I am not sure how deployment is done usually, maybe someone can deploy it for us.
I am all okay for these two options. See what youguys think.
As I see soft description for transaction is pretty important for reasons, and Billy currently doesn't allow user to set a soft description for transactions issued either by invoice or recurring payment, I think this is a must to have feature. Will add it after invoicing feature is finished.
We have customers that would like to use Billy but have projects implemented in PHP.
(migrated from balanced/balanced-api#183)
Program API interface to show usage:
buyer = balanced.Buyer.new( <account details> )
subscription = balanced.Subscription.get( <subscription id/name> )
subscription.enroll( buyer )
For reasoning. As a Balanced user I should not have to perform manual subscription handling, unless I need that control. Balanced should handle subscription creation, pricing, subscription interval, and apply filters to the subscription to effect payments, such as coupons.
I currently handle the subscription process, but have noticed that other services offer the above through their API thus removing the work further from me and allowing me to focus on my application instead of intricate details that ultimately I should not have to worry about.
The payment_uri
parameter for creating a subscription should be optional. However, it appears we have a bug here, when the payment_uri
is not given, Billy will interpret it as a empty string rather than None.
Currently months are handled using dateutil's relativedelta. A sample output:
day_start = datetime(year=2012,month=1, day=29)
for each in range(20):
... day_start += relativedelta(months=1)
... print day_start
...
2012-02-29 00:00:00
2012-03-29 00:00:00
2012-04-29 00:00:00
2012-05-29 00:00:00
2012-06-29 00:00:00
2012-07-29 00:00:00
2012-08-29 00:00:00
2012-09-29 00:00:00
2012-10-29 00:00:00
2012-11-29 00:00:00
2012-12-29 00:00:00
2013-01-29 00:00:00
2013-02-28 00:00:00
2013-03-28 00:00:00
2013-04-28 00:00:00
2013-05-28 00:00:00
2013-06-28 00:00:00
2013-07-28 00:00:00
2013-08-28 00:00:00
2013-09-28 00:00:00
How good is this 'monthly' strategy for a recurring billing system? Someone pointed out this http://bit.ly/15gLHsZ as an alternative strategy. I think this way makes more sense because you stay within the scope of the current month and it automatically adjusts for leapyears, shorter months, etc.
From IRC:
do u know if billy will eventually tie into the balanced dashboard? to view plans, subscriptions, transactions, etc.
Although billy has a RESTful API, it is still better to have a easy-to-use Python API client library that the usage might like what I mentioned in #25.
However, this is not a priority task as the API may not be stable for a period. Also, we should take care of naming problem as billy
is already used by the server project. Maybe we can use billy-client
for Python project name (in PyPi and github repo). And for Python package name (the_name/init.py), we can use billy_client
, or we can use a Python namespace package ( http://www.python.org/dev/peps/pep-0382/ ), make it under billy.client
? Well, not priority, think about it later.
I see we are using MIT and BSD license in dashboard, but I cannot find a LICENSE file in this repo. I think I should add one. Can I just copy the one from https://github.com/balanced/balanced-dashboard/blob/master/LICENSE ?
Scenario:
-Customer account balance automatically add $20 everytime the balance reaches $5
-Predefined microcharges reduce balance e.g. $0.01 per SMS reduce balance
Good billing/payment strategy for low cost products. Works well with creditline billing (see other feature)
Deploy billy to an EC2 instance with Chef.
@victorlin Write the steps to deploy here later.
The current REST API design is pretty much like what I mentioned in #33 (comment) . I try to make it looks like Balanced API, however, there are something different from details to details.
The Balanced API uses URI as the ID
for passing around, I don't know the design rationale here (about version distinguishing?). Maybe we can adjust it to do the same way.
Also, for Balanced API, some relative record will be returned when retrieving one record. For example, you get a Debit, there will be customer data or something like that in the response json body. I think the rationale here is for efficiency? (save some extra requests?). Anyway, I can also make the same to billy API, just need some rules of thumb to decide which relative records to include, and how deep it should be.
As we are processing money, I think maybe we should be careful with float numbers rounding up issue. Currently, I use
Numeric(10, 2)
for all money fields (also for discount field). I am not sure are two digits float number good enough. Also, we have discount
for subscriptions, and we have prorated_refund
for canceling a subscription. In this two cases, money amount could be rated. It could be
amount = original_amount * discount * prorated
After all, I have no money processing experience. I have no idea how to process them. Is there anything I should notice? What do youguys think?
For now we have unit tests and functional tests, but we don't have easy-to-setup integration testing environment yet. To make it as easy as possible to run integration tests and deploy, we should write a Vagrant environment with Chef code for it. Then we can run it easily like
git clone https://github.com/balanced/billy.git
cd billy
vagrant up
As I don't think it is a good idea to include Chef code directly in this project, I think we can use a .gitmodule
file to include chef code from another repo. The .gitmodule
might look like this.
[submodule "chef"]
path = chef
url = git://github.com/balanced/billy-chef
What is the preferred Linux distribution youguys are using? Ubuntu 12.04.3 LTS (Precise Pangolin)?
Once invoicing is implemented the best way to ensure that this code works as required will be to get Balanced to use it for production invoicing of marketplace fees.
A 3-step approach would be best
We have an internal discussion at Balanced on how to implement step 1.
It's unclear how Billy will handle invoice adjustments right now but once that is understood we can begin generate the invoices in Billy.
Maybe we should keep the coding style more closer to PEP8. It doesn't have to be 100% PEP8 style, as some of these rules are not required in certain situations (we can ignore that in flake8 configuration), for example, the E601, line too long rule, when we can always view and edit code in a big screen, this might not be too important.
However, the current PEP8 compliance situation is horrible. This would certain reduce readability to the code. Please read PEP8 article:
http://www.python.org/dev/peps/pep-0008/
And run flake8 ( https://pypi.python.org/pypi/flake8 ) with tests like this
flake8 billy
If you are using SublimeText, you can install SublimeLinter plugin
https://github.com/SublimeLinter/SublimeLinter
It can tell you which lines have coding style issue, which kind of issue it is
Current flake8 output:
billy\manage.py:5:1: F403 'from billy.models import *' used; unable to detect undefined names
billy\api\__init__.py:16:80: E501 line too long (83 > 79 characters)
billy\api\__init__.py:46:80: E501 line too long (89 > 79 characters)
billy\api\__init__.py:55:80: E501 line too long (98 > 79 characters)
billy\api\spec.py:8:1: F403 'from resources import *' used; unable to detect undefined names
billy\api\spec.py:60:24: E231 missing whitespace after ':'
billy\api\resources\__init__.py:2:1: F401 'Base' imported but unused
billy\api\resources\__init__.py:5:1: F401 'Home' imported but unused
billy\api\resources\__init__.py:8:1: F401 'GroupController' imported but unused
billy\api\resources\__init__.py:9:1: F401 'CustomerController' imported but unused
billy\api\resources\__init__.py:9:1: F401 'CustomerCreateForm' imported but unused
billy\api\resources\__init__.py:9:1: F401 'CustomerIndexController' imported but unused
billy\api\resources\__init__.py:9:1: F401 'CustomerUpdateForm' imported but unused
billy\api\resources\__init__.py:9:1: F401 'customer_view' imported but unused
billy\api\resources\__init__.py:10:37: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:11:37: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:12:1: F401 'CouponController' imported but unused
billy\api\resources\__init__.py:12:1: F401 'CouponCreateForm' imported but unused
billy\api\resources\__init__.py:12:1: F401 'CouponIndexController' imported but unused
billy\api\resources\__init__.py:12:1: F401 'CouponUpdateForm' imported but unused
billy\api\resources\__init__.py:12:1: F401 'coupon_view' imported but unused
billy\api\resources\__init__.py:12:80: E501 line too long (80 > 79 characters)
billy\api\resources\__init__.py:13:35: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:14:37: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:15:1: F401 'PlanController' imported but unused
billy\api\resources\__init__.py:15:1: F401 'PlanCreateForm' imported but unused
billy\api\resources\__init__.py:15:1: F401 'PlanIndexController' imported but unused
billy\api\resources\__init__.py:15:1: F401 'PlanUpdateForm' imported but unused
billy\api\resources\__init__.py:15:1: F401 'plan_view' imported but unused
billy\api\resources\__init__.py:15:80: E501 line too long (117 > 79 characters)
billy\api\resources\__init__.py:16:1: F401 'PayoutController' imported but unused
billy\api\resources\__init__.py:16:1: F401 'PayoutCreateForm' imported but unused
billy\api\resources\__init__.py:16:1: F401 'PayoutIndexController' imported but unused
billy\api\resources\__init__.py:16:1: F401 'PayoutUpdateForm' imported but unused
billy\api\resources\__init__.py:16:1: F401 'payout_view' imported but unused
billy\api\resources\__init__.py:16:80: E501 line too long (80 > 79 characters)
billy\api\resources\__init__.py:17:35: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:17:80: E501 line too long (82 > 79 characters)
billy\api\resources\__init__.py:18:1: F401 'PlanSubController' imported but unused
billy\api\resources\__init__.py:18:1: F401 'PlanSubCreateForm' imported but unused
billy\api\resources\__init__.py:18:1: F401 'PlanSubDeleteForm' imported but unused
billy\api\resources\__init__.py:18:1: F401 'PlanSubIndexController' imported but unused
billy\api\resources\__init__.py:18:1: F401 'plan_sub_view' imported but unused
billy\api\resources\__init__.py:19:46: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:19:80: E501 line too long (116 > 79 characters)
billy\api\resources\__init__.py:20:1: F401 'PayoutSubController' imported but unused
billy\api\resources\__init__.py:20:1: F401 'PayoutSubCreateForm' imported but unused
billy\api\resources\__init__.py:20:1: F401 'PayoutSubDeleteForm' imported but unused
billy\api\resources\__init__.py:20:1: F401 'PayoutSubIndexController' imported but unused
billy\api\resources\__init__.py:20:1: F401 'payout_sub_view' imported but unused
billy\api\resources\__init__.py:21:48: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:22:48: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:22:80: E501 line too long (105 > 79 characters)
billy\api\resources\__init__.py:23:1: F401 'PlanInvController' imported but unused
billy\api\resources\__init__.py:23:1: F401 'PlanInvIndexController' imported but unused
billy\api\resources\__init__.py:23:1: F401 'plan_inv_view' imported but unused
billy\api\resources\__init__.py:24:41: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:25:1: F401 'PayoutInvController' imported but unused
billy\api\resources\__init__.py:25:1: F401 'PayoutInvIndexController' imported but unused
billy\api\resources\__init__.py:25:1: F401 'payout_inv_view' imported but unused
billy\api\resources\__init__.py:26:43: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:27:43: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:28:1: F401 'PlanTransController' imported but unused
billy\api\resources\__init__.py:28:1: F401 'PlanTransIndexController' imported but unused
billy\api\resources\__init__.py:28:1: F401 'plan_trans_view' imported but unused
billy\api\resources\__init__.py:29:45: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:30:45: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:31:1: F401 'PayoutTransController' imported but unused
billy\api\resources\__init__.py:31:1: F401 'PayoutTransIndexController' imported but unused
billy\api\resources\__init__.py:31:1: F401 'payout_trans_view' imported but unused
billy\api\resources\__init__.py:32:47: E128 continuation line under-indented for visual indent
billy\api\resources\__init__.py:33:47: E128 continuation line under-indented for visual indent
billy\api\resources\base\__init__.py:48:1: F821 undefined name 'unpack'
billy\api\resources\coupon\form.py:3:1: F403 'from sqlalchemy.exc import *' used; unable to detect undefined names
billy\api\resources\coupon\form.py:30:5: E303 too many blank lines (2)
billy\api\resources\customer\form.py:3:1: F403 'from sqlalchemy.exc import *' used; unable to detect undefined names
billy\api\resources\group\__init__.py:56:5: E303 too many blank lines (2)
billy\api\resources\group\__init__.py:64:1: W391 blank line at end of file
billy\api\resources\payout\form.py:3:1: F403 'from sqlalchemy.exc import *' used; unable to detect undefined names
billy\api\resources\payout\form.py:13:41: E128 continuation line under-indented for visual indent
billy\api\resources\payout\form.py:29:5: E303 too many blank lines (2)
billy\api\resources\payout\form.py:36:34: E128 continuation line under-indented for visual indent
billy\api\resources\payout\form.py:37:34: E128 continuation line under-indented for visual indent
billy\api\resources\payout\form.py:38:34: E128 continuation line under-indented for visual indent
billy\api\resources\payout\form.py:39:34: E128 continuation line under-indented for visual indent
billy\api\resources\payout\form.py:40:34: E128 continuation line under-indented for visual indent
billy\api\resources\payout\form.py:41:34: E124 closing bracket does not match visual indentation
billy\api\resources\payout_invoice\__init__.py:8:80: E501 line too long (81 > 79 characters)
billy\api\resources\payout_invoice\__init__.py:23:80: E501 line too long (84 > 79 characters)
billy\api\resources\payout_subscription\form.py:3:1: F403 'from sqlalchemy.orm.exc import *' used; unable to detect undefined names
billy\api\resources\payout_subscription\form.py:15:41: E128 continuation line under-indented for visual indent
billy\api\resources\payout_subscription\form.py:41:41: E128 continuation line under-indented for visual indent
billy\api\resources\plan\form.py:3:1: F403 'from sqlalchemy.exc import *' used; unable to detect undefined names
billy\api\resources\plan\form.py:13:37: E128 continuation line under-indented for visual indent
billy\api\resources\plan\form.py:43:32: E128 continuation line under-indented for visual indent
billy\api\resources\plan\form.py:44:32: E128 continuation line under-indented for visual indent
billy\api\resources\plan\form.py:45:32: E128 continuation line under-indented for visual indent
billy\api\resources\plan\form.py:46:32: E128 continuation line under-indented for visual indent
billy\api\resources\plan\form.py:47:32: E128 continuation line under-indented for visual indent
billy\api\resources\plan\form.py:48:32: E124 closing bracket does not match visual indentation
billy\api\resources\plan_invoice\__init__.py:8:80: E501 line too long (81 > 79 characters)
billy\api\resources\plan_invoice\__init__.py:23:80: E501 line too long (84 > 79 characters)
billy\api\resources\plan_subscription\form.py:3:1: F403 'from sqlalchemy.orm.exc import *' used; unable to detect undefined names
billy\api\resources\plan_subscription\form.py:15:37: E128 continuation line under-indented for visual indent
billy\api\resources\plan_subscription\form.py:32:47: E128 continuation line under-indented for visual indent
billy\api\resources\plan_subscription\form.py:33:47: E128 continuation line under-indented for visual indent
billy\api\resources\plan_subscription\form.py:33:80: E501 line too long (98 > 79 characters)
billy\api\resources\plan_subscription\form.py:34:47: E128 continuation line under-indented for visual indent
billy\api\resources\plan_subscription\form.py:34:80: E501 line too long (87 > 79 characters)
billy\api\resources\plan_subscription\form.py:43:37: E128 continuation line under-indented for visual indent
billy\api\resources\plan_subscription\form.py:56:49: E128 continuation line under-indented for visual indent
billy\api\resources\plan_subscription\form.py:56:80: E501 line too long (113 > 79 characters)
billy\models\__init__.py:4:1: F401 'ProcessorType' imported but unused
billy\models\__init__.py:5:1: F401 'Base' imported but unused
billy\models\__init__.py:7:1: F401 'ChargeSubscription' imported but unused
billy\models\__init__.py:8:1: F401 'PayoutSubscription' imported but unused
billy\models\__init__.py:9:1: F401 'ChargePlanInvoice' imported but unused
billy\models\__init__.py:10:1: F401 'PayoutPlanInvoice' imported but unused
billy\models\__init__.py:11:1: F401 'ChargeTransaction' imported but unused
billy\models\__init__.py:11:1: F401 'ChargeTransactionStatus' imported but unused
billy\models\__init__.py:12:1: F401 'PayoutTransaction' imported but unused
billy\models\__init__.py:12:1: F401 'PayoutTransactionStatus' imported but unused
billy\models\__init__.py:13:1: F401 'PayoutPlan' imported but unused
billy\models\__init__.py:14:1: F401 'ChargePlan' imported but unused
billy\models\__init__.py:15:1: F401 'Customer' imported but unused
billy\models\__init__.py:16:1: F401 'Coupon' imported but unused
billy\models\__init__.py:17:1: F401 'Company' imported but unused
billy\models\base.py:6:1: F401 'event' imported but unused
billy\models\base.py:25:80: E501 line too long (82 > 79 characters)
billy\models\company.py:3:1: F401 'Enum' imported but unused
billy\models\company.py:3:1: F401 'ForeignKey' imported but unused
billy\models\company.py:4:1: F401 'backref' imported but unused
billy\models\company.py:6:80: E501 line too long (86 > 79 characters)
billy\models\company.py:45:5: E303 too many blank lines (4)
billy\models\company.py:105:15: E225 missing whitespace around operator
billy\models\company.py:119:5: E303 too many blank lines (2)
billy\models\company.py:152:80: E501 line too long (83 > 79 characters)
billy\models\coupons.py:4:1: F401 'Boolean' imported but unused
billy\models\coupons.py:17:80: E501 line too long (80 > 79 characters)
billy\models\coupons.py:75:5: E303 too many blank lines (2)
billy\models\coupons.py:79:80: E501 line too long (80 > 79 characters)
billy\models\coupons.py:91:5: E303 too many blank lines (2)
billy\models\customers.py:4:1: F401 'Integer' imported but unused
billy\models\customers.py:17:80: E501 line too long (80 > 79 characters)
billy\models\customers.py:24:40: E128 continuation line under-indented for visual indent
billy\models\customers.py:46:5: E303 too many blank lines (2)
billy\models\customers.py:50:44: E712 comparison to True should be 'if cond is True:' or 'if cond:'
billy\models\customers.py:53:5: E303 too many blank lines (2)
billy\models\customers.py:57:42: E712 comparison to True should be 'if cond is True:' or 'if cond:'
billy\models\customers.py:60:1: W391 blank line at end of file
billy\models\charge\invoice.py:76:45: E712 comparison to True should be 'if cond is True:' or 'if cond:'
billy\models\charge\invoice.py:90:35: E127 continuation line over-indented for visual indent
billy\models\charge\invoice.py:96:80: E501 line too long (83 > 79 characters)
billy\models\charge\invoice.py:137:5: E303 too many blank lines (2)
billy\models\charge\plan.py:19:80: E501 line too long (80 > 79 characters)
billy\models\charge\plan.py:34:5: E124 closing bracket does not match visual indentation
billy\models\charge\subscription.py:19:80: E501 line too long (80 > 79 characters)
billy\models\charge\subscription.py:35:45: E712 comparison to True should be 'if cond is True:' or 'if cond:'
billy\models\charge\subscription.py:40:5: E303 too many blank lines (2)
billy\models\charge\subscription.py:84:5: E303 too many blank lines (2)
billy\models\charge\subscription.py:104:5: E303 too many blank lines (2)
billy\models\charge\subscription.py:115:13: E122 continuation line missing indentation or outdented
billy\models\charge\subscription.py:115:34: E711 comparison to None should be 'if cond is None:'
billy\models\charge\transaction.py:4:1: F401 'relationship' imported but unused
billy\models\charge\transaction.py:11:26: E128 continuation line under-indented for visual indent
billy\models\charge\transaction.py:35:80: E501 line too long (84 > 79 characters)
billy\models\charge\transaction.py:45:27: W292 no newline at end of file
billy\models\payout\invoice.py:34:5: E124 closing bracket does not match visual indentation
billy\models\payout\invoice.py:54:63: E712 comparison to True should be 'if cond is True:' or 'if cond:'
billy\models\payout\invoice.py:75:32: E712 comparison to True should be 'if cond is True:' or 'if cond:'
billy\models\payout\invoice.py:81:5: E303 too many blank lines (2)
billy\models\payout\invoice.py:85:57: E712 comparison to False should be 'if cond is False:' or 'if not cond:'
billy\models\payout\invoice.py:118:1: W391 blank line at end of file
billy\models\payout\plan.py:18:80: E501 line too long (80 > 79 characters)
billy\models\payout\plan.py:23:36: E124 closing bracket does not match visual indentation
billy\models\payout\plan.py:32:5: E124 closing bracket does not match visual indentation
billy\models\payout\plan.py:52:28: W292 no newline at end of file
billy\models\payout\subscription.py:3:1: F401 'DateTime' imported but unused
billy\models\payout\subscription.py:24:42: E712 comparison to True should be 'if cond is True:' or 'if cond:'
billy\models\payout\subscription.py:47:45: E712 comparison to False should be 'if cond is False:' or 'if not cond:'
billy\models\payout\transaction.py:10:26: E128 continuation line under-indented for visual indent
billy\models\payout\transaction.py:28:5: E303 too many blank lines (2)
billy\models\payout\transaction.py:36:80: E501 line too long (82 > 79 characters)
billy\models\payout\transaction.py:46:27: W292 no newline at end of file
billy\models\processor\balanced.py:14:80: E501 line too long (82 > 79 characters)
billy\models\processor\dummy.py:14:80: E501 line too long (82 > 79 characters)
billy\settings\__init__.py:8:1: F403 'from all import *' used; unable to detect undefined names
billy\settings\__init__.py:10:1: F403 'from debug import *' used; unable to detect undefined names
billy\settings\__init__.py:12:1: F403 'from prod import *' used; unable to detect undefined names
billy\tests\__init__.py:18:80: E501 line too long (85 > 79 characters)
billy\tests\fixtures\__init__.py:3:1: F401 'sample_company' imported but unused
billy\tests\fixtures\__init__.py:4:1: F401 'sample_coupon' imported but unused
billy\tests\fixtures\__init__.py:5:1: F401 'sample_customer' imported but unused
billy\tests\fixtures\__init__.py:6:1: F401 'sample_payout' imported but unused
billy\tests\fixtures\__init__.py:7:1: F401 'sample_plan' imported but unused
billy\tests\fixtures\__init__.py:8:1: W391 blank line at end of file
billy\tests\fixtures\company.py:14:6: W292 no newline at end of file
billy\tests\fixtures\coupon.py:19:6: W292 no newline at end of file
billy\tests\test_api\__init__.py:17:1: E302 expected 2 blank lines, found 1
billy\tests\test_api\__init__.py:63:1: F821 undefined name 'TEST_API_KEYS'
billy\tests\test_api\test_coupon.py:4:5: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_coupon.py:6:80: E501 line too long (88 > 79 characters)
billy\tests\test_api\test_coupon.py:19:80: E501 line too long (83 > 79 characters)
billy\tests\test_api\test_coupon.py:31:5: E303 too many blank lines (2)
billy\tests\test_api\test_coupon.py:79:5: E303 too many blank lines (2)
billy\tests\test_api\test_coupon.py:109:26: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_coupon.py:113:80: E501 line too long (80 > 79 characters)
billy\tests\test_api\test_coupon.py:114:32: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_coupon.py:115:32: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_coupon.py:119:80: E501 line too long (80 > 79 characters)
billy\tests\test_api\test_coupon.py:120:32: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_coupon.py:137:9: E303 too many blank lines (2)
billy\tests\test_api\test_coupon.py:139:26: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_coupon.py:142:80: E501 line too long (80 > 79 characters)
billy\tests\test_api\test_coupon.py:143:32: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_coupon.py:144:32: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_coupon.py:145:63: W292 no newline at end of file
billy\tests\test_api\test_customer.py:4:5: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_customer.py:6:80: E501 line too long (88 > 79 characters)
billy\tests\test_api\test_customer.py:7:29: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_customer.py:19:80: E501 line too long (83 > 79 characters)
billy\tests\test_api\test_customer.py:31:5: E303 too many blank lines (2)
billy\tests\test_api\test_customer.py:79:5: E303 too many blank lines (2)
billy\tests\test_api\test_customer.py:109:26: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_customer.py:113:80: E501 line too long (80 > 79 characters)
billy\tests\test_api\test_customer.py:114:32: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_customer.py:115:32: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_customer.py:119:80: E501 line too long (80 > 79 characters)
billy\tests\test_api\test_customer.py:120:32: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_customer.py:137:9: E303 too many blank lines (2)
billy\tests\test_api\test_customer.py:139:26: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_customer.py:142:80: E501 line too long (80 > 79 characters)
billy\tests\test_api\test_customer.py:143:32: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_customer.py:144:32: E128 continuation line under-indented for visual indent
billy\tests\test_api\test_customer.py:145:63: W292 no newline at end of file
billy\tests\test_models\test_charge_invoice.py:21:80: E501 line too long (82 > 79 characters)
billy\tests\test_models\test_charge_invoice.py:27:5: E303 too many blank lines (2)
billy\tests\test_models\test_charge_invoice.py:51:80: E501 line too long (80 > 79 characters)
billy\tests\test_models\test_charge_invoice.py:65:1: F841 local variable 'all_due_new' is assigned to but never used
billy\tests\test_models\test_charge_invoice.py:74:80: E501 line too long (91 > 79 characters)
billy\tests\test_models\test_charge_invoice.py:75:9: E303 too many blank lines (3)
billy\tests\test_models\test_charge_invoice.py:80:80: E501 line too long (81 > 79 characters)
billy\tests\test_models\test_charge_invoice.py:98:1: W391 blank line at end of file
billy\tests\test_models\test_charge_plan.py:20:34: E261 at least two spaces before inline comment
billy\tests\test_models\test_charge_plan.py:21:35: E261 at least two spaces before inline comment
billy\tests\test_models\test_charge_plan.py:22:30: E261 at least two spaces before inline comment
billy\tests\test_models\test_charge_plan.py:23:43: E261 at least two spaces before inline comment
billy\tests\test_models\test_charge_plan.py:24:42: E261 at least two spaces before inline comment
billy\tests\test_models\test_charge_plan.py:36:1: F841 local variable 'sub' is assigned to but never used
billy\tests\test_models\test_charge_plan.py:47:1: F841 local variable 'sub2' is assigned to but never used
billy\tests\test_models\test_charge_transaction.py:37:79: W292 no newline at end of file
billy\tests\test_models\test_company.py:2:1: F401 'datetime' imported but unused
billy\tests\test_models\test_company.py:14:5: E303 too many blank lines (2)
billy\tests\test_models\test_company.py:17:48: E261 at least two spaces before inline comment
billy\tests\test_models\test_company.py:19:26: E261 at least two spaces before inline comment
billy\tests\test_models\test_company.py:30:9: E303 too many blank lines (2)
billy\tests\test_models\test_company.py:39:9: E303 too many blank lines (2)
billy\tests\test_models\test_company.py:49:1: W391 blank line at end of file
billy\tests\test_models\test_coupon.py:20:5: E303 too many blank lines (2)
billy\tests\test_models\test_coupon.py:27:27: E261 at least two spaces before inline comment
billy\tests\test_models\test_coupon.py:42:9: E303 too many blank lines (3)
billy\tests\test_models\test_coupon.py:52:1: W391 blank line at end of file
billy\tests\test_models\test_interface.py:6:29: E128 continuation line under-indented for visual indent
billy\tests\test_models\test_interface.py:18:9: E303 too many blank lines (2)
billy\tests\test_models\test_interface.py:22:9: E303 too many blank lines (2)
billy\tests\test_models\test_interface.py:26:9: E303 too many blank lines (2)
billy\tests\test_models\test_interface.py:43:9: E303 too many blank lines (2)
billy\utils\__init__.py:1:1: F401 'Intervals' imported but unused
billy\utils\fields.py:21:80: E501 line too long (81 > 79 characters)
billy\utils\fields.py:27:1: E302 expected 2 blank lines, found 1
billy\utils\fields.py:49:1: E303 too many blank lines (3)
billy\utils\fields.py:95:80: E501 line too long (81 > 79 characters)
billy\utils\fields.py:126:1: E302 expected 2 blank lines, found 1
billy\utils\fields.py:143:80: E501 line too long (82 > 79 characters)
billy\utils\fields.py:212:80: E501 line too long (84 > 79 characters)
billy\utils\fields.py:243:1: E302 expected 2 blank lines, found 1
billy\utils\fields.py:252:80: E501 line too long (81 > 79 characters)
billy\utils\intervals.py:6:1: F401 'Field' imported but unused
billy\utils\intervals.py:6:1: F401 'TextField' imported but unused
billy\utils\intervals.py:15:80: E501 line too long (81 > 79 characters)
billy\utils\models.py:10:1: E302 expected 2 blank lines, found 1
billy\utils\models.py:21:1: E303 too many blank lines (3)
billy\utils\models.py:65:1: W391 blank line at end of file
Similar to Refill billing but instead charges accumulate and clear after a certain limit.
Scenario:
-Give user a line of credit (e.g. $10)
-Charge microitems to balance
-Once balance hits line of credit a charge is made and the balance is cleared
Another good solution to microbilling.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.