2

I am working on a Braintree subscription search in order to retrieve customer IDs and subscription prices tied to these IDs. In my code I am following the suggestions from this post.

Here is an excerpt of my code:

gateway = braintree.BraintreeGateway(
    braintree.Configuration(
        environment=braintree.Environment.Production,
        merchant_id= 'our_merchant_id',
        public_key='our_public_key',
        private_key='our_private_key'
    )
)
subscriptions = gateway.subscription.search(
    braintree.SubscriptionSearch.status.in_list(
        braintree.Subscription.Status.Active,
        braintree.Subscription.Status.PastDue,
        braintree.Subscription.Status.Pending
    )
)

result = {}

for subscription in subscriptions.items:
    payment_method = gateway.payment_method.find(subscription.payment_method_token)
    result[payment_method.customer_id] = subscription.price

"""do something with result """

This approach works fine in the BT sandbox and on small queries around 100 records. However, whenever I try to query for more than about 120 subscriptions the BT server responds consistently with a 504 error. I would like to query for around 5000 subscriptions at a time in production. Any suggestions?

Traceback:

Traceback (most recent call last):
File "BT_subscrAmount.py", line 22, in <module>
for subscription in subscriptions.items:
File "/home/karen/miniconda3/lib/python3.6/site- 
packages/braintree/resource_collection.py", line 38, in items
for item in self.__method(self.__query, batch):
File "/home/karen/miniconda3/lib/python3.6/site- 
packages/braintree/subscription_gateway.py", line 79, in __fetch
response = self.config.http().post(self.config.base_merchant_path() + 
"/subscriptions/advanced_search", {"search": criteria})
File "/home/karen/miniconda3/lib/python3.6/site- 
packages/braintree/util/http.py", line 56, in post
return self.__http_do("POST", path, Http.ContentType.Xml, params)
File "/home/karen/miniconda3/lib/python3.6/site- 
packages/braintree/util/http.py", line 86, in __http_do
Http.raise_exception_from_status(status)
File "/home/karen/miniconda3/lib/python3.6/site- 
packages/braintree/util/http.py", line 49, in raise_exception_from_status
raise UnexpectedError("Unexpected HTTP_RESPONSE " + str(status))
braintree.exceptions.unexpected_error.UnexpectedError: Unexpected 
HTTP_RESPONSE 504
Landar
  • 278
  • 1
  • 10
Karen
  • 31
  • 4
  • https://github.com/braintree/braintree_node/issues/74 (BTW, there is almost certainly an error or stacktrace somewhere with more details.) –  Sep 25 '18 at 17:04
  • I have looked at this thread, but I am not doing a customer.find() but a subscription.search() and I am NOT experiencing this in sandbox but in production. Also, my code works with less than a 100 results. – Karen Sep 25 '18 at 17:07
  • Ok, so find the stacktrace and work out what is returning the 504 and find/ask the vendor for info on that HTTP error. The vendor defines these returns. It is almost certainly some sort of timeout. Remember, the idea with SO Q&A is that you show your research. –  Sep 25 '18 at 17:08
  • I have send a question with the traceback to BT, but they just told me to limit my queries. It does not make sense to limit my queries to a 100 or less. – Karen Sep 25 '18 at 17:09
  • [Edit] the question and put the traceback in as text. –  Sep 25 '18 at 17:10

2 Answers2

1

With the help from an amazing BT support engineer, we were finally able to figure out a solution to our problem.

The explanation:

When BT returns search results, they gather all associated data and return a large (serialized) response object to the client. In our case some of the subscriptions had a large number of associated transactions, which makes the process of fetching, serializing and returning them to the client slow. When making a request, the BT gateway returns pages in groups of 50 with a default timeout of 60 seconds. Any group with several large response objects will likely time out and produce a 504 error.

The solution:

To avoid this delay, instead of using the items method, we can use the id method from the ResourceCollections class and return just the ids of the objects. With the individual subscription id, we can fetch the individual subscription and only the attributes we need, like so:

subscriptions = gateway.subscription.search(
    braintree.SubscriptionSearch.status.in_list(
    braintree.Subscription.Status.Active
    )
)

subscription_ids = subscriptions.ids

def find_customer_id(id):
    subscription = gateway.subscription.find(id)
    payment_method = gateway.payment_method.find(subscription.payment_method_token)
    return (payment_method.customer_id, subscription.price) 

Hope this will help someone else!

Karen
  • 31
  • 4
0

Split your queries into small batches (5000+ subscriptions to become 50 calls of 100 subscriptions) and then aggregate as you get the responses back. Many APIs have rate-limits and response limits hard-coded.

Landar
  • 278
  • 1
  • 10
  • Yes, that's probably what I end up doing. The BT API's limit is 10000 though. It's just strange I cannot get more than 120 through. – Karen Sep 25 '18 at 18:19