Skip to content

Commit

Permalink
Merge pull request #17 from bisurance/recurring
Browse files Browse the repository at this point in the history
 Updated API for recurring interface (Customer, Mandate, Subscription)
  • Loading branch information
Thijs-Riezebeek committed Jan 5, 2017
2 parents 72ee7f6 + 60d5c08 commit 3662d1b
Show file tree
Hide file tree
Showing 14 changed files with 233 additions and 53 deletions.
61 changes: 35 additions & 26 deletions Mollie/API/Client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,54 @@
from .Error import *


class Client:
class Client(object):
CLIENT_VERSION = '1.1.4'
HTTP_GET = 'GET'
HTTP_POST = 'POST'
HTTP_DELETE = 'DELETE'
API_ENDPOINT = 'https://api.mollie.nl'
API_VERSION = 'v1'
HTTP_GET = 'GET'
HTTP_POST = 'POST'
HTTP_DELETE = 'DELETE'
API_ENDPOINT = 'https://api.mollie.nl'
API_VERSION = 'v1'
UNAME = ' '.join(platform.uname())
USER_AGENT = ' '.join(vs.replace(r'\s+', '-') for vs in [
'Mollie/' + CLIENT_VERSION,
'Python/' + sys.version.split(' ')[0],
'OpenSSL/' + ssl.OPENSSL_VERSION.split(' ')[1],
])

def __init__(self):
@staticmethod
def validateApiEndpoint(api_endpoint):
return api_endpoint.strip().rstrip('/')

@staticmethod
def validateApiKey(api_key):
api_key = api_key.strip()
if not re.compile(r'^(live|test)_\w+$').match(api_key):
raise Error('Invalid API key: "%s". An API key must start with "test_" or "live_".' % api_key)
return api_key

def __init__(self, api_key=None, api_endpoint=None):
from . import Resource

self.api_endpoint = self.API_ENDPOINT
self.api_endpoint = self.validateApiEndpoint(api_endpoint or self.API_ENDPOINT)
self.api_version = self.API_VERSION
self.api_key = ''
self.api_key = self.validateApiKey(api_key) if api_key else None
self.payments = Resource.Payments(self)
self.payment_refunds = Resource.Refunds(self)
self.issuers = Resource.Issuers(self)
self.methods = Resource.Methods(self)
self.version_strings = []
self.addVersionString('Mollie/' + self.CLIENT_VERSION)
self.addVersionString('Python/' + sys.version.split(' ')[0])
self.addVersionString('OpenSSL/' + ssl.OPENSSL_VERSION.split(' ')[1])
self.customers = Resource.Customers(self)
self.customer_mandates = Resource.Mandates(self)
self.customer_subscriptions = Resource.Subscriptions(self)
self.customer_payments = Resource.CustomerPayments(self)

def getApiEndpoint(self):
return self.api_endpoint

def setApiEndpoint(self, api_endpoint):
self.api_endpoint = api_endpoint.strip().rstrip('/')
self.api_endpoint = self.validateApiEndpoint(api_endpoint)

def setApiKey(self, api_key):
api_key = api_key.strip()
if not re.compile('^(live|test)_\w+$').match(api_key):
raise Error('Invalid API key: "%s". An API key must start with "test_" or "live_".' % api_key)
self.api_key = api_key

def addVersionString(self, version_string):
self.version_strings.append(version_string.replace(r'\s+', '-'))
self.api_key = self.validateApiKey(api_key)

def getCACert(self):
cacert = pkg_resources.resource_filename('Mollie.API', 'cacert.pem')
Expand All @@ -56,18 +67,16 @@ def getCACert(self):
def performHttpCall(self, http_method, path, data=None, params=None):
if not self.api_key:
raise Error('You have not set an API key. Please use setApiKey() to set the API key.')
url = self.api_endpoint + '/' + self.api_version + '/' + path
user_agent = ' '.join(self.version_strings)
uname = ' '.join(platform.uname())
url = '%s/%s/%s' % (self.api_endpoint, self.api_version, path)
try:
response = requests.request(
http_method, url,
verify=self.getCACert(),
headers={
'Accept': 'application/json',
'Authorization': 'Bearer ' + self.api_key,
'User-Agent': user_agent,
'X-Mollie-Client-Info': uname
'User-Agent': self.USER_AGENT,
'X-Mollie-Client-Info': self.UNAME,
},
params=params,
data=data
Expand Down
4 changes: 2 additions & 2 deletions Mollie/API/Error.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Error(Exception):
def __init__(self, message=None, field=None):
self.message = message
self.field = field
Exception.__init__(self, message)
self.field = field
5 changes: 5 additions & 0 deletions Mollie/API/Object/Customer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .Base import *


class Customer(Base):
pass
16 changes: 16 additions & 0 deletions Mollie/API/Object/Mandate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from .Base import *


class Mandate(Base):
STATUS_PENDING = 'pending'
STATUS_VALID = 'valid'
STATUS_INVALID = 'invalid'

def isPending(self):
return self['status'] == self.STATUS_PENDING

def isValid(self):
return self['status'] == self.STATUS_VALID

def isInvalid(self):
return self['status'] == self.STATUS_INVALID
38 changes: 29 additions & 9 deletions Mollie/API/Object/Payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,42 @@


class Payment(Base):
STATUS_OPEN = 'open'
STATUS_PENDING = 'pending'
STATUS_CANCELLED = 'cancelled'
STATUS_EXPIRED = 'expired'
STATUS_PAID = 'paid'
STATUS_PAIDOUT = 'paidout'
STATUS_REFUNDED = 'refunded'
STATUS_OPEN = 'open'
STATUS_PENDING = 'pending'
STATUS_CANCELLED = 'cancelled'
STATUS_EXPIRED = 'expired'
STATUS_PAID = 'paid'
STATUS_PAIDOUT = 'paidout'
STATUS_REFUNDED = 'refunded'
STATUS_FAILED = 'failed'
STATUS_CHARGED_BACK = 'charged_back'

def isOpen(self):
return self['status'] == self.STATUS_OPEN

def isPending(self):
return self['status'] == self.STATUS_PENDING

def isCancelled(self):
return self['status'] == self.STATUS_CANCELLED

def isExpired(self):
return self['status'] == self.STATUS_EXPIRED

def isPaid(self):
return 'paidDatetime' in self and self['paidDatetime']
return 'paidDatetime' in self and len(self['paidDatetime']) > 0

def isPaidout(self):
return self['status'] == self.STATUS_PAIDOUT

def isRefunded(self):
return self['status'] == self.STATUS_REFUNDED

def isFailed(self):
return self['status'] == self.STATUS_FAILED

def isChargedBack(self):
return self['status'] == self.STATUS_CHARGED_BACK

def getPaymentUrl(self):
if 'links' not in self:
Expand All @@ -26,4 +46,4 @@ def getPaymentUrl(self):


class Refund(Base):
pass
pass
24 changes: 24 additions & 0 deletions Mollie/API/Object/Subscription.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from .Base import *


class Subscription(Base):
STATUS_ACTIVE = 'active'
STATUS_PENDING = 'pending' # Waiting for a valid mandate.
STATUS_CANCELLED = 'cancelled'
STATUS_SUSPENDED = 'suspended' # Active, but mandate became invalid.
STATUS_COMPLETED = 'completed'

def isActive(self):
return self['status'] == self.STATUS_ACTIVE

def isPending(self):
return self['status'] == self.STATUS_PENDING

def isCancelled(self):
return self['status'] == self.STATUS_CANCELLED

def isSuspended(self):
return self['status'] == self.STATUS_SUSPENDED

def isCompleted(self):
return self['status'] == self.STATUS_COMPLETED
5 changes: 4 additions & 1 deletion Mollie/API/Object/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
from .Payment import *
from .Issuer import *
from .Method import *
from .List import *
from .List import *
from .Customer import *
from .Mandate import *
from .Subscription import *
15 changes: 6 additions & 9 deletions Mollie/API/Resource/Base.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ def create(self, data):
raise Error('Error encoding parameters into JSON: "%s"' % e.message)
return self.rest_create(data)

def get(self, resource_id):
return self.rest_read(resource_id)
def get(self, resource_id, **params):
return self.rest_read(resource_id, params)

def update(self, resource_id, data):
try:
Expand All @@ -66,18 +66,15 @@ def update(self, resource_id, data):
def delete(self, resource_id):
return self.rest_delete(resource_id)

def all(self, offset=0, count=DEFAULT_LIMIT):
return self.rest_list({
'offset': offset,
'count': count
})
def all(self, **params):
return self.rest_list(params)

def performApiCall(self, http_method, path, data=None, params=None):
body = self.client.performHttpCall(http_method, path, data, params)
try:
result = body.json()
result = body.json() if body.status_code != 204 else {}
except Exception as e:
raise Error('Unable to decode Mollie response: "%s".' % body)
raise Error('Unable to decode Mollie response (status code %d): "%s".' % (body.status_code, body.text))
if 'error' in result:
error = Error('Error executing API call (%s): %s.' % (result['error']['type'], result['error']['message']))
if 'field' in result['error']:
Expand Down
15 changes: 15 additions & 0 deletions Mollie/API/Resource/CustomerPayments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from Mollie.API.Resource import Payments


class CustomerPayments(Payments):
customer_id = None

def getResourceName(self):
return 'customers/%s/payments' % self.customer_id

def withParentId(self, customer_id):
self.customer_id = customer_id
return self

def on(self, customer):
return self.withParentId(customer['id'])
26 changes: 26 additions & 0 deletions Mollie/API/Resource/Customers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from .Base import *
from Mollie.API.Error import *
from Mollie.API.Object import Customer


class Customers(Base):
RESOURCE_ID_PREFIX = 'cst_'

def getResourceObject(self, result):
return Customer(result)

def get(self, customer_id):
if not customer_id or not customer_id.startswith(self.RESOURCE_ID_PREFIX):
raise Error(
'Invalid customer ID: "%s". A customer ID should start with "%s".' % (customer_id, self.RESOURCE_ID_PREFIX)
)
return super(Customers, self).get(customer_id)

def mandates(self, customer):
return self.client.customer_mandates.on(customer)

def subscriptions(self, customer):
return self.client.customer_subscriptions.on(customer)

def payments(self, customer):
return self.client.customer_payments.on(customer)
28 changes: 28 additions & 0 deletions Mollie/API/Resource/Mandates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from .Base import *
from Mollie.API.Error import *
from Mollie.API.Object import Mandate


class Mandates(Base):
RESOURCE_ID_PREFIX = 'mdt_'
customer_id = None

def getResourceObject(self, result):
return Mandate(result)

def get(self, mandate_id):
if not mandate_id or not mandate_id.startswith(self.RESOURCE_ID_PREFIX):
raise Error(
'Invalid mandate ID: "%s". A mandate ID should start with "%s".' % (mandate_id, self.RESOURCE_ID_PREFIX)
)
return super(Mandates, self).get(mandate_id)

def getResourceName(self):
return 'customers/%s/mandates' % self.customer_id

def withParentId(self, customer_id):
self.customer_id = customer_id
return self

def on(self, customer):
return self.withParentId(customer['id'])
17 changes: 11 additions & 6 deletions Mollie/API/Resource/Payments.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ def getResourceObject(self, result):
return Payment(result)

def get(self, payment_id):
if not payment_id or self.RESOURCE_ID_PREFIX not in payment_id:
if not payment_id or not payment_id.startswith(self.RESOURCE_ID_PREFIX):
raise Error(
'Invalid payment ID: "%s". A payment ID should start with "%s".' % (payment_id, self.RESOURCE_ID_PREFIX)
)
return super(Payments, self).get(payment_id)

def refund(self, payment):
return self.client.payment_refunds.on(payment).create()
def refunds(self, payment):
return self.client.payment_refunds.on(payment)

def refund(self, payment, data=None):
return self.refunds(payment).create(data)

class Refunds(Base):
payment_id = None
Expand All @@ -27,8 +29,11 @@ def getResourceObject(self, result):
return Refund(result)

def getResourceName(self):
return 'payments/%i/refunds' % self.payment_id
return 'payments/%s/refunds' % self.payment_id

def withParentId(self, payment_id):
self.payment_id = payment_id
return self

def on(self, payment):
self.payment_id = payment['id']
return self
return self.withParentId(payment['id'])
28 changes: 28 additions & 0 deletions Mollie/API/Resource/Subscriptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from .Base import *
from Mollie.API.Error import *
from Mollie.API.Object import Subscription


class Subscriptions(Base):
RESOURCE_ID_PREFIX = 'sub_'
customer_id = None

def getResourceObject(self, result):
return Subscription(result)

def get(self, subscription_id):
if not subscription_id or not subscription_id.startswith(self.RESOURCE_ID_PREFIX):
raise Error(
'Invalid subscription ID: "%s". A subscription ID should start with "%s".' % (subscription_id, self.RESOURCE_ID_PREFIX)
)
return super(Subscriptions, self).get(subscription_id)

def getResourceName(self):
return 'customers/%s/subscriptions' % self.customer_id

def withParentId(self, customer_id):
self.customer_id = customer_id
return self

def on(self, customer):
return self.withParentId(customer['id'])

0 comments on commit 3662d1b

Please sign in to comment.