Add support for custom fields in billing invoices
Customers (especially in Europe) need the ability to add Tax IDs, VAT IDs, and other custom fields to their invoices. Fixes #106
This commit is contained in:
parent
683d5080d8
commit
e7fa560787
9 changed files with 426 additions and 8 deletions
|
@ -13,6 +13,8 @@ from data import model
|
|||
from data.billing import PLANS
|
||||
|
||||
import features
|
||||
import uuid
|
||||
import json
|
||||
|
||||
def carderror_response(e):
|
||||
return {'carderror': e.message}, 402
|
||||
|
@ -96,6 +98,48 @@ def get_invoices(customer_id):
|
|||
}
|
||||
|
||||
|
||||
def get_invoice_fields(user):
|
||||
try:
|
||||
cus = billing.Customer.retrieve(user.stripe_id)
|
||||
except stripe.APIConnectionError:
|
||||
abort(503, message='Cannot contact Stripe')
|
||||
|
||||
if not 'metadata' in cus:
|
||||
cus.metadata = {}
|
||||
|
||||
return json.loads(cus.metadata.get('invoice_fields') or '[]'), cus
|
||||
|
||||
|
||||
def create_billing_invoice_field(user, title, value):
|
||||
new_field = {
|
||||
'uuid': str(uuid.uuid4()).split('-')[0],
|
||||
'title': title,
|
||||
'value': value
|
||||
}
|
||||
|
||||
invoice_fields, cus = get_invoice_fields(user)
|
||||
invoice_fields.append(new_field)
|
||||
|
||||
if not 'metadata' in cus:
|
||||
cus.metadata = {}
|
||||
|
||||
cus.metadata['invoice_fields'] = json.dumps(invoice_fields)
|
||||
cus.save()
|
||||
return new_field
|
||||
|
||||
|
||||
def delete_billing_invoice_field(user, field_uuid):
|
||||
invoice_fields, cus = get_invoice_fields(user)
|
||||
invoice_fields = [field for field in invoice_fields if not field['uuid'] == field_uuid]
|
||||
|
||||
if not 'metadata' in cus:
|
||||
cus.metadata = {}
|
||||
|
||||
cus.metadata['invoice_fields'] = json.dumps(invoice_fields)
|
||||
cus.save()
|
||||
return True
|
||||
|
||||
|
||||
@resource('/v1/plans/')
|
||||
@show_if(features.BILLING)
|
||||
class ListPlans(ApiResource):
|
||||
|
@ -367,3 +411,159 @@ class OrgnaizationInvoiceList(ApiResource):
|
|||
return get_invoices(organization.stripe_id)
|
||||
|
||||
raise Unauthorized()
|
||||
|
||||
|
||||
@resource('/v1/user/invoice/fields')
|
||||
@internal_only
|
||||
@show_if(features.BILLING)
|
||||
class UserInvoiceFieldList(ApiResource):
|
||||
""" Resource for listing and creating a user's custom invoice fields. """
|
||||
schemas = {
|
||||
'InvoiceField': {
|
||||
'id': 'InvoiceField',
|
||||
'type': 'object',
|
||||
'description': 'Description of an invoice field',
|
||||
'required': [
|
||||
'title', 'value'
|
||||
],
|
||||
'properties': {
|
||||
'title': {
|
||||
'type': 'string',
|
||||
'description': 'The title of the field being added',
|
||||
},
|
||||
'value': {
|
||||
'type': 'string',
|
||||
'description': 'The value of the field being added',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@require_user_admin
|
||||
@nickname('listUserInvoiceFields')
|
||||
def get(self):
|
||||
""" List the invoice fields for the current user. """
|
||||
user = get_authenticated_user()
|
||||
if not user.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
return {'fields': get_invoice_fields(user)[0]}
|
||||
|
||||
@require_user_admin
|
||||
@nickname('createUserInvoiceField')
|
||||
@validate_json_request('InvoiceField')
|
||||
def post(self):
|
||||
""" Creates a new invoice field. """
|
||||
user = get_authenticated_user()
|
||||
if not user.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
data = request.get_json()
|
||||
created_field = create_billing_invoice_field(user, data['title'], data['value'])
|
||||
return created_field
|
||||
|
||||
|
||||
@resource('/v1/user/invoice/field/<field_uuid>')
|
||||
@internal_only
|
||||
@show_if(features.BILLING)
|
||||
class UserInvoiceField(ApiResource):
|
||||
""" Resource for deleting a user's custom invoice fields. """
|
||||
@require_user_admin
|
||||
@nickname('deleteUserInvoiceField')
|
||||
def delete(self, field_uuid):
|
||||
""" Deletes the invoice field for the current user. """
|
||||
user = get_authenticated_user()
|
||||
if not user.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
result = delete_billing_invoice_field(user, field_uuid)
|
||||
if not result:
|
||||
abort(404)
|
||||
|
||||
return 'Okay', 201
|
||||
|
||||
|
||||
@resource('/v1/organization/<orgname>/invoice/fields')
|
||||
@path_param('orgname', 'The name of the organization')
|
||||
@related_user_resource(UserInvoiceFieldList)
|
||||
@internal_only
|
||||
@show_if(features.BILLING)
|
||||
class OrganizationInvoiceFieldList(ApiResource):
|
||||
""" Resource for listing and creating an organization's custom invoice fields. """
|
||||
schemas = {
|
||||
'InvoiceField': {
|
||||
'id': 'InvoiceField',
|
||||
'type': 'object',
|
||||
'description': 'Description of an invoice field',
|
||||
'required': [
|
||||
'title', 'value'
|
||||
],
|
||||
'properties': {
|
||||
'title': {
|
||||
'type': 'string',
|
||||
'description': 'The title of the field being added',
|
||||
},
|
||||
'value': {
|
||||
'type': 'string',
|
||||
'description': 'The value of the field being added',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@require_scope(scopes.ORG_ADMIN)
|
||||
@nickname('listOrgInvoiceFields')
|
||||
def get(self, orgname):
|
||||
""" List the invoice fields for the organization. """
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
organization = model.get_organization(orgname)
|
||||
if not organization.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
return {'fields': get_invoice_fields(organization)[0]}
|
||||
|
||||
abort(403)
|
||||
|
||||
@require_scope(scopes.ORG_ADMIN)
|
||||
@nickname('createOrgInvoiceField')
|
||||
@validate_json_request('InvoiceField')
|
||||
def post(self, orgname):
|
||||
""" Creates a new invoice field. """
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
organization = model.get_organization(orgname)
|
||||
if not organization.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
data = request.get_json()
|
||||
created_field = create_billing_invoice_field(organization, data['title'], data['value'])
|
||||
return created_field
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@resource('/v1/organization/<orgname>/invoice/field/<field_uuid>')
|
||||
@path_param('orgname', 'The name of the organization')
|
||||
@related_user_resource(UserInvoiceField)
|
||||
@internal_only
|
||||
@show_if(features.BILLING)
|
||||
class OrganizationInvoiceField(ApiResource):
|
||||
""" Resource for deleting an organization's custom invoice fields. """
|
||||
@require_scope(scopes.ORG_ADMIN)
|
||||
@nickname('deleteOrgInvoiceField')
|
||||
def delete(self, orgname, field_uuid):
|
||||
""" Deletes the invoice field for the current user. """
|
||||
permission = AdministerOrganizationPermission(orgname)
|
||||
if permission.can():
|
||||
organization = model.get_organization(orgname)
|
||||
if not organization.stripe_id:
|
||||
raise NotFound()
|
||||
|
||||
result = delete_billing_invoice_field(organization, field_uuid)
|
||||
if not result:
|
||||
abort(404)
|
||||
|
||||
return 'Okay', 201
|
||||
|
||||
abort(403)
|
||||
|
|
Reference in a new issue