Skip to content

Commit 2354b0d

Browse files
committed
2.7.0
1 parent 2a999b6 commit 2354b0d

16 files changed

+380
-14
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
*.pyc
2-
/tags
2+
/dist
33
/docs/_build
4+
/tags
5+
MANIFEST

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
## 2.7.0
2+
3+
* Added Customer search
4+
* Added dynamic descriptors to Subscriptions and Transactions
5+
* Added level 2 fields to Transactions:
6+
* tax_amount
7+
* tax_exempt
8+
* purchase_order_number
9+
110
## 2.6.1
211

312
* Added billing_address_id to allowed parameters for credit cards create and update

braintree/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
from credit_card_gateway import CreditCardGateway
88
from credit_card_verification import CreditCardVerification
99
from customer import Customer
10+
from customer_search import CustomerSearch
1011
from customer_gateway import CustomerGateway
1112
from discount import Discount
13+
from descriptor import Descriptor
1214
from error_codes import ErrorCodes
1315
from error_result import ErrorResult
1416
from errors import Errors

braintree/customer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ def find(customer_id):
110110

111111
return Configuration.gateway().customer.find(customer_id)
112112

113+
@staticmethod
114+
def search(*query):
115+
return Configuration.gateway().customer.search(*query)
116+
113117
@staticmethod
114118
def tr_data_for_create(tr_data, redirect_url):
115119
""" Builds tr_data for creating a Customer. """

braintree/customer_gateway.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def __init__(self, gateway):
1515

1616
def all(self):
1717
response = self.config.http().post("/customers/advanced_search_ids")
18-
return ResourceCollection(None, response, self.__fetch)
18+
return ResourceCollection({}, response, self.__fetch)
1919

2020
def confirm_transparent_redirect(self, query_string):
2121
id = self.gateway.transparent_redirect._parse_and_validate_query_string(query_string)["id"][0]
@@ -36,6 +36,13 @@ def find(self, customer_id):
3636
except NotFoundError:
3737
raise NotFoundError("customer with id " + customer_id + " not found")
3838

39+
def search(self, *query):
40+
if isinstance(query[0], list):
41+
query = query[0]
42+
43+
response = self.config.http().post("/customers/advanced_search_ids", {"search": self.__criteria(query)})
44+
return ResourceCollection(query, response, self.__fetch)
45+
3946
def tr_data_for_create(self, tr_data, redirect_url):
4047
Resource.verify_keys(tr_data, [{"customer": Customer.create_signature()}])
4148
tr_data["kind"] = TransparentRedirect.Kind.CreateCustomer
@@ -60,9 +67,18 @@ def update(self, customer_id, params={}):
6067
elif "api_error_response" in response:
6168
return ErrorResult(self.gateway, response["api_error_response"])
6269

63-
def __fetch(self, query, ids):
70+
def __criteria(self, query):
6471
criteria = {}
65-
criteria["ids"] = IdsSearch.ids.in_list(ids).to_param()
72+
for term in query:
73+
if criteria.get(term.name):
74+
criteria[term.name] = dict(criteria[term.name].items() + term.to_param().items())
75+
else:
76+
criteria[term.name] = term.to_param()
77+
return criteria
78+
79+
def __fetch(self, query, ids):
80+
criteria = self.__criteria(query)
81+
criteria["ids"] = braintree.customer_search.CustomerSearch.ids.in_list(ids).to_param()
6682
response = self.config.http().post("/customers/advanced_search", {"search": criteria})
6783
return [Customer(self.gateway, item) for item in ResourceCollection._extract_as_array(response["customers"], "customer")]
6884

braintree/customer_search.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from braintree.search import Search
2+
3+
class CustomerSearch:
4+
address_extended_address = Search.TextNodeBuilder("address_extended_address")
5+
address_first_name = Search.TextNodeBuilder("address_first_name")
6+
address_last_name = Search.TextNodeBuilder("address_last_name")
7+
address_locality = Search.TextNodeBuilder("address_locality")
8+
address_postal_code = Search.TextNodeBuilder("address_postal_code")
9+
address_region = Search.TextNodeBuilder("address_region")
10+
address_street_address = Search.TextNodeBuilder("address_street_address")
11+
cardholder_name = Search.TextNodeBuilder("cardholder_name")
12+
company = Search.TextNodeBuilder("company")
13+
created_at = Search.RangeNodeBuilder("created_at")
14+
credit_card_expiration_date = Search.EqualityNodeBuilder("credit_card_expiration_date")
15+
credit_card_number = Search.TextNodeBuilder("credit_card_number")
16+
email = Search.TextNodeBuilder("email")
17+
fax = Search.TextNodeBuilder("fax")
18+
first_name = Search.TextNodeBuilder("first_name")
19+
id = Search.TextNodeBuilder("id")
20+
ids = Search.MultipleValueNodeBuilder("ids")
21+
last_name = Search.TextNodeBuilder("last_name")
22+
payment_method_token = Search.TextNodeBuilder("payment_method_token")
23+
phone = Search.TextNodeBuilder("phone")
24+
website = Search.TextNodeBuilder("website")

braintree/descriptor.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from braintree.resource import Resource
2+
3+
class Descriptor(Resource):
4+
def __init__(self, gateway, attributes):
5+
Resource.__init__(self, gateway, attributes)

braintree/error_codes.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ class Customer(object):
8080
WebsiteIsInvalid = "81616"
8181
WebsiteIsTooLong = "81615"
8282

83+
class Descriptor(object):
84+
NameFormatIsInvalid = "92201"
85+
PhoneFormatIsInvalid = "92202"
86+
8387
class Subscription(object):
8488
BillingDayOfMonthCannotBeUpdated = "91918"
8589
BillingDayOfMonthIsInvalid = "91914"
@@ -103,6 +107,7 @@ class Subscription(object):
103107
PaymentMethodTokenCardTypeIsNotAccepted = "91902"
104108
PaymentMethodTokenIsInvalid = "91903"
105109
PaymentMethodTokenNotAssociatedWithCustomer = "91905"
110+
PlanBillingFrequencyCannotBeUpdated = "91922"
106111
PlanIdIsInvalid = "91904"
107112
PriceCannotBeBlank = "81903"
108113
PriceFormatIsInvalid = "81904"
@@ -134,8 +139,8 @@ class Modification(object):
134139

135140
class Transaction(object):
136141
AmountCannotBeNegative = "81501"
137-
AmountIsRequired = "81502"
138142
AmountIsInvalid = "81503"
143+
AmountIsRequired = "81502"
139144
AmountIsTooLarge = "81528"
140145
AmountMustBeGreaterThanZero = "81531"
141146
BillingAddressConflict = "91530"
@@ -144,16 +149,16 @@ class Transaction(object):
144149
CannotRefundUnlessSettled = "91506"
145150
CannotSubmitForSettlement = "91507"
146151
CreditCardIsRequired = "91508"
147-
CustomerDefaultPaymentMethodCardTypeIsNotAccepted = "81509"
148152
CustomFieldIsInvalid = "91526"
149153
CustomFieldIsTooLong = "81527"
150-
CustomerIdIsInvalid = "91510"
154+
CustomerDefaultPaymentMethodCardTypeIsNotAccepted = "81509"
151155
CustomerDoesNotHaveCreditCard = "91511"
156+
CustomerIdIsInvalid = "91510"
152157
HasAlreadyBeenRefunded = "91512"
153-
MerchantAccountNameIsInvalid = "91513" # Deprecated
154158
MerchantAccountIdIsInvalid = "91513"
155159
MerchantAccountIsSusped = "91514" # Deprecated
156160
MerchantAccountIsSuspended = "91514"
161+
MerchantAccountNameIsInvalid = "91513" # Deprecated
157162
OrderIdIsTooLong = "91501"
158163
PaymentMethodConflict = "91515"
159164
PaymentMethodDoesNotBelongToCustomer = "91516"
@@ -162,14 +167,17 @@ class Transaction(object):
162167
PaymentMethodTokenIsInvalid = "91518"
163168
ProcessorAuthorizationCodeCannotBeSet = "91519"
164169
ProcessorAuthorizationCodeIsInvalid = "81520"
170+
PurchaseOrderNumberIsTooLong = "91537"
165171
RefundAmountIsTooLarge = "91521"
166172
SettlementAmountIsTooLarge = "91522"
167173
SubscriptionDoesNotBelongToCustomer = "91529"
168174
SubscriptionIdIsInvalid = "91528"
169175
SubscriptionStatusMustBePastDue = "91531"
176+
TaxAmountCannotBeNegative = "81534"
177+
TaxAmountFormatIsInvalid = "81535"
178+
TaxAmountIsTooLarge = "81536"
170179
TypeIsInvalid = "91523"
171180
TypeIsRequired = "91524"
172181

173182
class Options(object):
174183
VaultIsDisabled = "91525"
175-

braintree/subscription.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import braintree
44
import warnings
55
from braintree.add_on import AddOn
6+
from braintree.descriptor import Descriptor
67
from braintree.discount import Discount
78
from braintree.exceptions.not_found_error import NotFoundError
89
from braintree.resource_collection import ResourceCollection
@@ -88,6 +89,9 @@ def create_signature():
8889
"trial_duration",
8990
"trial_duration_unit",
9091
"trial_period",
92+
{
93+
"descriptor": [ "name", "phone" ]
94+
},
9195
{
9296
"options": [
9397
"do_not_inherit_add_ons_or_discounts",
@@ -173,6 +177,9 @@ def update_signature():
173177
"payment_method_token",
174178
"plan_id",
175179
"price",
180+
{
181+
"descriptor": [ "name", "phone" ]
182+
},
176183
{
177184
"options": [ "prorate_charges", "replace_all_add_ons_and_discounts", "revert_subscription_on_proration_failure" ]
178185
}
@@ -205,6 +212,8 @@ def __init__(self, gateway, attributes):
205212
self.next_billing_period_amount = Decimal(self.next_billing_period_amount)
206213
if "add_ons" in attributes:
207214
self.add_ons = [AddOn(gateway, add_on) for add_on in self.add_ons]
215+
if "descriptor" in attributes:
216+
self.descriptor = Descriptor(gateway, attributes.pop("descriptor"))
208217
if "discounts" in attributes:
209218
self.discounts = [Discount(gateway, discount) for discount in self.discounts]
210219
if "transactions" in attributes:

braintree/transaction.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from braintree.resource_collection import ResourceCollection
1616
from braintree.transparent_redirect import TransparentRedirect
1717
from braintree.exceptions.not_found_error import NotFoundError
18+
from braintree.descriptor import Descriptor
1819

1920
class Transaction(Resource):
2021
"""
@@ -305,7 +306,7 @@ def create(params):
305306
@staticmethod
306307
def create_signature():
307308
return [
308-
"amount", "customer_id", "merchant_account_id", "order_id", "payment_method_token", "type",
309+
"amount", "customer_id", "merchant_account_id", "order_id", "payment_method_token", "purchase_order_number", "shipping_address_id", "tax_amount", "tax_exempt", "type",
309310
{
310311
"credit_card": [
311312
"token", "cardholder_name", "cvv", "expiration_date", "expiration_month", "expiration_year", "number"
@@ -336,7 +337,8 @@ def create_signature():
336337
"store_shipping_address_in_vault"
337338
]
338339
},
339-
{"custom_fields": ["__any_key__"]}
340+
{"custom_fields": ["__any_key__"]},
341+
{"descriptor": ["name", "phone"]}
340342
]
341343

342344
def __init__(self, gateway, attributes):
@@ -349,6 +351,8 @@ def __init__(self, gateway, attributes):
349351
Resource.__init__(self, gateway, attributes)
350352

351353
self.amount = Decimal(self.amount)
354+
if self.tax_amount:
355+
self.tax_amount = Decimal(self.tax_amount)
352356
if "billing" in attributes:
353357
self.billing_details = Address(gateway, attributes.pop("billing"))
354358
if "credit_card" in attributes:
@@ -363,6 +367,8 @@ def __init__(self, gateway, attributes):
363367
self.discounts = [Discount(gateway, discount) for discount in self.discounts]
364368
if "status_history" in attributes:
365369
self.status_history = [StatusEvent(gateway, status_event) for status_event in self.status_history]
370+
if "descriptor" in attributes:
371+
self.descriptor = Descriptor(gateway, attributes.pop("descriptor"))
366372

367373
@property
368374
def refund_id(self):

0 commit comments

Comments
 (0)