Question

Here's a prelude to the question I'm asking: I've taken to building a gateway agnostic payment API in Python for my company. At the moment I've only written code to support Authorize.net and wanted some feedback on the clarity of my API design from Python programmers with a bit more experience than I.

I chose to roll my own because the other packages that exist feel more like an after thought or are Authorize.net specific (I want to write a more general package with a cleaner interface). I got some inspiration from a package in particular (pythorize) but did not like their API.

Before I begin describing what I am doing, here's the link to the public repository on bitbucket of the package: paypy (note to those that may want to use it: code is stable, but the docs are severely lacking).

My current strategy is using a nested dictionary and passing that to the constructor of the payment method class. Example of creating a new user profile on the Authorize.net CIM API:

>>> options = {'tran_key' : 'test_tran_key',
...            'login'    : 'developer_login',
...            'testing'  : True,
...            'validation': 'testMode',
...            'customer': {'description': 'Some description of the customer profile', 
...                         'id'         : 22,
...                         'email'      : 'johnny_doe@gmail.com'},
...            'billing': [{'type': 'individual',
...                         'profile': {'city'      : 'Carlsbad',
...                                     'state'     : 'California',
...                                     'zip'       : '92009',
...                                     'firstname' : 'John',
...                                     'address'   : '12 Alicante Rd. Suite 9',
...                                     'lastname'  : 'Doe',
...                                     'country'   : 'USA',
...                                     'phone'     : '(858) 557-2674'},
...                         'payment': {'card': {'ccv'        : '524',
...                                              'number'     : '4111111111111111',
...                                              'expiration' : '2014-04'}}},
...                        {'type'    : 'individual',
...                         'profile' : {'city'      : 'Las Vegas',
...                                      'state'     : 'Nevada',
...                                      'zip'       : '79112',
...                                      'firstname' : 'John',
...                                      'address'   : '78 Cloud Front',
...                                      'lastname'  : 'Doe',
...                                      'country'   : 'USA',
...                                      'phone'     : '(858) 557-2674'},
...                         'payment': {'card': {'ccv'        : '499',
...                                              'number'     : '4111111111111111',
...                                              'expiration' : '2012-11'}}},
...                        {'profile': {'city'       : 'Carlsbad',
...                                     'state'      : 'California',
...                                     'zip'        : '92009',
...                                     'firstname'  : 'John',
...                                     'address'    : '12 Alicante Rd. Suite 9',
...                                     'lastname'   : 'Doe',
...                                     'company'    : 'Xmarks',
...                                     'country'    : 'USA',
...                                     'phone'      : '(858) 557-2674'},
...                         'payment': {'bank': {'name_on_account' : 'John Doe',
...                                              'account'         : '829330184383',
...                                              'type'            : 'checking',
...                                              'name'            : 'Bank of America',
...                                              'routing'         : '122400724'}}}],
...            'shipping': [{'city'       : 'Carlsbad',
...                          'state'      : 'California',
...                          'zip'        : '92009',
...                          'firstname'  : 'John',
...                          'address'    : '12 Alicante Rd. Suite 9',
...                          'lastname'   : 'Doe',
...                          'country'    : 'USA',
...                          'phone'      : '(858) 557-2674'}]}
>>> profile = Profile(options)
>>> result  = profile.create()
>>> result.code
'I00001'
>>> print 'Customer Profile ID:' + str(result)
Customer Profile ID: 2758851
>>> print 'Customer Payment Profile IDs:' + repr(result.payment_ids)
Customer Payment Profile IDs: ['2380878', '2380879', '2380880']
>>> print 'Customer Shipping Profile IDs:' + repr(result.shipping_ids)
Customer Shipping Profile IDs: ['2427568']
>>>
>>>
>>> options = {'id'        : str(result),
...            'tran_key' : '86U5pvA9TcxZ5b8D',
...            'testing'  : True,
...            'login'    : '5b3PhGX68'}
>>> profile = Profile(options)
>>> result  = profile.remove()
>>> result.code
'I00001'
>>> ^D

You will notice I use a couple of the magic methods (like str, etc...) for the result objects. I use that dictionary strategy for the AIM and ARB methods as well and figured it was the simplest method of conveying "options" to the payment API - as there will at some point be adapters for GoogleCheckout, Paypal, etc...

The other thought I had was to use descriptors and objects instead of dictionaries to convey the options data to the adapters.

As with all payment gateway API's (particularly PayPal's and Authorize.net's) the interfaces tend to be a bit messy and are not standardized in any way so it's hard to avoid some gateway dependent options.

Was it helpful?

Solution

Deeply nested dictionaries may not be unusual in Python, and maybe they then are "Pythonic" but it shure as heck isn't a good idea, so I would claim that it isn't Pythonic.

I'd make a nested hierarcy of classes instead. That would be much clearer, IMO, and also have the benefit of enabling you to do type checking.

In fact, I'd probably use some schema module to do that.

And how are you supposed to enter that data? People are reasonably not supposed to type in Python code, right?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top