├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .pylintrc ├── .readthedocs.yaml ├── ACKNOWLEDGEMENTS.md ├── CHANGELOG.md ├── Dockerfile ├── Jenkinsfile ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── Rakefile ├── braintree ├── __init__.py ├── account_updater_daily_report.py ├── ach_mandate.py ├── add_on.py ├── add_on_gateway.py ├── address.py ├── address_gateway.py ├── amex_express_checkout_card.py ├── android_pay_card.py ├── apple_pay_card.py ├── apple_pay_gateway.py ├── apple_pay_options.py ├── attribute_getter.py ├── authorization_adjustment.py ├── bin_data.py ├── blik_alias.py ├── braintree_gateway.py ├── client_token.py ├── client_token_gateway.py ├── configuration.py ├── connected_merchant_paypal_status_changed.py ├── connected_merchant_status_transitioned.py ├── credentials_parser.py ├── credit_card.py ├── credit_card_gateway.py ├── credit_card_verification.py ├── credit_card_verification_gateway.py ├── credit_card_verification_search.py ├── customer.py ├── customer_gateway.py ├── customer_search.py ├── customer_session_gateway.py ├── descriptor.py ├── disbursement.py ├── disbursement_detail.py ├── discount.py ├── discount_gateway.py ├── dispute.py ├── dispute_details │ ├── __init__.py │ ├── evidence.py │ ├── paypal_message.py │ └── status_history.py ├── dispute_gateway.py ├── dispute_search.py ├── document_upload.py ├── document_upload_gateway.py ├── enriched_customer_data.py ├── environment.py ├── error_codes.py ├── error_result.py ├── errors.py ├── europe_bank_account.py ├── exceptions │ ├── __init__.py │ ├── authentication_error.py │ ├── authorization_error.py │ ├── braintree_error.py │ ├── configuration_error.py │ ├── gateway_timeout_error.py │ ├── http │ │ ├── __init__.py │ │ ├── connection_error.py │ │ ├── invalid_response_error.py │ │ └── timeout_error.py │ ├── invalid_challenge_error.py │ ├── invalid_signature_error.py │ ├── not_found_error.py │ ├── request_timeout_error.py │ ├── server_error.py │ ├── service_unavailable_error.py │ ├── test_operation_performed_in_production_error.py │ ├── too_many_requests_error.py │ ├── unexpected_error.py │ └── upgrade_required_error.py ├── exchange_rate_quote.py ├── exchange_rate_quote_gateway.py ├── exchange_rate_quote_input.py ├── exchange_rate_quote_payload.py ├── exchange_rate_quote_request.py ├── facilitated_details.py ├── facilitator_details.py ├── granted_payment_instrument_update.py ├── graphql │ ├── __init__.py │ ├── enums │ │ ├── __init__.py │ │ ├── recommendations.py │ │ └── recommended_payment_option.py │ ├── inputs │ │ ├── __init__.py │ │ ├── create_customer_session_input.py │ │ ├── customer_recommendations_input.py │ │ ├── customer_session_input.py │ │ ├── monetary_amount_input.py │ │ ├── paypal_payee_input.py │ │ ├── paypal_purchase_unit_input.py │ │ ├── phone_input.py │ │ └── update_customer_session_input.py │ ├── types │ │ ├── __init__.py │ │ ├── customer_recommendations_payload.py │ │ ├── payment_options.py │ │ └── payment_recommendation.py │ └── unions │ │ ├── __init__.py │ │ └── customer_recommendations.py ├── iban_bank_account.py ├── ids_search.py ├── liability_shift.py ├── local_payment.py ├── local_payment_completed.py ├── local_payment_expired.py ├── local_payment_funded.py ├── local_payment_reversed.py ├── masterpass_card.py ├── merchant.py ├── merchant_account │ ├── __init__.py │ ├── address_details.py │ └── merchant_account.py ├── merchant_account_gateway.py ├── merchant_gateway.py ├── meta_checkout_card.py ├── meta_checkout_token.py ├── modification.py ├── monetary_amount.py ├── oauth_access_revocation.py ├── oauth_credentials.py ├── oauth_gateway.py ├── package_details.py ├── paginated_collection.py ├── paginated_result.py ├── partner_merchant.py ├── payment_facilitator.py ├── payment_instrument_type.py ├── payment_method.py ├── payment_method_customer_data_updated_metadata.py ├── payment_method_gateway.py ├── payment_method_nonce.py ├── payment_method_nonce_gateway.py ├── payment_method_parser.py ├── paypal_account.py ├── paypal_account_gateway.py ├── paypal_here.py ├── paypal_payment_resource.py ├── paypal_payment_resource_gateway.py ├── plan.py ├── plan_gateway.py ├── processor_response_types.py ├── resource.py ├── resource_collection.py ├── revoked_payment_method_metadata.py ├── risk_data.py ├── samsung_pay_card.py ├── search.py ├── sepa_direct_debit_account.py ├── sepa_direct_debit_account_gateway.py ├── settlement_batch_summary.py ├── settlement_batch_summary_gateway.py ├── signature_service.py ├── ssl │ └── api_braintreegateway_com.ca.crt ├── status_event.py ├── sub_merchant.py ├── subscription.py ├── subscription_details.py ├── subscription_gateway.py ├── subscription_search.py ├── subscription_status_event.py ├── successful_result.py ├── test │ ├── __init__.py │ ├── authentication_ids.py │ ├── credit_card_defaults.py │ ├── credit_card_numbers.py │ ├── merchant_account.py │ ├── nonces.py │ └── venmo_sdk.py ├── testing_gateway.py ├── three_d_secure_info.py ├── transaction.py ├── transaction_amounts.py ├── transaction_details.py ├── transaction_gateway.py ├── transaction_line_item.py ├── transaction_line_item_gateway.py ├── transaction_review.py ├── transaction_search.py ├── unknown_payment_method.py ├── us_bank_account.py ├── us_bank_account_gateway.py ├── us_bank_account_verification.py ├── us_bank_account_verification_gateway.py ├── us_bank_account_verification_search.py ├── util │ ├── __init__.py │ ├── constants.py │ ├── crypto.py │ ├── datetime_parser.py │ ├── experimental.py │ ├── generator.py │ ├── graphql_client.py │ ├── http.py │ ├── parser.py │ └── xml_util.py ├── validation_error.py ├── validation_error_collection.py ├── venmo_account.py ├── venmo_profile_data.py ├── version.py ├── visa_checkout_card.py ├── webhook_notification.py ├── webhook_notification_gateway.py ├── webhook_testing.py └── webhook_testing_gateway.py ├── ci.sh ├── dev_requirements.txt ├── docs └── requirements.txt ├── polaris.yml ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── fixtures ├── bt_logo.png ├── gif_extension_bt_logo.gif ├── malformed_pdf.pdf └── too_long.pdf ├── integration ├── __init__.py ├── test_add_ons.py ├── test_address.py ├── test_android_pay.py ├── test_apple_pay.py ├── test_braintree_gateway.py ├── test_client_token.py ├── test_credentials_parser.py ├── test_credit_card.py ├── test_credit_card_verification.py ├── test_credit_card_verification_search.py ├── test_customer.py ├── test_customer_search.py ├── test_customer_session_gateway.py ├── test_disbursement.py ├── test_discounts.py ├── test_dispute_search.py ├── test_disputes.py ├── test_document_upload.py ├── test_exchange_rate_quote.py ├── test_graphql_client.py ├── test_http.py ├── test_masterpass.py ├── test_merchant.py ├── test_merchant_account.py ├── test_oauth.py ├── test_package_tracking.py ├── test_payment_method.py ├── test_payment_method_nonce.py ├── test_payment_method_us_bank_account.py ├── test_paypal_account.py ├── test_paypal_payment_resource.py ├── test_plan.py ├── test_samsung_pay.py ├── test_search.py ├── test_sepa_direct_debit_account.py ├── test_settlement_batch_summary.py ├── test_subscription.py ├── test_test_helper.py ├── test_testing_gateway.py ├── test_transaction.py ├── test_transaction_gateway.py ├── test_transaction_line_item.py ├── test_transaction_line_item_gateway.py ├── test_transaction_payment_facilitator.py ├── test_transaction_search.py ├── test_transaction_with_us_bank_account.py ├── test_us_bank_account.py ├── test_us_bank_account_verification.py └── test_visa_checkout.py ├── test_helper.py └── unit ├── __init__.py ├── graphql ├── __init__.py ├── test_create_customer_session_input.py ├── test_customer_recommendations.py ├── test_customer_recommendations_input.py ├── test_customer_session_input.py ├── test_monetary_amount_input.py ├── test_paypal_payee_input.py ├── test_paypal_purchase_unit_input.py ├── test_phone_input.py └── test_update_customer_session_input.py ├── merchant_account ├── __init__.py └── test_address_details.py ├── test_address.py ├── test_android_pay_card.py ├── test_apple_pay_card.py ├── test_apple_pay_gateway.py ├── test_authorization_adjustment.py ├── test_client_token.py ├── test_configuration.py ├── test_credit_card.py ├── test_credit_card_verification.py ├── test_crypto.py ├── test_customer.py ├── test_customer_session_gateway.py ├── test_disbursement.py ├── test_disbursement_detail.py ├── test_dispute.py ├── test_document_upload.py ├── test_environment.py ├── test_error_result.py ├── test_errors.py ├── test_europe_bank_account.py ├── test_exchange_rate_quote_gateway.py ├── test_exchange_rate_quote_input.py ├── test_exchange_rate_quote_request.py ├── test_exports.py ├── test_graphql_client.py ├── test_http.py ├── test_liability_shift.py ├── test_meta_checkout_card.py ├── test_meta_checkout_token.py ├── test_oauth_access_revocation.py ├── test_paginated_collection.py ├── test_partner_merchant.py ├── test_payment_method_gateway.py ├── test_payment_method_nonce.py ├── test_payment_method_parser.py ├── test_paypal_payment_resource_gateway.py ├── test_resource.py ├── test_resource_collection.py ├── test_risk_data.py ├── test_samsung_pay_card.py ├── test_search.py ├── test_sepa_direct_debit_account.py ├── test_setup.py ├── test_signature_service.py ├── test_subscription.py ├── test_subscription_search.py ├── test_successful_result.py ├── test_three_d_secure_info.py ├── test_transaction.py ├── test_unknown_payment_method.py ├── test_us_bank_account.py ├── test_us_bank_account_verification.py ├── test_validation_error_collection.py ├── test_visa_checkout_card.py ├── test_webhooks.py ├── test_xml_util.py └── util ├── __init__.py ├── test_constants.py ├── test_datetime_parser.py └── test_graphql_client.py /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @PayPal-Braintree/team-sdk-server 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### General information 4 | 5 | * SDK/Library version: 6 | * Environment: 7 | * Language, language version, and OS: 8 | 9 | ### Issue description 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | # Checklist 4 | 5 | - [ ] Added changelog entry 6 | - [ ] I alphabetized all attributes, parameters, and methods by name in any class file I changed 7 | - [ ] Ran unit tests (`python3 -m unittest discover tests/unit`) 8 | - [ ] I have linked the JIRA ticket in the summary section 9 | - [ ] I have reviewed the JIRA ticket to ensure all AC's are met 10 | - [ ] I understand that unless this is a Draft PR or has a DO NOT MERGE label, this PR is considered to be in a deploy ready state and can be deployed if merged to main 11 | 12 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /dist 3 | /docs/_build 4 | /tags 5 | MANIFEST 6 | build 7 | /venv 8 | .idea 9 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | python: 4 | install: 5 | - requirements: docs/requirements.txt 6 | -------------------------------------------------------------------------------- /ACKNOWLEDGEMENTS.md: -------------------------------------------------------------------------------- 1 | Acknowledgements 2 | ---------------- 3 | 4 | The Braintree SDK uses code from the following libraries: 5 | 6 | * [requests](https://github.com/kennethreitz/requests), Apache License, Version 2.0 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye 2 | 3 | RUN apt-get update 4 | RUN apt-get -y install python3 rake 5 | RUN apt-get -y install python3-pip 6 | RUN pip3 install --upgrade pip 7 | RUN pip3 install --upgrade setuptools 8 | 9 | RUN echo 'alias python=python3' >> ~/.bashrc 10 | WORKDIR /braintree-python 11 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!groovy 2 | 3 | def FAILED_STAGE 4 | 5 | pipeline { 6 | agent none 7 | 8 | environment { 9 | REPO_NAME = "braintree-python" 10 | SLACK_CHANNEL = "#auto-team-sdk-builds" 11 | } 12 | 13 | stages { 14 | stage("Audit") { 15 | parallel { 16 | // Runs a static code analysis scan and posts results to the PayPal Polaris server 17 | stage("CodeQL") { 18 | agent { 19 | node { 20 | label "" 21 | customWorkspace "workspace/${REPO_NAME}" 22 | } 23 | } 24 | 25 | steps { 26 | codeQLv2(python: true) 27 | } 28 | 29 | post { 30 | failure { 31 | script { 32 | FAILED_STAGE = env.STAGE_NAME 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2009-2014 Braintree, a division of PayPal, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include braintree/ssl/* 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: console build 2 | 3 | console: build 4 | docker run -it -v="$(PWD):/braintree-python" --net="host" braintree-python /bin/bash -l -c "pip3 install -r dev_requirements.txt;pip3 install pylint;bash" 5 | 6 | build: 7 | docker build -t braintree-python . 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :default => :test 2 | 3 | task :test => ["test:all"] 4 | 5 | namespace :test do 6 | 7 | # Usage: 8 | # rake test:unit 9 | # rake test:unit[test_file] 10 | # rake test:unit[test_file,test_class,test_method] 11 | desc "run unit tests" 12 | task :unit, [:file_name, :class_name, :test_name] do |task, args| 13 | if args.file_name.nil? 14 | sh "python3 -m unittest discover tests/unit" 15 | elsif args.class_name.nil? 16 | sh "python3 -m unittest tests/unit/#{args.file_name}.py" 17 | else 18 | sh "python3 -m unittest tests.unit.#{args.file_name}.#{args.class_name}.#{args.test_name}" 19 | end 20 | end 21 | 22 | # Usage: 23 | # rake test:integration 24 | # rake test:integration[test_file] 25 | # rake test:integration[test_file,test_class,test_method] 26 | desc "run integration tests" 27 | task :integration, [:file_name, :class_name, :test_name] do |task, args| 28 | if args.file_name.nil? 29 | sh "python3 -m unittest discover tests/integration" 30 | elsif args.class_name.nil? 31 | sh "python3 -m unittest tests/integration/#{args.file_name}.py" 32 | else 33 | sh "python3 -m unittest tests.integration.#{args.file_name}.#{args.class_name}.#{args.test_name}" 34 | end 35 | end 36 | 37 | task :all => [:unit, :integration] 38 | end 39 | 40 | task :clean do 41 | rm_rf "build" 42 | rm_rf "dist" 43 | rm_f "MANIFEST" 44 | end 45 | 46 | namespace :pypi do 47 | desc "Upload a new version to PyPI" 48 | task :upload => :clean do 49 | sh "python3 setup.py sdist bdist_wheel" 50 | sh "twine upload dist/*" 51 | end 52 | end 53 | 54 | namespace :lint do 55 | # We are only checking linting errors (for now), 56 | # so we use --disable to skip refactor(R), convention(C), and warning(W) messages 57 | desc "Evaluate test code quality using pylintrc file" 58 | task :tests do 59 | puts `pylint --disable=R,C,W tests --rcfile=.pylintrc --disable=R0801 --disable=W0232` 60 | end 61 | 62 | desc "Evaluate app code quality using pylintrc file" 63 | task :code do 64 | puts `pylint --disable=R,C,W braintree --rcfile=.pylintrc` 65 | end 66 | 67 | desc "Evaluate library code quality using pylintrc file" 68 | task :all do 69 | puts `pylint --disable=R,C,W braintree tests --rcfile=.pylintrc` 70 | end 71 | end 72 | 73 | task :lint => "lint:all" 74 | -------------------------------------------------------------------------------- /braintree/account_updater_daily_report.py: -------------------------------------------------------------------------------- 1 | from braintree.configuration import Configuration 2 | from braintree.resource import Resource 3 | 4 | class AccountUpdaterDailyReport(Resource): 5 | 6 | def __init__(self, gateway, attributes): 7 | Resource.__init__(self, gateway, attributes) 8 | if "report_url" in attributes: 9 | self.report_url = attributes.pop("report_url") 10 | if "report_date" in attributes: 11 | self.report_date = attributes.pop("report_date") 12 | 13 | def __repr__(self): 14 | detail_list = ["report_url", "report_date"] 15 | return super(AccountUpdaterDailyReport, self).__repr__(detail_list) 16 | -------------------------------------------------------------------------------- /braintree/ach_mandate.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.util.datetime_parser import parse_datetime 3 | from braintree.resource import Resource 4 | 5 | class AchMandate(Resource): 6 | 7 | def __init__(self, gateway, attributes): 8 | Resource.__init__(self, gateway, attributes) 9 | -------------------------------------------------------------------------------- /braintree/add_on.py: -------------------------------------------------------------------------------- 1 | from braintree.configuration import Configuration 2 | from braintree.modification import Modification 3 | 4 | class AddOn(Modification): 5 | @staticmethod 6 | def all(): 7 | return Configuration.gateway().add_on.all() 8 | -------------------------------------------------------------------------------- /braintree/add_on_gateway.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.add_on import AddOn 3 | from braintree.resource_collection import ResourceCollection 4 | 5 | class AddOnGateway(object): 6 | def __init__(self, gateway): 7 | self.gateway = gateway 8 | self.config = gateway.config 9 | 10 | def all(self): 11 | response = self.config.http().get(self.config.base_merchant_path() + "/add_ons/") 12 | add_ons = {"add_on": response["add_ons"]} 13 | return [AddOn(self.gateway, item) for item in ResourceCollection._extract_as_array(add_ons, "add_on")] 14 | -------------------------------------------------------------------------------- /braintree/amex_express_checkout_card.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | from warnings import warn 4 | 5 | class AmexExpressCheckoutCard(Resource): 6 | """ 7 | A class representing Braintree Amex Express Checkout card objects. Deprecated 8 | """ 9 | def __init__(self, gateway, attributes): 10 | warn("AmexExpressCheckoutCard is deprecated") 11 | Resource.__init__(self, gateway, attributes) 12 | 13 | if "subscriptions" in attributes: 14 | self.subscriptions = [braintree.subscription.Subscription(gateway, subscription) for subscription in self.subscriptions] 15 | 16 | @property 17 | def expiration_date(self): 18 | return self.expiration_month + "/" + self.expiration_year 19 | -------------------------------------------------------------------------------- /braintree/apple_pay_card.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | 4 | class ApplePayCard(Resource): 5 | """ 6 | A class representing Braintree Apple Pay card objects. 7 | """ 8 | class CardType(object): 9 | """ 10 | Contants representing the type of the credit card. Available types are: 11 | 12 | * Braintree.ApplePayCard.AmEx 13 | * Braintree.ApplePayCard.MasterCard 14 | * Braintree.ApplePayCard.Visa 15 | """ 16 | 17 | AmEx = "Apple Pay - American Express" 18 | MasterCard = "Apple Pay - MasterCard" 19 | Visa = "Apple Pay - Visa" 20 | 21 | def __init__(self, gateway, attributes): 22 | Resource.__init__(self, gateway, attributes) 23 | if hasattr(self, 'expired'): 24 | self.is_expired = self.expired 25 | 26 | if "subscriptions" in attributes: 27 | self.subscriptions = [braintree.subscription.Subscription(gateway, subscription) for subscription in self.subscriptions] 28 | 29 | @property 30 | def expiration_date(self): 31 | if not self.expiration_month or not self.expiration_year: 32 | return None 33 | return self.expiration_month + "/" + self.expiration_year 34 | 35 | @staticmethod 36 | def signature(): 37 | options = ["make_default"] 38 | 39 | signature = [ 40 | "customer_id", 41 | "cardholder_name", 42 | "expiration_month", 43 | "expiration_year", 44 | "number", 45 | "cryptogram", 46 | "eci_indicator", 47 | "token", 48 | { 49 | "options": options 50 | }, 51 | { 52 | "billing_address": [ 53 | "company", 54 | "country_code_alpha2", 55 | "country_code_alpha3", 56 | "country_code_numeric", 57 | "country_name", 58 | "extended_address", 59 | "first_name", 60 | "last_name", 61 | "locality", 62 | "postal_code", 63 | "phone_number", 64 | "region", 65 | "street_address" 66 | ] 67 | } 68 | ] 69 | 70 | return signature 71 | 72 | -------------------------------------------------------------------------------- /braintree/apple_pay_gateway.py: -------------------------------------------------------------------------------- 1 | from html import escape 2 | from braintree.apple_pay_options import ApplePayOptions 3 | from braintree.error_result import ErrorResult 4 | from braintree.successful_result import SuccessfulResult 5 | from braintree.exceptions.unexpected_error import UnexpectedError 6 | 7 | class ApplePayGateway(object): 8 | def __init__(self, gateway): 9 | self.gateway = gateway 10 | self.config = gateway.config 11 | 12 | def register_domain(self, domain): 13 | response = self.config.http().post(self.config.base_merchant_path() + "/processing/apple_pay/validate_domains", {'url': domain}) 14 | 15 | if "response" in response and response["response"]["success"]: 16 | return SuccessfulResult() 17 | elif response["api_error_response"]: 18 | return ErrorResult(self.gateway, response["api_error_response"]) 19 | 20 | def unregister_domain(self, domain): 21 | self.config.http().delete(self.config.base_merchant_path() + "/processing/apple_pay/unregister_domain?url=" + escape(domain)) 22 | return SuccessfulResult() 23 | 24 | def registered_domains(self): 25 | response = self.config.http().get(self.config.base_merchant_path() + "/processing/apple_pay/registered_domains") 26 | 27 | if "response" in response: 28 | response = ApplePayOptions(response.pop("response")) 29 | 30 | return response.domains 31 | -------------------------------------------------------------------------------- /braintree/apple_pay_options.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class ApplePayOptions(AttributeGetter): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/attribute_getter.py: -------------------------------------------------------------------------------- 1 | class AttributeGetter(object): 2 | """ 3 | Helper class for objects that define their attributes from dictionaries 4 | passed in during instantiation. 5 | 6 | Example: 7 | 8 | a = AttributeGetter({'foo': 'bar', 'baz': 5}) 9 | a.foo 10 | >> 'bar' 11 | a.baz 12 | >> 5 13 | 14 | Typically inherited by subclasses instead of directly instantiated. 15 | """ 16 | def __init__(self, attributes=None): 17 | if attributes is None: 18 | attributes = {} 19 | self._setattrs = [] 20 | for key, val in attributes.items(): 21 | setattr(self, key, val) 22 | self._setattrs.append(key) 23 | if key == "global_id": 24 | setattr(self, "graphql_id", val) 25 | self._setattrs.append("graphql_id") 26 | 27 | def __repr__(self, detail_list=None): 28 | if detail_list is None: 29 | detail_list = self._setattrs 30 | 31 | details = ", ".join("%s: %r" % (attr, getattr(self, attr)) 32 | for attr in detail_list 33 | if hasattr(self, attr)) 34 | return "<%s {%s} at %d>" % (self.__class__.__name__, details, id(self)) 35 | -------------------------------------------------------------------------------- /braintree/authorization_adjustment.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from braintree.attribute_getter import AttributeGetter 3 | 4 | class AuthorizationAdjustment(AttributeGetter): 5 | def __init__(self, attributes): 6 | AttributeGetter.__init__(self, attributes) 7 | if getattr(self, "amount", None) is not None: 8 | self.amount = Decimal(self.amount) 9 | -------------------------------------------------------------------------------- /braintree/bin_data.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class BinData(AttributeGetter): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/blik_alias.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | 3 | from braintree.resource import Resource 4 | 5 | class BlikAlias(Resource): 6 | """ 7 | A class representing a BlikAlias. 8 | 9 | For more information on BlikAliases, see https://developer.paypal.com/braintree/docs/guides/local-payment-methods/blik-one-click 10 | 11 | """ 12 | 13 | def __init__(self, gateway, attributes): 14 | Resource.__init__(self, gateway, attributes) 15 | 16 | -------------------------------------------------------------------------------- /braintree/client_token.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import urllib 4 | from braintree.configuration import Configuration 5 | from braintree.signature_service import SignatureService 6 | from braintree.util.crypto import Crypto 7 | from braintree import exceptions 8 | 9 | 10 | class ClientToken(object): 11 | 12 | @staticmethod 13 | def generate(params=None, gateway=None): 14 | if params is None: 15 | params = {} 16 | if gateway is None: 17 | gateway = Configuration.gateway().client_token 18 | 19 | return gateway.generate(params) 20 | 21 | @staticmethod 22 | def generate_signature(): 23 | return [ 24 | "customer_id", 25 | "merchant_account_id", 26 | "proxy_merchant_id", 27 | "version", 28 | {"domains": ["__any_key__"]}, 29 | {"options": ["fail_on_duplicate_payment_method", "fail_on_duplicate_payment_method_for_customer", "make_default", "verify_card"]} 30 | ] 31 | -------------------------------------------------------------------------------- /braintree/client_token_gateway.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | from braintree.client_token import ClientToken 4 | from braintree import exceptions 5 | 6 | 7 | class ClientTokenGateway(object): 8 | def __init__(self, gateway): 9 | self.gateway = gateway 10 | self.config = gateway.config 11 | 12 | def generate(self, params=None): 13 | if params is None: 14 | params = {} 15 | if "options" in params and "customer_id" not in params: 16 | for option in ["fail_on_duplicate_payment_method", "fail_on_duplicate_payment_method_for_customer", "make_default", "verify_card"]: 17 | if option in params["options"]: 18 | raise exceptions.InvalidSignatureError("cannot specify %s without a customer_id" % option) 19 | 20 | if "version" not in params: 21 | params["version"] = 2 22 | 23 | Resource.verify_keys(params, ClientToken.generate_signature()) 24 | params = {'client_token': params} 25 | 26 | response = self.config.http().post(self.config.base_merchant_path() + "/client_token", params) 27 | 28 | if "client_token" in response: 29 | return response["client_token"]["value"] 30 | else: 31 | raise ValueError(response["api_error_response"]["message"]) 32 | -------------------------------------------------------------------------------- /braintree/connected_merchant_paypal_status_changed.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | 3 | class ConnectedMerchantPayPalStatusChanged(Resource): 4 | 5 | def __init__(self, gateway, attributes): 6 | Resource.__init__(self, gateway, attributes) 7 | 8 | @property 9 | def merchant_id(self): 10 | return self.merchant_public_id 11 | -------------------------------------------------------------------------------- /braintree/connected_merchant_status_transitioned.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | 3 | class ConnectedMerchantStatusTransitioned(Resource): 4 | 5 | def __init__(self, gateway, attributes): 6 | Resource.__init__(self, gateway, attributes) 7 | 8 | @property 9 | def merchant_id(self): 10 | return self.merchant_public_id 11 | -------------------------------------------------------------------------------- /braintree/credentials_parser.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import braintree 4 | from braintree.exceptions.configuration_error import ConfigurationError 5 | from braintree.environment import Environment 6 | 7 | class CredentialsParser(object): 8 | def __init__(self, client_id=None, client_secret=None, access_token=None): 9 | self.client_id = client_id 10 | self.client_secret = client_secret 11 | self.access_token = access_token 12 | 13 | def parse_client_credentials(self): 14 | if self.client_id is None and self.client_secret is not None: 15 | raise ConfigurationError("Missing client_id when constructing BraintreeGateway") 16 | if self.client_secret is None and self.client_id is not None: 17 | raise ConfigurationError("Missing client_secret when constructing BraintreeGateway") 18 | if not self.client_id.startswith("client_id"): 19 | raise ConfigurationError("Value passed for client_id is not a client_id") 20 | if not self.client_secret.startswith("client_secret"): 21 | raise ConfigurationError("Value passed for client_secret is not a client_secret") 22 | 23 | client_id_environment = self.get_environment(self.client_id) 24 | client_secret_environment = self.get_environment(self.client_secret) 25 | 26 | if client_id_environment is client_secret_environment: 27 | self.environment = client_id_environment 28 | else: 29 | raise ConfigurationError(" ".join([ 30 | "Mismatched credential environments: client_id environment is:", 31 | str(client_id_environment), 32 | "and client_secret environment is:", 33 | str(client_secret_environment) 34 | ])) 35 | 36 | def parse_access_token(self): 37 | self.environment = self.get_environment(self.access_token) 38 | self.merchant_id = self.get_merchant_id(self.access_token) 39 | 40 | def get_environment(self, credential): 41 | parts = credential.split("$") 42 | return Environment.All.get(parts[1]) 43 | 44 | def get_merchant_id(self, credential): 45 | parts = credential.split("$") 46 | return parts[2] 47 | -------------------------------------------------------------------------------- /braintree/credit_card_verification_search.py: -------------------------------------------------------------------------------- 1 | from braintree.credit_card import CreditCard 2 | from braintree.credit_card_verification import CreditCardVerification 3 | from braintree.search import Search 4 | from braintree.util import Constants 5 | 6 | class CreditCardVerificationSearch: 7 | credit_card_cardholder_name = Search.TextNodeBuilder("credit_card_cardholder_name") 8 | id = Search.TextNodeBuilder("id") 9 | credit_card_expiration_date = Search.EqualityNodeBuilder("credit_card_expiration_date") 10 | credit_card_number = Search.PartialMatchNodeBuilder("credit_card_number") 11 | credit_card_card_type = Search.MultipleValueNodeBuilder("credit_card_card_type", Constants.get_all_constant_values_from_class(CreditCard.CardType)) 12 | ids = Search.MultipleValueNodeBuilder("ids") 13 | created_at = Search.RangeNodeBuilder("created_at") 14 | status = Search.MultipleValueNodeBuilder("status", Constants.get_all_constant_values_from_class(CreditCardVerification.Status)) 15 | billing_postal_code = Search.TextNodeBuilder("billing_address_details_postal_code") 16 | customer_email = Search.TextNodeBuilder("customer_email") 17 | customer_id = Search.TextNodeBuilder("customer_id") 18 | payment_method_token = Search.TextNodeBuilder("payment_method_token") 19 | -------------------------------------------------------------------------------- /braintree/customer_search.py: -------------------------------------------------------------------------------- 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 | address_country_name = Search.TextNodeBuilder("address_country_name") 12 | cardholder_name = Search.TextNodeBuilder("cardholder_name") 13 | company = Search.TextNodeBuilder("company") 14 | created_at = Search.RangeNodeBuilder("created_at") 15 | credit_card_expiration_date = Search.EqualityNodeBuilder("credit_card_expiration_date") 16 | credit_card_number = Search.TextNodeBuilder("credit_card_number") 17 | email = Search.TextNodeBuilder("email") 18 | fax = Search.TextNodeBuilder("fax") 19 | first_name = Search.TextNodeBuilder("first_name") 20 | id = Search.TextNodeBuilder("id") 21 | ids = Search.MultipleValueNodeBuilder("ids") 22 | last_name = Search.TextNodeBuilder("last_name") 23 | payment_method_token = Search.TextNodeBuilder("payment_method_token") 24 | payment_method_token_with_duplicates = Search.IsNodeBuilder("payment_method_token_with_duplicates") 25 | phone = Search.TextNodeBuilder("phone") 26 | website = Search.TextNodeBuilder("website") 27 | paypal_account_email = Search.TextNodeBuilder("paypal_account_email") 28 | -------------------------------------------------------------------------------- /braintree/descriptor.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | 3 | class Descriptor(Resource): 4 | def __init__(self, gateway, attributes): 5 | Resource.__init__(self, gateway, attributes) 6 | -------------------------------------------------------------------------------- /braintree/disbursement.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from braintree.resource import Resource 3 | from braintree.transaction_search import TransactionSearch 4 | from braintree.merchant_account import MerchantAccount 5 | 6 | class Disbursement(Resource): 7 | class Type(object): 8 | """ 9 | """ 10 | 11 | Credit = "credit" 12 | Debit = "debit" 13 | 14 | def __init__(self, gateway, attributes): 15 | Resource.__init__(self, gateway, attributes) 16 | self.amount = Decimal(self.amount) 17 | self.merchant_account = MerchantAccount(gateway, attributes["merchant_account"]) 18 | 19 | def __repr__(self): 20 | detail_list = ["amount", "disbursement_date", "exception_message", "follow_up_action", "id", "success", "retry"] 21 | return super(Disbursement, self).__repr__(detail_list) 22 | 23 | def transactions(self): 24 | return self.gateway.transaction.search([TransactionSearch.ids.in_list(self.transaction_ids)]) 25 | 26 | def is_credit(self): 27 | return self.disbursement_type == Disbursement.Type.Credit 28 | 29 | def is_debit(self): 30 | return self.disbursement_type == Disbursement.Type.Debit 31 | -------------------------------------------------------------------------------- /braintree/disbursement_detail.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from braintree.attribute_getter import AttributeGetter 3 | 4 | class DisbursementDetail(AttributeGetter): 5 | def __init__(self, attributes): 6 | AttributeGetter.__init__(self, attributes) 7 | 8 | if getattr(self, "settlement_amount", None) is not None: 9 | self.settlement_amount = Decimal(self.settlement_amount) 10 | if getattr(self, "settlement_currency_exchange_rate", None) is not None: 11 | self.settlement_currency_exchange_rate = Decimal(self.settlement_currency_exchange_rate) 12 | 13 | @property 14 | def is_valid(self): 15 | return self.disbursement_date is not None 16 | -------------------------------------------------------------------------------- /braintree/discount.py: -------------------------------------------------------------------------------- 1 | from braintree.modification import Modification 2 | from braintree.configuration import Configuration 3 | 4 | 5 | class Discount(Modification): 6 | 7 | @staticmethod 8 | def all(): 9 | return Configuration.gateway().discount.all() 10 | -------------------------------------------------------------------------------- /braintree/discount_gateway.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.discount import Discount 3 | from braintree.resource_collection import ResourceCollection 4 | 5 | class DiscountGateway(object): 6 | def __init__(self, gateway): 7 | self.gateway = gateway 8 | self.config = gateway.config 9 | 10 | def all(self): 11 | response = self.config.http().get(self.config.base_merchant_path() + "/discounts/") 12 | discounts = {"discount": response["discounts"]} 13 | return [Discount(self.gateway, item) for item in ResourceCollection._extract_as_array(discounts, "discount")] 14 | -------------------------------------------------------------------------------- /braintree/dispute_details/__init__.py: -------------------------------------------------------------------------------- 1 | from braintree.dispute_details.evidence import DisputeEvidence 2 | from braintree.dispute_details.paypal_message import DisputePayPalMessage 3 | from braintree.dispute_details.status_history import DisputeStatusHistory 4 | -------------------------------------------------------------------------------- /braintree/dispute_details/evidence.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class DisputeEvidence(AttributeGetter): 4 | def __init__(self, attributes): 5 | attributes["tag"] = attributes.get("category") 6 | AttributeGetter.__init__(self, attributes) 7 | -------------------------------------------------------------------------------- /braintree/dispute_details/paypal_message.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class DisputePayPalMessage(AttributeGetter): 4 | def __init__(self, attributes): 5 | AttributeGetter.__init__(self, attributes) 6 | -------------------------------------------------------------------------------- /braintree/dispute_details/status_history.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class DisputeStatusHistory(AttributeGetter): 4 | def __init__(self, attributes): 5 | AttributeGetter.__init__(self, attributes) 6 | -------------------------------------------------------------------------------- /braintree/dispute_search.py: -------------------------------------------------------------------------------- 1 | from braintree.search import Search 2 | 3 | class DisputeSearch: 4 | amount_disputed = Search.RangeNodeBuilder("amount_disputed") 5 | amount_won = Search.RangeNodeBuilder("amount_won") 6 | case_number = Search.TextNodeBuilder("case_number") 7 | # NEXT_MAJOR_VERSION Remove this attribute 8 | # DEPRECATED The chargeback_protection_level attribute is deprecated in favor of protection_level 9 | chargeback_protection_level = Search.MultipleValueNodeBuilder("chargeback_protection_level") 10 | protection_level = Search.MultipleValueNodeBuilder("protection_level") 11 | customer_id = Search.TextNodeBuilder("customer_id") 12 | disbursement_date = Search.RangeNodeBuilder("disbursement_date") 13 | effective_date = Search.RangeNodeBuilder("effective_date") 14 | id = Search.TextNodeBuilder("id") 15 | kind = Search.MultipleValueNodeBuilder("kind") 16 | merchant_account_id = Search.MultipleValueNodeBuilder("merchant_account_id") 17 | pre_dispute_program = Search.MultipleValueNodeBuilder("pre_dispute_program") 18 | reason = Search.MultipleValueNodeBuilder("reason") 19 | reason_code = Search.MultipleValueNodeBuilder("reason_code") 20 | received_date = Search.RangeNodeBuilder("received_date") 21 | reference_number = Search.TextNodeBuilder("reference_number") 22 | reply_by_date = Search.RangeNodeBuilder("reply_by_date") 23 | status = Search.MultipleValueNodeBuilder("status") 24 | transaction_id = Search.TextNodeBuilder("transaction_id") 25 | transaction_source = Search.MultipleValueNodeBuilder("transaction_source") 26 | -------------------------------------------------------------------------------- /braintree/document_upload.py: -------------------------------------------------------------------------------- 1 | import mimetypes 2 | from braintree.successful_result import SuccessfulResult 3 | from braintree.resource import Resource 4 | from braintree.configuration import Configuration 5 | 6 | 7 | class DocumentUpload(Resource): 8 | """ 9 | A class representing a DocumentUpload. 10 | 11 | An example of creating a document upload with all available fields: 12 | 13 | result = braintree.DocumentUpload.create( 14 | { 15 | "kind": braintree.DocumentUpload.Kind.EvidenceDocument, 16 | "file": open("path/to/file", "rb"), 17 | } 18 | ) 19 | 20 | For more information on DocumentUploads, see https://developer.paypal.com/braintree/docs/reference/request/document-upload/create 21 | 22 | """ 23 | 24 | class Kind(object): 25 | EvidenceDocument = "evidence_document" 26 | 27 | @staticmethod 28 | def create(params=None): 29 | """ 30 | Create a DocumentUpload 31 | 32 | File and Kind are required: 33 | 34 | result = braintree.DocumentUpload.create( 35 | { 36 | "kind": braintree.DocumentUpload.Kind.EvidenceDocument, 37 | "file": open("path/to/file", "rb"), 38 | } 39 | ) 40 | 41 | """ 42 | if params is None: 43 | params = {} 44 | return Configuration.gateway().document_upload.create(params) 45 | 46 | @staticmethod 47 | def create_signature(): 48 | return [ 49 | "kind", 50 | "file", 51 | ] 52 | 53 | def __init__(self, gateway, attributes): 54 | Resource.__init__(self, gateway, attributes) 55 | -------------------------------------------------------------------------------- /braintree/document_upload_gateway.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | import mimetypes 3 | from braintree.document_upload import DocumentUpload 4 | from braintree.error_result import ErrorResult 5 | from braintree.resource import Resource 6 | from braintree.successful_result import SuccessfulResult 7 | 8 | 9 | class DocumentUploadGateway(object): 10 | def __init__(self, gateway): 11 | self.gateway = gateway 12 | self.config = gateway.config 13 | 14 | def create(self, params=None): 15 | if params is None: 16 | params = {} 17 | Resource.verify_keys(params, DocumentUpload.create_signature()) 18 | 19 | if "file" in params and not hasattr(params["file"], "read"): 20 | raise ValueError("file must be a file handle") 21 | 22 | response = self.config.http().post_multipart(self.config.base_merchant_path() + "/document_uploads", *self.__payload(params)) 23 | 24 | if "api_error_response" in response: 25 | return ErrorResult(self.gateway, response["api_error_response"]) 26 | else: 27 | return SuccessfulResult({"document_upload": DocumentUpload(self, response["document_upload"])}) 28 | 29 | def __file_name(self, file): 30 | return file.name.split("/")[-1] 31 | 32 | def __content_type(self, file): 33 | return mimetypes.guess_type(file.name)[0] 34 | 35 | def __payload(self, params): 36 | file = params.pop("file", None) 37 | files = { 38 | "file": (self.__file_name(file), file, self.__content_type(file)) 39 | } 40 | params["document_upload[kind]"] = params["kind"] 41 | 42 | return (files, params) 43 | -------------------------------------------------------------------------------- /braintree/enriched_customer_data.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | from braintree.venmo_profile_data import VenmoProfileData 3 | 4 | class EnrichedCustomerData(Resource): 5 | """ 6 | A class representing Braintree EnrichedCustomerData object. 7 | """ 8 | def __init__(self, gateway, attributes): 9 | Resource.__init__(self, gateway, attributes) 10 | self.profile_data = VenmoProfileData(gateway, attributes.pop("profile_data")) 11 | -------------------------------------------------------------------------------- /braintree/errors.py: -------------------------------------------------------------------------------- 1 | from braintree.validation_error_collection import ValidationErrorCollection 2 | 3 | class Errors(object): 4 | def __init__(self, data): 5 | if "errors" not in data: 6 | data["errors"] = [] 7 | self.errors = ValidationErrorCollection(data) 8 | self.size = self.errors.deep_size 9 | 10 | @property 11 | def deep_errors(self): 12 | return self.errors.deep_errors 13 | 14 | def for_object(self, key): 15 | return self.errors.for_object(key) 16 | 17 | def __len__(self): 18 | return self.size 19 | -------------------------------------------------------------------------------- /braintree/europe_bank_account.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | from braintree.configuration import Configuration 4 | 5 | #NEXT_MAJOR_VERSION this was specific to iDEAL integrations and can be removed 6 | class EuropeBankAccount(Resource): 7 | class MandateType(object): 8 | """ 9 | Constants representing the type of the mandate. Available type: 10 | * Braintree.EuropeBankAccount.MandateType.Business 11 | * Braintree.EuropeBankAccount.MandateType.Consumer 12 | """ 13 | Business = "business" 14 | Consumer = "consumer" 15 | 16 | @staticmethod 17 | def signature(): 18 | signature = [ 19 | "billing_address", 20 | "customer_id", 21 | "token", 22 | "masked_iban", 23 | "bic", 24 | "mandate_reference_number", 25 | "mandate_accepted_at", 26 | "account_holder_name" 27 | ] 28 | return signature 29 | -------------------------------------------------------------------------------- /braintree/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.authentication_error import AuthenticationError 2 | from braintree.exceptions.authorization_error import AuthorizationError 3 | from braintree.exceptions.configuration_error import ConfigurationError 4 | from braintree.exceptions.gateway_timeout_error import GatewayTimeoutError 5 | from braintree.exceptions.invalid_challenge_error import InvalidChallengeError 6 | from braintree.exceptions.invalid_signature_error import InvalidSignatureError 7 | from braintree.exceptions.not_found_error import NotFoundError 8 | from braintree.exceptions.request_timeout_error import RequestTimeoutError 9 | from braintree.exceptions.server_error import ServerError 10 | from braintree.exceptions.service_unavailable_error import ServiceUnavailableError 11 | from braintree.exceptions.test_operation_performed_in_production_error import TestOperationPerformedInProductionError 12 | from braintree.exceptions.too_many_requests_error import TooManyRequestsError 13 | from braintree.exceptions.unexpected_error import UnexpectedError 14 | from braintree.exceptions.upgrade_required_error import UpgradeRequiredError 15 | -------------------------------------------------------------------------------- /braintree/exceptions/authentication_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class AuthenticationError(BraintreeError): 4 | """ 5 | Raised when the client library cannot authenticate with the gateway. This generally means the public_key/private key are incorrect, or the user is not active. 6 | 7 | See https://developer.paypal.com/braintree/docs/reference/general/exceptions/python#authentication-error 8 | """ 9 | pass 10 | -------------------------------------------------------------------------------- /braintree/exceptions/authorization_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class AuthorizationError(BraintreeError): 4 | """ 5 | Raised when the user does not have permission to complete the requested operation. 6 | 7 | See https://developer.paypal.com/braintree/docs/reference/general/exceptions/python#authorization-error 8 | """ 9 | pass 10 | -------------------------------------------------------------------------------- /braintree/exceptions/braintree_error.py: -------------------------------------------------------------------------------- 1 | class BraintreeError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /braintree/exceptions/configuration_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.unexpected_error import UnexpectedError 2 | 3 | class ConfigurationError(UnexpectedError): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/exceptions/gateway_timeout_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class GatewayTimeoutError(BraintreeError): 4 | """ 5 | Raised when a gateway response timeout occurs. 6 | """ 7 | pass 8 | -------------------------------------------------------------------------------- /braintree/exceptions/http/__init__.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.http.connection_error import ConnectionError 2 | from braintree.exceptions.http.invalid_response_error import InvalidResponseError 3 | from braintree.exceptions.http.timeout_error import TimeoutError 4 | -------------------------------------------------------------------------------- /braintree/exceptions/http/connection_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.unexpected_error import UnexpectedError 2 | 3 | class ConnectionError(UnexpectedError): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/exceptions/http/invalid_response_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.unexpected_error import UnexpectedError 2 | 3 | class InvalidResponseError(UnexpectedError): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/exceptions/http/timeout_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.unexpected_error import UnexpectedError 2 | 3 | class TimeoutError(UnexpectedError): 4 | pass 5 | 6 | 7 | class ConnectTimeoutError(TimeoutError): 8 | pass 9 | 10 | 11 | class ReadTimeoutError(TimeoutError): 12 | pass 13 | -------------------------------------------------------------------------------- /braintree/exceptions/invalid_challenge_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class InvalidChallengeError(BraintreeError): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/exceptions/invalid_signature_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class InvalidSignatureError(BraintreeError): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/exceptions/not_found_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class NotFoundError(BraintreeError): 4 | """ 5 | Raised when an object is not found in the gateway, such as a Transaction.find("bad_id"). 6 | 7 | See https://developer.paypal.com/braintree/docs/reference/general/exceptions/python#not-found-error 8 | """ 9 | pass 10 | -------------------------------------------------------------------------------- /braintree/exceptions/request_timeout_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class RequestTimeoutError(BraintreeError): 4 | """ 5 | Raised when a client request timeout occurs. 6 | """ 7 | pass 8 | -------------------------------------------------------------------------------- /braintree/exceptions/server_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class ServerError(BraintreeError): 4 | """ 5 | Raised when the gateway raises an error. Please contact support at support@getbraintree.com. 6 | 7 | See https://developer.paypal.com/braintree/docs/reference/general/exceptions/python#server-error 8 | """ 9 | pass 10 | -------------------------------------------------------------------------------- /braintree/exceptions/service_unavailable_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class ServiceUnavailableError(BraintreeError): 4 | """ 5 | Raised when the gateway is unavailable. 6 | """ 7 | pass 8 | -------------------------------------------------------------------------------- /braintree/exceptions/test_operation_performed_in_production_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class TestOperationPerformedInProductionError(BraintreeError): 4 | """ 5 | Raised when an operation that should be used for testing is used in a production environment 6 | """ 7 | pass 8 | -------------------------------------------------------------------------------- /braintree/exceptions/too_many_requests_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class TooManyRequestsError(BraintreeError): 4 | """ 5 | Raised when the rate limit request threshold is exceeded. 6 | """ 7 | pass 8 | -------------------------------------------------------------------------------- /braintree/exceptions/unexpected_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class UnexpectedError(BraintreeError): 4 | """ Raised for unknown or unexpected errors. """ 5 | pass 6 | -------------------------------------------------------------------------------- /braintree/exceptions/upgrade_required_error.py: -------------------------------------------------------------------------------- 1 | from braintree.exceptions.braintree_error import BraintreeError 2 | 3 | class UpgradeRequiredError(BraintreeError): 4 | """ 5 | Raised for unsupported client library versions. 6 | 7 | See https://developer.paypal.com/braintree/docs/reference/general/exceptions/python#upgrade-required-error 8 | """ 9 | pass 10 | -------------------------------------------------------------------------------- /braintree/exchange_rate_quote.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | from braintree.monetary_amount import MonetaryAmount 3 | 4 | class ExchangeRateQuote(AttributeGetter): 5 | def __init__(self,attributes): 6 | AttributeGetter.__init__(self,attributes) -------------------------------------------------------------------------------- /braintree/exchange_rate_quote_gateway.py: -------------------------------------------------------------------------------- 1 | from braintree.exchange_rate_quote_payload import ExchangeRateQuotePayload 2 | from braintree.error_result import ErrorResult 3 | from braintree.successful_result import SuccessfulResult 4 | 5 | class ExchangeRateQuoteGateway(object): 6 | def __init__(self, gateway, graphql_client = None): 7 | self.gateway = gateway 8 | self.config = gateway.config 9 | self.graphql_client = None if graphql_client is None else graphql_client 10 | 11 | def generate(self, request): 12 | definition = """ 13 | mutation ($exchangeRateQuoteRequest: GenerateExchangeRateQuoteInput!) { 14 | generateExchangeRateQuote(input: $exchangeRateQuoteRequest) { 15 | quotes { 16 | id 17 | baseAmount {value, currencyCode} 18 | quoteAmount {value, currencyCode} 19 | exchangeRate 20 | tradeRate 21 | expiresAt 22 | refreshesAt 23 | } 24 | } 25 | }""" 26 | 27 | param = request.to_graphql_variables() 28 | graphql_client = self.graphql_client if self.graphql_client is not None else self.gateway.graphql_client 29 | response = graphql_client.query(definition, param) 30 | 31 | if "data" in response and "generateExchangeRateQuote" in response["data"]: 32 | result = response["data"]["generateExchangeRateQuote"] 33 | self.exchange_rate_quote_payload = ExchangeRateQuotePayload(result) 34 | return SuccessfulResult({"exchange_rate_quote_payload": self.exchange_rate_quote_payload}) 35 | elif "errors" in response: 36 | error_codes = response["errors"][0] 37 | error_codes["errors"] = dict() 38 | return ErrorResult(self.gateway, error_codes) -------------------------------------------------------------------------------- /braintree/exchange_rate_quote_input.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class ExchangeRateQuoteInput(AttributeGetter): 4 | def __init__(self,parent,attributes): 5 | self.parent = parent 6 | AttributeGetter.__init__(self,attributes) 7 | 8 | def done(self): 9 | return self.parent 10 | 11 | def to_graphql_variables(self): 12 | variables = dict() 13 | variables["baseCurrency"] = self.base_currency if getattr(self,"base_currency",None) is not None else None 14 | variables["quoteCurrency"] = self.quote_currency if getattr(self,"quote_currency",None) is not None else None 15 | variables["baseAmount"] = self.base_amount if getattr(self,"base_amount",None) is not None else None 16 | variables["markup"] = self.markup if getattr(self,"markup",None) is not None else None 17 | return variables -------------------------------------------------------------------------------- /braintree/exchange_rate_quote_payload.py: -------------------------------------------------------------------------------- 1 | from braintree.exchange_rate_quote import ExchangeRateQuote 2 | from braintree.monetary_amount import MonetaryAmount 3 | 4 | class ExchangeRateQuotePayload(object): 5 | def __init__(self, data): 6 | quote_objs = data.get("quotes") 7 | if(quote_objs is not None): 8 | self.quotes = list() 9 | for quote_obj in quote_objs: 10 | base_amount_obj = quote_obj.get("baseAmount") 11 | quote_amount_obj = quote_obj.get("quoteAmount") 12 | base_attrs = {"value":base_amount_obj.get("value"), 13 | "currency_code":base_amount_obj.get("currencyCode")} 14 | base_amount = MonetaryAmount(base_attrs) 15 | quote_attrs = {"value":quote_amount_obj.get("value"), 16 | "currency_code":quote_amount_obj.get("currencyCode")} 17 | quote_amount = MonetaryAmount(quote_attrs) 18 | attributes = {"id":quote_obj.get("id"), 19 | "exchange_rate":quote_obj.get("exchangeRate"), 20 | "trade_rate":quote_obj.get("tradeRate"), 21 | "expires_at":quote_obj.get("expiresAt"), 22 | "refreshes_at":quote_obj.get("refreshesAt"), 23 | "base_amount":base_amount, 24 | "quote_amount":quote_amount} 25 | quote = ExchangeRateQuote(attributes) 26 | self.quotes.append(quote) 27 | 28 | def get_quotes(self): 29 | return self.quotes -------------------------------------------------------------------------------- /braintree/exchange_rate_quote_request.py: -------------------------------------------------------------------------------- 1 | from braintree.exchange_rate_quote_input import ExchangeRateQuoteInput 2 | 3 | class ExchangeRateQuoteRequest(object): 4 | def __init__(self): 5 | self.quotes = list() 6 | 7 | def add_exchange_rate_quote_input(self,attributes): 8 | new_input = ExchangeRateQuoteInput(self,attributes) 9 | self.quotes.append(new_input) 10 | return new_input 11 | 12 | def to_graphql_variables(self): 13 | variables = dict() 14 | input = dict() 15 | 16 | quote_list = list() 17 | for quote in self.quotes: 18 | quote_list.append(quote.to_graphql_variables()) 19 | input["quotes"] = quote_list 20 | variables["exchangeRateQuoteRequest"] = input 21 | return variables -------------------------------------------------------------------------------- /braintree/facilitated_details.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class FacilitatedDetails(AttributeGetter): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/facilitator_details.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class FacilitatorDetails(AttributeGetter): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/granted_payment_instrument_update.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | 3 | class GrantedPaymentInstrumentUpdate(Resource): 4 | 5 | def __init__(self, gateway, attributes): 6 | Resource.__init__(self, gateway, attributes) 7 | self.payment_method_nonce = attributes["payment_method_nonce"]["nonce"] 8 | -------------------------------------------------------------------------------- /braintree/graphql/__init__.py: -------------------------------------------------------------------------------- 1 | from braintree.graphql.enums import ( 2 | RecommendedPaymentOption, 3 | Recommendations, 4 | ) 5 | from braintree.graphql.inputs import ( 6 | PhoneInput, 7 | CustomerSessionInput, 8 | CreateCustomerSessionInput, 9 | UpdateCustomerSessionInput, 10 | CustomerRecommendationsInput, 11 | MonetaryAmountInput, 12 | PayPalPayeeInput, 13 | PayPalPurchaseUnitInput, 14 | ) 15 | from braintree.graphql.types import ( 16 | CustomerRecommendationsPayload, 17 | PaymentOptions, 18 | PaymentRecommendation 19 | ) 20 | from braintree.graphql.unions import ( 21 | CustomerRecommendations 22 | ) 23 | 24 | -------------------------------------------------------------------------------- /braintree/graphql/enums/__init__.py: -------------------------------------------------------------------------------- 1 | from braintree.graphql.enums.recommended_payment_option import RecommendedPaymentOption 2 | from braintree.graphql.enums.recommendations import Recommendations -------------------------------------------------------------------------------- /braintree/graphql/enums/recommendations.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from braintree.util.experimental import Experimental 3 | 4 | @Experimental 5 | # This enum is Experiemental and may change in future releases. 6 | class Recommendations(Enum): 7 | """ 8 | Represents available types of customer recommendations that can be retrieved using a PayPal customer session. 9 | """ 10 | 11 | PAYMENT_RECOMMENDATIONS = "PAYMENT_RECOMMENDATIONS" 12 | -------------------------------------------------------------------------------- /braintree/graphql/enums/recommended_payment_option.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from braintree.util.experimental import Experimental 3 | 4 | 5 | @Experimental 6 | # This enum is Experiemental and may change in future releases. 7 | class RecommendedPaymentOption(Enum): 8 | """ 9 | Represents available payment options related to PayPal customer session recommendations. 10 | """ 11 | 12 | PAYPAL = "PAYPAL" 13 | VENMO = "VENMO" 14 | -------------------------------------------------------------------------------- /braintree/graphql/inputs/__init__.py: -------------------------------------------------------------------------------- 1 | from braintree.graphql.inputs.phone_input import PhoneInput 2 | from braintree.graphql.inputs.customer_session_input import CustomerSessionInput 3 | from braintree.graphql.inputs.create_customer_session_input import ( 4 | CreateCustomerSessionInput, 5 | ) 6 | from braintree.graphql.inputs.update_customer_session_input import ( 7 | UpdateCustomerSessionInput, 8 | ) 9 | from braintree.graphql.inputs.customer_recommendations_input import CustomerRecommendationsInput 10 | from braintree.graphql.inputs.paypal_payee_input import PayPalPayeeInput 11 | from braintree.graphql.inputs.monetary_amount_input import MonetaryAmountInput 12 | from braintree.graphql.inputs.paypal_purchase_unit_input import PayPalPurchaseUnitInput 13 | -------------------------------------------------------------------------------- /braintree/graphql/inputs/monetary_amount_input.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from typing import Dict 3 | from braintree.util.experimental import Experimental 4 | 5 | @Experimental 6 | # This class is Experiemental and may change in future releases. 7 | class MonetaryAmountInput: 8 | """ 9 | Represents a monetary amount with a currency code. 10 | """ 11 | 12 | def __init__( 13 | self, 14 | value: Decimal = None, 15 | currency_code: str = None 16 | ): 17 | 18 | self._value = value 19 | self._currency_code = currency_code 20 | 21 | 22 | def to_graphql_variables(self) -> Dict: 23 | """ 24 | Returns a dictionary representing the input object, to pass as variables to a GraphQL mutation. 25 | """ 26 | variables = {} 27 | if self._value is not None: 28 | variables["value"] = str(self._value) 29 | if self._currency_code is not None: 30 | variables["currencyCode"] = self._currency_code 31 | 32 | return variables -------------------------------------------------------------------------------- /braintree/graphql/inputs/paypal_payee_input.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | from braintree.util.experimental import Experimental 3 | 4 | @Experimental 5 | # This class is Experiemental and may change in future releases. 6 | class PayPalPayeeInput: 7 | """ 8 | The details for the merchant who receives the funds and fulfills the order. 9 | The merchant is also known as the payee. 10 | """ 11 | 12 | def __init__( 13 | self, 14 | email_address: str = None, 15 | client_id: str = None, 16 | ): 17 | self._email_address = email_address 18 | self._client_id = client_id 19 | 20 | def to_graphql_variables(self) -> Dict: 21 | """ 22 | Returns a dictionary representing the input object, to pass as variables to a GraphQL mutation. 23 | """ 24 | variables = {} 25 | if self._email_address is not None: 26 | variables["emailAddress"] = self._email_address 27 | if self._client_id is not None: 28 | variables["clientId"] = self._client_id 29 | return variables 30 | 31 | @staticmethod 32 | def builder(): 33 | """ 34 | Creates a builder instance for fluent construction of PayPalPayeeInput objects. 35 | """ 36 | return PayPalPayeeInput.Builder() 37 | 38 | class Builder: 39 | def __init__(self): 40 | self._email_address = None 41 | self._client_id = None 42 | 43 | def email_address(self, email_address: str): 44 | """ 45 | Sets the email address of this merchant. 46 | """ 47 | self._email_address = email_address 48 | return self 49 | 50 | def client_id(self, client_id: str): 51 | """ 52 | Sets the public ID for the payee- or merchant-created app. 53 | """ 54 | self._client_id = client_id 55 | return self 56 | 57 | def build(self): 58 | return PayPalPayeeInput( 59 | self._email_address, self._client_id 60 | ) 61 | -------------------------------------------------------------------------------- /braintree/graphql/inputs/paypal_purchase_unit_input.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | from braintree.graphql.inputs.monetary_amount_input import MonetaryAmountInput 3 | from braintree.graphql.inputs.paypal_payee_input import PayPalPayeeInput 4 | from braintree.util.experimental import Experimental 5 | 6 | 7 | @Experimental 8 | class PayPalPurchaseUnitInput: 9 | """ 10 | Payee and Amount of the item purchased. 11 | """ 12 | 13 | def __init__( 14 | self, 15 | amount: MonetaryAmountInput = None, 16 | payee: PayPalPayeeInput = None 17 | ): 18 | 19 | self._amount = amount 20 | self._payee = payee 21 | 22 | def to_graphql_variables(self) -> Dict: 23 | """ 24 | Returns a dictionary representing the input object, to pass as variables to a GraphQL mutation. 25 | """ 26 | variables = {} 27 | if self._payee is not None: 28 | variables["payee"] = self._payee.to_graphql_variables() 29 | if self._amount is not None: 30 | variables["amount"] = self._amount.to_graphql_variables() 31 | return variables 32 | 33 | @staticmethod 34 | def builder(amount: MonetaryAmountInput): 35 | """ 36 | Creates a builder instance for fluent construction of PayPalPurchaseUnit objects. 37 | 38 | Args: 39 | amount (MonetaryAmountInput): The total order amount. The amount must be a positive number. 40 | 41 | """ 42 | return PayPalPurchaseUnitInput.Builder(amount) 43 | 44 | class Builder: 45 | def __init__(self, amount: MonetaryAmountInput): 46 | self._amount = amount 47 | self._payee = None 48 | 49 | def payee(self, payee: PayPalPayeeInput): 50 | """ 51 | Sets the PayPal payee. 52 | """ 53 | self._payee = payee 54 | return self 55 | 56 | def build(self): 57 | return PayPalPurchaseUnitInput( 58 | self._amount, 59 | self._payee 60 | ) -------------------------------------------------------------------------------- /braintree/graphql/inputs/phone_input.py: -------------------------------------------------------------------------------- 1 | from braintree.util.experimental import Experimental 2 | 3 | @Experimental 4 | # This class is Experiemental and may change in future releases. 5 | class PhoneInput: 6 | """ 7 | Phone number input for PayPal customer session. 8 | """ 9 | 10 | def __init__( 11 | self, 12 | country_phone_code: str = None, 13 | phone_number: str = None, 14 | extension_number: str = None 15 | ): 16 | self._country_phone_code = country_phone_code 17 | self._phone_number = phone_number 18 | self._extension_number = extension_number 19 | 20 | def to_graphql_variables(self): 21 | variables = {} 22 | if self._country_phone_code is not None: 23 | variables["countryPhoneCode"] = self._country_phone_code 24 | if self._phone_number is not None: 25 | variables["phoneNumber"] = self._phone_number 26 | if self._extension_number is not None: 27 | variables["extensionNumber"] = self._extension_number 28 | return variables 29 | 30 | @staticmethod 31 | def builder(): 32 | """ 33 | Creates a builder instance for fluent construction of PhoneInput objects. 34 | """ 35 | return PhoneInput.Builder() 36 | 37 | class Builder: 38 | def __init__(self): 39 | self._country_phone_code = None 40 | self._phone_number = None 41 | self._extension_number = None 42 | 43 | def country_phone_code(self, country_phone_code: str): 44 | """ 45 | Sets the country phone code for the phone number. 46 | """ 47 | self._country_phone_code = country_phone_code 48 | return self 49 | 50 | def phone_number(self, phone_number: str): 51 | """ 52 | Sets the phone number. 53 | """ 54 | self._phone_number = phone_number 55 | return self 56 | 57 | def extension_number(self, extension_number: str): 58 | """ 59 | Sets the extension number. 60 | """ 61 | self._extension_number = extension_number 62 | return self 63 | 64 | def build(self): 65 | return PhoneInput( 66 | self._country_phone_code, self._phone_number, self._extension_number 67 | ) 68 | -------------------------------------------------------------------------------- /braintree/graphql/types/__init__.py: -------------------------------------------------------------------------------- 1 | from braintree.graphql.types.customer_recommendations_payload import CustomerRecommendationsPayload 2 | from braintree.graphql.types.payment_options import PaymentOptions 3 | from braintree.graphql.types.payment_recommendation import PaymentRecommendation -------------------------------------------------------------------------------- /braintree/graphql/types/payment_options.py: -------------------------------------------------------------------------------- 1 | from braintree.graphql.enums import RecommendedPaymentOption 2 | from braintree.util.experimental import Experimental 3 | 4 | @Experimental 5 | # This class is Experiemental and may change in future releases. 6 | class PaymentOptions: 7 | """ 8 | Represents the payment method and priority associated with a PayPal customer session. 9 | """ 10 | 11 | def __init__(self, payment_option: RecommendedPaymentOption, recommended_priority: int): 12 | self.payment_option = payment_option 13 | self.recommended_priority = recommended_priority 14 | -------------------------------------------------------------------------------- /braintree/graphql/types/payment_recommendation.py: -------------------------------------------------------------------------------- 1 | from braintree.graphql.enums import RecommendedPaymentOption 2 | from braintree.util.experimental import Experimental 3 | 4 | @Experimental 5 | # This class is Experiemental and may change in future releases. 6 | class PaymentRecommendation: 7 | """ 8 | Represents a single payment method and priority associated with a PayPal customer session. 9 | """ 10 | 11 | def __init__(self, payment_option: RecommendedPaymentOption, recommended_priority: int): 12 | self.payment_option = payment_option 13 | self.recommended_priority = recommended_priority 14 | -------------------------------------------------------------------------------- /braintree/graphql/unions/__init__.py: -------------------------------------------------------------------------------- 1 | from braintree.graphql.unions.customer_recommendations import CustomerRecommendations 2 | -------------------------------------------------------------------------------- /braintree/graphql/unions/customer_recommendations.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | from braintree.graphql.types.payment_options import PaymentOptions 3 | from braintree.graphql.types.payment_recommendation import PaymentRecommendation 4 | from braintree.util.experimental import Experimental 5 | 6 | @Experimental 7 | # This class is Experiemental and may change in future releases. 8 | class CustomerRecommendations: 9 | """ 10 | A union of all possible customer recommendations associated with a PayPal customer session. 11 | """ 12 | 13 | def __init__( 14 | self, 15 | payment_recommendations: Optional[List[PaymentRecommendation]] = None 16 | ): 17 | """ 18 | Initialize customer recommendations. 19 | 20 | Args: 21 | payment_recommendations: A list of PaymentRecommendation objects 22 | """ 23 | # Initialize payment_options 24 | self.payment_options = [] 25 | 26 | # Initialize payment_recommendations 27 | if payment_recommendations is not None: 28 | self.payment_recommendations = payment_recommendations 29 | 30 | self.payment_options = [ 31 | PaymentOptions( 32 | recommendation.payment_option, 33 | recommendation.recommended_priority 34 | ) 35 | for recommendation in payment_recommendations 36 | ] 37 | else: 38 | self.payment_recommendations = [] 39 | -------------------------------------------------------------------------------- /braintree/iban_bank_account.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | 3 | class IbanBankAccount(Resource): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/ids_search.py: -------------------------------------------------------------------------------- 1 | from braintree.search import Search 2 | 3 | class IdsSearch: 4 | ids = Search.MultipleValueNodeBuilder("ids") 5 | -------------------------------------------------------------------------------- /braintree/liability_shift.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class LiabilityShift(AttributeGetter): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/local_payment.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | 4 | class LocalPayment(Resource): 5 | pass 6 | -------------------------------------------------------------------------------- /braintree/local_payment_completed.py: -------------------------------------------------------------------------------- 1 | from braintree.blik_alias import BlikAlias 2 | from braintree.resource import Resource 3 | from braintree.transaction import Transaction 4 | 5 | class LocalPaymentCompleted(Resource): 6 | def __init__(self, gateway, attributes): 7 | Resource.__init__(self, gateway, attributes) 8 | 9 | if "transaction" in attributes: 10 | self.transaction = Transaction(gateway, attributes.pop("transaction")) 11 | if "blik_aliases" in attributes: 12 | self.blik_aliases = [BlikAlias(gateway, blik_alias) for blik_alias in self.blik_aliases] 13 | -------------------------------------------------------------------------------- /braintree/local_payment_expired.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | 3 | class LocalPaymentExpired(Resource): 4 | """ 5 | A class representing Braintree LocalPaymentExpired webhook. 6 | """ 7 | def __init__(self, gateway, attributes): 8 | Resource.__init__(self, gateway, attributes) 9 | 10 | -------------------------------------------------------------------------------- /braintree/local_payment_funded.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | from braintree.transaction import Transaction 3 | 4 | class LocalPaymentFunded(Resource): 5 | """ 6 | A class representing Braintree LocalPaymentFunded webhook. 7 | """ 8 | def __init__(self, gateway, attributes): 9 | Resource.__init__(self, gateway, attributes) 10 | 11 | self.transaction = Transaction(gateway, attributes.pop("transaction")) 12 | -------------------------------------------------------------------------------- /braintree/local_payment_reversed.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | 3 | class LocalPaymentReversed(Resource): 4 | """ 5 | A class representing Braintree LocalPaymentReversed webhook. 6 | """ 7 | def __init__(self, gateway, attributes): 8 | Resource.__init__(self, gateway, attributes) 9 | 10 | -------------------------------------------------------------------------------- /braintree/masterpass_card.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.address import Address 3 | from braintree.resource import Resource 4 | from warnings import warn 5 | 6 | class MasterpassCard(Resource): 7 | """ 8 | A class representing Masterpass card. Deprecated 9 | """ 10 | def __init__(self, gateway, attributes): 11 | warn("MasterpassCard is deprecated") 12 | Resource.__init__(self, gateway, attributes) 13 | 14 | if "billing_address" in attributes: 15 | self.billing_address = Address(gateway, self.billing_address) 16 | else: 17 | self.billing_address = None 18 | 19 | if "subscriptions" in attributes: 20 | self.subscriptions = [braintree.subscription.Subscription(gateway, subscription) for subscription in self.subscriptions] 21 | 22 | @property 23 | def expiration_date(self): 24 | return self.expiration_month + "/" + self.expiration_year 25 | 26 | @property 27 | def masked_number(self): 28 | return self.bin + "******" + self.last_4 29 | 30 | -------------------------------------------------------------------------------- /braintree/merchant.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | from braintree.merchant_account import MerchantAccount 3 | 4 | class Merchant(Resource): 5 | def __init__(self, gateway, attributes): 6 | Resource.__init__(self, gateway, attributes) 7 | 8 | if "merchant_accounts" in attributes: 9 | self.merchant_accounts = [MerchantAccount(gateway, ma) for ma in attributes.get("merchant_accounts")] 10 | -------------------------------------------------------------------------------- /braintree/merchant_account/__init__.py: -------------------------------------------------------------------------------- 1 | from braintree.merchant_account.merchant_account import MerchantAccount -------------------------------------------------------------------------------- /braintree/merchant_account/address_details.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class AddressDetails(AttributeGetter): 4 | detail_list = [ 5 | "street_address", 6 | "locality", 7 | "region", 8 | "postal_code", 9 | ] 10 | 11 | def __init__(self, attributes): 12 | AttributeGetter.__init__(self, attributes) 13 | 14 | def __repr__(self): 15 | return super(AddressDetails, self).__repr__(self.detail_list) 16 | -------------------------------------------------------------------------------- /braintree/merchant_account/merchant_account.py: -------------------------------------------------------------------------------- 1 | from braintree.configuration import Configuration 2 | from braintree.resource import Resource 3 | 4 | class MerchantAccount(Resource): 5 | class Status(object): 6 | Active = "active" 7 | Pending = "pending" 8 | Suspended = "suspended" 9 | 10 | class FundingDestination(object): 11 | Bank = "bank" 12 | Email = "email" 13 | MobilePhone = "mobile_phone" 14 | FundingDestinations = FundingDestination 15 | 16 | def __init__(self, gateway, attributes): 17 | Resource.__init__(self, gateway, attributes) 18 | 19 | def __repr__(self): 20 | detail_list = [ 21 | "id", 22 | "currency_iso_code", 23 | "default", 24 | "status", 25 | ] 26 | return super(MerchantAccount, self).__repr__(detail_list) 27 | 28 | @staticmethod 29 | def create(params=None): 30 | if params is None: 31 | params = {} 32 | return Configuration.gateway().merchant_account.create(params) 33 | 34 | @staticmethod 35 | def update(id, attributes): 36 | return Configuration.gateway().merchant_account.update(id, attributes) 37 | 38 | @staticmethod 39 | def find(id): 40 | return Configuration.gateway().merchant_account.find(id) -------------------------------------------------------------------------------- /braintree/merchant_gateway.py: -------------------------------------------------------------------------------- 1 | from braintree.error_result import ErrorResult 2 | from braintree.resource import Resource 3 | from braintree.resource_collection import ResourceCollection 4 | from braintree.successful_result import SuccessfulResult 5 | from braintree.exceptions.not_found_error import NotFoundError 6 | from braintree.merchant import Merchant 7 | from braintree.oauth_credentials import OAuthCredentials 8 | 9 | 10 | class MerchantGateway(object): 11 | def __init__(self, gateway): 12 | self.gateway = gateway 13 | self.config = gateway.config 14 | 15 | def create(self, params): 16 | return self.__create_merchant(params) 17 | 18 | def __create_merchant(self, params=None): 19 | if params is None: 20 | params = {} 21 | response = self.config.http().post("/merchants/create_via_api", { 22 | "merchant": params 23 | }) 24 | 25 | if "response" in response and "merchant" in response["response"]: 26 | return SuccessfulResult({ 27 | "merchant": Merchant(self.gateway, response["response"]["merchant"]), 28 | "credentials": OAuthCredentials(self.gateway, response["response"]["credentials"]) 29 | }) 30 | else: 31 | return ErrorResult(self.gateway, response["api_error_response"]) 32 | 33 | -------------------------------------------------------------------------------- /braintree/meta_checkout_card.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.address import Address 3 | from braintree.resource import Resource 4 | 5 | class MetaCheckoutCard(Resource): 6 | def __init__(self, gateway, attributes): 7 | Resource.__init__(self, gateway, attributes) 8 | 9 | @property 10 | def expiration_date(self): 11 | if not self.expiration_month or not self.expiration_year: 12 | return None 13 | return self.expiration_month + "/" + self.expiration_year 14 | 15 | @property 16 | def masked_number(self): 17 | return self.bin + "******" + self.last_4 18 | -------------------------------------------------------------------------------- /braintree/meta_checkout_token.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.address import Address 3 | from braintree.resource import Resource 4 | 5 | class MetaCheckoutToken(Resource): 6 | def __init__(self, gateway, attributes): 7 | Resource.__init__(self, gateway, attributes) 8 | 9 | @property 10 | def expiration_date(self): 11 | if not self.expiration_month or not self.expiration_year: 12 | return None 13 | return self.expiration_month + "/" + self.expiration_year 14 | 15 | @property 16 | def masked_number(self): 17 | return self.bin + "******" + self.last_4 18 | -------------------------------------------------------------------------------- /braintree/modification.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from braintree.resource import Resource 3 | 4 | class Modification(Resource): 5 | def __init__(self, gateway, attributes): 6 | Resource.__init__(self, gateway, attributes) 7 | self.amount = Decimal(self.amount) 8 | -------------------------------------------------------------------------------- /braintree/monetary_amount.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from braintree.attribute_getter import AttributeGetter 3 | 4 | class MonetaryAmount(AttributeGetter): 5 | def __init__(self,attributes): 6 | AttributeGetter.__init__(self,attributes) 7 | if getattr(self, "value", None) is not None: 8 | self.value = Decimal(self.value) -------------------------------------------------------------------------------- /braintree/oauth_access_revocation.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | 3 | class OAuthAccessRevocation(Resource): 4 | """ 5 | A class representing an OAuth access revocation. 6 | """ 7 | 8 | def __init__(self, attributes): 9 | Resource.__init__(self, None, attributes) 10 | -------------------------------------------------------------------------------- /braintree/oauth_credentials.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | 3 | class OAuthCredentials(Resource): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/package_details.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | from warnings import warn 3 | 4 | class PackageDetails(AttributeGetter): 5 | """ 6 | A class representing the package tracking information of a transaction. 7 | 8 | An example of package details including all available fields:: 9 | 10 | result = braintree.PackageDetails.create({ 11 | "id": "my_id", 12 | "carrier": "a_carrier", 13 | "tracking_number": "my_tracking_number", 14 | "paypal_tracking_id": "my_paypal_tracking_id", 15 | "paypal_tracker_id": "my_paypal_tracker_id", 16 | }) 17 | 18 | """ 19 | detail_list = [ 20 | "id", 21 | "carrier", 22 | "tracking_number", 23 | # NEXT_MAJOR_VERSION remove paypal_tracking_id 24 | "paypal_tracking_id", 25 | "paypal_tracker_id", 26 | ] 27 | 28 | def __init__(self, attributes): 29 | AttributeGetter.__init__(self, attributes) 30 | -------------------------------------------------------------------------------- /braintree/paginated_collection.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | 3 | class PaginatedCollection(object): 4 | """ 5 | A class representing results from a paginated list. Supports the iterator protocol:: 6 | 7 | results = braintree.MerchantAccount.all() 8 | for merchant_account in results.items: 9 | print merchant_account.id 10 | """ 11 | 12 | def __init__(self, method): 13 | self.__method = method 14 | 15 | @property 16 | def items(self): 17 | """ Returns a generator allowing iteration over all of the results. """ 18 | current_page = 0 19 | total_items = 0 20 | while True: 21 | current_page += 1 22 | 23 | results = self.__method(current_page) 24 | total_items = results.total_items 25 | 26 | for item in results.current_page: 27 | yield item 28 | 29 | if current_page * results.page_size >= total_items: 30 | break 31 | 32 | def __iter__(self): 33 | return self.items 34 | -------------------------------------------------------------------------------- /braintree/paginated_result.py: -------------------------------------------------------------------------------- 1 | class PaginatedResult(object): 2 | """ 3 | An instance of this class is returned from paginated operations 4 | """ 5 | 6 | def __init__(self, total_items, page_size, current_page): 7 | self.total_items = total_items 8 | self.page_size = page_size 9 | self.current_page = current_page 10 | -------------------------------------------------------------------------------- /braintree/partner_merchant.py: -------------------------------------------------------------------------------- 1 | from braintree.configuration import Configuration 2 | from braintree.resource import Resource 3 | 4 | class PartnerMerchant(Resource): 5 | 6 | def __init__(self, gateway, attributes): 7 | Resource.__init__(self, gateway, attributes) 8 | if "partner_merchant_id" in attributes: 9 | self.partner_merchant_id = attributes.pop("partner_merchant_id") 10 | if "private_key" in attributes: 11 | self.private_key = attributes.pop("private_key") 12 | if "public_key" in attributes: 13 | self.public_key = attributes.pop("public_key") 14 | if "merchant_public_id" in attributes: 15 | self.merchant_public_id = attributes.pop("merchant_public_id") 16 | if "client_side_encryption_key" in attributes: 17 | self.client_side_encryption_key = attributes.pop("client_side_encryption_key") 18 | 19 | def __repr__(self): 20 | detail_list = ["partner_merchant_id", "public_key", "merchant_public_id", "client_side_encryption_key"] 21 | return super(PartnerMerchant, self).__repr__(detail_list) 22 | -------------------------------------------------------------------------------- /braintree/payment_facilitator.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | from braintree.sub_merchant import SubMerchant 3 | 4 | class PaymentFacilitator(AttributeGetter): 5 | def __init__(self, attributes): 6 | AttributeGetter.__init__(self, attributes) 7 | if "sub_merchant" in attributes: 8 | self.sub_merchant = SubMerchant(attributes["sub_merchant"]) 9 | -------------------------------------------------------------------------------- /braintree/payment_instrument_type.py: -------------------------------------------------------------------------------- 1 | 2 | class PaymentInstrumentType(): 3 | # NEXT_MAJOR_VERSION remove amex express checkout, masterpass, 4 | # and SamsungPayCard. They have been deprecated 5 | AmexExpressCheckoutCard = "amex_express_checkout_card" 6 | AndroidPayCard = "android_pay_card" 7 | ApplePayCard = "apple_pay_card" 8 | CreditCard = "credit_card" 9 | EuropeBankAccount = "europe_bank_account" 10 | LocalPayment = "local_payment" 11 | MasterpassCard = "masterpass_card" 12 | MetaCheckoutCard = "meta_checkout_card" 13 | MetaCheckoutToken = "meta_checkout_token" 14 | PayPalAccount = "paypal_account" 15 | PayPalHere = "paypal_here" 16 | SamsungPayCard = "samsung_pay_card" 17 | SepaDirectDebitAccount = "sepa_debit_account" 18 | UsBankAccount = "us_bank_account" 19 | VenmoAccount = "venmo_account" 20 | VisaCheckoutCard = "visa_checkout_card" 21 | -------------------------------------------------------------------------------- /braintree/payment_method_customer_data_updated_metadata.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | from braintree.payment_method_parser import parse_payment_method 3 | from braintree.enriched_customer_data import EnrichedCustomerData 4 | 5 | class PaymentMethodCustomerDataUpdatedMetadata(Resource): 6 | """ 7 | A class representing Braintree PaymentMethodCustomerDataUpdatedMetadata webhook. 8 | """ 9 | def __init__(self, gateway, attributes): 10 | Resource.__init__(self, gateway, attributes) 11 | self.payment_method = parse_payment_method(gateway, attributes["payment_method"]) 12 | if attributes["enriched_customer_data"]: 13 | self.enriched_customer_data = EnrichedCustomerData(gateway, attributes["enriched_customer_data"]) 14 | 15 | -------------------------------------------------------------------------------- /braintree/payment_method_nonce.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | from braintree.configuration import Configuration 4 | from braintree.three_d_secure_info import ThreeDSecureInfo 5 | from braintree.bin_data import BinData 6 | 7 | class PaymentMethodNonce(Resource): 8 | @staticmethod 9 | def create(payment_method_token, params = {}): 10 | return Configuration.gateway().payment_method_nonce.create(payment_method_token, params) 11 | 12 | @staticmethod 13 | def find(payment_method_nonce): 14 | return Configuration.gateway().payment_method_nonce.find(payment_method_nonce) 15 | 16 | def __init__(self, gateway, attributes): 17 | Resource.__init__(self, gateway, attributes) 18 | 19 | if "three_d_secure_info" in attributes and not attributes["three_d_secure_info"] is None: 20 | self.three_d_secure_info = ThreeDSecureInfo(attributes["three_d_secure_info"]) 21 | else: 22 | self.three_d_secure_info = None 23 | 24 | if "authentication_insight" in attributes and not attributes["authentication_insight"] is None: 25 | self.authentication_insight = attributes["authentication_insight"] 26 | else: 27 | self.authentication_insight = None 28 | 29 | if "bin_data" in attributes and not attributes["bin_data"] is None: 30 | self.bin_data = BinData(attributes["bin_data"]) 31 | else: 32 | self.bin_data = None 33 | -------------------------------------------------------------------------------- /braintree/payment_method_nonce_gateway.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.payment_method_nonce import PaymentMethodNonce 3 | 4 | from braintree.error_result import ErrorResult 5 | from braintree.exceptions.not_found_error import NotFoundError 6 | from braintree.resource import Resource 7 | from braintree.resource_collection import ResourceCollection 8 | from braintree.successful_result import SuccessfulResult 9 | 10 | class PaymentMethodNonceGateway(object): 11 | def __init__(self, gateway): 12 | self.gateway = gateway 13 | self.config = gateway.config 14 | 15 | def create(self, payment_method_token, params = {"payment_method_nonce": {}}): 16 | try: 17 | schema = [{"payment_method_nonce": ["merchant_account_id", "authentication_insight", {"authentication_insight_options": ["amount", "recurring_customer_consent", "recurring_max_amount"]}]}] 18 | Resource.verify_keys(params, schema) 19 | response = self.config.http().post(self.config.base_merchant_path() + "/payment_methods/" + payment_method_token + "/nonces", params) 20 | if "api_error_response" in response: 21 | return ErrorResult(self.gateway, response["api_error_response"]) 22 | else: 23 | payment_method_nonce = self._parse_payment_method_nonce(response) 24 | return SuccessfulResult({"payment_method_nonce": payment_method_nonce}) 25 | except NotFoundError: 26 | raise NotFoundError("payment method with token " + repr(payment_method_token) + " not found") 27 | 28 | def find(self, payment_method_nonce): 29 | try: 30 | if payment_method_nonce is None or payment_method_nonce.strip() == "": 31 | raise NotFoundError() 32 | 33 | response = self.config.http().get(self.config.base_merchant_path() + "/payment_method_nonces/" + payment_method_nonce) 34 | return self._parse_payment_method_nonce(response) 35 | except NotFoundError: 36 | raise NotFoundError("payment method nonce with id " + repr(payment_method_nonce) + " not found") 37 | 38 | def _parse_payment_method_nonce(self, response): 39 | return PaymentMethodNonce(self.gateway, response["payment_method_nonce"]) 40 | -------------------------------------------------------------------------------- /braintree/paypal_account.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | from braintree.configuration import Configuration 4 | 5 | 6 | class PayPalAccount(Resource): 7 | @staticmethod 8 | def find(paypal_account_token): 9 | return Configuration.gateway().paypal_account.find(paypal_account_token) 10 | 11 | @staticmethod 12 | def delete(paypal_account_token): 13 | return Configuration.gateway().paypal_account.delete(paypal_account_token) 14 | 15 | @staticmethod 16 | def update(paypal_account_token, params=None): 17 | if params is None: 18 | params = {} 19 | return Configuration.gateway().paypal_account.update(paypal_account_token, params) 20 | 21 | @staticmethod 22 | def signature(): 23 | signature = [ 24 | "token", 25 | {"options": ["make_default"]} 26 | ] 27 | return signature 28 | 29 | def __init__(self, gateway, attributes): 30 | Resource.__init__(self, gateway, attributes) 31 | if "subscriptions" in attributes: 32 | self.subscriptions = [braintree.subscription.Subscription(gateway, subscription) for subscription in self.subscriptions] 33 | -------------------------------------------------------------------------------- /braintree/paypal_account_gateway.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.paypal_account import PayPalAccount 3 | from braintree.error_result import ErrorResult 4 | from braintree.exceptions.not_found_error import NotFoundError 5 | from braintree.resource import Resource 6 | from braintree.successful_result import SuccessfulResult 7 | 8 | 9 | class PayPalAccountGateway(object): 10 | def __init__(self, gateway): 11 | self.gateway = gateway 12 | self.config = gateway.config 13 | 14 | def find(self, paypal_account_token): 15 | try: 16 | if paypal_account_token is None or paypal_account_token.strip() == "": 17 | raise NotFoundError() 18 | 19 | response = self.config.http().get(self.config.base_merchant_path() + "/payment_methods/paypal_account/" + paypal_account_token) 20 | if "paypal_account" in response: 21 | return PayPalAccount(self.gateway, response["paypal_account"]) 22 | except NotFoundError: 23 | raise NotFoundError("paypal account with token " + repr(paypal_account_token) + " not found") 24 | 25 | def delete(self, paypal_account_token): 26 | self.config.http().delete(self.config.base_merchant_path() + "/payment_methods/paypal_account/" + paypal_account_token) 27 | return SuccessfulResult() 28 | 29 | def update(self, paypal_account_token, params=None): 30 | if params is None: 31 | params = {} 32 | Resource.verify_keys(params, PayPalAccount.signature()) 33 | response = self.config.http().put(self.config.base_merchant_path() + "/payment_methods/paypal_account/" + paypal_account_token, {"paypal_account": params}) 34 | if "paypal_account" in response: 35 | return SuccessfulResult({"paypal_account": PayPalAccount(self.gateway, response["paypal_account"])}) 36 | elif "api_error_response" in response: 37 | return ErrorResult(self.gateway, response["api_error_response"]) 38 | -------------------------------------------------------------------------------- /braintree/paypal_here.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | 4 | class PayPalHere(Resource): 5 | def __init__(self, gateway, attributes): 6 | Resource.__init__(self, gateway, attributes) 7 | 8 | -------------------------------------------------------------------------------- /braintree/paypal_payment_resource_gateway.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | from braintree.paypal_payment_resource import PayPalPaymentResource 3 | from braintree.payment_method_nonce import PaymentMethodNonce 4 | from braintree.util.xml_util import XmlUtil 5 | from braintree.error_result import ErrorResult 6 | from braintree.successful_result import SuccessfulResult 7 | from braintree.exceptions.unexpected_error import UnexpectedError 8 | 9 | class PayPalPaymentResourceGateway(object): 10 | def __init__(self, gateway): 11 | self.gateway = gateway 12 | self.config = gateway.config 13 | 14 | def update(self, params): 15 | Resource.verify_keys(params, PayPalPaymentResource.update_signature()) 16 | 17 | response = self.config.http().put( 18 | self.config.base_merchant_path() + "/paypal/payment_resource", 19 | params 20 | ) 21 | 22 | if "payment_method_nonce" in response: 23 | return SuccessfulResult({"payment_method_nonce": PaymentMethodNonce(self.gateway, response["payment_method_nonce"])}) 24 | elif "api_error_response" in response: 25 | return ErrorResult(self.gateway, response["api_error_response"]) 26 | else: 27 | raise UnexpectedError("Couldn't parse response") 28 | 29 | 30 | -------------------------------------------------------------------------------- /braintree/plan_gateway.py: -------------------------------------------------------------------------------- 1 | import re 2 | import braintree 3 | from braintree.plan import Plan 4 | from braintree.error_result import ErrorResult 5 | from braintree.exceptions.not_found_error import NotFoundError 6 | from braintree.resource import Resource 7 | from braintree.resource_collection import ResourceCollection 8 | from braintree.successful_result import SuccessfulResult 9 | 10 | class PlanGateway(object): 11 | def __init__(self, gateway): 12 | self.gateway = gateway 13 | self.config = gateway.config 14 | 15 | def all(self): 16 | response = self.config.http().get(self.config.base_merchant_path() + "/plans/") 17 | return [Plan(self.gateway, item) for item in ResourceCollection._extract_as_array(response, "plans")] 18 | 19 | def create(self, params=None): 20 | if params is None: 21 | params = {} 22 | Resource.verify_keys(params, Plan.create_signature()) 23 | response = self.config.http().post(self.config.base_merchant_path() + "/plans", {"plan": params}) 24 | if "plan" in response: 25 | return SuccessfulResult({"plan": Plan(self.gateway, response["plan"])}) 26 | elif "api_error_response" in response: 27 | return ErrorResult(self.gateway, response["api_error_response"]) 28 | 29 | def find(self, plan_id): 30 | try: 31 | if plan_id is None or plan_id.strip() == "": 32 | raise NotFoundError() 33 | response = self.config.http().get(self.config.base_merchant_path() + "/plans/" + plan_id) 34 | return Plan(self.gateway, response["plan"]) 35 | except NotFoundError: 36 | raise NotFoundError("Plan with id " + repr(plan_id) + " not found") 37 | 38 | def update(self, plan_id, params=None): 39 | if params is None: 40 | params = {} 41 | Resource.verify_keys(params, Plan.update_signature()) 42 | response = self.config.http().put(self.config.base_merchant_path() + "/plans/" + plan_id, {"plan": params}) 43 | if "plan" in response: 44 | return SuccessfulResult({"plan": Plan(self.gateway, response["plan"])}) 45 | elif "api_error_response" in response: 46 | return ErrorResult(self.gateway, response["api_error_response"]) 47 | 48 | -------------------------------------------------------------------------------- /braintree/processor_response_types.py: -------------------------------------------------------------------------------- 1 | class ProcessorResponseTypes(object): 2 | """ 3 | A set of constants representing processor response types. 4 | """ 5 | 6 | Approved = "approved" 7 | SoftDeclined = "soft_declined" 8 | HardDeclined = "hard_declined" 9 | -------------------------------------------------------------------------------- /braintree/resource_collection.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.exceptions.unexpected_error import UnexpectedError 3 | 4 | class ResourceCollection(object): 5 | """ 6 | A class representing results from a search. Supports the iterator protocol:: 7 | 8 | results = braintree.Transaction.search("411111") 9 | for transaction in results: 10 | print transaction.id 11 | """ 12 | 13 | def __init__(self, query, results, method): 14 | if "search_results" not in results: 15 | raise UnexpectedError("Unprocessable entity due to an invalid request") 16 | self.__ids = results["search_results"]["ids"] 17 | self.__method = method 18 | self.__page_size = results["search_results"]["page_size"] 19 | self.__query = query 20 | 21 | @property 22 | def maximum_size(self): 23 | """ 24 | Returns the approximate size of the results. The size is approximate due to race conditions when pulling 25 | back results. Due to its inexact nature, maximum_size should be avoided. 26 | """ 27 | return len(self.__ids) 28 | 29 | @property 30 | def first(self): 31 | """ Returns the first item in the results. """ 32 | return self.__method(self.__query, self.__ids[0:1])[0] 33 | 34 | @property 35 | def items(self): 36 | """ Returns a generator allowing iteration over all of the results. """ 37 | for batch in self.__batch_ids(): 38 | for item in self.__method(self.__query, batch): 39 | yield item 40 | 41 | @property 42 | def ids(self): 43 | """ Returns the list of ids in the search result. """ 44 | return self.__ids 45 | 46 | def __iter__(self): 47 | return self.items 48 | 49 | def __batch_ids(self): 50 | for i in range(0, len(self.__ids), self.__page_size): 51 | yield self.__ids[i:i+self.__page_size] 52 | 53 | 54 | @staticmethod 55 | def _extract_as_array(results, attribute): 56 | if not attribute in results: 57 | return [] 58 | 59 | value = results[attribute] 60 | if not isinstance(value, list): 61 | value = [value] 62 | return value 63 | 64 | -------------------------------------------------------------------------------- /braintree/revoked_payment_method_metadata.py: -------------------------------------------------------------------------------- 1 | from braintree.payment_method_parser import parse_payment_method 2 | from braintree.resource import Resource 3 | 4 | class RevokedPaymentMethodMetadata(Resource): 5 | 6 | def __init__(self, gateway, attributes): 7 | self.revoked_payment_method = parse_payment_method(gateway, attributes) 8 | self.customer_id = self.revoked_payment_method.customer_id 9 | self.token = self.revoked_payment_method.token 10 | -------------------------------------------------------------------------------- /braintree/risk_data.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | from braintree.liability_shift import LiabilityShift 3 | 4 | class RiskData(AttributeGetter): 5 | def __init__(self, attributes): 6 | AttributeGetter.__init__(self, attributes) 7 | if "liability_shift" in attributes: 8 | self.liability_shift = LiabilityShift(attributes["liability_shift"]) 9 | -------------------------------------------------------------------------------- /braintree/samsung_pay_card.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.address import Address 3 | from braintree.resource import Resource 4 | 5 | # NEXT_MAJOR_VERSION remove this class 6 | # SamsungPay is deprecated 7 | class SamsungPayCard(Resource): 8 | def __init__(self, gateway, attributes): 9 | Resource.__init__(self, gateway, attributes) 10 | 11 | if "billing_address" in attributes: 12 | self.billing_address = Address(gateway, self.billing_address) 13 | else: 14 | self.billing_address = None 15 | 16 | if "subscriptions" in attributes: 17 | self.subscriptions = [braintree.subscription.Subscription(gateway, subscription) for subscription in self.subscriptions] 18 | 19 | @property 20 | def expiration_date(self): 21 | if not self.expiration_month or not self.expiration_year: 22 | return None 23 | return self.expiration_month + "/" + self.expiration_year 24 | 25 | @property 26 | def masked_number(self): 27 | return self.bin + "******" + self.last_4 28 | -------------------------------------------------------------------------------- /braintree/sepa_direct_debit_account.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | from braintree.configuration import Configuration 4 | 5 | 6 | class SepaDirectDebitAccount(Resource): 7 | @staticmethod 8 | def find(sepa_direct_debit_account_token): 9 | return Configuration.gateway().sepa_direct_debit_account.find(sepa_direct_debit_account_token) 10 | 11 | @staticmethod 12 | def delete(sepa_direct_debit_account_token): 13 | return Configuration.gateway().sepa_direct_debit_account.delete(sepa_direct_debit_account_token) 14 | 15 | def __init__(self, gateway, attributes): 16 | Resource.__init__(self, gateway, attributes) 17 | if "subscriptions" in attributes: 18 | self.subscriptions = [braintree.subscription.Subscription(gateway, subscription) for subscription in self.subscriptions] 19 | -------------------------------------------------------------------------------- /braintree/sepa_direct_debit_account_gateway.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.sepa_direct_debit_account import SepaDirectDebitAccount 3 | from braintree.error_result import ErrorResult 4 | from braintree.exceptions.not_found_error import NotFoundError 5 | from braintree.resource import Resource 6 | from braintree.successful_result import SuccessfulResult 7 | 8 | 9 | class SepaDirectDebitAccountGateway(object): 10 | def __init__(self, gateway): 11 | self.gateway = gateway 12 | self.config = gateway.config 13 | 14 | def find(self, sepa_direct_debit_account_token): 15 | try: 16 | if sepa_direct_debit_account_token is None or sepa_direct_debit_account_token.strip() == "": 17 | raise NotFoundError() 18 | 19 | response = self.config.http().get(self.config.base_merchant_path() + "/payment_methods/sepa_debit_account/" + sepa_direct_debit_account_token) 20 | if "sepa_debit_account" in response: 21 | return SepaDirectDebitAccount(self.gateway, response["sepa_debit_account"]) 22 | except NotFoundError: 23 | raise NotFoundError("sepa direct debit account with token " + repr(sepa_direct_debit_account_token) + " not found") 24 | 25 | def delete(self, sepa_direct_debit_account_token): 26 | self.config.http().delete(self.config.base_merchant_path() + "/payment_methods/sepa_debit_account/" + sepa_direct_debit_account_token) 27 | return SuccessfulResult() 28 | -------------------------------------------------------------------------------- /braintree/settlement_batch_summary.py: -------------------------------------------------------------------------------- 1 | from braintree.util.http import Http 2 | import braintree 3 | import warnings 4 | from braintree.exceptions.not_found_error import NotFoundError 5 | from braintree.resource_collection import ResourceCollection 6 | from braintree.successful_result import SuccessfulResult 7 | from braintree.error_result import ErrorResult 8 | from braintree.resource import Resource 9 | from braintree.configuration import Configuration 10 | 11 | class SettlementBatchSummary(Resource): 12 | @staticmethod 13 | def generate(settlement_date, group_by_custom_field=None): 14 | return Configuration.gateway().settlement_batch_summary.generate(settlement_date, group_by_custom_field) 15 | -------------------------------------------------------------------------------- /braintree/settlement_batch_summary_gateway.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | from braintree.settlement_batch_summary import SettlementBatchSummary 4 | from braintree.successful_result import SuccessfulResult 5 | from braintree.error_result import ErrorResult 6 | 7 | class SettlementBatchSummaryGateway(object): 8 | def __init__(self, gateway): 9 | self.gateway = gateway 10 | self.config = gateway.config 11 | 12 | def generate(self, settlement_date, group_by_custom_field=None): 13 | criteria = {"settlement_date": settlement_date} 14 | 15 | if group_by_custom_field: 16 | criteria["group_by_custom_field"] = group_by_custom_field 17 | 18 | response = self.config.http().post(self.config.base_merchant_path() + '/settlement_batch_summary', {"settlement_batch_summary": criteria}) 19 | if "settlement_batch_summary" in response: 20 | return SuccessfulResult({"settlement_batch_summary": SettlementBatchSummary(self.gateway, response["settlement_batch_summary"])}) 21 | elif "api_error_response" in response: 22 | return ErrorResult(self.gateway, response["api_error_response"]) 23 | -------------------------------------------------------------------------------- /braintree/signature_service.py: -------------------------------------------------------------------------------- 1 | import urllib 2 | from braintree.util.crypto import Crypto 3 | 4 | class SignatureService(object): 5 | 6 | def __init__(self, private_key, hashfunc=Crypto.sha1_hmac_hash): 7 | self.private_key = private_key 8 | self.hmac_hash = hashfunc 9 | 10 | def sign(self, data): 11 | equalities = ['%s=%s' % (str(key), str(data[key])) for key in data] 12 | data_string = '&'.join(equalities) 13 | return "%s|%s" % (self.hash(data_string), data_string) 14 | 15 | def hash(self, data): 16 | return self.hmac_hash(self.private_key, data) 17 | -------------------------------------------------------------------------------- /braintree/status_event.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from braintree.resource import Resource 3 | 4 | class StatusEvent(Resource): 5 | def __init__(self, gateway, attributes): 6 | Resource.__init__(self, gateway, attributes) 7 | 8 | self.amount = Decimal(self.amount) 9 | -------------------------------------------------------------------------------- /braintree/sub_merchant.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class SubMerchant(AttributeGetter): 4 | def __init__(self, attributes): 5 | AttributeGetter.__init__(self, attributes) -------------------------------------------------------------------------------- /braintree/subscription_details.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class SubscriptionDetails(AttributeGetter): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/subscription_search.py: -------------------------------------------------------------------------------- 1 | from braintree.util import Constants 2 | from braintree import Subscription 3 | from braintree.search import Search 4 | 5 | class SubscriptionSearch: 6 | billing_cycles_remaining = Search.RangeNodeBuilder("billing_cycles_remaining") 7 | created_at = Search.RangeNodeBuilder("created_at") 8 | days_past_due = Search.RangeNodeBuilder("days_past_due") 9 | id = Search.TextNodeBuilder("id") 10 | ids = Search.MultipleValueNodeBuilder("ids") 11 | in_trial_period = Search.MultipleValueNodeBuilder("in_trial_period") 12 | merchant_account_id = Search.MultipleValueNodeBuilder("merchant_account_id") 13 | next_billing_date = Search.RangeNodeBuilder("next_billing_date") 14 | plan_id = Search.MultipleValueOrTextNodeBuilder("plan_id") 15 | price = Search.RangeNodeBuilder("price") 16 | status = Search.MultipleValueNodeBuilder("status", Constants.get_all_constant_values_from_class(Subscription.Status)) 17 | transaction_id = Search.TextNodeBuilder("transaction_id") 18 | -------------------------------------------------------------------------------- /braintree/subscription_status_event.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from braintree.resource import Resource 3 | 4 | class SubscriptionStatusEvent(Resource): 5 | def __init__(self, gateway, attributes): 6 | Resource.__init__(self, gateway, attributes) 7 | 8 | self.balance = Decimal(self.balance) 9 | self.price = Decimal(self.price) 10 | -------------------------------------------------------------------------------- /braintree/successful_result.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class SuccessfulResult(AttributeGetter): 4 | """ 5 | An instance of this class is returned from most operations when the request is successful. Call the name of the resource (eg, customer, credit_card, etc) to get the object:: 6 | 7 | result = Transaction.sale({..}) 8 | if result.is_success: 9 | transaction = result.transaction 10 | else: 11 | print [error.code for error in result.errors.all] 12 | """ 13 | 14 | @property 15 | def is_success(self): 16 | """ Returns whether the result from the gateway is a successful response. """ 17 | return True 18 | -------------------------------------------------------------------------------- /braintree/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braintree/braintree_python/7f96e2a1f7ab990c9387d9a0ef8a86a6cf5346bf/braintree/test/__init__.py -------------------------------------------------------------------------------- /braintree/test/authentication_ids.py: -------------------------------------------------------------------------------- 1 | class AuthenticationIds(object): 2 | ThreeDSecureVisaFullAuthentication = "fake-three-d-secure-visa-full-authentication-id" 3 | ThreeDSecureVisaLookupTimeout = "fake-three-d-secure-visa-lookup-timeout-id" 4 | ThreeDSecureVisaFailedSignature = "fake-three-d-secure-visa-failed-signature-id" 5 | ThreeDSecureVisaFailedAuthentication = "fake-three-d-secure-visa-failed-authentication-id" 6 | ThreeDSecureVisaAttemptsNonParticipating = "fake-three-d-secure-visa-attempts-non-participating-id" 7 | ThreeDSecureVisaNoteEnrolled = "fake-three-d-secure-visa-not-enrolled-id" 8 | ThreeDSecureVisaUnavailable = "fake-three-d-secure-visa-unavailable-id" 9 | ThreeDSecureVisaMPILookupError = "fake-three-d-secure-visa-mpi-lookup-error-id" 10 | ThreeDSecureVisaMPIAuthenticateError = "fake-three-d-secure-visa-mpi-authenticate-error-id" 11 | ThreeDSecureVisaAuthenticationUnavailable = "fake-three-d-secure-visa-authentication-unavailable-id" 12 | ThreeDSecureVisaBypassedAuthentication = "fake-three-d-secure-visa-bypassed-authentication-id" 13 | ThreeDSecureTwoVisaSuccessfulFrictionlessAuthentication = "fake-three-d-secure-two-visa-successful-frictionless-authentication-id" 14 | ThreeDSecureTwoVisaSuccessfulStepUpAuthentication = "fake-three-d-secure-two-visa-successful-step-up-authentication-id" 15 | ThreeDSecureTwoVisaErrorOnLookup = "fake-three-d-secure-two-visa-error-on-lookup-id" 16 | ThreeDSecureTwoVisaTimeoutOnLookup = "fake-three-d-secure-two-visa-timeout-on-lookup-id" 17 | -------------------------------------------------------------------------------- /braintree/test/credit_card_defaults.py: -------------------------------------------------------------------------------- 1 | class CreditCardDefaults(object): 2 | CountryOfIssuance = "USA" 3 | IssuingBank = "NETWORK ONLY" 4 | -------------------------------------------------------------------------------- /braintree/test/credit_card_numbers.py: -------------------------------------------------------------------------------- 1 | class CreditCardNumbers(object): 2 | class CardTypeIndicators(object): 3 | Business = "4229989800000003" 4 | Commercial = "4111111111131010" 5 | Consumer = "4229989700000004" 6 | Corporate = "4229989100000000" 7 | DurbinRegulated = "4111161010101010" 8 | Debit = "4117101010101010" 9 | Healthcare = "4111111510101010" 10 | Payroll = "4111111114101010" 11 | Prepaid = "4111111111111210" 12 | PrepaidReloadable = "4229989900000002" 13 | Purchase = "4229989500000006" 14 | IssuingBank = "4111111141010101" 15 | CountryOfIssuance = "4111111111121102" 16 | 17 | No = "4111111111310101" 18 | Unknown = "4111111111112101" 19 | 20 | Maestro = "6304000000000000" # :nodoc: 21 | MasterCard = "5555555555554444" 22 | MasterCardInternational = "5105105105105100" # :nodoc: 23 | 24 | Visa = "4012888888881881" 25 | VisaInternational = "4009348888881881" # :nodoc: 26 | VisaPrepaid = "4500600000000061" 27 | 28 | Discover = "6011111111111117" 29 | Elo = "5066991111111118" 30 | 31 | Hiper = "6370950000000005" 32 | Hipercard = "6062820524845321" 33 | Amex = "378734493671000" 34 | 35 | class FailsSandboxVerification(object): 36 | AmEx = "378734493671000" 37 | Discover = "6011000990139424" 38 | MasterCard = "5105105105105100" 39 | Visa = "4000111111111115" 40 | 41 | class AmexPayWithPoints(object): 42 | Success = "371260714673002" 43 | IneligibleCard = "378267515471109" 44 | InsufficientPoints = "371544868764018" 45 | 46 | class Disputes(object): 47 | Chargeback = "4023898493988028" 48 | -------------------------------------------------------------------------------- /braintree/test/merchant_account.py: -------------------------------------------------------------------------------- 1 | Approve = "approve_me" 2 | 3 | InsufficientFundsContactUs = "insufficient_funds__contact" 4 | AccountNotAuthorizedContactUs = "account_not_authorized__contact" 5 | BankRejectedUpdateFundingInformation = "bank_rejected__update" 6 | BankRejectedNone = "bank_rejected__none" 7 | -------------------------------------------------------------------------------- /braintree/test/venmo_sdk.py: -------------------------------------------------------------------------------- 1 | def generate_test_payment_method_code(number): 2 | return "stub-" + number 3 | 4 | VisaPaymentMethodCode = generate_test_payment_method_code("4111111111111111") 5 | InvalidPaymentMethodCode = generate_test_payment_method_code("invalid-payment-method-code") 6 | 7 | Session = "stub-session" 8 | InvalidSession = "stub-invalid-session" 9 | -------------------------------------------------------------------------------- /braintree/three_d_secure_info.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class ThreeDSecureInfo(AttributeGetter): 4 | pass 5 | -------------------------------------------------------------------------------- /braintree/transaction_amounts.py: -------------------------------------------------------------------------------- 1 | class TransactionAmounts(object): 2 | """ A class of constants for transaction amounts that will cause different statuses. """ 3 | 4 | Authorize = "1000.00" 5 | Decline = "2000.00" 6 | HardDecline = "2015.00" 7 | Fail = "3000.00" 8 | -------------------------------------------------------------------------------- /braintree/transaction_details.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from braintree.attribute_getter import AttributeGetter 3 | 4 | class TransactionDetails(AttributeGetter): 5 | def __init__(self, attributes): 6 | AttributeGetter.__init__(self, attributes) 7 | 8 | if getattr(self, "amount", None) is not None: 9 | self.amount = Decimal(self.amount) 10 | -------------------------------------------------------------------------------- /braintree/transaction_line_item.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | import warnings 3 | 4 | from braintree.attribute_getter import AttributeGetter 5 | from braintree.resource import Resource 6 | from braintree.configuration import Configuration 7 | 8 | class TransactionLineItem(AttributeGetter): 9 | pass 10 | 11 | # NEXT_MAJOR_VERSION this can be an enum! they were added as of python 3.4 and we support 3.5+ 12 | class Kind(object): 13 | """ 14 | Constants representing transaction line item kinds. Available kinds are: 15 | 16 | * braintree.TransactionLineItem.Kind.Credit 17 | * braintree.TransactionLineItem.Kind.Debit 18 | """ 19 | 20 | Credit = "credit" 21 | Debit = "debit" 22 | 23 | def __init__(self, attributes): 24 | AttributeGetter.__init__(self, attributes) 25 | 26 | @staticmethod 27 | def find_all(transaction_id): 28 | """ 29 | Find all line items on a transaction, given a transaction_id. This returns an array of TransactionLineItems. 30 | This will raise a :class:`NotFoundError ` if the provided 31 | transaction_id is not found. :: 32 | 33 | transaction_line_items = braintree.TransactionLineItem.find_all("my_transaction_id") 34 | """ 35 | return Configuration.gateway().transaction_line_item.find_all(transaction_id) 36 | -------------------------------------------------------------------------------- /braintree/transaction_line_item_gateway.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.error_result import ErrorResult 3 | from braintree.resource import Resource 4 | from braintree.resource_collection import ResourceCollection 5 | from braintree.transaction_line_item import TransactionLineItem 6 | from braintree.exceptions.not_found_error import NotFoundError 7 | from braintree.exceptions.request_timeout_error import RequestTimeoutError 8 | 9 | class TransactionLineItemGateway(object): 10 | def __init__(self, gateway): 11 | self.gateway = gateway 12 | self.config = gateway.config 13 | 14 | def find_all(self, transaction_id): 15 | try: 16 | if transaction_id is None or transaction_id.strip() == "": 17 | raise NotFoundError() 18 | response = self.config.http().get(self.config.base_merchant_path() + "/transactions/" + transaction_id + "/line_items") 19 | if "line_items" in response: 20 | return [TransactionLineItem(item) for item in ResourceCollection._extract_as_array(response, "line_items")] 21 | else: 22 | raise RequestTimeoutError() 23 | except NotFoundError: 24 | raise NotFoundError("transaction line items with id " + repr(transaction_id) + " not found") 25 | -------------------------------------------------------------------------------- /braintree/transaction_review.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | 3 | class TransactionReview(Resource): 4 | """ 5 | A class representing a Transaction Review. 6 | """ 7 | 8 | def __init__(self, attributes): 9 | Resource.__init__(self, None, attributes) 10 | -------------------------------------------------------------------------------- /braintree/unknown_payment_method.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | 4 | class UnknownPaymentMethod(Resource): 5 | def image_url(self): 6 | return "https://assets.braintreegateway.com/payment_method_logo/unknown.png" 7 | -------------------------------------------------------------------------------- /braintree/us_bank_account.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | from braintree.configuration import Configuration 4 | from braintree.ach_mandate import AchMandate 5 | from braintree.us_bank_account_verification import UsBankAccountVerification 6 | 7 | class UsBankAccount(Resource): 8 | 9 | @staticmethod 10 | def find(token): 11 | return Configuration.gateway().us_bank_account.find(token) 12 | 13 | @staticmethod 14 | def sale(token, transactionRequest): 15 | transactionRequest["payment_method_token"] = token 16 | if not "options" in transactionRequest: 17 | transactionRequest["options"] = {} 18 | transactionRequest["options"]["submit_for_settlement"] = True 19 | return Configuration.gateway().transaction.sale(transactionRequest) 20 | 21 | @staticmethod 22 | def signature(): 23 | signature = [ 24 | "routing_number", 25 | "last_4", 26 | "account_type", 27 | "account_holder_name", 28 | "token", 29 | "image_url", 30 | "bank_name", 31 | "ach_mandate" 32 | ] 33 | return signature 34 | 35 | def __init__(self, gateway, attributes): 36 | Resource.__init__(self, gateway, attributes) 37 | if attributes.get("ach_mandate") is not None: 38 | self.ach_mandate = AchMandate(gateway, self.ach_mandate) 39 | else: 40 | self.ach_mandate = None 41 | 42 | if attributes.get("verifications") is not None: 43 | self.verifications = [UsBankAccountVerification(gateway, v) for v in self.verifications] 44 | else: 45 | self.verifications = None 46 | -------------------------------------------------------------------------------- /braintree/us_bank_account_gateway.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.us_bank_account import UsBankAccount 3 | from braintree.exceptions.not_found_error import NotFoundError 4 | 5 | class UsBankAccountGateway(object): 6 | def __init__(self, gateway): 7 | self.gateway = gateway 8 | self.config = gateway.config 9 | 10 | def find(self, us_bank_account_token): 11 | try: 12 | if us_bank_account_token is None or us_bank_account_token.strip() == "": 13 | raise NotFoundError() 14 | 15 | response = self.config.http().get(self.config.base_merchant_path() + "/payment_methods/us_bank_account/" + us_bank_account_token) 16 | if "us_bank_account" in response: 17 | return UsBankAccount(self.gateway, response["us_bank_account"]) 18 | except NotFoundError: 19 | raise NotFoundError("US bank account with token " + repr(us_bank_account_token) + " not found") 20 | 21 | -------------------------------------------------------------------------------- /braintree/us_bank_account_verification_search.py: -------------------------------------------------------------------------------- 1 | from braintree.us_bank_account import UsBankAccount 2 | from braintree.us_bank_account_verification import UsBankAccountVerification 3 | from braintree.search import Search 4 | from braintree.util import Constants 5 | 6 | class UsBankAccountVerificationSearch: 7 | # Text fields 8 | account_holder_name = Search.TextNodeBuilder("account_holder_name") 9 | customer_email = Search.TextNodeBuilder("customer_email") 10 | customer_id = Search.TextNodeBuilder("customer_id") 11 | id = Search.TextNodeBuilder("id") 12 | payment_method_token = Search.TextNodeBuilder("payment_method_token") 13 | routing_number = Search.TextNodeBuilder("routing_number") 14 | 15 | # Multiple value fields 16 | ids = Search.MultipleValueNodeBuilder("ids") 17 | status = Search.MultipleValueNodeBuilder( 18 | "status", 19 | Constants.get_all_constant_values_from_class(UsBankAccountVerification.Status) 20 | ) 21 | verification_method = Search.MultipleValueNodeBuilder( 22 | "verification_method", 23 | Constants.get_all_constant_values_from_class(UsBankAccountVerification.VerificationMethod) 24 | ) 25 | 26 | # Range fields 27 | created_at = Search.RangeNodeBuilder("created_at") 28 | 29 | # Equality fields 30 | account_type = Search.EqualityNodeBuilder("account_type") 31 | 32 | # Ends-with fields 33 | account_number = Search.EndsWithNodeBuilder("account_number") 34 | -------------------------------------------------------------------------------- /braintree/util/__init__.py: -------------------------------------------------------------------------------- 1 | from braintree.util.constants import Constants 2 | from braintree.util.crypto import Crypto 3 | from braintree.util.generator import Generator 4 | from braintree.util.http import Http 5 | from braintree.util.graphql_client import GraphQLClient 6 | from braintree.util.parser import Parser 7 | from braintree.util.xml_util import XmlUtil 8 | from braintree.util.experimental import Experimental 9 | -------------------------------------------------------------------------------- /braintree/util/constants.py: -------------------------------------------------------------------------------- 1 | #NEXT_MAJOR_VERSION we can change all Constants to enums, not sure if we'll still need this 2 | class Constants(object): 3 | @staticmethod 4 | def get_all_constant_values_from_class(klass): 5 | return [klass.__dict__[item] for item in dir(klass) if not item.startswith("__")] 6 | def get_all_enum_values(enum_class): 7 | return [item.value for item in enum_class] 8 | -------------------------------------------------------------------------------- /braintree/util/crypto.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | import sys 4 | 5 | text_type = str 6 | 7 | class Crypto: 8 | @staticmethod 9 | def sha1_hmac_hash(secret_key, content): 10 | if isinstance(secret_key, text_type): 11 | secret_key = secret_key.encode('ascii') 12 | if isinstance(content, text_type): 13 | content = content.encode('ascii') 14 | return hmac.new(hashlib.sha1(secret_key).digest(), content, hashlib.sha1).hexdigest() 15 | 16 | @staticmethod 17 | def sha256_hmac_hash(secret_key, content): 18 | if isinstance(secret_key, text_type): 19 | secret_key = secret_key.encode('ascii') 20 | if isinstance(content, text_type): 21 | content = content.encode('ascii') 22 | return hmac.new(hashlib.sha256(secret_key).digest(), content, hashlib.sha256).hexdigest() 23 | 24 | @staticmethod 25 | def secure_compare(left, right): 26 | if left is None or right is None: 27 | return False 28 | 29 | left_bytes = [ord(char) for char in left] 30 | right_bytes = [ord(char) for char in right] 31 | 32 | if len(left_bytes) != len(right_bytes): 33 | return False 34 | 35 | result = 0 36 | for left_byte, right_byte in zip(left_bytes, right_bytes): 37 | result |= left_byte ^ right_byte 38 | return result == 0 39 | -------------------------------------------------------------------------------- /braintree/util/datetime_parser.py: -------------------------------------------------------------------------------- 1 | import re 2 | from datetime import datetime 3 | from datetime import timedelta 4 | 5 | 6 | _OFFSET_REGEX = re.compile(r'(\+|\-)(\d\d):(\d\d)$') 7 | _SYMBOLS_REGEX = re.compile(r'[-:Z]') 8 | 9 | 10 | def parse_datetime(timestamp): 11 | offset_matches = _OFFSET_REGEX.findall(timestamp) 12 | 13 | if len(offset_matches) == 0: 14 | timestamp = _SYMBOLS_REGEX.sub('', timestamp) 15 | without_seconds = datetime.strptime(timestamp[:13], '%Y%m%dT%H%M') 16 | seconds = timedelta(seconds=float(timestamp[13:])) 17 | return without_seconds + seconds 18 | else: 19 | time_without_offset = parse_datetime(timestamp[:-6]) 20 | 21 | try: 22 | offset_matches = offset_matches[0] 23 | offset_is_negative = offset_matches[0] == '-' 24 | offset_hours = int(offset_matches[1]) 25 | offset_minutes = int(offset_matches[2]) 26 | except IndexError: 27 | pass 28 | offset = timedelta(hours=offset_hours, minutes=offset_minutes) 29 | 30 | if offset_is_negative: 31 | return time_without_offset + offset 32 | else: 33 | return time_without_offset - offset 34 | -------------------------------------------------------------------------------- /braintree/util/experimental.py: -------------------------------------------------------------------------------- 1 | def Experimental(cls): 2 | """ 3 | Experimental features may change at any time. 4 | 5 | Decorator to mark a class as experimental. 6 | Adds an '_is_experimental' attribute to the class. 7 | """ 8 | cls._is_experimental = True 9 | return cls -------------------------------------------------------------------------------- /braintree/util/xml_util.py: -------------------------------------------------------------------------------- 1 | from braintree.util.parser import Parser 2 | from braintree.util.generator import Generator 3 | 4 | class XmlUtil(object): 5 | @staticmethod 6 | def xml_from_dict(dict): 7 | return Generator(dict).generate() 8 | 9 | @staticmethod 10 | def dict_from_xml(xml): 11 | return Parser(xml).parse() 12 | -------------------------------------------------------------------------------- /braintree/validation_error.py: -------------------------------------------------------------------------------- 1 | from braintree.attribute_getter import AttributeGetter 2 | 3 | class ValidationError(AttributeGetter): 4 | """ 5 | A validation error returned from the server, with information about the error: 6 | 7 | * **attribute**: The field which had an error. 8 | * **code**: A numeric error code. See :class:`ErrorCodes ` 9 | * **message**: A description of the error. Note: error messages may change, but the code will not. 10 | """ 11 | pass 12 | -------------------------------------------------------------------------------- /braintree/venmo_account.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.resource import Resource 3 | 4 | class VenmoAccount(Resource): 5 | """ 6 | A class representing Braintree Venmo accounts. 7 | """ 8 | def __init__(self, gateway, attributes): 9 | Resource.__init__(self, gateway, attributes) 10 | 11 | if "subscriptions" in attributes: 12 | self.subscriptions = [braintree.subscription.Subscription(gateway, subscription) for subscription in self.subscriptions] 13 | -------------------------------------------------------------------------------- /braintree/venmo_profile_data.py: -------------------------------------------------------------------------------- 1 | from braintree.resource import Resource 2 | 3 | class VenmoProfileData(Resource): 4 | """ 5 | A class representing Braintree VenmoProfileData object. 6 | """ 7 | def __init__(self, gateway, attributes): 8 | Resource.__init__(self, gateway, attributes) 9 | 10 | -------------------------------------------------------------------------------- /braintree/version.py: -------------------------------------------------------------------------------- 1 | Version = "4.36.0" 2 | -------------------------------------------------------------------------------- /braintree/visa_checkout_card.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.address import Address 3 | from braintree.resource import Resource 4 | from braintree.credit_card_verification import CreditCardVerification 5 | 6 | class VisaCheckoutCard(Resource): 7 | """ 8 | A class representing Visa Checkout card 9 | """ 10 | def __init__(self, gateway, attributes): 11 | Resource.__init__(self, gateway, attributes) 12 | 13 | if "billing_address" in attributes: 14 | self.billing_address = Address(gateway, self.billing_address) 15 | else: 16 | self.billing_address = None 17 | 18 | if "subscriptions" in attributes: 19 | self.subscriptions = [braintree.subscription.Subscription(gateway, subscription) for subscription in self.subscriptions] 20 | 21 | if "verifications" in attributes: 22 | sorted_verifications = sorted(attributes["verifications"], key=lambda verification: verification["created_at"], reverse=True) 23 | if len(sorted_verifications) > 0: 24 | self.verification = CreditCardVerification(gateway, sorted_verifications[0]) 25 | 26 | @property 27 | def expiration_date(self): 28 | if not self.expiration_month or not self.expiration_year: 29 | return None 30 | return self.expiration_month + "/" + self.expiration_year 31 | 32 | @property 33 | def masked_number(self): 34 | return self.bin + "******" + self.last_4 35 | 36 | -------------------------------------------------------------------------------- /braintree/webhook_testing.py: -------------------------------------------------------------------------------- 1 | import braintree 2 | from braintree.configuration import Configuration 3 | 4 | class WebhookTesting(object): 5 | @staticmethod 6 | def sample_notification(kind, id, source_merchant_id=None): 7 | return Configuration.gateway().webhook_testing.sample_notification(kind, id, source_merchant_id) 8 | -------------------------------------------------------------------------------- /ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "$1" == "http" ]]; then 4 | python_tests="test:http" 5 | fi 6 | 7 | if [[ "$1" == "python3" ]]; then 8 | /usr/local/lib/python3.3/bin/python3 -m unittest discover 9 | else 10 | env rake $python_tests --trace 11 | fi 12 | -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | twine>=1.9,<2.0 2 | -r requirements.txt 3 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | docutils<0.18 2 | -------------------------------------------------------------------------------- /polaris.yml: -------------------------------------------------------------------------------- 1 | #This is the default configuration file generated by Polaris. Documentation on configuration options can be found here https://sig-docs.synopsys.com/polaris/topics/c_conf-overview.html 2 | version: "1" 3 | project: 4 | name: ${scm.git.repo} 5 | branch: ${scm.git.branch} 6 | revision: 7 | name: ${scm.git.commit} 8 | date: ${scm.git.commit.date} 9 | groups: 10 | # Required: This allows the Jenkins Polaris service account to create new projects 11 | braintree-jenkins: "Contributor" 12 | PP_SSO_POLARIS_USER: "Observer" 13 | PP_SSO_POLARIS_SECURITY_CHAMPION: "Contributor" 14 | PP_SSO_POLARIS_ADMIN: "Administrator" 15 | capture: 16 | fileSystem: 17 | ears: 18 | extensions: [ear] 19 | files: 20 | - directory: ${project.projectDir} 21 | java: 22 | files: 23 | - directory: ${project.projectDir} 24 | javascript: 25 | files: 26 | - directory: ${project.projectDir} 27 | - excludeRegex: node_modules|bower_components|vendor 28 | php: 29 | files: 30 | - directory: ${project.projectDir} 31 | python: 32 | files: 33 | - directory: ${project.projectDir} 34 | ruby: 35 | files: 36 | - directory: ${project.projectDir} 37 | typescript: 38 | files: 39 | - directory: ${project.projectDir} 40 | wars: 41 | extensions: [war] 42 | files: 43 | - directory: ${project.projectDir} 44 | analyze: 45 | mode: central 46 | install: 47 | coverity: 48 | version: default 49 | serverUrl: ${POLARIS_SERVER_URL} 50 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=0.11.0,<3.0 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | except ImportError: 4 | from distutils.core import setup 5 | 6 | long_description = """ 7 | The Braintree Python SDK provides integration access to the Braintree Gateway. 8 | 9 | 1. https://github.com/braintree/braintree_python - README and Samples 10 | 2. https://developer.paypal.com/braintree/docs/reference/overview - API Reference 11 | """ 12 | 13 | setup( 14 | name="braintree", 15 | version="4.36.0", 16 | description="Braintree Python Library", 17 | long_description=long_description, 18 | author="Braintree", 19 | author_email="support@braintreepayments.com", 20 | url="https://developer.paypal.com/braintree/docs/reference/overview", 21 | packages=["braintree", "braintree.dispute_details", "braintree.exceptions", "braintree.graphql", "braintree.graphql.enums", "braintree.graphql.inputs", "braintree.graphql.types", "braintree.graphql.unions", "braintree.exceptions.http", "braintree.merchant_account", "braintree.util", "braintree.test"], 22 | package_data={"braintree": ["ssl/*"]}, 23 | install_requires=["requests>=0.11.1,<3.0"], 24 | zip_safe=False, 25 | license="MIT", 26 | classifiers=[ 27 | "License :: OSI Approved :: MIT License", 28 | "Programming Language :: Python :: 3.5", 29 | "Programming Language :: Python :: 3.6", 30 | "Programming Language :: Python :: 3.7", 31 | "Programming Language :: Python :: 3.8", 32 | "Programming Language :: Python :: 3.9", 33 | "Programming Language :: Python :: 3.10", 34 | "Programming Language :: Python :: 3.11", 35 | "Programming Language :: Python :: 3.12" 36 | ] 37 | ) 38 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braintree/braintree_python/7f96e2a1f7ab990c9387d9a0ef8a86a6cf5346bf/tests/__init__.py -------------------------------------------------------------------------------- /tests/fixtures/bt_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braintree/braintree_python/7f96e2a1f7ab990c9387d9a0ef8a86a6cf5346bf/tests/fixtures/bt_logo.png -------------------------------------------------------------------------------- /tests/fixtures/gif_extension_bt_logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braintree/braintree_python/7f96e2a1f7ab990c9387d9a0ef8a86a6cf5346bf/tests/fixtures/gif_extension_bt_logo.gif -------------------------------------------------------------------------------- /tests/fixtures/malformed_pdf.pdf: -------------------------------------------------------------------------------- 1 | XXXXXXXXXXX¾?^gfkbfu¶šÙú”¼Åxc¯ÄÖñ•:x„BºS·É=] óqçÅÒÐwî+= x wÁ¼/7ú7ÀݛeÀÅA¿8?À­‹6Á2[î(†¤Ö@+3[ß} 2 | -------------------------------------------------------------------------------- /tests/fixtures/too_long.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braintree/braintree_python/7f96e2a1f7ab990c9387d9a0ef8a86a6cf5346bf/tests/fixtures/too_long.pdf -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braintree/braintree_python/7f96e2a1f7ab990c9387d9a0ef8a86a6cf5346bf/tests/integration/__init__.py -------------------------------------------------------------------------------- /tests/integration/test_add_ons.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestAddOn(unittest.TestCase): 4 | def test_all_returns_all_add_ons(self): 5 | new_id = str(random.randint(1, 1000000)) 6 | attributes = { 7 | "amount": "100.00", 8 | "description": "some description", 9 | "id": new_id, 10 | "kind": "add_on", 11 | "name": "python_add_on", 12 | "never_expires": False, 13 | "number_of_billing_cycles": 1 14 | } 15 | 16 | Configuration.instantiate().http().post(Configuration.instantiate().base_merchant_path() + "/modifications/create_modification_for_tests", {"modification": attributes}) 17 | 18 | add_ons = AddOn.all() 19 | 20 | for add_on in add_ons: 21 | if add_on.id == new_id: 22 | break 23 | else: 24 | add_on = None 25 | 26 | self.assertNotEqual(None, add_on) 27 | 28 | self.assertEqual(Decimal("100.00"), add_on.amount) 29 | self.assertEqual("some description", add_on.description) 30 | self.assertEqual(new_id, add_on.id) 31 | self.assertEqual("add_on", add_on.kind) 32 | self.assertEqual("python_add_on", add_on.name) 33 | self.assertEqual(False, add_on.never_expires) 34 | self.assertEqual(add_on.number_of_billing_cycles, 1) 35 | -------------------------------------------------------------------------------- /tests/integration/test_android_pay.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestAndroidPay(unittest.TestCase): 4 | @staticmethod 5 | def get_gateway(): 6 | config = Configuration("development", "integration_merchant_id", 7 | public_key="integration_public_key", 8 | private_key="integration_private_key") 9 | return BraintreeGateway(config) 10 | 11 | def test_bin_fields(self): 12 | customer = Customer.create().customer 13 | result = PaymentMethod.create({ 14 | "customer_id": customer.id, 15 | "payment_method_nonce": Nonces.AndroidPayCardVisa 16 | }) 17 | 18 | self.assertTrue(result.is_success) 19 | 20 | android_pay_card = result.payment_method 21 | self.assertIsNotNone(android_pay_card.prepaid_reloadable) 22 | self.assertIsNotNone(android_pay_card.business) 23 | self.assertIsNotNone(android_pay_card.consumer) 24 | self.assertIsNotNone(android_pay_card.corporate) 25 | self.assertIsNotNone(android_pay_card.purchase) 26 | 27 | customer = Customer.find(customer.id) 28 | self.assertEqual(len(customer.android_pay_cards), 1) 29 | self.assertEqual(result.payment_method.token, customer.android_pay_cards[0].token) 30 | -------------------------------------------------------------------------------- /tests/integration/test_disbursement.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from datetime import date 3 | 4 | class TestDisbursement(unittest.TestCase): 5 | def test_disbursement_finds_transactions(self): 6 | disbursement = Disbursement(Configuration.gateway(), { 7 | "merchant_account": { 8 | "id": "ma_card_processor_brazil", 9 | "status": "active", 10 | 11 | }, 12 | "id": "123456", 13 | "exception_message": "invalid_account_number", 14 | "amount": "100.00", 15 | "disbursement_date": date(2013, 4, 10), 16 | "follow_up_action": "update", 17 | "transaction_ids": ["transaction_with_installments_and_adjustments"] 18 | }) 19 | 20 | transactions = disbursement.transactions() 21 | self.assertEqual(1, transactions.maximum_size) 22 | self.assertEqual("transaction_with_installments_and_adjustments", transactions.first.id) 23 | -------------------------------------------------------------------------------- /tests/integration/test_discounts.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestDiscounts(unittest.TestCase): 4 | 5 | def test_all_returns_all_discounts(self): 6 | new_id = str(random.randint(1, 1000000)) 7 | attributes = { 8 | "amount": "100.00", 9 | "description": "some description", 10 | "id": new_id, 11 | "kind": "discount", 12 | "name": "python_discount", 13 | "never_expires": False, 14 | "number_of_billing_cycles": 1 15 | } 16 | 17 | Configuration.instantiate().http().post(Configuration.instantiate().base_merchant_path() + "/modifications/create_modification_for_tests", {"modification": attributes}) 18 | 19 | discounts = Discount.all() 20 | 21 | for discount in discounts: 22 | if discount.id == new_id: 23 | break 24 | else: 25 | discount = None 26 | 27 | self.assertNotEqual(None, discount) 28 | 29 | self.assertEqual(Decimal("100.00"), discount.amount) 30 | self.assertEqual("some description", discount.description) 31 | self.assertEqual(new_id, discount.id) 32 | self.assertEqual("discount", discount.kind) 33 | self.assertEqual("python_discount", discount.name) 34 | self.assertEqual(False, discount.never_expires) 35 | self.assertEqual(1, discount.number_of_billing_cycles) 36 | -------------------------------------------------------------------------------- /tests/integration/test_graphql_client.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from braintree.configuration import Configuration 4 | from braintree.environment import Environment 5 | 6 | class TestGraphQLClient(TestCase): 7 | 8 | @staticmethod 9 | def get_graphql_client(environment): 10 | config = Configuration(environment, "integration_merchant_id", 11 | public_key="integration_public_key", 12 | private_key="integration_private_key") 13 | return config.graphql_client() 14 | 15 | def test_graphql_makes_valid_queries_without_variables(self): 16 | definition = ''' 17 | query { 18 | ping 19 | } 20 | ''' 21 | graphql_client = self.get_graphql_client(Environment.Development) 22 | response = graphql_client.query(definition) 23 | 24 | self.assertTrue("data" in response) 25 | self.assertTrue("ping" in response["data"]) 26 | self.assertTrue("pong" == response["data"]["ping"]) 27 | 28 | def test_graphql_makes_valid_queries_with_variables(self): 29 | definition = ''' 30 | mutation CreateClientToken($input: CreateClientTokenInput!) { 31 | createClientToken(input: $input) { 32 | clientToken 33 | } 34 | } 35 | ''' 36 | 37 | variables = { 38 | "input": { 39 | "clientToken": { 40 | "merchantAccountId": "ABC123" 41 | } 42 | } 43 | } 44 | 45 | graphql_client = self.get_graphql_client(Environment.Development) 46 | response = graphql_client.query(definition, variables) 47 | 48 | self.assertTrue("data" in response) 49 | self.assertTrue("createClientToken" in response["data"]) 50 | self.assertTrue("clientToken" in response["data"]["createClientToken"]) 51 | -------------------------------------------------------------------------------- /tests/integration/test_test_helper.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree.test.nonces import Nonces 3 | 4 | class TestTestHelper(unittest.TestCase): 5 | def setUp(self): 6 | self.transaction = Transaction.sale({ 7 | "credit_card": { 8 | "number": "4111111111111111", 9 | "expiration_date": "05/2010", 10 | "cvv": "100" 11 | }, 12 | "amount": "100.00", 13 | "options": { 14 | "submit_for_settlement": "true" 15 | } 16 | }).transaction 17 | 18 | def test_settle_transaction_settles_transaction(self): 19 | TestHelper.settle_transaction(self.transaction.id) 20 | self.assertEqual(Transaction.Status.Settled, Transaction.find(self.transaction.id).status) 21 | 22 | def test_settlement_confirm_transaction(self): 23 | TestHelper.settlement_confirm_transaction(self.transaction.id) 24 | self.assertEqual(Transaction.Status.SettlementConfirmed, Transaction.find(self.transaction.id).status) 25 | 26 | def test_settlement_decline_transaction(self): 27 | TestHelper.settlement_decline_transaction(self.transaction.id) 28 | self.assertEqual(Transaction.Status.SettlementDeclined, Transaction.find(self.transaction.id).status) 29 | -------------------------------------------------------------------------------- /tests/integration/test_testing_gateway.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree.configuration import Configuration 3 | from braintree.exceptions.test_operation_performed_in_production_error import TestOperationPerformedInProductionError 4 | 5 | class TestTestingGateway(unittest.TestCase): 6 | def setUp(self): 7 | config = Configuration(braintree.Environment.Production, "merchant_id", "public_key", "private_key") 8 | braintree_gateway = BraintreeGateway(config) 9 | self.gateway = TestingGateway(braintree_gateway) 10 | 11 | def test_error_is_raised_in_production_for_settle_transaction(self): 12 | with self.assertRaises(TestOperationPerformedInProductionError): 13 | self.gateway.settle_transaction("") 14 | 15 | def test_error_is_raised_in_production_for_make_past_due(self): 16 | with self.assertRaises(TestOperationPerformedInProductionError): 17 | self.gateway.make_past_due("") 18 | 19 | def test_error_is_raised_in_production_for_settlement_confirm_transaction(self): 20 | with self.assertRaises(TestOperationPerformedInProductionError): 21 | self.gateway.settlement_confirm_transaction("") 22 | 23 | def test_error_is_raised_in_production_for_settlement_decline_transaction(self): 24 | with self.assertRaises(TestOperationPerformedInProductionError): 25 | self.gateway.settlement_decline_transaction("") 26 | 27 | def test_error_is_raised_in_production_for_create_3ds_verification(self): 28 | with self.assertRaises(TestOperationPerformedInProductionError): 29 | self.gateway.create_3ds_verification("", "") 30 | 31 | def test_error_is_raised_in_production(self): 32 | with self.assertRaises(TestOperationPerformedInProductionError): 33 | self.gateway.settle_transaction("") 34 | -------------------------------------------------------------------------------- /tests/integration/test_transaction_line_item.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree.test.credit_card_numbers import CreditCardNumbers 3 | 4 | class TestTransactionLineItem(unittest.TestCase): 5 | 6 | def test_transaction_line_item_find_all_returns_line_items(self): 7 | transaction = Transaction.sale({ 8 | "amount": "35.05", 9 | "credit_card": { 10 | "number": CreditCardNumbers.Visa, 11 | "expiration_date": "05/2009", 12 | }, 13 | "line_items": [{ 14 | "quantity": "1.0232", 15 | "name": "Name #1", 16 | "kind": TransactionLineItem.Kind.Debit, 17 | "unit_amount": "45.1232", 18 | "total_amount": "45.15", 19 | "upc_code": "123456789", 20 | "upc_type": "UPC-A", 21 | "image_url": "https://google.com/image.png", 22 | }] 23 | }).transaction 24 | 25 | line_items = TransactionLineItem.find_all(transaction.id) 26 | self.assertEqual(1, len(line_items)) 27 | lineItem = line_items[0] 28 | self.assertEqual("1.0232", lineItem.quantity) 29 | self.assertEqual("Name #1", lineItem.name) 30 | self.assertEqual(TransactionLineItem.Kind.Debit, lineItem.kind) 31 | self.assertEqual("45.1232", lineItem.unit_amount) 32 | self.assertEqual("45.15", lineItem.total_amount) 33 | self.assertEqual("123456789", lineItem.upc_code) 34 | self.assertEqual("UPC-A", lineItem.upc_type) 35 | self.assertEqual("https://google.com/image.png",lineItem.image_url) 36 | 37 | -------------------------------------------------------------------------------- /tests/integration/test_transaction_line_item_gateway.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestTransactionLineItemGateway(unittest.TestCase): 4 | 5 | def test_transaction_line_item_gateway_find_all_raises_when_transaction_not_found(self): 6 | with self.assertRaises(NotFoundError): 7 | transaction_id = "willnotbefound" 8 | TransactionLineItem.find_all(transaction_id) 9 | -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braintree/braintree_python/7f96e2a1f7ab990c9387d9a0ef8a86a6cf5346bf/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/graphql/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braintree/braintree_python/7f96e2a1f7ab990c9387d9a0ef8a86a6cf5346bf/tests/unit/graphql/__init__.py -------------------------------------------------------------------------------- /tests/unit/graphql/test_customer_recommendations.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import unittest 2 | from braintree.graphql.enums.recommended_payment_option import RecommendedPaymentOption 3 | from braintree.graphql.types.payment_recommendation import PaymentRecommendation 4 | from braintree.graphql.types.payment_options import PaymentOptions 5 | from braintree.graphql.unions.customer_recommendations import CustomerRecommendations 6 | 7 | class TestCustomerRecommendations(unittest.TestCase): 8 | def test_init_with_payment_recommendations(self): 9 | rec1 = PaymentRecommendation(RecommendedPaymentOption.PAYPAL, 1) 10 | rec2 = PaymentRecommendation(RecommendedPaymentOption.VENMO, 2) 11 | recommendations = [rec1, rec2] 12 | 13 | customer_recs = CustomerRecommendations(payment_recommendations=recommendations) 14 | 15 | self.assertEqual(customer_recs.payment_recommendations, recommendations) 16 | 17 | def test_init_with_none(self): 18 | customer_recs = CustomerRecommendations() 19 | self.assertEqual(customer_recs.payment_recommendations, []) 20 | 21 | def test_payment_options_extracted(self): 22 | rec1 = PaymentRecommendation(RecommendedPaymentOption.PAYPAL, 1) 23 | rec2 = PaymentRecommendation(RecommendedPaymentOption.VENMO, 2) 24 | recommendations = [rec1, rec2] 25 | 26 | customer_recs = CustomerRecommendations(payment_recommendations=recommendations) 27 | 28 | expected_payment_options = [ 29 | PaymentOptions(RecommendedPaymentOption.PAYPAL, 1), 30 | PaymentOptions(RecommendedPaymentOption.VENMO, 2) 31 | ] 32 | 33 | self.assertEqual(customer_recs.payment_options[0].payment_option, expected_payment_options[0].payment_option) 34 | self.assertEqual(customer_recs.payment_options[0].recommended_priority, expected_payment_options[0].recommended_priority) 35 | self.assertEqual(customer_recs.payment_options[1].payment_option, expected_payment_options[1].payment_option) 36 | self.assertEqual(customer_recs.payment_options[1].recommended_priority, expected_payment_options[1].recommended_priority) 37 | -------------------------------------------------------------------------------- /tests/unit/graphql/test_monetary_amount_input.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import unittest 2 | from decimal import Decimal 3 | from braintree.graphql import MonetaryAmountInput 4 | 5 | class TestMonetaryAmountInput(unittest.TestCase): 6 | def test_monetary_amount_input_to_graphql_variables(self): 7 | input = MonetaryAmountInput(currency_code="EUR", value=Decimal("15.50")) 8 | 9 | graphql_variables = input.to_graphql_variables() 10 | 11 | self.assertEqual("EUR", graphql_variables["currencyCode"]) 12 | self.assertEqual("15.50", graphql_variables["value"]) 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/unit/graphql/test_paypal_payee_input.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import unittest 2 | from braintree.graphql import PayPalPayeeInput 3 | 4 | class TestPayPalPayeeInput(unittest.TestCase): 5 | def test_to_graphql_variables(self): 6 | input = ( 7 | PayPalPayeeInput.builder() 8 | .client_id("1") 9 | .email_address("test@paypal.com") 10 | .build() 11 | ) 12 | graphql_variables = input.to_graphql_variables() 13 | 14 | self.assertEqual("1", graphql_variables["clientId"]) 15 | self.assertEqual("test@paypal.com", graphql_variables["emailAddress"]) -------------------------------------------------------------------------------- /tests/unit/graphql/test_paypal_purchase_unit_input.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import unittest 2 | from decimal import Decimal 3 | from braintree.graphql import MonetaryAmountInput, PayPalPayeeInput, PayPalPurchaseUnitInput 4 | 5 | class TestPayPalPurchaseUnit(unittest.TestCase): 6 | def test_to_graphql_variables_all_fields(self): 7 | amount = MonetaryAmountInput(currency_code="USD", value=Decimal("10.00")) 8 | payee = ( 9 | PayPalPayeeInput.builder() 10 | .email_address("test@example.com") 11 | .client_id("client456") 12 | .build() 13 | ) 14 | 15 | purchase_unit = PayPalPurchaseUnitInput.builder(amount).payee(payee).build() 16 | graphql_variables = purchase_unit.to_graphql_variables() 17 | 18 | self.assertIn("amount", graphql_variables) 19 | self.assertIn("payee", graphql_variables) -------------------------------------------------------------------------------- /tests/unit/graphql/test_phone_input.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import unittest 2 | from braintree.graphql import PhoneInput 3 | 4 | class TestPhoneInput(unittest.TestCase): 5 | def test_to_graphql_variables(self): 6 | input_ = PhoneInput.builder() \ 7 | .country_phone_code("1") \ 8 | .phone_number("5551234567") \ 9 | .extension_number("1234").build() 10 | 11 | graphql_variables = input_.to_graphql_variables() 12 | 13 | self.assertEqual("1",graphql_variables["countryPhoneCode"]) 14 | self.assertEqual("5551234567",graphql_variables["phoneNumber"]) 15 | self.assertEqual("1234",graphql_variables["extensionNumber"]) 16 | -------------------------------------------------------------------------------- /tests/unit/graphql/test_update_customer_session_input.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import unittest 2 | from braintree.graphql import UpdateCustomerSessionInput, CustomerSessionInput, PhoneInput 3 | 4 | class TestUpdateCustomerSessionInput(unittest.TestCase): 5 | def test_to_graphql_variables_with_all_fields(self): 6 | phone_input = PhoneInput.builder() \ 7 | .country_phone_code("1") \ 8 | .phone_number("5551234567") \ 9 | .extension_number("1234").build() 10 | 11 | customer_input = CustomerSessionInput.builder() \ 12 | .email("test@example.com") \ 13 | .phone(phone_input) \ 14 | .device_fingerprint_id("device_fingerprint_id") \ 15 | .paypal_app_installed(True) \ 16 | .venmo_app_installed(False).build() 17 | 18 | input_ = UpdateCustomerSessionInput.builder("session_id") \ 19 | .merchant_account_id("merchant_account_id") \ 20 | .customer(customer_input).build() 21 | 22 | 23 | graphql_variables = input_.to_graphql_variables() 24 | 25 | self.assertEqual("merchant_account_id", graphql_variables["merchantAccountId"]) 26 | self.assertEqual("session_id", graphql_variables["sessionId"]) 27 | 28 | customer_variables = graphql_variables["customer"] 29 | self.assertEqual("test@example.com", customer_variables["email"]) 30 | self.assertEqual("device_fingerprint_id", customer_variables["deviceFingerprintId"]) 31 | self.assertTrue(customer_variables["paypalAppInstalled"]) 32 | self.assertFalse(customer_variables["venmoAppInstalled"]) 33 | self.assertEqual("1", customer_variables["phone"]["countryPhoneCode"]) 34 | self.assertEqual("5551234567", customer_variables["phone"]["phoneNumber"]) 35 | self.assertEqual("1234", customer_variables["phone"]["extensionNumber"]) 36 | 37 | def test_to_graphql_variables_without_optional_fields(self): 38 | input_ = UpdateCustomerSessionInput.builder("session_id") \ 39 | .merchant_account_id("merchant_account_id").build() 40 | 41 | graphql_variables = input_.to_graphql_variables() 42 | 43 | self.assertEqual("merchant_account_id", graphql_variables["merchantAccountId"]) 44 | self.assertEqual("session_id", graphql_variables["sessionId"]) 45 | self.assertNotIn("customer", graphql_variables) 46 | 47 | 48 | -------------------------------------------------------------------------------- /tests/unit/merchant_account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braintree/braintree_python/7f96e2a1f7ab990c9387d9a0ef8a86a6cf5346bf/tests/unit/merchant_account/__init__.py -------------------------------------------------------------------------------- /tests/unit/merchant_account/test_address_details.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree.merchant_account.address_details import AddressDetails 3 | 4 | class TestAddressDetails(unittest.TestCase): 5 | def test_repr_has_all_fields(self): 6 | details = AddressDetails({ 7 | "street_address": "123 First St", 8 | "region": "Las Vegas", 9 | "locality": "NV", 10 | "postal_code": "89913" 11 | }) 12 | 13 | regex = r"" 14 | 15 | matches = re.match(regex, repr(details)) 16 | self.assertTrue(matches) 17 | -------------------------------------------------------------------------------- /tests/unit/test_android_pay_card.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestAndroidPayCard(unittest.TestCase): 4 | def test_expiration_date(self): 5 | card = AndroidPayCard(None, { 6 | "customer_id": "12345", 7 | "number": "4111111111111111", 8 | "expiration_month": "05", 9 | "expiration_year": "2014", 10 | "cvv": "100", 11 | "cardholder_name": "John Doe" 12 | }) 13 | 14 | self.assertEqual("05/2014", card.expiration_date) 15 | 16 | def test_expiration_date_no_month(self): 17 | card = AndroidPayCard(None, { 18 | "customer_id": "12345", 19 | "number": "4111111111111111", 20 | "expiration_month": "", 21 | "expiration_year": "2014", 22 | "cvv": "100", 23 | "cardholder_name": "John Doe" 24 | }) 25 | 26 | self.assertEqual(None, card.expiration_date) 27 | 28 | def test_expiration_date_no_year(self): 29 | card = AndroidPayCard(None, { 30 | "customer_id": "12345", 31 | "number": "4111111111111111", 32 | "expiration_month": "05", 33 | "expiration_year": "", 34 | "cvv": "100", 35 | "cardholder_name": "John Doe" 36 | }) 37 | 38 | self.assertEqual(None, card.expiration_date) 39 | 40 | def test_bin_data(self): 41 | card = AndroidPayCard(None, { 42 | "customer_id": "12345", 43 | "number": "4111111111111111", 44 | "expiration_month": "05", 45 | "expiration_year": "2014", 46 | "cvv": "100", 47 | "cardholder_name": "John Doe", 48 | "business": "Yes", 49 | "consumer": "No", 50 | "corporate": "Yes", 51 | "purchase": "No", 52 | }) 53 | 54 | self.assertEqual(CreditCard.Business.Yes, card.business) 55 | self.assertEqual(CreditCard.Consumer.No, card.consumer) 56 | self.assertEqual(CreditCard.Corporate.Yes, card.corporate) 57 | self.assertEqual(CreditCard.Purchase.No, card.purchase) 58 | 59 | -------------------------------------------------------------------------------- /tests/unit/test_apple_pay_card.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestApplePayCard(unittest.TestCase): 4 | def test_expiration_date(self): 5 | card = ApplePayCard(None, { 6 | "customer_id": "12345", 7 | "number": "4111111111111111", 8 | "expiration_month": "05", 9 | "expiration_year": "2014", 10 | "cvv": "100", 11 | "cardholder_name": "John Doe" 12 | }) 13 | 14 | self.assertEqual("05/2014", card.expiration_date) 15 | 16 | def test_expiration_date_no_month(self): 17 | card = ApplePayCard(None, { 18 | "customer_id": "12345", 19 | "number": "4111111111111111", 20 | "expiration_month": "", 21 | "expiration_year": "2014", 22 | "cvv": "100", 23 | "cardholder_name": "John Doe" 24 | }) 25 | 26 | self.assertEqual(None, card.expiration_date) 27 | 28 | def test_expiration_date_no_year(self): 29 | card = ApplePayCard(None, { 30 | "customer_id": "12345", 31 | "number": "4111111111111111", 32 | "expiration_month": "05", 33 | "expiration_year": "", 34 | "cvv": "100", 35 | "cardholder_name": "John Doe" 36 | }) 37 | 38 | self.assertEqual(None, card.expiration_date) 39 | 40 | def test_bin_data(self): 41 | card = ApplePayCard(None, { 42 | "business": "Unknown", 43 | "consumer": "Unknown", 44 | "corporate": "Unknown", 45 | "purchase": "Unknown" 46 | }) 47 | 48 | self.assertEqual(CreditCard.Business.Unknown, card.business) 49 | self.assertEqual(CreditCard.Consumer.Unknown, card.consumer) 50 | self.assertEqual(CreditCard.Corporate.Unknown, card.corporate) 51 | self.assertEqual(CreditCard.Purchase.Unknown, card.purchase) 52 | -------------------------------------------------------------------------------- /tests/unit/test_apple_pay_gateway.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree.apple_pay_gateway import ApplePayGateway 3 | from unittest.mock import MagicMock 4 | 5 | class TestApplePayGateway(unittest.TestCase): 6 | @staticmethod 7 | def setup_apple_pay_gateway_and_mock_http(): 8 | braintree_gateway = BraintreeGateway(Configuration.instantiate()) 9 | apple_pay_gateway = ApplePayGateway(braintree_gateway) 10 | http_mock = MagicMock(name='config.http') 11 | braintree_gateway.config.http = http_mock 12 | return apple_pay_gateway, http_mock 13 | 14 | def test_registered_domains(self): 15 | apple_pay_gateway, http_mock = self.setup_apple_pay_gateway_and_mock_http() 16 | apple_pay_gateway.registered_domains() 17 | self.assertTrue("get('/merchants/integration_merchant_id/processing/apple_pay/registered_domains')" in str(http_mock.mock_calls)) 18 | 19 | def test_register_domain(self): 20 | apple_pay_gateway, http_mock = self.setup_apple_pay_gateway_and_mock_http() 21 | apple_pay_gateway.register_domain('test.example.com') 22 | self.assertTrue("post('/merchants/integration_merchant_id/processing/apple_pay/validate_domains', {'url': 'test.example.com'})" in str(http_mock.mock_calls)) 23 | 24 | def test_unregister_domain(self): 25 | apple_pay_gateway, http_mock = self.setup_apple_pay_gateway_and_mock_http() 26 | apple_pay_gateway.unregister_domain('test.example.com') 27 | self.assertTrue("delete('/merchants/integration_merchant_id/processing/apple_pay/unregister_domain?url=test.example.com')" in str(http_mock.mock_calls)) 28 | -------------------------------------------------------------------------------- /tests/unit/test_authorization_adjustment.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from datetime import datetime 3 | from braintree.authorization_adjustment import AuthorizationAdjustment 4 | 5 | class TestAuthorizationAdjustment(unittest.TestCase): 6 | def test_constructor(self): 7 | attributes = { 8 | "amount": "-20.00", 9 | "timestamp": datetime(2017, 7, 12, 1, 2, 3), 10 | "success": True, 11 | "processor_response_code": "1000", 12 | "processor_response_text": "Approved", 13 | } 14 | 15 | authorization_adjustment = AuthorizationAdjustment(attributes) 16 | 17 | self.assertEqual(authorization_adjustment.amount, Decimal("-20.00")) 18 | self.assertEqual(authorization_adjustment.timestamp, datetime(2017, 7, 12, 1, 2, 3)) 19 | self.assertEqual(authorization_adjustment.success, True) 20 | self.assertEqual(authorization_adjustment.processor_response_code, "1000") 21 | self.assertEqual(authorization_adjustment.processor_response_text, "Approved") 22 | 23 | def test_constructor_with_amount_as_None(self): 24 | attributes = { 25 | "amount": None, 26 | "timestamp": datetime(2017, 7, 12, 1, 2, 3), 27 | "success": True, 28 | "processor_response_code": "1000", 29 | } 30 | 31 | authorization_adjustment = AuthorizationAdjustment(attributes) 32 | 33 | self.assertEqual(authorization_adjustment.amount, None) 34 | self.assertEqual(authorization_adjustment.timestamp, datetime(2017, 7, 12, 1, 2, 3)) 35 | self.assertEqual(authorization_adjustment.success, True) 36 | self.assertEqual(authorization_adjustment.processor_response_code, "1000") 37 | 38 | def test_constructor_without_amount(self): 39 | attributes = { 40 | "timestamp": datetime(2017, 7, 12, 1, 2, 3), 41 | "success": True, 42 | "processor_response_code": "1000", 43 | "processor_response_text": "Approved", 44 | } 45 | 46 | authorization_adjustment = AuthorizationAdjustment(attributes) 47 | 48 | self.assertEqual(authorization_adjustment.timestamp, datetime(2017, 7, 12, 1, 2, 3)) 49 | self.assertEqual(authorization_adjustment.success, True) 50 | self.assertEqual(authorization_adjustment.processor_response_code, "1000") 51 | self.assertEqual(authorization_adjustment.processor_response_text, "Approved") 52 | -------------------------------------------------------------------------------- /tests/unit/test_client_token.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestClientToken(unittest.TestCase): 4 | def test_credit_card_options_require_customer_id(self): 5 | for option in ["fail_on_duplicate_payment_method", "fail_on_duplicate_payment_method_for_customer", "make_default", "verify_card"]: 6 | with self.assertRaisesRegex(InvalidSignatureError, option): 7 | ClientToken.generate({ 8 | "options": {option: True} 9 | }) 10 | 11 | def test_generate_delegates_client_token_generation_to_gateway(self): 12 | class MockGateway: 13 | def generate(self, _): 14 | return "mock_client_token" 15 | 16 | mock_gateway = MockGateway() 17 | client_token = ClientToken.generate({}, mock_gateway) 18 | 19 | self.assertEqual("mock_client_token", client_token) 20 | -------------------------------------------------------------------------------- /tests/unit/test_crypto.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestCrypto(unittest.TestCase): 4 | def test_sha1_hmac_hash(self): 5 | actual = Crypto.sha1_hmac_hash("secretKey", "hello world") 6 | self.assertEqual("d503d7a1a6adba1e6474e9ff2c4167f9dfdf4247", actual) 7 | 8 | def test_sha256_hmac_hash(self): 9 | actual = Crypto.sha256_hmac_hash("secret-key", "secret-message") 10 | self.assertEqual("68e7f2ecab71db67b1aca2a638f5122810315c3013f27c2196cd53e88709eecc", actual) 11 | 12 | def test_secure_compare_returns_true_when_same(self): 13 | self.assertTrue(Crypto.secure_compare("a_string", "a_string")) 14 | 15 | def test_secure_compare_returns_false_when_different_lengths(self): 16 | self.assertFalse(Crypto.secure_compare("a_string", "a_string_that_is_longer")) 17 | 18 | def test_secure_compare_returns_false_when_different(self): 19 | self.assertFalse(Crypto.secure_compare("a_string", "a_strong")) 20 | -------------------------------------------------------------------------------- /tests/unit/test_customer.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestCustomer(unittest.TestCase): 4 | def test_create_raise_exception_with_bad_keys(self): 5 | with self.assertRaisesRegex(KeyError, "'Invalid keys: bad_key'"): 6 | Customer.create({"bad_key": "value"}) 7 | 8 | def test_create_raise_exception_with_bad_nested_keys(self): 9 | with self.assertRaisesRegex(KeyError, "'Invalid keys: credit_card\[bad_key\]'"): 10 | Customer.create({"credit_card": {"bad_key": "value"}}) 11 | 12 | def test_update_raise_exception_with_bad_keys(self): 13 | with self.assertRaisesRegex(KeyError, "'Invalid keys: bad_key'"): 14 | Customer.update("id", {"bad_key": "value"}) 15 | 16 | def test_update_raise_exception_with_bad_nested_keys(self): 17 | with self.assertRaisesRegex(KeyError, "'Invalid keys: credit_card\[bad_key\]'"): 18 | Customer.update("id", {"credit_card": {"bad_key": "value"}}) 19 | 20 | def test_finding_empty_id_raises_not_found_exception(self): 21 | with self.assertRaises(NotFoundError): 22 | Customer.find(" ") 23 | 24 | def test_finding_none_raises_not_found_exception(self): 25 | with self.assertRaises(NotFoundError): 26 | Customer.find(None) 27 | 28 | def test_initialize_sets_paypal_accounts(self): 29 | customer = Customer("gateway", { 30 | "paypal_accounts": [ 31 | {"token": "token1"}, 32 | {"token": "token2"} 33 | ], 34 | "sepa_debit_accounts": [ 35 | {"token": "sdd1"}, 36 | {"token": "sdd2"} 37 | ] 38 | }) 39 | 40 | self.assertEqual(2, len(customer.paypal_accounts)) 41 | self.assertEqual("token1", customer.paypal_accounts[0].token) 42 | self.assertEqual("token2", customer.paypal_accounts[1].token) 43 | self.assertEqual(2, len(customer.sepa_direct_debit_accounts)) 44 | self.assertEqual("sdd1", customer.sepa_direct_debit_accounts[0].token) 45 | self.assertEqual("sdd2", customer.sepa_direct_debit_accounts[1].token) 46 | -------------------------------------------------------------------------------- /tests/unit/test_disbursement.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from datetime import date 3 | 4 | class TestDisbursement(unittest.TestCase): 5 | attributes = { 6 | "merchant_account": { 7 | "id": "merchant_account", 8 | "status": "active", 9 | }, 10 | "id": "123456", 11 | "exception_message": "invalid_account_number", 12 | "amount": "100.00", 13 | "disbursement_date": date(2013, 4, 10), 14 | "follow_up_action": "update", 15 | "transaction_ids": ["asdf", "qwer"], 16 | "disbursement_type": "credit" 17 | } 18 | 19 | def test_constructor(self): 20 | disbursement = Disbursement(None, TestDisbursement.attributes) 21 | 22 | self.assertEqual("123456", disbursement.id) 23 | self.assertEqual(Decimal("100.00"), disbursement.amount) 24 | self.assertEqual(["asdf", "qwer"], disbursement.transaction_ids) 25 | self.assertEqual("active", disbursement.merchant_account.status) 26 | self.assertEqual("merchant_account", disbursement.merchant_account.id) 27 | 28 | def test_credit(self): 29 | disbursement = Disbursement(None, TestDisbursement.attributes) 30 | 31 | self.assertTrue(disbursement.is_credit()) 32 | 33 | def test_debit(self): 34 | thing = TestDisbursement.attributes 35 | thing["disbursement_type"] = "debit" 36 | 37 | disbursement = Disbursement(None, TestDisbursement.attributes) 38 | 39 | self.assertTrue(disbursement.is_debit()) 40 | -------------------------------------------------------------------------------- /tests/unit/test_disbursement_detail.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree.disbursement_detail import DisbursementDetail 3 | 4 | class TestDisbursementDetail(unittest.TestCase): 5 | def test_is_valid_true(self): 6 | detail_hash = { 7 | 'settlement_amount': '27.00', 8 | 'settlement_currency_iso_code': 'USD', 9 | 'settlement_currency_exchange_rate': '1', 10 | 'disbursed_at': datetime(2013, 4, 11, 0, 0, 0), 11 | 'disbursement_date': date(2013, 4, 10), 12 | 'funds_held': False 13 | } 14 | disbursement_details = DisbursementDetail(detail_hash) 15 | self.assertTrue(disbursement_details.is_valid) 16 | 17 | def test_is_valid_false(self): 18 | detail_hash = { 19 | 'settlement_amount': None, 20 | 'settlement_currency_iso_code': None, 21 | 'settlement_currency_exchange_rate': None, 22 | 'disbursed_at': None, 23 | 'disbursement_date': None, 24 | 'funds_held': None 25 | } 26 | disbursement_details = DisbursementDetail(detail_hash) 27 | self.assertEqual(False, disbursement_details.is_valid) 28 | -------------------------------------------------------------------------------- /tests/unit/test_document_upload.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestDocumentUpload(unittest.TestCase): 4 | def test_create_raises_exception_with_bad_keys(self): 5 | with self.assertRaisesRegex(KeyError, "'Invalid keys: bad_key'"): 6 | DocumentUpload.create({"bad_key": "value"}) 7 | -------------------------------------------------------------------------------- /tests/unit/test_error_result.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestErrorResult(unittest.TestCase): 4 | def test_it_initializes_params_and_errors(self): 5 | errors = { 6 | "scope": { 7 | "errors": [{"code": 123, "message": "something is invalid", "attribute": "something"}] 8 | } 9 | } 10 | 11 | result = ErrorResult("gateway", {"errors": errors, "params": "params", "message": "brief description"}) 12 | self.assertFalse(result.is_success) 13 | self.assertEqual("params", result.params) 14 | self.assertEqual(1, result.errors.size) 15 | self.assertEqual("something is invalid", result.errors.for_object("scope")[0].message) 16 | self.assertEqual("something", result.errors.for_object("scope")[0].attribute) 17 | self.assertEqual(123, result.errors.for_object("scope")[0].code) 18 | 19 | def test_it_ignores_other_params(self): 20 | errors = { 21 | "scope": { 22 | "errors": [{"code": 123, "message": "something is invalid", "attribute": "something"}] 23 | } 24 | } 25 | 26 | result = ErrorResult("gateway", {"errors": errors, "params": "params", "message": "brief description", "other": "stuff"}) 27 | self.assertFalse(result.is_success) 28 | 29 | def test_transaction_is_none_if_not_set(self): 30 | result = ErrorResult("gateway", {"errors": {}, "params": {}, "message": "brief description"}) 31 | self.assertTrue(result.transaction is None) 32 | 33 | def test_verification_is_none_if_not_set(self): 34 | result = ErrorResult("gateway", {"errors": {}, "params": {}, "message": "brief description"}) 35 | self.assertTrue(result.credit_card_verification is None) 36 | -------------------------------------------------------------------------------- /tests/unit/test_errors.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestErrors(unittest.TestCase): 4 | def test_errors_for_the_given_scope(self): 5 | errors = Errors({"level1": {"errors": [{"code": "code1", "attribute": "attr", "message": "message"}]}}) 6 | self.assertEqual(1, errors.for_object("level1").size) 7 | self.assertEqual(1, len(errors.for_object("level1"))) 8 | 9 | def test_for_object_returns_empty_errors_collection_if_no_errors_at_given_scope(self): 10 | errors = Errors({"level1": {"errors": [{"code": "code1", "attribute": "attr", "message": "message"}]}}) 11 | self.assertEqual(0, errors.for_object("no_errors_here").size) 12 | self.assertEqual(0, len(errors.for_object("no_errors_here"))) 13 | 14 | def test_size_returns_number_of_errors_at_first_level_if_only_one_level_exists(self): 15 | test_hash = { 16 | "level1": {"errors": [{"code": "code1", "attribute": "attr", "message": "message"}]} 17 | } 18 | self.assertEqual(1, Errors(test_hash).size) 19 | self.assertEqual(1, len(Errors(test_hash))) 20 | 21 | def test_size_returns_number_of_errors_at_all_levels(self): 22 | test_hash = { 23 | "level1": { 24 | "errors": [{"code": "code1", "attribute": "attr", "message": "message"}], 25 | "level2": { 26 | "errors": [ 27 | {"code": "code2", "attribute": "attr", "message": "message"}, 28 | {"code": "code3", "attribute": "attr", "message": "message"} 29 | ] 30 | } 31 | } 32 | } 33 | self.assertEqual(3, Errors(test_hash).size) 34 | self.assertEqual(3, len(Errors(test_hash))) 35 | 36 | def test_deep_errors_returns_all_errors(self): 37 | test_hash = { 38 | "level1": { 39 | "errors": [{"code": "code1", "attribute": "attr", "message": "message"}], 40 | "level2": { 41 | "errors": [ 42 | {"code": "code2", "attribute": "attr", "message": "message"}, 43 | {"code": "code3", "attribute": "attr", "message": "message"} 44 | ] 45 | } 46 | } 47 | } 48 | 49 | errors = Errors(test_hash).deep_errors 50 | self.assertEqual(["code1", "code2", "code3"], [error.code for error in errors]) 51 | -------------------------------------------------------------------------------- /tests/unit/test_europe_bank_account.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestEuropeBankAccount(unittest.TestCase): 4 | def test_mandate_type_constants(self): 5 | self.assertEqual("business", EuropeBankAccount.MandateType.Business) 6 | self.assertEqual("consumer", EuropeBankAccount.MandateType.Consumer) 7 | -------------------------------------------------------------------------------- /tests/unit/test_exchange_rate_quote_input.py: -------------------------------------------------------------------------------- 1 | from braintree.exchange_rate_quote_request import ExchangeRateQuoteInput 2 | from tests.test_helper import * 3 | 4 | class TestExchangeRateQuoteInput(unittest.TestCase): 5 | def test_to_graphql_variables(self): 6 | attributes = {"base_currency":"USD", 7 | "quote_currency":"EUR", 8 | "base_amount":"10.15", 9 | "markup":"5.00"} 10 | input = ExchangeRateQuoteInput(None,attributes) 11 | 12 | map = input.to_graphql_variables() 13 | self.assertEqual(map.get("baseCurrency"), "USD") 14 | self.assertEqual(map.get("quoteCurrency"), "EUR") 15 | self.assertEqual(map.get("baseAmount"), "10.15") 16 | self.assertEqual(map.get("markup"), "5.00") 17 | 18 | def test_to_graphql_variables_without_markup_and_base_amount(self): 19 | attributes = {"base_currency":"USD", 20 | "quote_currency":"CAD"} 21 | input = ExchangeRateQuoteInput(None,attributes) 22 | 23 | map = input.to_graphql_variables() 24 | self.assertEqual(map.get("baseCurrency"), "USD") 25 | self.assertEqual(map.get("quoteCurrency"), "CAD") 26 | self.assertIsNone(map.get("baseAmount")) 27 | self.assertIsNone(map.get("markup")) 28 | 29 | def test_to_graphql_variables_with_all_empty_fields(self): 30 | input = ExchangeRateQuoteInput(None, None) 31 | 32 | map = input.to_graphql_variables() 33 | self.assertIsNone(map.get("baseCurrency")) 34 | self.assertIsNone(map.get("quoteCurrency")) 35 | self.assertIsNone(map.get("baseAmount")) 36 | self.assertIsNone(map.get("markup")) -------------------------------------------------------------------------------- /tests/unit/test_liability_shift.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree import * 3 | 4 | class TestLiabilityShift(unittest.TestCase): 5 | def test_initialization_of_attributes(self): 6 | liability_shift = LiabilityShift( 7 | { 8 | "responsible_party": "paypal", 9 | "conditions": ["unauthorized"], 10 | } 11 | ) 12 | self.assertEqual("paypal", liability_shift.responsible_party) 13 | self.assertEqual(["unauthorized"], liability_shift.conditions) 14 | -------------------------------------------------------------------------------- /tests/unit/test_meta_checkout_card.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree.meta_checkout_card import MetaCheckoutCard 3 | 4 | class TestMetaCheckoutCard(unittest.TestCase): 5 | def test_initialization(self): 6 | card = MetaCheckoutCard(None, { 7 | "bin": "abc1234", 8 | "card_type": "Visa", 9 | "cardholder_name": "John Doe", 10 | "container_id": "a-container-id", 11 | "expiration_month": "05", 12 | "expiration_year": "2024", 13 | "is_network_tokenized": False, 14 | "last_4": "5678" 15 | }) 16 | 17 | self.assertEqual(card.bin, "abc1234") 18 | self.assertEqual(card.card_type, "Visa") 19 | self.assertEqual(card.cardholder_name, "John Doe") 20 | self.assertEqual(card.container_id, "a-container-id") 21 | self.assertEqual(card.expiration_month, "05") 22 | self.assertEqual(card.expiration_year, "2024") 23 | self.assertEqual(card.is_network_tokenized, False) 24 | self.assertEqual(card.last_4, "5678") 25 | 26 | def test_expiration_date(self): 27 | card = MetaCheckoutCard(None, { 28 | "bin": "abc123", 29 | "card_type": "Visa", 30 | "cardholder_name": "John Doe", 31 | "container_id": "a-container-id", 32 | "expiration_month": "05", 33 | "expiration_year": "2024", 34 | "is_network_tokenized": False, 35 | "last_4": "5678" 36 | }) 37 | 38 | self.assertEqual(card.expiration_date, "05/2024") 39 | 40 | def test_masked_number(self): 41 | card = MetaCheckoutCard(None, { 42 | "bin": "abc123", 43 | "card_type": "Visa", 44 | "cardholder_name": "John Doe", 45 | "container_id": "a-container-id", 46 | "expiration_month": "05", 47 | "expiration_year": "2024", 48 | "is_network_tokenized": False, 49 | "last_4": "5678" 50 | }) 51 | 52 | self.assertEqual(card.masked_number, "abc123******5678") 53 | -------------------------------------------------------------------------------- /tests/unit/test_meta_checkout_token.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree.meta_checkout_card import MetaCheckoutCard 3 | 4 | class TestMetaCheckoutToken(unittest.TestCase): 5 | def test_initialization(self): 6 | card = MetaCheckoutCard(None, { 7 | "bin": "abc1234", 8 | "card_type": "Visa", 9 | "cardholder_name": "John Doe", 10 | "container_id": "a-container-id", 11 | "cryptogram": "a-cryptogram", 12 | "ecommerce_indicator": "01", 13 | "expiration_month": "05", 14 | "expiration_year": "2024", 15 | "is_network_tokenized": True, 16 | "last_4": "5678" 17 | }) 18 | 19 | self.assertEqual(card.bin, "abc1234") 20 | self.assertEqual(card.card_type, "Visa") 21 | self.assertEqual(card.cardholder_name, "John Doe") 22 | self.assertEqual(card.container_id, "a-container-id") 23 | self.assertEqual(card.cryptogram, "a-cryptogram") 24 | self.assertEqual(card.ecommerce_indicator, "01") 25 | self.assertEqual(card.expiration_month, "05") 26 | self.assertEqual(card.expiration_year, "2024") 27 | self.assertEqual(card.is_network_tokenized, True) 28 | self.assertEqual(card.last_4, "5678") 29 | 30 | def test_expiration_date(self): 31 | card = MetaCheckoutCard(None, { 32 | "bin": "abc123", 33 | "card_type": "Visa", 34 | "cardholder_name": "John Doe", 35 | "container_id": "a-container-id", 36 | "expiration_month": "05", 37 | "expiration_year": "2024", 38 | "is_network_tokenized": True, 39 | "last_4": "5678" 40 | }) 41 | 42 | self.assertEqual(card.expiration_date, "05/2024") 43 | 44 | def test_masked_number(self): 45 | card = MetaCheckoutCard(None, { 46 | "bin": "abc123", 47 | "card_type": "Visa", 48 | "cardholder_name": "John Doe", 49 | "container_id": "a-container-id", 50 | "expiration_month": "05", 51 | "expiration_year": "2024", 52 | "is_network_tokenized": True, 53 | "last_4": "5678" 54 | }) 55 | 56 | self.assertEqual(card.masked_number, "abc123******5678") 57 | -------------------------------------------------------------------------------- /tests/unit/test_oauth_access_revocation.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | import datetime 3 | 4 | class TestOAuthAccessRevocation(unittest.TestCase): 5 | def test_assigns_merchant_id(self): 6 | revocation = OAuthAccessRevocation({"merchant_id": "abc123xyz"}) 7 | 8 | self.assertEqual(revocation.merchant_id, "abc123xyz") 9 | -------------------------------------------------------------------------------- /tests/unit/test_paginated_collection.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | from braintree.paginated_collection import PaginatedCollection 4 | from braintree.paginated_result import PaginatedResult 5 | 6 | class TestPaginatedCollection(unittest.TestCase): 7 | def test_fetches_once_when_page_and_total_sizes_match(self): 8 | def paging_function(current_page): 9 | if current_page > 1: 10 | raise "too many pages fetched" 11 | else: 12 | return PaginatedResult(1, 1, [1]) 13 | collection = PaginatedCollection(paging_function) 14 | 15 | items = [i for i in collection.items] 16 | self.assertEqual(1, len(items)) 17 | 18 | def test_fetches_collections_less_than_one_page(self): 19 | def paging_function(current_page): 20 | if current_page > 1: 21 | raise "too many pages fetched" 22 | else: 23 | return PaginatedResult(2, 5, [1, 2]) 24 | collection = PaginatedCollection(paging_function) 25 | 26 | items = [i for i in collection.items] 27 | self.assertEqual(2, len(items)) 28 | self.assertEqual(1, items[0]) 29 | self.assertEqual(2, items[1]) 30 | 31 | def test_fetches_multiple_pages(self): 32 | def paging_function(current_page): 33 | if current_page > 2: 34 | raise "too many pages fetched" 35 | else: 36 | return PaginatedResult(2, 1, [current_page]) 37 | collection = PaginatedCollection(paging_function) 38 | 39 | items = [i for i in collection.items] 40 | self.assertEqual(2, len(items)) 41 | self.assertEqual(1, items[0]) 42 | self.assertEqual(2, items[1]) 43 | -------------------------------------------------------------------------------- /tests/unit/test_partner_merchant.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestPartnerMerchant(unittest.TestCase): 4 | def test_representation(self): 5 | merchant = PartnerMerchant(None, {"partner_merchant_id": "abc123", 6 | "private_key": "my_private_key", 7 | "public_key": "my_public_key", 8 | "merchant_public_id": "foobar", 9 | "client_side_encryption_key": "cse_key"}) 10 | self.assertTrue("partner_merchant_id: 'abc123'" in repr(merchant)) 11 | self.assertTrue("public_key: 'my_public_key'" in repr(merchant)) 12 | self.assertTrue("merchant_public_id: 'foobar'" in repr(merchant)) 13 | self.assertTrue("client_side_encryption_key: 'cse_key'" in repr(merchant)) 14 | 15 | self.assertFalse("private_key: 'my_private_key'" in repr(merchant)) 16 | -------------------------------------------------------------------------------- /tests/unit/test_payment_method_nonce.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestPaymentMethodNonce(unittest.TestCase): 4 | def test_finding_empty_id_raises_not_found_exception(self): 5 | with self.assertRaises(NotFoundError): 6 | PaymentMethodNonce.find(" ") 7 | 8 | def test_finding_None_id_raises_not_found_exception(self): 9 | with self.assertRaises(NotFoundError): 10 | PaymentMethodNonce.find(None) 11 | -------------------------------------------------------------------------------- /tests/unit/test_payment_method_parser.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree.payment_method_parser import parse_payment_method 3 | from unittest.mock import MagicMock 4 | 5 | class TestPaymentMethodParser(unittest.TestCase): 6 | def test_parse_response_returns_a_credit_card(self): 7 | credit_card = parse_payment_method(BraintreeGateway(None), { 8 | "credit_card": {"bin": "411111", "last_4": "1111"} 9 | }) 10 | 11 | self.assertEqual(CreditCard, credit_card.__class__) 12 | self.assertEqual("411111", credit_card.bin) 13 | self.assertEqual("1111", credit_card.last_4) 14 | 15 | def test_parse_response_returns_a_paypal_account(self): 16 | paypal_account = parse_payment_method(BraintreeGateway(None), { 17 | "paypal_account": {"token": "1234", "default": False} 18 | }) 19 | 20 | self.assertEqual(PayPalAccount, paypal_account.__class__) 21 | self.assertEqual("1234", paypal_account.token) 22 | self.assertFalse(paypal_account.default) 23 | 24 | def test_parse_response_returns_a_sepa_direct_debit_account(self): 25 | sdd_account = parse_payment_method(BraintreeGateway(None), { 26 | "sepa_debit_account": {"token": "1234", "default": False} 27 | }) 28 | 29 | self.assertEqual(SepaDirectDebitAccount, sdd_account.__class__) 30 | self.assertEqual("1234", sdd_account.token) 31 | self.assertFalse(sdd_account.default) 32 | 33 | def test_parse_response_returns_an_unknown_payment_method(self): 34 | unknown_payment_method = parse_payment_method(BraintreeGateway(None), { 35 | "new_fancy_payment_method": { 36 | "token": "1234", 37 | "default": True, 38 | "other_fancy_thing": "is-shiny" 39 | } 40 | }) 41 | 42 | self.assertEqual(UnknownPaymentMethod, unknown_payment_method.__class__) 43 | self.assertEqual("1234", unknown_payment_method.token) 44 | self.assertTrue(unknown_payment_method.default) 45 | 46 | -------------------------------------------------------------------------------- /tests/unit/test_resource_collection.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestResourceCollection(unittest.TestCase): 4 | collection_data = { 5 | "search_results": { 6 | "page_size": 2, 7 | "ids": ["0", "1", "2", "3", "4"] 8 | } 9 | } 10 | 11 | class TestResource: 12 | items = ["a", "b", "c", "d", "e"] 13 | 14 | @staticmethod 15 | def fetch(_, ids): 16 | return [TestResourceCollection.TestResource.items[int(resource_id)] for resource_id in ids] 17 | 18 | def test_iterating_over_contents(self): 19 | collection = ResourceCollection("some_query", self.collection_data, TestResourceCollection.TestResource.fetch) 20 | new_items = [] 21 | index = 0 22 | for item in collection.items: 23 | self.assertEqual(TestResourceCollection.TestResource.items[index], item) 24 | new_items.append(item) 25 | index += 1 26 | 27 | self.assertEqual(5, len(new_items)) 28 | 29 | def test_iterate_using_iterator_protocol(self): 30 | collection = ResourceCollection("some_query", self.collection_data, TestResourceCollection.TestResource.fetch) 31 | for test_elem, coll_elem in zip(self.TestResource.items, collection): 32 | self.assertEqual(test_elem, coll_elem) 33 | 34 | def test_ids_returns_array_of_ids(self): 35 | collection = ResourceCollection("some_query", self.collection_data, TestResourceCollection.TestResource.fetch) 36 | self.assertEqual(collection.ids, self.collection_data['search_results']['ids']) 37 | 38 | def test_ids_returns_array_of_empty_ids(self): 39 | empty_collection_data = { 40 | "search_results": { 41 | "page_size": 2, 42 | "ids": [] 43 | } 44 | } 45 | collection = ResourceCollection("some_query", empty_collection_data, TestResourceCollection.TestResource.fetch) 46 | self.assertEqual(collection.ids, []) 47 | 48 | def test_no_search_results(self): 49 | bad_collection_data = {} 50 | with self.assertRaisesRegex(UnexpectedError, "Unprocessable entity due to an invalid request"): 51 | ResourceCollection("some_query", bad_collection_data, TestResourceCollection.TestResource.fetch) 52 | -------------------------------------------------------------------------------- /tests/unit/test_risk_data.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree import * 3 | 4 | class TestRiskData(unittest.TestCase): 5 | def test_initialization_of_attributes(self): 6 | risk_data = RiskData( 7 | { 8 | "id": "123", 9 | "decision": "Unknown", 10 | "device_data_captured": True, 11 | "fraud_service_provider": 12 | "some_fraud_provider", 13 | "transaction_risk_score": "42", 14 | "decision_reasons": ["reason"], 15 | "liability_shift": { 16 | "responsible_party": "paypal", 17 | "conditions": ["unauthorized"], 18 | } 19 | } 20 | ) 21 | self.assertEqual("123", risk_data.id) 22 | self.assertEqual("Unknown", risk_data.decision) 23 | self.assertEqual(True, risk_data.device_data_captured) 24 | self.assertEqual("some_fraud_provider", risk_data.fraud_service_provider) 25 | self.assertEqual("42", risk_data.transaction_risk_score) 26 | self.assertEqual(["reason"], risk_data.decision_reasons) 27 | self.assertEqual("paypal", risk_data.liability_shift.responsible_party) 28 | self.assertEqual(["unauthorized"], risk_data.liability_shift.conditions) 29 | -------------------------------------------------------------------------------- /tests/unit/test_samsung_pay_card.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | # NEXT_MAJOR_VERSION remove this class 4 | # SamsungPay is deprecated 5 | class TestSamsungPayCard(unittest.TestCase): 6 | def test_expiration_date(self): 7 | card = SamsungPayCard(None, { 8 | "customer_id": "12345", 9 | "number": "4111111111111111", 10 | "expiration_month": "05", 11 | "expiration_year": "2014", 12 | "cvv": "100", 13 | "cardholder_name": "John Doe" 14 | }) 15 | 16 | self.assertEqual("05/2014", card.expiration_date) 17 | 18 | def test_expiration_date_no_month(self): 19 | card = SamsungPayCard(None, { 20 | "customer_id": "12345", 21 | "number": "4111111111111111", 22 | "expiration_month": "", 23 | "expiration_year": "2014", 24 | "cvv": "100", 25 | "cardholder_name": "John Doe" 26 | }) 27 | 28 | self.assertEqual(None, card.expiration_date) 29 | 30 | def test_expiration_date_no_year(self): 31 | card = SamsungPayCard(None, { 32 | "customer_id": "12345", 33 | "number": "4111111111111111", 34 | "expiration_month": "05", 35 | "expiration_year": "", 36 | "cvv": "100", 37 | "cardholder_name": "John Doe" 38 | }) 39 | 40 | self.assertEqual(None, card.expiration_date) -------------------------------------------------------------------------------- /tests/unit/test_sepa_direct_debit_account.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from datetime import date 3 | 4 | class TestSepaDirectDebitAccount(unittest.TestCase): 5 | def test_constructor(self): 6 | attributes = { 7 | "bank_reference_token": "a-reference-token", 8 | "created_at": date(2013, 4, 10), 9 | "customer_global_id": "a-customer-global-id", 10 | "global_id": "a-global-id", 11 | "image_url": "a-image-url", 12 | "last_4": "4321", 13 | "mandate_type": "ONE_OFF", 14 | "merchant_or_partner_customer_id": "a-mp-customer-id", 15 | "subscriptions": [{"price": "10.00"}], 16 | "updated_at": date(2013, 4, 10), 17 | "view_mandate_url": "a-view-mandate-url", 18 | } 19 | 20 | sepa_direct_debit_account = SepaDirectDebitAccount({}, attributes) 21 | self.assertEqual(sepa_direct_debit_account.bank_reference_token, "a-reference-token") 22 | self.assertEqual(sepa_direct_debit_account.created_at, date(2013, 4, 10)) 23 | self.assertEqual(sepa_direct_debit_account.customer_global_id, "a-customer-global-id") 24 | self.assertEqual(sepa_direct_debit_account.global_id, "a-global-id") 25 | self.assertEqual(sepa_direct_debit_account.image_url, "a-image-url") 26 | self.assertEqual(sepa_direct_debit_account.last_4, "4321") 27 | self.assertEqual(sepa_direct_debit_account.mandate_type, "ONE_OFF") 28 | self.assertEqual(sepa_direct_debit_account.merchant_or_partner_customer_id, "a-mp-customer-id") 29 | subscription = sepa_direct_debit_account.subscriptions[0] 30 | self.assertEqual(type(subscription), Subscription) 31 | self.assertEqual(subscription.price, Decimal("10.00")) 32 | self.assertEqual(sepa_direct_debit_account.updated_at, date(2013, 4, 10)) 33 | self.assertEqual(sepa_direct_debit_account.view_mandate_url, "a-view-mandate-url") 34 | -------------------------------------------------------------------------------- /tests/unit/test_setup.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | import os 3 | 4 | class TestSetup(unittest.TestCase): 5 | def test_packages_includes_all_packages(self): 6 | with open('setup.py', 'r') as f: 7 | setup_contents = f.read() 8 | packages_line = re.findall('packages=.*', setup_contents) 9 | packages_from_setup = re.findall('"(.*?)"', str(packages_line)) 10 | 11 | packages_from_directories = ['braintree'] 12 | directories_that_dont_have_packages = ['braintree.ssl' ] 13 | for dirname, dirnames, _ in os.walk('braintree'): 14 | for subdirname in dirnames: 15 | package_from_directory = re.sub('/', '.', os.path.join(dirname, subdirname)) 16 | if package_from_directory not in directories_that_dont_have_packages and subdirname != '__pycache__': 17 | packages_from_directories.append(package_from_directory) 18 | 19 | mismatch_message = "List of packages in setup.py doesn't match subdirectories of 'braintree' - " \ 20 | + "add your new directory to 'packages, or if none, `git clean -df` to remove a stale directory" 21 | self.assertEqual(sorted(packages_from_directories), sorted(packages_from_setup), mismatch_message) 22 | -------------------------------------------------------------------------------- /tests/unit/test_signature_service.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class FakeDigest(object): 4 | 5 | @staticmethod 6 | def hmac_hash(key, data): 7 | return "%s_signed_with_%s" % (data, key) 8 | 9 | class TestSignatureService(unittest.TestCase): 10 | 11 | def test_hashes_with_digest(self): 12 | signature_service = SignatureService("fake_key", FakeDigest.hmac_hash) 13 | signed = signature_service.sign({"foo": "bar"}) 14 | self.assertEqual("foo=bar_signed_with_fake_key|foo=bar", signed) 15 | -------------------------------------------------------------------------------- /tests/unit/test_subscription.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestSubscription(unittest.TestCase): 4 | def test_create_raises_exception_with_bad_keys(self): 5 | with self.assertRaisesRegex(KeyError, "'Invalid keys: bad_key'"): 6 | Subscription.create({"bad_key": "value"}) 7 | 8 | def test_update_raises_exception_with_bad_keys(self): 9 | with self.assertRaisesRegex(KeyError, "'Invalid keys: bad_key'"): 10 | Subscription.update("id", {"bad_key": "value"}) 11 | 12 | def test_finding_empty_id_raises_not_found_exception(self): 13 | with self.assertRaises(NotFoundError): 14 | Subscription.find(" ") 15 | 16 | def test_finding_None_id_raises_not_found_exception(self): 17 | with self.assertRaises(NotFoundError): 18 | Subscription.find(None) 19 | -------------------------------------------------------------------------------- /tests/unit/test_subscription_search.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestSubscriptionSearch(unittest.TestCase): 4 | def test_billing_cycles_remaining_is_a_range_node(self): 5 | self.assertEqual(Search.RangeNodeBuilder, type(SubscriptionSearch.billing_cycles_remaining)) 6 | 7 | def test_created_at_is_a_range_node(self): 8 | self.assertEqual(Search.RangeNodeBuilder, type(SubscriptionSearch.created_at)) 9 | 10 | def test_days_past_due_is_a_range_node(self): 11 | self.assertEqual(Search.RangeNodeBuilder, type(SubscriptionSearch.days_past_due)) 12 | 13 | def test_id_is_a_text_node(self): 14 | self.assertEqual(Search.TextNodeBuilder, type(SubscriptionSearch.id)) 15 | 16 | def test_merchant_account_id_is_a_multiple_value_node(self): 17 | self.assertEqual(Search.MultipleValueNodeBuilder, type(SubscriptionSearch.merchant_account_id)) 18 | 19 | def test_plan_id_is_a_multiple_value_or_text_node(self): 20 | self.assertEqual(Search.MultipleValueOrTextNodeBuilder, type(SubscriptionSearch.plan_id)) 21 | 22 | def test_price_is_a_range_node(self): 23 | self.assertEqual(Search.RangeNodeBuilder, type(SubscriptionSearch.price)) 24 | 25 | def test_status_is_a_multiple_value_node(self): 26 | self.assertEqual(Search.MultipleValueNodeBuilder, type(SubscriptionSearch.status)) 27 | 28 | def test_in_trial_period_is_multiple_value_node(self): 29 | self.assertEqual(Search.MultipleValueNodeBuilder, type(SubscriptionSearch.in_trial_period)) 30 | 31 | def test_status_whitelist(self): 32 | SubscriptionSearch.status.in_list( 33 | Subscription.Status.Active, 34 | Subscription.Status.Canceled, 35 | Subscription.Status.Expired, 36 | Subscription.Status.PastDue 37 | ) 38 | 39 | def test_status_not_in_whitelist(self): 40 | with self.assertRaises(AttributeError): 41 | SubscriptionSearch.status.in_list( 42 | Subscription.Status.Active, 43 | Subscription.Status.Canceled, 44 | Subscription.Status.Expired, 45 | "not a status" 46 | ) 47 | 48 | def test_ids_is_a_multiple_value_node(self): 49 | self.assertEqual(Search.MultipleValueNodeBuilder, type(SubscriptionSearch.ids)) 50 | -------------------------------------------------------------------------------- /tests/unit/test_successful_result.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestSuccessfulResult(unittest.TestCase): 4 | def test_is_success(self): 5 | self.assertTrue(SuccessfulResult({}).is_success) 6 | 7 | def test_attributes_are_exposed(self): 8 | result = SuccessfulResult({"name": "drew"}) 9 | self.assertEqual("drew", result.name) 10 | -------------------------------------------------------------------------------- /tests/unit/test_three_d_secure_info.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree import * 3 | 4 | class TestThreeDSecureInfo(unittest.TestCase): 5 | def test_initialization_of_attributes(self): 6 | three_d_secure_info = ThreeDSecureInfo({ 7 | "enrolled": "Y", 8 | "status": "authenticate_successful", 9 | "liability_shifted": True, 10 | "liability_shift_possible": True, 11 | "cavv": "some_cavv", 12 | "xid": "some_xid", 13 | "ds_transaction_id": "some_ds_txn_id", 14 | "eci_flag": "07", 15 | "three_d_secure_version": "1.0.2", 16 | }) 17 | 18 | self.assertEqual("Y", three_d_secure_info.enrolled) 19 | self.assertEqual("authenticate_successful", three_d_secure_info.status) 20 | self.assertEqual(True, three_d_secure_info.liability_shifted) 21 | self.assertEqual(True, three_d_secure_info.liability_shift_possible) 22 | self.assertEqual("some_cavv", three_d_secure_info.cavv) 23 | self.assertEqual("some_xid", three_d_secure_info.xid) 24 | self.assertEqual("some_ds_txn_id", three_d_secure_info.ds_transaction_id) 25 | self.assertEqual("07", three_d_secure_info.eci_flag) 26 | self.assertEqual("1.0.2", three_d_secure_info.three_d_secure_version) 27 | -------------------------------------------------------------------------------- /tests/unit/test_unknown_payment_method.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestUnknownPaymentMethod(unittest.TestCase): 4 | def test_image_url(self): 5 | unknown_payment_method = UnknownPaymentMethod("gateway", {"token": "TOKEN"}) 6 | self.assertEqual("https://assets.braintreegateway.com/payment_method_logo/unknown.png", unknown_payment_method.image_url()) 7 | -------------------------------------------------------------------------------- /tests/unit/test_us_bank_account.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from datetime import date 3 | from braintree.us_bank_account import UsBankAccount 4 | from braintree.us_bank_account_verification import UsBankAccountVerification 5 | 6 | class TestUsBankAccount(unittest.TestCase): 7 | def test_constructor(self): 8 | attributes = { 9 | "last_four": "1234", 10 | "routing_number": "55555", 11 | "account_type": "fake-account", 12 | "account_holder_name": "John Doe", 13 | "token": "7777-7777", 14 | "image_url": "some.png", 15 | "bank_name": "Chase", 16 | "ach_mandate": None, 17 | } 18 | 19 | us_bank_account = UsBankAccount({}, attributes) 20 | self.assertEqual(us_bank_account.last_four, "1234") 21 | self.assertEqual(us_bank_account.routing_number, "55555") 22 | self.assertEqual(us_bank_account.account_type, "fake-account") 23 | self.assertEqual(us_bank_account.account_holder_name, "John Doe") 24 | self.assertEqual(us_bank_account.token, "7777-7777") 25 | self.assertEqual(us_bank_account.image_url, "some.png") 26 | self.assertEqual(us_bank_account.bank_name, "Chase") 27 | self.assertEqual(us_bank_account.ach_mandate, None) 28 | 29 | attributes["ach_mandate"] = {"text":"Some mandate", "accepted_at": date(2013, 4, 10)} 30 | us_bank_account_mandated = UsBankAccount({}, attributes) 31 | self.assertEqual(us_bank_account_mandated.ach_mandate.text, "Some mandate") 32 | self.assertEqual(us_bank_account_mandated.ach_mandate.accepted_at, date(2013, 4, 10)) 33 | 34 | def test_converts_verifications_to_objects(self): 35 | attributes = { 36 | "verifications": [ 37 | { 38 | "status": "verified", 39 | "verification_method": "network_check", 40 | }, 41 | ], 42 | } 43 | 44 | us_bank_account = UsBankAccount({}, attributes) 45 | self.assertEqual(len(us_bank_account.verifications), 1) 46 | 47 | verification = us_bank_account.verifications[0] 48 | 49 | self.assertEqual(verification.status, UsBankAccountVerification.Status.Verified) 50 | self.assertEqual(verification.verification_method, UsBankAccountVerification.VerificationMethod.NetworkCheck) 51 | -------------------------------------------------------------------------------- /tests/unit/test_us_bank_account_verification.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from tests.test_helper import * 3 | 4 | from braintree.us_bank_account_verification import UsBankAccountVerification 5 | 6 | class TestUsBankAccountVerification(unittest.TestCase): 7 | def test_finding_empty_id_raises_not_found_exception(self): 8 | with self.assertRaises(NotFoundError): 9 | UsBankAccountVerification.find(" ") 10 | 11 | def test_finding_none_raises_not_found_exception(self): 12 | with self.assertRaises(NotFoundError): 13 | UsBankAccountVerification.find(None) 14 | 15 | def test_attributes(self): 16 | attributes = { 17 | "id": "my_favorite_id", 18 | "status": "verified", 19 | "verification_method": "independent_check", 20 | "verification_determined_at": datetime(2018, 11, 11, 23, 59, 59), 21 | "us_bank_account": { 22 | "token": "abc123", 23 | "last_4": 9999, 24 | }, 25 | "additional_processor_response": "Yikes" 26 | } 27 | 28 | verification = UsBankAccountVerification({}, attributes) 29 | 30 | self.assertEqual(verification.id, "my_favorite_id") 31 | self.assertEqual(verification.status, UsBankAccountVerification.Status.Verified) 32 | self.assertEqual(verification.verification_determined_at, datetime(2018, 11, 11, 23, 59, 59)) 33 | self.assertEqual(verification.additional_processor_response, "Yikes") 34 | self.assertEqual( 35 | verification.verification_method, 36 | UsBankAccountVerification.VerificationMethod.IndependentCheck 37 | ) 38 | 39 | self.assertEqual(verification.us_bank_account.token, "abc123") 40 | self.assertEqual(verification.us_bank_account.last_4, 9999) 41 | -------------------------------------------------------------------------------- /tests/unit/test_visa_checkout_card.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | from braintree.visa_checkout_card import VisaCheckoutCard 3 | 4 | class TestVisaCheckoutCard(unittest.TestCase): 5 | def test_expiration_date(self): 6 | card = VisaCheckoutCard(None, { 7 | "customer_id": "12345", 8 | "number": "4111111111111111", 9 | "expiration_month": "05", 10 | "expiration_year": "2014", 11 | "cvv": "100", 12 | "cardholder_name": "John Doe" 13 | }) 14 | 15 | self.assertEqual("05/2014", card.expiration_date) 16 | 17 | def test_expiration_date_no_month(self): 18 | card = VisaCheckoutCard(None, { 19 | "customer_id": "12345", 20 | "number": "4111111111111111", 21 | "expiration_month": "", 22 | "expiration_year": "2014", 23 | "cvv": "100", 24 | "cardholder_name": "John Doe" 25 | }) 26 | 27 | self.assertEqual(None, card.expiration_date) 28 | 29 | def test_expiration_date_no_year(self): 30 | card = VisaCheckoutCard(None, { 31 | "customer_id": "12345", 32 | "number": "4111111111111111", 33 | "expiration_month": "05", 34 | "expiration_year": "", 35 | "cvv": "100", 36 | "cardholder_name": "John Doe" 37 | }) 38 | 39 | self.assertEqual(None, card.expiration_date) 40 | 41 | def test_bin_data(self): 42 | card = VisaCheckoutCard(None, { 43 | "business": "No", 44 | "consumer": "Yes", 45 | "corporate": "No", 46 | "purchase": "Yes", 47 | }) 48 | 49 | self.assertEqual(card.business, CreditCard.Business.No) 50 | self.assertEqual(card.consumer, CreditCard.Consumer.Yes) 51 | self.assertEqual(card.corporate, CreditCard.Corporate.No) 52 | self.assertEqual(card.purchase, CreditCard.Purchase.Yes) 53 | -------------------------------------------------------------------------------- /tests/unit/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/braintree/braintree_python/7f96e2a1f7ab990c9387d9a0ef8a86a6cf5346bf/tests/unit/util/__init__.py -------------------------------------------------------------------------------- /tests/unit/util/test_constants.py: -------------------------------------------------------------------------------- 1 | from tests.test_helper import * 2 | 3 | class TestConstants(unittest.TestCase): 4 | def test_get_all_constant_values_from_class(self): 5 | self.assertEqual(["Active", "Canceled", "Expired", "Past Due", "Pending"], Constants.get_all_constant_values_from_class(Subscription.Status)) 6 | -------------------------------------------------------------------------------- /tests/unit/util/test_datetime_parser.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from braintree.util.datetime_parser import parse_datetime as parse 3 | from datetime import datetime 4 | 5 | 6 | class TestDateParser(unittest.TestCase): 7 | def test_parses_with_zulu_and_symbols(self): 8 | self.assertEqual(parse('2017-04-19T18:51:21Z'), datetime(2017, 4, 19, 18, 51, 21)) 9 | self.assertEqual(parse('2017-04-19T18:51:21.45Z'), datetime(2017, 4, 19, 18, 51, 21, 450000)) 10 | 11 | def test_parses_with_zulu_and_no_symbols(self): 12 | self.assertEqual(parse('20170419T185121Z'), datetime(2017, 4, 19, 18, 51, 21)) 13 | self.assertEqual(parse('20170419T185121.123Z'), datetime(2017, 4, 19, 18, 51, 21, 123000)) 14 | 15 | def test_parses_with_zero_offset(self): 16 | self.assertEqual(parse('2017-04-19T18:51:21+00:00'), datetime(2017, 4, 19, 18, 51, 21)) 17 | self.assertEqual(parse('2017-04-19T18:51:21.420+00:00'), datetime(2017, 4, 19, 18, 51, 21, 420000)) 18 | 19 | def test_parses_with_negative_offset(self): 20 | self.assertEqual(parse('2017-04-19T18:51:21-01:30'), datetime(2017, 4, 19, 20, 21, 21)) 21 | self.assertEqual(parse('2017-04-19T18:51:21.987-01:30'), datetime(2017, 4, 19, 20, 21, 21, 987000)) 22 | 23 | def test_parses_with_positive_offset(self): 24 | self.assertEqual(parse('2017-04-19T18:51:21+07:00'), datetime(2017, 4, 19, 11, 51, 21)) 25 | self.assertEqual(parse('2017-04-19T18:51:21.765+07:00'), datetime(2017, 4, 19, 11, 51, 21, 765000)) 26 | 27 | def test_raises_with_bad_input(self): 28 | with self.assertRaises(ValueError): 29 | parse('20170420') 30 | with self.assertRaises(ValueError): 31 | parse('20170420Z') 32 | -------------------------------------------------------------------------------- /tests/unit/util/test_graphql_client.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from braintree.util.graphql_client import GraphQLClient 3 | 4 | 5 | class TestGraphQLClient(unittest.TestCase): 6 | 7 | def test_get_validation_errors_returns_none_when_no_errors(self): 8 | response = {"data": {}} 9 | self.assertIsNone(GraphQLClient.get_validation_errors(response)) 10 | 11 | def test_get_validation_errors_returns_none_when_errors_not_a_list(self): 12 | response = {"errors": "some string"} 13 | self.assertIsNone(GraphQLClient.get_validation_errors(response)) 14 | 15 | def test_get_validation_errors_returns_formatted_errors(self): 16 | response = { 17 | "errors": [ 18 | {"message": "Error 1", "extensions": {"legacyCode": "123"}}, 19 | {"message": "Error 2", "extensions": {}}, 20 | ] 21 | } 22 | 23 | expected_errors = { 24 | "errors": [ 25 | {"attribute": "", "code": "123", "message": "Error 1"}, 26 | {"attribute": "", "code": None, "message": "Error 2"}, 27 | ] 28 | } 29 | 30 | self.assertEqual(GraphQLClient.get_validation_errors(response), expected_errors) 31 | 32 | 33 | --------------------------------------------------------------------------------