Code Monkey home page Code Monkey logo

braintree_python's Introduction

Braintree Python library

The Braintree Python library provides integration access to the Braintree Gateway.

TLS 1.2 required

The Payment Card Industry (PCI) Council has mandated that early versions of TLS be retired from service. All organizations that handle credit card information are required to comply with this standard. As part of this obligation, Braintree has updated its services to require TLS 1.2 for all HTTPS connections. Braintrees require HTTP/1.1 for all connections. Please see our technical documentation for more information.

Dependencies

The Braintree Python SDK is tested against Python versions 3.5.3 and 3.12.0.

The Python core development community has released End-of-Life branches for Python versions 2.7 - 3.4, and are no longer receiving security updates. As a result, Braintree no longer supports these versions of Python.

Versions

Braintree employs a deprecation policy for our SDKs. For more information on the statuses of an SDK check our developer docs.

Major version number Status Released Deprecated Unsupported
4.x.x Active March 2020 TBA TBA
3.x.x Inactive June 2014 March 2022 March 2023

Documentation

Updating from an Inactive, Deprecated, or Unsupported version of this SDK? Check our Migration Guide for tips.

Quick Start Example

import braintree

gateway = braintree.BraintreeGateway(
    braintree.Configuration(
        environment=braintree.Environment.Sandbox,
        merchant_id="your_merchant_id",
        public_key="your_public_key",
        private_key="your_private_key",
    )
)

result = gateway.transaction.sale({
    "amount": "1000.00",
    "payment_method_nonce": nonce_from_the_client,
    "options": {
        "submit_for_settlement": True
    }
})

if result.is_success:
    print("success!: " + result.transaction.id)
elif result.transaction:
    print("Error processing transaction:")
    print("  code: " + result.transaction.processor_response_code)
    print("  text: " + result.transaction.processor_response_text)
else:
    for error in result.errors.deep_errors:
        print("attribute: " + error.attribute)
        print("  code: " + error.code)
        print("  message: " + error.message)

Developing

  1. Create a virtualenv called venv:

    virtualenv venv
    
  2. Start the virtualenv:

    source venv/bin/activate
    
  3. Install dependencies:

    pip3 install -r dev_requirements.txt
    

Developing (Docker)

The Makefile and Dockerfile will build an image containing the dependencies and drop you to a terminal where you can run tests.

make

Testing

Our friends at Venmo have an open source library designed to simplify testing of applications using this library.

If you wish to run the tests, make sure you are set up for development (see instructions above). The unit specs can be run by anyone on any system, but the integration specs are meant to be run against a local development server of our gateway code. These integration specs are not meant for public consumption and will likely fail if run on your system. To run unit tests use rake (rake test:unit) or unittest (python3 -m unittest discover tests/unit).

License

See the LICENSE file for more info.

braintree_python's People

Contributors

amacy avatar arthurzam avatar beane avatar billwerges avatar bmcdonnel avatar braintreeps avatar chrissiedunham avatar crookedneighbor avatar dan-manges avatar debrado avatar demerino avatar edebill avatar ericbraintree avatar ethier avatar felixonmars avatar fuhrysteve avatar iartarisi avatar jingminglake avatar jonjchew avatar joshuaknox avatar jplukarski avatar kate-paypal avatar lkorth avatar matthewarkin avatar mborosh-bt avatar pgr0ss avatar saralvasquez avatar sestevens avatar sharma7n avatar skunkworks avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

braintree_python's Issues

pycurl is unable to write to .crt when braintree installed as zipped egg

I have 'braintree' listed as an install_requires in my setup.py file, using setuptools on Python 2.6. Running python setup.py develop installs braintree as a zipped .egg, which causes this error:

error: (77, 'error setting certificate verify locations:\n  CAfile: /path/to/virtualenv/lib/python2.6/site-packages/braintree-2.10.0-py2.6.egg/braintree/ssl/sandbox_braintreegateway_com.ca.crt\n  CApath: /etc/ssl/certs\n')

Being a zipped .egg, Python cannot write to the data files within the braintree distribution. Setting zip_safe=False in braintree_python/setup.py should correct this issue.

-Ron

ErrorResult could use a top-level 'code' attribute to match the 'message' attribute

The ErrorResult class has a top-level message attribute which is a convenient alternative to groveling through the children of errors when only the first/main error message is needed for display. Unfortunately, there is no matching code attribute, so there's no good way to take programmatic action based on what kind of error it is.

As far as I can tell, there is also no documentation indicating which child of errors was used for the top-level message, so grabbing an error code from there would seem unreliable at best.

3.11.0 failed Python 3.x tests

Failed with Python 3.4.2. It works fine with Python 2.7.9. Below are the detailed errors:

.............................................................................E...E............................................................................................................................
======================================================================
ERROR: test_create_signature (tests.unit.test_payment_method_gateway.TestPaymentMethodGateway)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/felix/projects/arch/archlinuxcn/python-braintree/src/braintree_python-3.11.0/tests/unit/test_payment_method_gateway.py", line 67, in test_create_signature
    self.assertItemsEqual(expected_signature, actual_signature)
AttributeError: 'TestPaymentMethodGateway' object has no attribute 'assertItemsEqual'

======================================================================
ERROR: test_update_signature (tests.unit.test_payment_method_gateway.TestPaymentMethodGateway)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/felix/projects/arch/archlinuxcn/python-braintree/src/braintree_python-3.11.0/tests/unit/test_payment_method_gateway.py", line 99, in test_update_signature
    self.assertItemsEqual(expected_signature, actual_signature)
AttributeError: 'TestPaymentMethodGateway' object has no attribute 'assertItemsEqual'

----------------------------------------------------------------------
Ran 206 tests in 0.084s

FAILED (errors=2)

HTTP POST failing with latest requests lib

I have recently updated the requests library in my project to version 2.6.0. Specifically, I ran "pip install --upgrade requests[security]" and the following requirements were changed:

< XlsxWriter==0.6.2

---
> XlsxWriter==0.6.3
17d16
< cffi==0.9.2
20d18
< cryptography==0.8
33d30
< django-sql-stacktrace==0.2.2
40d36
< enum34==1.0.4
65d60
< ndg-httpsclient==0.3.3
71,73d65
< pyOpenSSL==0.14
< pyasn1==0.1.7
< pycparser==2.10
81c73
< requests==2.6.0

---
> requests==2.4.3
87c79
< six==1.9.0

---
> six==1.8.0

After making that update I see the following errors occurring when attempting to communicate with braintree:

  File "/Users/andrew/code/EatWith/EatWith/apps/credit_cards/braintree/braintree_interaction_manager.py", line 231, in braintree_sale
    result = braintree.Transaction.sale(braintree_data)
  File "/Users/andrew/.virtualenvs/EatWith/lib/python2.7/site-packages/braintree/transaction.py", line 312, in sale
    return Transaction.create(params)
  File "/Users/andrew/.virtualenvs/EatWith/lib/python2.7/site-packages/braintree/transaction.py", line 407, in create
    return Configuration.gateway().transaction.create(params)
  File "/Users/andrew/.virtualenvs/EatWith/lib/python2.7/site-packages/braintree/transaction_gateway.py", line 33, in create
    return self._post("/transactions", {"transaction": params})
  File "/Users/andrew/.virtualenvs/EatWith/lib/python2.7/site-packages/braintree/transaction_gateway.py", line 137, in _post
    response = self.config.http().post(url, params)
  File "/Users/andrew/.virtualenvs/EatWith/lib/python2.7/site-packages/braintree/util/http.py", line 49, in post
    return self.__http_do("POST", path, params)
  File "/Users/andrew/.virtualenvs/EatWith/lib/python2.7/site-packages/braintree/util/http.py", line 71, in __http_do
    raise e
TypeError: data must be a byte string

I have observed this behavior both on localhost (on my macbook) running in the development environment, as well as running on the heroku cedar-14 stack (ubuntu based) in the production environment.

It seems to me that the most recent updates to the requests library have introduce an incompatibility with the braintree lib. Does this sound correct?

Also, I see support for different http strategies in the configuration object, but I can't find documentation or examples anywhere showing how to use a different strategy. Would appreciate any pointers to references that may be available.

'Configuration' has no attribute 'environment'

This code:

WebhookTesting.sample_notification(
    WebhookNotification.Kind.SubscriptionWentPastDue,
    'some_id'
)

throws this exception:

Traceback (most recent call last):
  File "/vagrant/plug/payment/tests/acceptance/test_braintree_webhooks.py", line 21, in test
    'some_id'
  File "/usr/local/lib/python2.7/dist-packages/braintree/webhook_testing.py", line 7, in sample_notification
    return Configuration.gateway().webhook_testing.sample_notification(kind, id)
  File "/usr/local/lib/python2.7/dist-packages/braintree/configuration.py", line 41, in gateway
    return braintree.braintree_gateway.BraintreeGateway(Configuration.instantiate())
  File "/usr/local/lib/python2.7/dist-packages/braintree/configuration.py", line 46, in instantiate
    environment=Configuration.environment,
AttributeError: type object 'Configuration' has no attribute 'environment'

I am using version 3.11.1.

Extract dictionnary from object

It would be great if we could get the dictionnary representing a braintree object.
We could extract data or send some objects directly to the clients like plans for example.

I managed to do this by adding this small function to your AttributeGetter but it's just monkey patching :

def serialize(self):
    data = {}
    for key in self._setattrs:
        if hasattr(getattr(self, key), "serialize"):
            data[key] = getattr(self, key).serialize()
        else:
            data[key] = getattr(self, key)
    return data

If I want to get the dictionnary from an object, I'll be able to do my_object.serialize()

strptime not thread safe

I've been getting intermittent errors in parsing webhooks from braintree:

    return self.__node_content(root, child.nodeValue)
  File "/opt/rh/python27/root/usr/lib/python2.7/site-packages/braintree/util/parser.py", line 87, in __node_content
    return self.__convert_to_datetime(content)
  File "/opt/rh/python27/root/usr/lib/python2.7/site-packages/braintree/util/parser.py", line 42, in __convert_to_datetime
    return datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ")
AttributeError: _strptime

I think the cause is this:

https://bugs.python.org/issue7980

In my case I'm using braintree_python in Django and runserver. It sounds like this would occur in our prod environment using uwsgi too. Since the webhooks can be retried, this isn't a terrible problem, but it could be an issue in areas other than webhook.

Since the dates are consistent and well formatted from Braintree, perhaps just working around strptime will be easier.

result.is_success is False but result.errors.size == 0

While testing the python api against the sandbox server, I am inducing a subscription failure using a $2,099.00 plan. When Subscription.create() fails, result.message is set but result.errors.size is 0. This breaks code that looks in result.errors.deep_errors[0] for the error code of the failure (see issue #6) apparently giving me no programmatic way to determine the error code.

Plan not returning price as decimal

In [12]: plans[0]      
Out[12]: <Plan {billing_day_of_month: None, trial_duration: None, description: u'A brick', billing_frequency: 1, trial_period: False, price: u'10.00', currency_iso_code: u'USD', updated_at: datetime.datetime(2015, 4, 3, 15, 10, 30), trial_duration_unit: u'day', number_of_billing_cycles: None, discounts: [], add_ons: [], created_at: datetime.datetime(2015, 4, 3, 15, 10, 30), merchant_id: u'tbb7hb44zx28jhsh', id: u'concrete-brick', name: u'Concrete'} at 62648720>

In [13]: plans[0].price
Out[13]: u'10.00'

Would be easier to use if it was parsed as a decimal.

python braintree package will not install with m2crypto 0.20.1

On Ubuntu Lucid, pip install braintree fails with the following error:

VersionConflict: (M2Crypto 0.20.1 (/usr/lib/pymodules/python2.6), Requirement.parse('M2Crypto==0.20.2'))

From what I see in the m2crypto changelog, there's no reason the braintree package shouldn't work with m2crypto 0.20.1. Can you folks update your version requirement so it will work with Ubuntu's standard m2crypto installation?

http://websvn.osafoundation.org/filedetails.php?repname=m2crypto&path=%2Ftrunk%2FCHANGES

PaymentMethod.create() doesn't accept all allowed params

According to this: https://developers.braintreepayments.com/javascript+python/reference/request/payment-method/create

It looks like this method should allow these additional parameters:

  • number
  • expiration_date
  • expiration_month
  • expiration_year
  • cardholder_name
  • cvv
  • venmo_sdk_payment_method_code

However, trying to use any combination of those results in an error such as:

  vi +9   /usr/local/lib/python2.7/dist-packages/braintree/payment_method.py  # create
    return Configuration.gateway().payment_method.create(params)
  vi +22  /usr/local/lib/python2.7/dist-packages/braintree/payment_method_gateway.py  # create
    Resource.verify_keys(params, PaymentMethod.create_signature())
  vi +17  /usr/local/lib/python2.7/dist-packages/braintree/resource.py  # verify_keys
    raise KeyError("Invalid keys: " + keys_string)
KeyError: 'Invalid keys: expiration_month, cardholder_name, expiration_year, cvv, number'

NameError: global name 'requests' is not defined

Running the example causes an exception:

Traceback (most recent call last):
  File "braintree_test.py", line 26, in <module>
    "expiration_year": "2012"
  File "/home/gonvaled/.virtualenvs/python2.7.3-wavilon1/local/lib/python2.7/site-packages/braintree/transaction.py", line 297, in sale
    return Transaction.create(params)
  File "/home/gonvaled/.virtualenvs/python2.7.3-wavilon1/local/lib/python2.7/site-packages/braintree/transaction.py", line 392, in create
    return Configuration.gateway().transaction.create(params)
  File "/home/gonvaled/.virtualenvs/python2.7.3-wavilon1/local/lib/python2.7/site-packages/braintree/transaction_gateway.py", line 33, in create
    return self._post("/transactions", {"transaction": params})
  File "/home/gonvaled/.virtualenvs/python2.7.3-wavilon1/local/lib/python2.7/site-packages/braintree/transaction_gateway.py", line 137, in _post
    response = self.config.http().post(url, params)
  File "/home/gonvaled/.virtualenvs/python2.7.3-wavilon1/local/lib/python2.7/site-packages/braintree/util/http.py", line 40, in post
    return self.__http_do("POST", path, params)
  File "/home/gonvaled/.virtualenvs/python2.7.3-wavilon1/local/lib/python2.7/site-packages/braintree/util/http.py", line 55, in __http_do
    status, response_body = http_strategy.http_do(http_verb, full_path, self.__headers(), request_body)
  File "/home/gonvaled/.virtualenvs/python2.7.3-wavilon1/local/lib/python2.7/site-packages/braintree/util/http_strategy/requests_strategy.py", line 12, in http_do
    response = requests.request(
NameError: global name 'requests' is not defined
import braintree

braintree.Configuration.configure(
    braintree.Environment.Sandbox,
    BRAINTREE_MERCHANT_ID,
    BRAINTREE_PUBLIC_KEY,
    BRAINTREE_PRIVATE_KEY
)

result = braintree.Transaction.sale({
    "amount": "1000.00",
    "credit_card": {
        "number": "4111111111111111",
        "expiration_month": "05",
        "expiration_year": "2012"
    }
})

if result.is_success:
    print "success!: " + result.transaction.id
elif result.transaction:
    print "Error processing transaction:"
    print "  message: " + result.message
    print "  code:    " + result.transaction.processor_response_code
    print "  text:    " + result.transaction.processor_response_text
else:
    print "message: " + result.message
    for error in result.errors.deep_errors:
        print "attribute: " + error.attribute
        print "  code: " + error.code
        print "  message: " + error.message

I have python 2.7.3, and according to pip freeze:

requests==1.1.0
braintree==2.26.0

Running on Crunchbang Linux:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 7.2 (wheezy)
Release:    7.2
Codename:   wheezy

$ uname -a
Linux pegasus 3.2.0-4-686-pae #1 SMP Debian 3.2.51-1 i686 GNU/Linux

i'm getting the following error. not sure if this is on braintree server side

braintree1/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:315: SNIMissingWarning: An HTTPS request has been made, but the SNI (Subject Name Indication) extension to TLS is not available on this platform. This may cause the server to present an incorrect TLS certificate, which can cause validation failures. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#snimissingwarning.
SNIMissingWarning

braintree1/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:120: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
InsecurePlatformWarning

Webhook verify Accept Header incorrect

When you set up webhook on braintree it sends a request to the server to check it can receive that request. The docs recommend getting that request, verifying and returning the response, for example:

https://developers.braintreepayments.com/javascript+python/guides/webhooks/create#bt-challenge

What they don't say is that the request from braintree will send Accept headers in the request that look like this:

[u'/; q=0.5', u'application/xml']

But the Braintree end of things does not accept any response or "application/xml". It only accepts one things and that is the response echoed back, no XML or JSON encoding applied. It does not accept /.

The request for the verify end point should send an Accept header of: text/plain.

Transparent Redirect field name brackets make integration difficult

This issue is not python-specific, but since I don't see a general Braintree tracker, I'm writing it up here.

The bracket characters in Transparent Redirect field names make Braintree integration rather a pain. Aside from looking confusingly similar to first-class subscripting syntax in various programming languages, I have seen two specific problems so far:

  1. They are incompatible with web form construction/validation libraries that use native-language variables to represent form fields. (For example, the WTForms python library.) Since brackets are not allowed in most languages' variable names, creating a Braintree form in a project that uses such libraries requires either laboriously writing form-processing code by hand, or else jumping through hoops to make the libraries work with reduced functionality.
  2. They do not conform to the HTML ID and NAME requirements, and therefore produce invalid HTML when used in templating systems that generate <input id=""> and <label for=""> tags from field names.

There are workarounds for these problems, of course, but they make for overly-complicated special-case code, wasted time, and frustrated programmers.

Would you folks consider supporting an alternative syntax for embedding structure in your field names? Separating sub-fields with double-underscores ought to work nicely. For example, instead of writing credit_card[options][make_default] one might write credit_card__options__make_default, which would be no longer than the former syntax and would also work as a variable/id name in every language that comes to mind.

TransparentRedirect.confirm() is tricky to use when non-braintree query parameters are involved

I'd like to add a query parameter to my application's after-transparent-redirect landing url, indicating what page the user should be taken to next. This would allow a single endpoint to check transparent redirect results and send the user on his way, no matter what application flow is in progress when the transparent redirect takes place. (In other words, code reuse.) I'd also like to be able to use arbitrary query parameters for other reasons.

Unfortunately, TransparentRedirect.confirm() chokes when passed a query string without its expected query parameters, and the braintree api doesn't seem to offer away to distinguish its parameters from others that might be present. This effectively leaves two ways to avoid the non-braintree exception:

  1. Parse the query string, filter out all expected non-braintree parameters, check whether any others remain, and if they do, call TransparentRedirect.confirm().
  2. Look in the braintree code for the query parameters it expects, parse the query string, check for braintree parameters, and if they are present, call TransparentRedirect.confirm().

The first approach is cumbersome, especially when multiple non-braintree query parameters might be in use. The second approach is cleaner, but very fragile. This would be much cleaner if the braintree api provided a way to detect whether transparent redirect results are present in a query string, or if TransparentRedirect.confirm() reported missing query parameters efficiently and gracefully.

(Related note: the OpenID protocol also uses redirects, and prefixes its query parameters with 'openid.' to help with issues like this.)

Documentation does not state where exceptions are found.

The documentation lists all the exceptions Braintree can throw, but does not say where to find them in the braintree package. It is easy to find them once you go looking, but stating in the documentation where to find each exception would be even clearer.

Additionally, the exceptions are listed in the documentation in a human friendly way with spaces, while the actual exception classes are named slightly differently.

For a good example, look at the errors and exceptions section of the requests documentation. It states each error it throws, with the actual class name of the error, states which package it exists in, and mentioned the base class each exception inherits from. All this information should be found in the documentation.

server certificate verification failed

Traceback (most recent call last):

  File "/home/ubuntu/.virtualenvs/app/lib/python2.6/site-packages/django/core/handlers/base.py", line 117, in get_response
    response = middleware_method(request, e)

  File "/home/ubuntu/.virtualenvs/app/lib/python2.6/site-packages/django/core/handlers/base.py", line 111, in get_response
    response = callback(request, *callback_args, **callback_kwargs)

  File "/home/ubuntu/webapps/app/common/decorators.py", line 24, in new_func
    return view_func(request, *args, **kwargs)

  File "/home/ubuntu/.virtualenvs/app/lib/python2.6/site-packages/django/contrib/auth/decorators.py", line 23, in _wrapped_view
    return view_func(request, *args, **kwargs)

  File "/home/ubuntu/webapps/app/app/profiles/views/expert.py", line 651, in checkout
    form_initial_data_dict = payments_helper.get_billing_info_dict(request.user)

  File "/home/ubuntu/webapps/app/common/payments/utils.py", line 199, in get_billing_info_dict
    customer = braintree.Customer.find(customer_id)

  File "/home/ubuntu/.virtualenvs/app/lib/python2.6/site-packages/braintree/customer.py", line 111, in find
    return Configuration.gateway().customer.find(customer_id)

  File "/home/ubuntu/.virtualenvs/app/lib/python2.6/site-packages/braintree/customer_gateway.py", line 34, in find
    response = self.config.http().get("/customers/" + customer_id)

  File "/home/ubuntu/.virtualenvs/app/lib/python2.6/site-packages/braintree/util/http.py", line 47, in get
    return self.__http_do("GET", path)

  File "/home/ubuntu/.virtualenvs/app/lib/python2.6/site-packages/braintree/util/http.py", line 54, in __http_do
    self.__verify_ssl()

  File "/home/ubuntu/.virtualenvs/app/lib/python2.6/site-packages/braintree/util/http.py", line 116, in __verify_ssl
    curl.perform()

error: (60, 'server certificate verification failed. CAfile: /home/ubuntu/.virtualenvs/app/lib/python2.6/site-packages/braintree/ssl/www_braintreegateway_com.ca.crt CRLfile: none')

braintree calls raise NoCertificate when setdefaulttimeout() has been used.

I'm currently seeing this problem with Customer.create(), but it probably applies to all braintree API calls which contact the server. These calls are failing, raising a spurious NoCertificate exception with this message: "peer did not return certificate"

This appears to be caused by a known bug in m2crypto:
https://bugzilla.osafoundation.org/show_bug.cgi?id=2341
http://bytes.com/topic/python/answers/586302-wierd-m2crypto-bug-phony-peer-did-not-return-certificate-error

Our application calls socket.setdefaulttimeout() to make it gracefully handle cases when third-party libraries contact unresponsive servers. This is still pretty common in the python world, since per-connection timeout support was only recently added to urllib2 and many libraries have not yet started using that feature. That means we can't reasonably stop using socket.setdefaulttimeout(). Unfortunately, it causes braintree calls to fail.

So, as long as the braintree library depends on m2crypto for its https support, it seems we are stuck with either an incompatibility or (if we were to disable socket timeouts to work around the bug) a reliability problem.

Perhaps it would make sense for the braintree python library to use pycurl instead of m2crypto, or better yet, expose a pluggable http/https fetcher API? (The latter would be especially nice when we migrate to App Engine. Of course, such a plugin API should support caller-specified timeouts. :)

exceptions need a common base class

All the exceptions in the braintree package derive directly from Exception, which makes them indistinguishable from standard python exceptions unless I write special cases for each one of them (and hope new ones never show up). For example:

try:
    braintree.Customer.create( dict( first_name="John", last_name="Doe"))
except (AuthenticationError, 
    AuthorizationError, 
    DownForMaintenanceError,
    ForgedQueryStringError,
    NotFoundError,
    ServerError,
    UnexpectedError,
    UpgradeRequiredError):
    print "braintree raised an exception"

All these exception classes really need a common ancestor that doesn't belong to the python standard library. braintree.Error, perhaps.

Webhook `timestamp`s are not timezone aware

The timestamps on WebhookNotification instances are 'naive' datetime instances (see the datetime docs). This means that they do not have any associated timezone - they are just a time. Python best practices state that datetime/time objects should always have a timezone. Given that your API states in other locations that times are returned as UTC, attaching the UTC timezone to datetimes returned by the API would make everything consistent with itself and Python best practices.

TypeError: unicode argument without an encoding

When trying to parse the webhooks signature and payload, I am getting:

Traceback (most recent call last):
  File "/src/djangoapp/apps/braintree/views.py", line 61, in webhook
    request.POST['bt_signature'], request.POST['bt_payload']
  File "/.virtualenvs/djangoapp/local/lib/python2.7/site-packages/braintree/webhook_notification.py", line 17, in parse
    return Configuration.gateway().webhook_notification.parse(signature, payload)
  File "/.virtualenvs/djangoapp/local/lib/python2.7/site-packages/braintree/webhook_notification_gateway.py", line 13, in parse
    self.__validate_signature(signature, payload)
  File "/.virtualenvs/djangoapp/local/lib/python2.7/site-packages/braintree/webhook_notification_gateway.py", line 32, in __validate_signature
    if not Crypto.secure_compare(payload_signature, matching_signature):
  File "/.virtualenvs/djangoapp/local/lib/python2.7/site-packages/braintree/util/crypto.py", line 15, in secure_compare
    right_bytes = bytearray(right)
TypeError: unicode argument without an encoding

I am using Django 1.4, and have this code in my Django view which is throwing the exception:

webhook_notification = braintree.WebhookNotification.parse(
    request.POST['bt_signature'], request.POST['bt_payload']
)

This was taken from your Python docs here: https://www.braintreepayments.com/docs/python/webhooks/parsing

AttributeError: 'unicode' object has no attribute 'items'

This line misses Python2 unicode strings, eventually causing an AttributeError exception.

Traceback (most recent call last):
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/braintree/subscription.py", line 150, in update
    return Configuration.gateway().subscription.update(subscription_id, params)
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/braintree/subscription_gateway.py", line 59, in update
    Resource.verify_keys(params, Subscription.update_signature())
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/braintree/resource.py", line 9, in verify_keys
    params_keys = Resource.__flattened_params_keys(params)
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/braintree/resource.py", line 27, in __flattened_params_keys
    keys += Resource.__flattened_params_keys(val, full_key)
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/braintree/resource.py", line 30, in __flattened_params_keys
    keys += Resource.__flattened_params_keys(item, full_key)
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/braintree/resource.py", line 24, in __flattened_params_keys
    for key, val in params.items():
AttributeError: 'unicode' object has no attribute 'items'

Should be basestring instead of str, or even better use six.string_types for Python 3 compatibility.

TypeError: cannot make memory view because object does not have the buffer interface

This error happens when running braintree.Customer.create({'email': email}) where email is the user's email address.

Traceback (most recent call last):
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/flask/app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/flask_login.py", line 792, in decorated_view
    return func(*args, **kwargs)
  File "/opt/wakatime/current/app/api_v1.py", line 295, in post_users_current_payment_method
    app.current_user.update_payment_method(processor_type, **form.data)
  File "/opt/wakatime/current/app/models.py", line 924, in update_payment_method
    self.processor.update_payment_method(kwargs['token'])
  File "/opt/wakatime/current/app/models.py", line 3435, in update_payment_method
    'customer_id': self.customer.id,
  File "/opt/wakatime/current/app/models.py", line 3319, in customer
    self._customer = PaymentProcessor.get_customer(self.user, self.type)[0]
  File "/opt/wakatime/current/app/models.py", line 3294, in get_customer
    'email': user.email,
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/braintree/customer.py", line 102, in create
    return Configuration.gateway().customer.create(params)
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/braintree/customer_gateway.py", line 26, in create
    return self._post("/customers", {"customer": params})
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/braintree/customer_gateway.py", line 88, in _post
    response = self.config.http().post(self.config.base_merchant_path() + url, params)
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/braintree/util/http.py", line 49, in post
    return self.__http_do("POST", path, params)
  File "/opt/wakatime/current/venv/local/lib/python2.7/site-packages/braintree/util/http.py", line 72, in __http_do
    raise e
TypeError: cannot make memory view because object does not have the buffer interface

Docs refer to properties as methods

There are a couple of places where the docs mention a method that is actually a property. Examples:

http://www.braintreepaymentsolutions.com/docs/python/general/result_objects#success_results
"If the API call was successful, the success method on the result object will return true."

http://www.braintreepaymentsolutions.com/docs/python/general/validation_errors#errors_at_specific_levels
"From a specific level, you can also get the number of errors at that level using the size method."

Since methods and properties have different calling semantics, this might lead a developer to write invalid code. For example, testing size() instead of size.

Configuration doc string needs updating

The example code in the configuration.py doc string currently refers to a constant called braintree.Environment.SANDBOX, which does not exist. It should read braintree.Environment.Sandbox.

Need to be more generous about numeric customer ids

When call Customer.update, customer id was assumed to be a string. It should be more fault tolerant to allow other types like integer to be allowed.

Here is the code to reproduce:

In [87]: bt_customer
Out[87]: 
{'credit_card': {'billing_address': {'country_code_alpha2': 'US',
   'extended_address': '',
   'locality': 'New York',
   'postal_code': '10016',
   'region': 'NY',
   'street_address': '2 Park Ave'},
  'cardholder_name': 'Ye Wang',
  'expiration_date': '03/15',
  'number': '4000111111111115',
  'options': {'make_default': True, 'update_existing_token': '1717648'}},
 'first_name': 'Ye',
 'last_name': 'Wang'}

In [88]: result1 = braintree.Customer.update(2963, bt_customer)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-88-5096251d7619> in <module>()
----> 1 result1 = braintree.Customer.update(2963, bt_customer)

/Users/ye/.envs/dbi/lib/python2.7/site-packages/braintree/customer.pyc in update(customer_id, params)
    165         """
    166 
--> 167         return Configuration.gateway().customer.update(customer_id, params)
    168 
    169     @staticmethod

/Users/ye/.envs/dbi/lib/python2.7/site-packages/braintree/customer_gateway.pyc in update(self, customer_id, params)
     64     def update(self, customer_id, params={}):
     65         Resource.verify_keys(params, Customer.update_signature())
---> 66         response = self.config.http().put("/customers/" + customer_id, {"customer": params})
     67         if "customer" in response:
     68             return SuccessfulResult({"customer": Customer(self.gateway, response["customer"])})

TypeError: cannot concatenate 'str' and 'int' objects

A simple fix of using string formatting on line 66 of customer_gateway.py would fix it.

response = self.config.http().put("/customers/%s" % customer_id, {"customer": params})

Subscription.find('') raises UnexpectedError

While testing, I came across the following exception when calling Subscription.find(""):

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.6/dist-packages/braintree/subscription.py", line 109, in find
    return Configuration.gateway().subscription.find(subscription_id)
  File "/usr/local/lib/python2.6/dist-packages/braintree/subscription_gateway.py", line 33, in find
    response = self.config.http().get("/subscriptions/" + subscription_id)
  File "/usr/local/lib/python2.6/dist-packages/braintree/util/http.py", line 47, in get
    return self.__http_do("GET", path)
  File "/usr/local/lib/python2.6/dist-packages/braintree/util/http.py", line 70, in __http_do
    Http.raise_exception_from_status(status)
  File "/usr/local/lib/python2.6/dist-packages/braintree/util/http.py", line 34, in raise_exception_from_status
    raise UnexpectedError("Unexpected HTTP_RESPONSE " + str(status))
braintree.exceptions.unexpected_error.UnexpectedError: Unexpected HTTP_RESPONSE 405

Not a big deal, since I don't expect to pass an empty string to find() in practice, but I thought you folks would like to know since it seems like the expected result would have been a NotFoundError.

Missing values while paying with a fake android pay token

After paying with a fake-android-pay-amex-nonce, if I inspect the response and try to get the bin or last_4, they are None, which causes masked_number to also fail

ipdb> type(credit_card)
<class 'braintree.credit_card.CreditCard'>
ipdb> pi credit_card
credit_card._setattrs      = [u'bin', u'expiration_month', u'unique_number_identifier', u'prepaid', u'expirat
credit_card.billing_address= None
credit_card.bin            = None
credit_card.card_type      = None
credit_card.cardholder_name= None
credit_card.commercial     = u'Unknown'
credit_card.country_of_issuance= u'Unknown'
credit_card.customer_location= None
credit_card.debit          = u'Unknown'
credit_card.durbin_regulated= u'Unknown'
credit_card.expiration_month= ''
credit_card.expiration_year= ''
credit_card.gateway        = <braintree.braintree_gateway.BraintreeGateway object at 0x4670a10>
credit_card.healthcare     = u'Unknown'
credit_card.image_url      = u'https://assets.braintreegateway.com/payment_method_logo/unknown.png?environmen
credit_card.is_expired     = <function expired at 0x3221578>
credit_card.issuing_bank   = u'Unknown'
credit_card.last_4         = None
credit_card.payroll        = u'Unknown'
credit_card.prepaid        = u'Unknown'
credit_card.product_id     = u'Unknown'
credit_card.token          = u'74tj66'
credit_card.unique_number_identifier= None
credit_card.venmo_sdk      = False
ipdb> credit_card.bin
ipdb> credit_card.last_4
ipdb> credit_card.masked_number
*** TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'

setup.py will fail when trying to install under python 3.3

The 2to3 utility is needed to convert braintree_python to be compatible with python 3. This would be better if it was done as part of the setup.py (that way python 3 users could use pip) I have added the code that is needed to do this. Setuptools is needed for this to work but thats a pretty standard module. After doing this the

python setup.py build
python setup.py install

works fine.

import distutils.core
import sys

# a hack to just get the version without calling the package init 
# which is necessary as the import will fail on python > 3 due to the 
# 2to3 needing to be run as part of the install
sys.path.append("braintree")
from version import Version

# Importing setuptools adds some features like "setup.py develop", but
# it's optional so swallow the error if it's not there.
try:
    import setuptools
except ImportError:
    pass

major, minor = sys.version_info[:2]

kwargs = {}
if major >= 3:
    import setuptools  # setuptools is required for use_2to3
    kwargs["use_2to3"] = True

distutils.core.setup(
    name="braintree",
    version=Version,
    description="Braintree Python Library",
    author="Braintree",
    author_email="[email protected]",
    url="https://www.braintreepayments.com/docs/python",
    packages=["braintree", "braintree.exceptions", "braintree.util", "braintree.test", "braintree.util.http_strategy"],
    package_data={"braintree": ["ssl/*"]},
    install_requires=["requests>=0.11.1,<2.0"],
    zip_safe=False,
    **kwargs
)

Access to the error response code

It looks like there are some errors that return an error code, but the result of the parsing in ErrorResult is that the actual response code is thrown away.

Here's an example, given the gist at https://gist.github.com/andymckay/a9340d1b0a7ce483025e:

In [6]: from braintree.error_result import ErrorResult

In [7]: err = ErrorResult(None, data)

In [8]: data['message']
Out[8]: u'Invalid Secure Payment Data'

In [9]: data['transaction']['processor_response_code']
Out[9]: u'2078'

You can access that in the response, but once it gets parsed into a Transaction, its lost, because Transaction doesn't parse that at all:

In [10]: err.params
Out[10]: 
{u'descriptor': {u'name': u'Mozilla*product', u'url': u'mozilla.org'},
 u'payment_method_token': u'7fk5cg',
 u'plan_id': u'mozilla-concrete-brick',
 u'trial_period': u'false'}

In [12]: err.transaction
Out[12]: <Transaction {amount: Decimal('2078.00'), credit_card: {u'bin': u'411111', u'card_type': u'Visa', u'unique_number_identifier': u'21ee4272998b10107aeee9d50d6fe1ae', u'expiration_year': u'2016', u'prepaid': u'Unknown', u'durbin_regulated': u'Unknown', u'commercial': u'Unknown', u'healthcare': u'Unknown', u'payroll': u'Unknown', u'issuing_bank': u'Unknown', u'last_4': u'1111', u'expiration_month': u'12', u'cardholder_name': u'a', u'token': u'7fk5cg', u'customer_location': u'US', u'image_url': u'https://assets.braintreegateway.com/payment_method_logo/visa.png?environment=sandbox', u'country_of_issuance': u'Unknown', u'debit': u'Unknown', u'venmo_sdk': False, u'product_id': u'Unknown'}} at 4426039504>

Note that if you pass the Transaction information to the Verification object, you can get something useful:

In [15]: cc = CreditCardVerification(None, data['transaction'])

In [16]: cc.processor_response_code
Out[16]: u'2078'

We'd like to be able to return that unique to braintree response code up the stack, log it etc. But we can't unless we monkey patch ErrorResult. Unless I'm missing something on how to process the response for this kind of error.

datetime is not timezone aware

The docs mention than search results and such are always in UTC, but the datetime.datetime objects being created with this library are not timezone aware. I can understand not wanting to add pytz just to accomplish this, so you could define your own UTC object (as shown in the datetime docs) that could be used in this case.

2.14.0 is missing a package in setup.py

I believe setup.py is missing the following package: braintree.util.http_strategy

When I attempt to install the latest python version (2.14.0) I get the following error:
ImportError: No module named http_strategy.pycurl_strategy

Cannot Update default payment method

Hi,

Hope you can find some time to check on my problem.

Currently, i'm creating a maintenance application and display the payment method used by clients. Clients may have multiple payment method.

image

My concern here is i am not able to update the default payment method for the selected customer using this code.

result = braintree.CreditCard.update(token,{
"options": {
"make_default": True,
},
})

After checking the command prompt for any logs. I've found this

image

Below is the complete code of the method i've used.

image

Hope you can assist me.
Thanks!

There is no `subscription_changed_plan` webhook or similar

There are webhooks for every state change of a subscription, and for all attempted transactions, but there is no webhook for when a subscription changes plan.

In our application, different plans open up extra features of the application. The standard plan has a limited feature set, while the Delux Ultimate Pro++ plan has all of the features. People can upgrade and downgrade their plan at any time. Tracking these changes is quite important to our application.

With the other webhooks provided by braintree, our application can rely solely on the events posted to us by braintree for subscription status management. This great, because it allows us to not make a duplicate backend in our application to manage subscriptions, as the client can just use braintree if they want to manually add, manage and remove subscriptions. However, if the client upgrades a user to another plan through the braintree administration interface, our application will not know about it and the customer will not be upgraded.

Could a webhook be added to notify applications when the plan of a subscription changes?

Provide `__repr__` to all classes

Please provide an informative __repr__ method to all Braintree classes, so we could see something more useful than <braintree.credit_card.CreditCard object at 0x038ED550>.

Perhaps something like <Nancy's AmEx-4413 credit card at 0x038ED550>.

Unable to use braintree_python on Google App Engine

AI responses are gzip-encoded, but the python library (probably the requests library) treats them as ASCII.

File "xtras/braintree/customer.py", line 126, in find
    return Configuration.gateway().customer.find(customer_id)
  File "xtras/braintree/customer_gateway.py", line 36, in find
    response = self.config.http().get("/customers/" + customer_id)
  File "xtras/braintree/util/http.py", line 57, in get
    return self.__http_do("GET", path)
  File "xtras/braintree/util/http.py", line 82, in __http_do
    return XmlUtil.dict_from_xml(response_body)
  File "xtras/braintree/util/xml_util.py", line 11, in dict_from_xml
    return Parser(xml).parse()
  File "xtras/braintree/util/parser.py", line 15, in __init__
    self.doc = minidom.parseString("><".join(re.split(">\s+<", xml)).strip())
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xml/dom/minidom.py", line 1928, in parseString
    return expatbuilder.parseString(string)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xml/dom/expatbuilder.py", line 940, in parseString
    return builder.parseString(string)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/xml/dom/expatbuilder.py", line 223, in parseString
    parser.Parse(string, True)
UnicodeEncodeError: 'ascii' codec can't encode character u'\ufffd' in position 0: ordinal not in range(128)

Modifying http_do() in util/http.py to unzip the content, makes the library work again, but this is probably a fragile way to fix this. (unzip() is just a wrapper to unzip raw data, nothing special.)

   def http_do(self, http_verb, path, headers, request_body):
        response = self.__request_function(http_verb)(
            self.environment.base_url + path,
            headers=headers,
            data=request_body,
            verify=self.environment.ssl_certificate,
            timeout=self.config.timeout
        )
        if response.headers['content-encoding']=='gzip':
            return [response.status_code, unzip(response.content)]
        else:
            return [response.status_code, response.text]

Can not retrieve single plan via `braintree.Plan.find(plan_id)` or similar methods

Customers, Transactions, and probably other models have a find(id) method that allows you to get a single instance back from Braintree. Plans do not have this. If I want to retrieve a single plan from Braintree, I have to get all plans and find the one with a matching id.

Could a Plan.find(plan_id) method be created, similar to the methods for Customers and Transactions?

Braintree fails to install over pip

I have python 3.3 running on fedora 19 64-bit with virtualenv and requests installed. When running

pip install braintree

I am given the following traceback error

Downloading/unpacking braintree
Downloading braintree-2.23.1.tar.gz
Running setup.py egg_info for package braintree
  Traceback (most recent call last):
  File "<string>", line 16, in <module>
  File "/home/.../venv/build/braintree/setup.py", line 1, in <module>
    import braintree
  File "./braintree/__init__.py", line 1, in <module>
    from braintree.add_on import AddOn
  File "./braintree/add_on.py", line 1, in <module>
    from braintree.configuration import Configuration
  File "./braintree/configuration.py", line 4, in <module>
    import braintree.util.http_strategy.pycurl_strategy
  File "./braintree/util/http_strategy/pycurl_strategy.py", line 1, in <module>
    import httplib
ImportError: No module named 'httplib'
Complete output from command python setup.py egg_info:
Traceback (most recent call last):

File "<string>", line 16, in <module>

File "/home/.../venv/build/braintree/setup.py", line 1, in <module>

import braintree

File "./braintree/__init__.py", line 1, in <module>

  from braintree.add_on import AddOn

File "./braintree/add_on.py", line 1, in <module>

  from braintree.configuration import Configuration

File "./braintree/configuration.py", line 4, in <module>

  import braintree.util.http_strategy.pycurl_strategy

File "./braintree/util/http_strategy/pycurl_strategy.py", line 1, in <module>

  import httplib

ImportError: No module named 'httplib'

----------------------------------------
Cleaning up...
Command python setup.py egg_info failed with error code 1 in        
/home/.../venv/build/braintree
Storing complete log in ~/.pip/pip.log

Document how to test would be nice

How you run the unit tests in this library isn't obvious. I figured out how to test the unit tests:

pip install nose
nosetests tests/unit

Less clear how to run the integration tests:

nosetests test/integration/
[snip]
FAILED (errors=464, failures=2)

Am I meant to be running a server? It's making lots of urllib3 connections:

ConnectionError: ('Connection aborted.', error(61, 'Connection refused'))
-------------------- >> begin captured logging << --------------------
requests.packages.urllib3.connectionpool: INFO: Starting new HTTP connection (1): localhost

Got an issue and possible pull request to send to you, but don't know how to run the tests :)

Feature Request: get_view_url() on Subscription and other objects

It would be very convenient for our application's administrators if its web interface could link directly to Braintree's web page for any given subscription, customer, etc. Would you folks consider adding a method on such objects to generate the appropriate URL for viewing the object in Braintree's UI?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.