braintree / braintree_python

Braintree Python library
https://developer.paypal.com/braintree/docs/start/overview
MIT License
242 stars 116 forks source link

Print transactions in JSON format #137

Open mailtobash opened 3 years ago

mailtobash commented 3 years ago

We are writing a connector to write JSON files of transactions that we search in the past day. Version 3.57.1

import braintree
from braintree import Environment, BraintreeGateway, Configuration, TransactionSearch

gateway = braintree.BraintreeGateway(braintree.Configuration(environment=braintree.Environment.Sandbox,merchant_id="XXXXXX",public_key="XXXXX",private_key="XXXXXX"))
results=gateway.transaction.search(TransactionSearch.created_at.between("2021-05-05T09:03:00.000Z", "2021-05-05T10:03:00Z"))

for i in results.items:
    print(i)

Returns :

<Transaction {id: '9r6g66mr', additional_processor_response: None, amount: Decimal('1.00'), authorization_adjustments: [], authorization_expires_at: datetime.datetime(2021, 5, 12, 9, 14, 34), avs_error_response_code: None, avs_postal_code_response_code: 'M', avs_street_address_response_code: 'I', channel: None, created_at: datetime.datetime(2021, 5, 5, 9, 14, 33), credit_card_details: <CreditCard {token: '3qddmkg', bin: '401288', last_4: '1881', card_type: 'Visa', expiration_month: '12', expiration_year: '2022', customer_location: 'US', cardholder_name: None, image_url: 'https://assets.braintreegateway.com/payment_method_logo/visa.png?environment=sandbox', prepaid: 'No', healthcare: 'Unknown', debit: 'Unknown', durbin_regulated: 'Unknown', commercial: 'Unknown', payroll: 'Unknown', issuing_bank: 'Unknown', country_of_issuance: 'Unknown', product_id: 'Unknown', global_id: 'cGF5bWVudG1ldGhvZF9jY18zcWRkbWtn', account_type: None, unique_number_identifier: 'f2425aa6f4215f1be772e5144f480c70', venmo_sdk: False} at 4482418672>, currency_iso_code: 'AUD', cvv_response_code: 'M', discount_amount: None, disputes: [], escrow_status: None, gateway_rejection_reason: None, master_merchant_account_id: None, merchant_account_id: 'belongmobileverificationAUD', network_response_code: 'XX', network_response_text: 'sample network response text', network_transaction_id: '020210505091434', order_id: None, payment_instrument_type: 'credit_card', plan_id: None, processor_authorization_code: 'W95P6L', processor_response_code: '1000', processor_response_text: 'Approved', processor_settlement_response_code: '', processor_settlement_response_text: '', purchase_order_number: None, recurring: False, refund_id: None, refunded_transaction_id: None, service_fee_amount: None, settlement_batch_id: None, shipping_amount: None, ships_from_postal_code: None, status: 'authorization_expired', status_history: [<StatusEvent {timestamp: datetime.datetime(2021, 5, 5, 9, 14, 34), status: 'authorized', amount: Decimal('1.00'), user: 'System-User-Sandbox', transaction_source: 'api'} at 4482418896>, <StatusEvent {timestamp: datetime.datetime(2021, 5, 12, 9, 47, 31), status: 'authorization_expired', amount: Decimal('1.00'), transaction_source: 'recurring'} at 4482418952>], sub_merchant_account_id: None, subscription_id: None, tax_amount: None, tax_exempt: False, type: 'sale', updated_at: datetime.datetime(2021, 5, 12, 9, 47, 31), voice_referral_number: None} at 4482418560>

Is there a way to format it as JSON? I tried emulating the __repr__ method but it is still printing bjects like StatuEvent as <StatusEvent ..... at 1214132321>

Here is what I tried:

detail_list = i._setattrs
details = ", ".join("\"%s\": %r" % (attr, getattr(i, attr)) for attr in detail_list if (hasattr(i, attr) and getattr(i, attr) is not None))
json_data="{%s}"%(details)

Thank you

hollabaq86 commented 2 years ago

👋 @mailtobash I don't believe we have something like this in the python SDK, though we do have similar methods in some of the other SDKs (though they're not well-maintained at this point in time).

I'll leave this open so we can track interest, but I don't have an ETA on when this would be released.

mailtobash commented 2 years ago

If anyone is interested, below is how I implemented a JSON serializer to convert to JSON and write a JSON array to a file.

from datetime import datetime, date
from braintree import Environment, Configuration, TransactionSearch
from braintree.attribute_getter import AttributeGetter
from braintree.status_event import StatusEvent
from braintree.braintree_gateway import BraintreeGateway
import json

def braintree_object_tree_to_dict(braintree_object):
    data = braintree_object.__dict__.copy()
    for key, value in data.items():
        if isinstance(value, AttributeGetter):
            data[key] = braintree_object_tree_to_dict(value)
    return data

def encode_bt_transaction_json(obj):
    # Serialize date times into ISO with Z
    if isinstance(obj, datetime):
        return str(obj.isoformat()) + "Z"
    if isinstance(obj, date):
        return str(obj.isoformat()) + "Z"

    # Do not attempt to serialize methods
    # Culprit being CreditCard.expired that is not useful because it is a helper method
    # that gets all expired cards from the server. We have expiration in credit_card object
    if hasattr(obj, '__call__'):
        return None

    # Custom serialization for StatusEvent
    if isinstance(obj, StatusEvent):
        return {"timestamp": obj.timestamp, "amount": obj.amount, "status": obj.status,
                "user": str(obj.user) if hasattr(obj, "user") else "", "transaction_source": obj.transaction_source}

    # Useless object we dont need it
    if isinstance(obj, BraintreeGateway):
        return None
    raise TypeError(str(type(obj)) + " is not JSON serializable")

# Invocation to the method above:
gateway = BraintreeGateway(Configuration(Environment.Sandbox, merchant_id=merch_id, pub_key,pvt_key))

interval_start=datetime(2009, 1, 1)
interval_end=datetime.datetime(2011, 1, 1)

# search function
search_results = gateway.transaction.search(TransactionSearch.created_at.between(interval_start, interval_end))

try:
        json_results_arr = []

        if search_results:
            for result in search_results.items:
                data = braintree_object_tree_to_dict(result)
                json_result = simplejson.dumps(data, iterable_as_array=True, use_decimal=True,
                                            default=encode_bt_transaction_json, indent=4)
                json_results_arr.append(json_result)

        # Create JSON string comma separated
        json_string = ",".join(json_results_arr)
        # Write as a JSON array
        json_string = "[" + json_string + "]"
        # Write the file
        with open(file_path, 'a') as outfile:
            outfile.write(json_string)
except Exception as e:
        raise Exception("Exception: " + str(e))
hollabaq86 commented 2 years ago

for internal tracking, ticket 1941

nemmison commented 1 year ago

This is something we are looking for as well please

ValeriyVeseliak commented 1 month ago

If anyone is still interested in modifying braintree objects to dict or json, here is my solution:

def braintree_object_to_dict(braintree_object):
        """
        Recursively convert a Braintree object and its nested objects to a dictionary.
        """
        if isinstance(braintree_object, (Configuration, AddOnGateway, BraintreeGateway)):
            return None
        data = braintree_object.__dict__.copy()
        for key, value in data.items():
            if isinstance(value, AttributeGetter):
                data[key] = braintree_object_to_dict(value)
            elif isinstance(value, Decimal):
                data[key] = float(value)
            elif isinstance(value, (datetime, date)):
                data[key] = str(value.isoformat()) + "Z"
            elif hasattr(value, "__dict__"):  # Handle nested objects
                data[key] = braintree_object_to_dict(value)
            elif isinstance(value, list):  # Handle lists of objects
                data[key] = [braintree_object_to_dict(item) if hasattr(item, "__dict__") else
                             (float(item) if isinstance(item, Decimal)
                              else str(item.isoformat()) + "Z" if isinstance(item, (datetime, date))
                             else item) for item in
                             value]
        # Remove private attributes if necessary
        data.pop('_setattrs', None)
        return data