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

Rewrite #33

Merged
merged 158 commits into from
Sep 5, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
158 commits
Select commit Hold shift + click to select a range
c7eb8c6
Fix #14, add setup.py and python distribute files
fangpenlin Jul 30, 2013
0120ae7
Use a better import style
fangpenlin Jul 31, 2013
7de6f6f
Fix tons of import issue
fangpenlin Aug 1, 2013
d90fd5a
Rollback some API descrpition in readme file
fangpenlin Aug 6, 2013
8403639
Remove unused file
fangpenlin Aug 6, 2013
6a58756
Add new model and tables module
fangpenlin Aug 6, 2013
708b459
Update utile
fangpenlin Aug 6, 2013
ee99d29
Clean up code
fangpenlin Aug 6, 2013
5f002f1
Clean old database code (refectory and add them back later)
fangpenlin Aug 6, 2013
f55b656
Clean requirements up
fangpenlin Aug 18, 2013
915b063
Remove old code
fangpenlin Aug 18, 2013
eeea898
Implement a rough framework to continue work on
fangpenlin Aug 18, 2013
7522759
Add more tests for plan model
fangpenlin Aug 6, 2013
e23462d
Add update plan in plan model, improve test
fangpenlin Aug 7, 2013
9d47dca
Update plan model
fangpenlin Aug 7, 2013
46ea7b6
Improve plan model
fangpenlin Aug 7, 2013
b3972f8
Add HTML test coverage output
fangpenlin Aug 7, 2013
d53479c
Update plan model
fangpenlin Aug 7, 2013
3baba37
Update plan model
fangpenlin Aug 16, 2013
df26b7a
Add missing transaction in test_plan module
fangpenlin Aug 16, 2013
0560c8d
Use freezegun for testing datetime
fangpenlin Aug 16, 2013
4afd87c
Add external id and description fields to plan model
fangpenlin Aug 16, 2013
6241186
Implement plan delete
fangpenlin Aug 16, 2013
e6fa7a7
Add test for get_plan_by_guid
fangpenlin Aug 16, 2013
83581db
Add tests for generic utils
fangpenlin Aug 16, 2013
ab5ae50
Update api key generation test
fangpenlin Aug 16, 2013
7c16a02
Add company model
fangpenlin Aug 18, 2013
c309b7f
Add relationship between plan and company
fangpenlin Aug 16, 2013
d034305
Add customer model
fangpenlin Aug 17, 2013
2c94d9c
Add subscription model and tests
fangpenlin Aug 17, 2013
52e7c63
Add transaction model and tests
fangpenlin Aug 18, 2013
1b00636
Add missing line in transaction test
fangpenlin Aug 18, 2013
e5ae84b
Keep test cases for only one testing purpose
fangpenlin Aug 18, 2013
a25e47e
Ignore testing code from testing coverage
fangpenlin Aug 18, 2013
bd63086
Add started_at and scheduled_at fields to subscription and transactio…
fangpenlin Aug 18, 2013
f19a387
Add date time schedule model and tests
fangpenlin Aug 18, 2013
bd71802
Add yield_transactions for subscription model
fangpenlin Aug 18, 2013
718bf39
Add missing dateutil in requirments
fangpenlin Aug 18, 2013
d910732
Update tests for yield_transactions
fangpenlin Aug 18, 2013
ebb69aa
Add missing __init__.py for test_utils package
fangpenlin Aug 18, 2013
7e0fe02
Add more tests for yield_transactions
fangpenlin Aug 18, 2013
38a7e76
Improve test coverage for schedule model
fangpenlin Aug 18, 2013
60423c6
Add more tests for yield_transactions
fangpenlin Aug 18, 2013
e9e074a
Refactory model naming
fangpenlin Aug 19, 2013
49ea4a1
Format multiline statement style
fangpenlin Aug 19, 2013
e47fdc4
Replace deprecated relation with relationship
fangpenlin Aug 19, 2013
de33cf3
Use Numeric for discount field in subscription table
fangpenlin Aug 19, 2013
a20a7de
Fix test failure caused by decimal discount
fangpenlin Aug 19, 2013
bc1fedb
Add interval argument support to schedule model
fangpenlin Aug 19, 2013
0cd38ef
Add schedule with interval in data model
fangpenlin Aug 19, 2013
dfdefa3
Improve testing coverage for schedule model
fangpenlin Aug 19, 2013
8edd123
Remove unused import
fangpenlin Aug 19, 2013
b72d835
Add test for yield_transactions
fangpenlin Aug 19, 2013
5d2a870
Implement cancel subscription with prorated refund
fangpenlin Aug 19, 2013
7eccb8d
Add tests for subscription and transaction model
fangpenlin Aug 19, 2013
1315a1b
Update documents of subscription model
fangpenlin Aug 19, 2013
8f49326
Add tests for subscription discount
fangpenlin Aug 19, 2013
c68b7d9
Fix #35, round down money to cent
fangpenlin Aug 20, 2013
3692472
Adjust model, move payment_uri from customer to subscription
fangpenlin Aug 20, 2013
dfbfd0e
Replace discount field with amount in subscription
fangpenlin Aug 20, 2013
edb445c
Add processor and tests
fangpenlin Aug 20, 2013
185edb1
Add tests for balanced processor charge method
fangpenlin Aug 20, 2013
bf25347
Add test for debit already created case of balanced processor
fangpenlin Aug 20, 2013
5041d4c
Add more tests for balanced processor
fangpenlin Aug 20, 2013
d604db6
Fix balanced processor bug
fangpenlin Aug 21, 2013
b00aeff
Add tests for prepare customer of balanced processor
fangpenlin Aug 21, 2013
c703e43
Add error handling for transaction processing
fangpenlin Aug 21, 2013
760be7c
Use unicode for UUID generation
fangpenlin Aug 21, 2013
959d31f
Add description for transaction in balanced
fangpenlin Aug 21, 2013
2190e77
Add tests for process_transactions of transaction model
fangpenlin Aug 21, 2013
a163b0a
Add functional tests framework for API
fangpenlin Aug 22, 2013
b5f237b
Add more tests for company API
fangpenlin Aug 22, 2013
80e590d
Add customer tests
fangpenlin Aug 22, 2013
c0cbf68
Move test requirements to a file from setup.py
fangpenlin Aug 22, 2013
410d704
Add get_by_api_key to company model
fangpenlin Aug 22, 2013
39c0ae5
Add api key check
fangpenlin Aug 22, 2013
9a199e8
Add missing unicode_literals
fangpenlin Aug 22, 2013
c3c3fbd
Add plan API and tests
fangpenlin Aug 22, 2013
9808edb
Add tests for cross company resource access
fangpenlin Aug 22, 2013
ff78157
Add test for getting non-exist plan
fangpenlin Aug 22, 2013
63c69cb
Add company guid in customer and plan API response
fangpenlin Aug 22, 2013
2063a8f
Add subscription API
fangpenlin Aug 22, 2013
fe2744b
Add missing __init__.py for tests package
fangpenlin Aug 22, 2013
3f52533
Add more tests for subscription
fangpenlin Aug 22, 2013
e9b6dc3
Add more tests for subscription
fangpenlin Aug 22, 2013
0ba9b97
Update subscription tests
fangpenlin Aug 22, 2013
4282871
Fix a typo
fangpenlin Aug 22, 2013
4a7239c
Add transaction API
fangpenlin Aug 22, 2013
feec384
Fix a transaction test issue
fangpenlin Aug 22, 2013
3828abc
Add tests for initializedb, and YEAH, 100% test coverage!
fangpenlin Aug 22, 2013
289e65f
Add process transactions script
fangpenlin Aug 23, 2013
960e675
Add balanced API configure and logging for processor
fangpenlin Aug 23, 2013
7bca849
Add some logging message for transaction model
fangpenlin Aug 23, 2013
d5d5a23
Adjust SQLAlchemy logging level to WARN
fangpenlin Aug 23, 2013
ad091f9
Fix balanced API key configuration bug
fangpenlin Aug 23, 2013
0eb2a21
Add basic authentication parsing
fangpenlin Aug 23, 2013
5c30dc3
Add missing unicode_literals
fangpenlin Aug 23, 2013
276b67e
Add logging for subscription model
fangpenlin Aug 23, 2013
a0d8058
Yield transactions in process transactions script
fangpenlin Aug 23, 2013
5af9d22
Fix some logging issue
fangpenlin Aug 23, 2013
4587e7b
Fix created record has different updated_at and created_at bug
fangpenlin Aug 24, 2013
56b60ff
Add list_by_company_guid to transaction model
fangpenlin Aug 24, 2013
e5415ad
Add transaction list in API
fangpenlin Aug 24, 2013
772f74d
Add more log messages in transaction model
fangpenlin Aug 24, 2013
da90b7e
Add yielding transactions from specific subscriptions
fangpenlin Aug 24, 2013
3d42dfd
Use base64 encode instead string.encode('base64') in test_auth.py (av…
fangpenlin Aug 24, 2013
54d0eaa
Add simple integration tests
fangpenlin Aug 24, 2013
2e3fd45
Exclude integration tests from default tests
fangpenlin Aug 24, 2013
ecbcfd4
Add a test for a process_transactions bug
fangpenlin Aug 25, 2013
65a5fbf
Fix duplicate processing to processor issue
fangpenlin Aug 25, 2013
fae1546
Fix some typos
fangpenlin Aug 25, 2013
b36d186
Improve list_by_company_guid of transaction model
fangpenlin Aug 26, 2013
22720f9
Add list transactions by subscription to transaction model
fangpenlin Aug 26, 2013
fd968c1
Yield transactions right after subscription is created
fangpenlin Aug 26, 2013
5064d00
Make processor configurable
fangpenlin Aug 26, 2013
85365ee
Add started at support for creating subscription
fangpenlin Aug 26, 2013
cac87cd
Remove unused code (add them back is they are required)
fangpenlin Aug 26, 2013
f93b770
Add time zone convert for started_at parameter for subscription view
fangpenlin Aug 26, 2013
84f8190
Add input validation for subscription view
fangpenlin Aug 26, 2013
d95c80b
Add record existing validation for subscription view
fangpenlin Aug 26, 2013
a5b8e05
Remove unused import
fangpenlin Aug 26, 2013
c14681e
Add validation for all creating views
fangpenlin Aug 26, 2013
89705b7
Update read me
fangpenlin Aug 26, 2013
2b8a2dc
Update README.md
fangpenlin Aug 26, 2013
bf9b7d8
Cover a special case of plan creating view
fangpenlin Aug 26, 2013
99cebcc
Make sure started_at of subscription model in past is not allowed
fangpenlin Aug 26, 2013
aec3308
Make suer started_at in past cannot pass API param validation
fangpenlin Aug 26, 2013
332dc60
Fix test imports cause error for pyramid scanning issue
fangpenlin Aug 27, 2013
9614f40
Make database URL for unit testing be configurable
fangpenlin Aug 27, 2013
973dcec
Fix some major errors of unit tests with PostgreSQL
fangpenlin Aug 27, 2013
d07e75b
Make functional testing database configurable
fangpenlin Aug 27, 2013
8316951
Fix a test failure of subscription model
fangpenlin Aug 27, 2013
7e2a049
Fix a trandaction model test failure
fangpenlin Aug 27, 2013
bab0811
Fix a transaction view test failure
fangpenlin Aug 27, 2013
270da99
Add Chef submodule
fangpenlin Aug 27, 2013
f7f2c0d
Add vagrant file
fangpenlin Aug 27, 2013
4628c87
Add password for postgresql database
fangpenlin Aug 27, 2013
9fbbed1
Update vagrant file to solve a deploying issue caused by old Chef ver…
fangpenlin Aug 29, 2013
d47e3b7
Fix balanced processor API key is not configured and return id rather…
fangpenlin Aug 29, 2013
60ff67a
Process transaction right away when subscription is created
fangpenlin Aug 29, 2013
5abdb2b
Clear race condition concern in balanced payment processor
fangpenlin Sep 3, 2013
004d5f1
Add missing payment URI for subscription API
fangpenlin Sep 3, 2013
d9eb88a
Create card for basic integration test scenario
fangpenlin Sep 3, 2013
aabdf28
Add transaction check in basic integration testing scenario
fangpenlin Sep 3, 2013
11b6b93
Add chef submodule folder
fangpenlin Sep 3, 2013
e38e826
Fix SQLAlchemy Unicode warning
fangpenlin Sep 3, 2013
50073b3
Change meta tag naming of Balanced resource object
fangpenlin Sep 5, 2013
d134d1e
Make sure only charge transaction can be refunded
fangpenlin Sep 5, 2013
c505294
Implement refund of processor
fangpenlin Sep 5, 2013
9563d79
Add test for refund of processor
fangpenlin Sep 5, 2013
b12cfac
Fix prorated refund rate bug (the refund rate was inverted)
fangpenlin Sep 5, 2013
72c3823
Fix subscription zero prorated refund test failure
fangpenlin Sep 5, 2013
e6d3dda
Implement subscription cancel
fangpenlin Sep 5, 2013
da53f7d
Add test for making sure cancel subscription API will not be accessed…
fangpenlin Sep 5, 2013
b0d22c2
Remove unnecessary type check in process_one of transaction model
fangpenlin Sep 5, 2013
b1913f5
Add refund_amount argument to cancel method of subscription model
fangpenlin Sep 5, 2013
b3e9d31
Implement cancel subscription with refund_amount
fangpenlin Sep 5, 2013
8a9237e
Mark not finished transactions as canceled when canceling a subscription
fangpenlin Sep 5, 2013
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[run]
omit=
billy/tests/*
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ develop-eggs
.installed.cfg
lib
lib64
cover

# Installer logs
pip-log.txt
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "chef"]
path = chef
url = https://github.com/victorlin/billy-chef.git
2 changes: 1 addition & 1 deletion MAINFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
include distribute_setup.py
recursive-include billy
prune env
prune env
95 changes: 75 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,87 @@ Billy - The Open Source Recurring Billing System, powered by Balanced

## Running It

There are three major parts to billy: the models, the api, and the web layer.
This library currently has the API and the models.
To run billy (development mode), you need to install the package first.
As we don't want to mess the global Python environment, you should
create a virtual environmnet first and switch to it

1. Create a pgsql DB called 'billy' with 'test' user and no password
2. Install requirements ```python setup.py develop```
3. Create the tables: ```python manage.py create_tables```
4. To run the api server run: ```python manage.py run_api```
5. Cron job this hourly: ```python manage.py billy_tasks```
```
virtualenv --no-site-packages env
source env/bin/activate
```

Congrats. You've got recurring billing.
If above works correctly, you should see

## Models
```
(env) $
```

The models should have all the methods necessary to bill recuringly. Generally,
modifactions to the underlying data should be done via the model methods not,
directly. This is to ensure that all the accounting is handled correctly.
in you command line tool. The `(env)` indicates that you are currently
in the virtual Python environment. Then you need to install the billy project.
Here you run

To see how they work check out the interface tests
(tests/models/test_interface.py)
```
python setup.py develop
```

## Api
This should install all required dependencies. Then you need to create
tables in database, here you type

Check out the spec at api/spec.json, which is generated using api/spec.py
```
initialize_billy_db development.ini
```

This should create all necessary tables for you in a default SQLite database.

#### Major Todos:
- Redo import strucutre
- Redo commit/flush/rollback handling.
- Better transient exception handling.
Then, to run the API web server, here you type

```
pserve development.ini --reload
```

To process recurring transactions, here you can type

```
process_billy_tx development.ini
```

You can setup a crontab job to run the process_billy_tx periodically.

## Running Unit and Functional Tests

To run tests, after installing billy project and all dependencies, you need
to install dependencies for testing, here you type:

```
pip install -r test_requirements.txt
```

And to run the tests, here you type

```
python setup.py nosetests
```

or, if you prefer run specific tests, you can run

```
nosetests billy/tests/functional
```

## Running Integration Tests

To run integration tests, here you type

```
nosetests billy/tests/integration
```

The default testing target URL is `http://127.0.0.1:6543`, to modify it, you can
set environment variable `BILLY_TEST_URL`. To change balanced API key, you can set
`BILLY_TEST_PROCESSOR_KEY` variable. For example

```
export BILLY_TEST_URL=http://example-billy-api.com
export BILLY_TEST_PROCESSOR_KEY=MY_SECRET_KEY_HERE
nosetests billy/tests/integration
```
121 changes: 121 additions & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# All Vagrant configuration is done here. The most common configuration
# options are documented and commented below. For a complete reference,
# please see the online documentation at vagrantup.com.

# Every Vagrant virtual environment requires a box to build off of.
config.vm.box = "precise64"

# The url from where the 'config.vm.box' box will be fetched if it
# doesn't already exist on the user's system.
config.vm.box_url = "http://files.vagrantup.com/precise64.box"

# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
# config.vm.network :forwarded_port, guest: 80, host: 8080

# Create a private network, which allows host-only access to the machine
# using a specific IP.
# config.vm.network :private_network, ip: "192.168.33.10"

# Create a public network, which generally matched to bridged network.
# Bridged networks make the machine appear as another physical device on
# your network.
# config.vm.network :public_network

# If true, then any SSH connections made will enable agent forwarding.
# Default value: false
# config.ssh.forward_agent = true

# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
# config.vm.synced_folder "../data", "/vagrant_data"

# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
# config.vm.provider :virtualbox do |vb|
# # Don't boot with headless mode
# vb.gui = true
#
# # Use VBoxManage to customize the VM. For example to change memory:
# vb.customize ["modifyvm", :id, "--memory", "1024"]
# end
#
# View the documentation for the provider you're using for more
# information on available options.

# Enable provisioning with Puppet stand alone. Puppet manifests
# are contained in a directory path relative to this Vagrantfile.
# You will need to create the manifests directory and a manifest in
# the file precise64.pp in the manifests_path directory.
#
# An example Puppet manifest to provision the message of the day:
#
# # group { "puppet":
# # ensure => "present",
# # }
# #
# # File { owner => 0, group => 0, mode => 0644 }
# #
# # file { '/etc/motd':
# # content => "Welcome to your Vagrant-built virtual machine!
# # Managed by Puppet.\n"
# # }
#
# config.vm.provision :puppet do |puppet|
# puppet.manifests_path = "manifests"
# puppet.manifest_file = "init.pp"
# end

# Enable provisioning with chef solo, specifying a cookbooks path, roles
# path, and data_bags path (all relative to this Vagrantfile), and adding
# some recipes and/or roles.
#
config.vm.provision :shell, :inline => "gem install chef --version 11.6.0 --no-rdoc --no-ri --conservative"
config.vm.provision :chef_solo do |chef|
chef.cookbooks_path = "chef/cookbooks"
chef.add_recipe "billy"

chef.json = {
:postgresql => {
:password => {
:postgres => "billie jean"
}
}
}
end

# Enable provisioning with chef server, specifying the chef server URL,
# and the path to the validation key (relative to this Vagrantfile).
#
# The Opscode Platform uses HTTPS. Substitute your organization for
# ORGNAME in the URL and validation key.
#
# If you have your own Chef Server, use the appropriate URL, which may be
# HTTP instead of HTTPS depending on your configuration. Also change the
# validation key to validation.pem.
#
# config.vm.provision :chef_client do |chef|
# chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME"
# chef.validation_key_path = "ORGNAME-validator.pem"
# end
#
# If you're using the Opscode platform, your validator client is
# ORGNAME-validator, replacing ORGNAME with your organization name.
#
# If you have your own Chef Server, the default validation client name is
# chef-validator, unless you changed the configuration.
#
# chef.validation_client_name = "ORGNAME-validator"
end
27 changes: 27 additions & 0 deletions billy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from __future__ import unicode_literals

from pyramid.config import Configurator

from billy.models import setup_database
from billy.request import APIRequest


def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.

"""
# setup database
settings = setup_database(global_config, **settings)
config = Configurator(
settings=settings,
request_factory=APIRequest,
)
# add basic authentication parsing
config.add_tween('billy.api.auth.basic_auth_tween_factory')
# provides table entity to json renderers
config.include('.renderers')
# provides api views
config.include('.api')

config.scan(ignore=b'billy.tests')
return config.make_wsgi_app()
60 changes: 6 additions & 54 deletions billy/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,57 +1,9 @@
from __future__ import unicode_literals
import re

import difflib
from flask import request
from flask.ext.restful import Api
from flask.ext.restful.utils import unauthorized, error_data
from flask.signals import got_request_exception
from werkzeug.http import HTTP_STATUS_CODES
from werkzeug.exceptions import HTTPException


class ApiFixed(Api):

def handle_error(self, e):
"""Error handler for the API transforms a raised exception into a Flask
response, with the appropriate HTTP status code and body.

:param e: the raised Exception object
:type e: Exception

"""
got_request_exception.send(self.app, exception=e)
if isinstance(e, HTTPException):
return e
code = getattr(e, 'code', 500)
data = getattr(e, 'data', error_data(code))

if code >= 500:
self.app.logger.exception("Internal Error")

if code == 404 and ('message' not in data or
data['message'] == HTTP_STATUS_CODES[404]):
rules = dict([(re.sub('(<.*>)', '', rule.rule), rule.rule)
for rule in self.app.url_map.iter_rules()])
close_matches = difflib.get_close_matches(
request.path, rules.keys())
if close_matches:
# If we already have a message, add punctuation and
# continue it.
if "message" in data:
data["message"] += ". "
else:
data["message"] = ""

data['message'] += 'You have requested this URI [' + request.path + \
'] but did you mean ' + \
' or '.join((rules[match]
for match in close_matches)) + ' ?'

resp = self.make_response(data, code)

if code == 401:
resp = unauthorized(resp,
self.app.config.get("HTTP_BASIC_AUTH_REALM", "flask-restful"))

return resp
def includeme(config):
config.include('.company', route_prefix='/v1')
config.include('.customer', route_prefix='/v1')
config.include('.plan', route_prefix='/v1')
config.include('.subscription', route_prefix='/v1')
config.include('.transaction', route_prefix='/v1')
15 changes: 0 additions & 15 deletions billy/api/app.py

This file was deleted.

Loading