From 5bf9af6f1ff77432d72a4007e04b2d72b9cc136a Mon Sep 17 00:00:00 2001 From: Braintree Date: Tue, 7 Nov 2023 18:28:39 +0000 Subject: [PATCH] 4.24.0 Co-authored-by: Debra Do Co-authored-by: Sara Vasquez --- CHANGELOG.md | 8 +- README.md | 2 +- braintree/meta_checkout_card.py | 17 + braintree/meta_checkout_token.py | 17 + braintree/payment_instrument_type.py | 2 + braintree/payment_method.py | 2 + braintree/test/nonces.py | 3 + braintree/transaction.py | 54 +++- braintree/us_bank_account_verification.py | 11 +- braintree/version.py | 2 +- braintree/webhook_notification.py | 1 + braintree/webhook_testing_gateway.py | 26 ++ setup.py | 2 +- tests/integration/test_credit_card.py | 2 +- tests/integration/test_dispute_search.py | 25 +- tests/integration/test_payment_method.py | 16 +- .../integration/test_payment_method_nonce.py | 36 ++- .../test_payment_method_us_bank_account.py | 75 ++++- tests/integration/test_transaction.py | 301 +++++++++++++++++- tests/integration/test_transaction_search.py | 27 ++ tests/integration/test_us_bank_account.py | 2 +- tests/unit/test_meta_checkout_card.py | 52 +++ tests/unit/test_meta_checkout_token.py | 56 ++++ tests/unit/test_payment_method_gateway.py | 2 + tests/unit/test_transaction.py | 20 ++ .../unit/test_us_bank_account_verification.py | 4 +- tests/unit/test_webhooks.py | 24 ++ 27 files changed, 742 insertions(+), 47 deletions(-) create mode 100644 braintree/meta_checkout_card.py create mode 100644 braintree/meta_checkout_token.py create mode 100644 tests/unit/test_meta_checkout_card.py create mode 100644 tests/unit/test_meta_checkout_token.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b75b6a8..c9b3385e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog -## Unreleased +## 4.24.0 +* Add `SubscriptionBillingSkipped` to `WebhookNotification.Kind` +* Add `arrivalDate` and `ticketIssuerAddress` to `Transaction.sale` request and `industry` data support for Transaction.submitForSettlement +* Add `date_of_birth` and `country_code` to IndustryData params +* Add `MetaCheckoutCard`, `MetaCheckoutToken` payment methods +* Add `MetaCheckoutCardDetails`, `MetaCheckoutTokenDetails` to `Transaction` +* Add `verification_add_ons` to `PaymentMethod` create options for `ACH NetworkCheck` * Fix unittest compatibility with Python 3.12 (Thanks @mgorny) ## 4.23.0 diff --git a/README.md b/README.md index a70d99e8..7ed921fb 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The Braintree Python library provides integration access to the Braintree Gatewa * [requests](http://docs.python-requests.org/en/latest/) -The Braintree Python SDK is tested against Python versions 3.5.3 and 3.8.0. +The Braintree Python SDK is tested against Python versions 3.5.3 and 3.12.0. _The Python core development community has released [End-of-Life branches](https://devguide.python.org/devcycle/#end-of-life-branches) for Python versions 2.7 - 3.4, and are no longer receiving [security updates](https://devguide.python.org/#branchstatus). As a result, Braintree no longer supports these versions of Python._ diff --git a/braintree/meta_checkout_card.py b/braintree/meta_checkout_card.py new file mode 100644 index 00000000..25b8b8e8 --- /dev/null +++ b/braintree/meta_checkout_card.py @@ -0,0 +1,17 @@ +import braintree +from braintree.address import Address +from braintree.resource import Resource + +class MetaCheckoutCard(Resource): + def __init__(self, gateway, attributes): + Resource.__init__(self, gateway, attributes) + + @property + def expiration_date(self): + if not self.expiration_month or not self.expiration_year: + return None + return self.expiration_month + "/" + self.expiration_year + + @property + def masked_number(self): + return self.bin + "******" + self.last_4 diff --git a/braintree/meta_checkout_token.py b/braintree/meta_checkout_token.py new file mode 100644 index 00000000..16419ea8 --- /dev/null +++ b/braintree/meta_checkout_token.py @@ -0,0 +1,17 @@ +import braintree +from braintree.address import Address +from braintree.resource import Resource + +class MetaCheckoutToken(Resource): + def __init__(self, gateway, attributes): + Resource.__init__(self, gateway, attributes) + + @property + def expiration_date(self): + if not self.expiration_month or not self.expiration_year: + return None + return self.expiration_month + "/" + self.expiration_year + + @property + def masked_number(self): + return self.bin + "******" + self.last_4 diff --git a/braintree/payment_instrument_type.py b/braintree/payment_instrument_type.py index 3ca33e36..748ce32d 100644 --- a/braintree/payment_instrument_type.py +++ b/braintree/payment_instrument_type.py @@ -9,6 +9,8 @@ class PaymentInstrumentType(): EuropeBankAccount = "europe_bank_account" LocalPayment = "local_payment" MasterpassCard = "masterpass_card" + MetaCheckoutCard = "meta_checkout_card" + MetaCheckoutToken = "meta_checkout_token" PayPalAccount = "paypal_account" PayPalHere = "paypal_here" SamsungPayCard = "samsung_pay_card" diff --git a/braintree/payment_method.py b/braintree/payment_method.py index d1cb02a5..69977457 100644 --- a/braintree/payment_method.py +++ b/braintree/payment_method.py @@ -37,6 +37,7 @@ def signature(type): "skip_advanced_fraud_checking", "us_bank_account_verification_method", "verification_account_type", + "verification_add_ons", "verification_amount", "verification_merchant_account_id", "verify_card", @@ -123,6 +124,7 @@ def update_signature(): "us_bank_account_verification_method", "venmo_sdk_session", "verification_account_type", + "verification_add_ons", "verification_amount", "verification_merchant_account_id", "verify_card", diff --git a/braintree/test/nonces.py b/braintree/test/nonces.py index 4d25cbfb..2c87c550 100644 --- a/braintree/test/nonces.py +++ b/braintree/test/nonces.py @@ -70,6 +70,8 @@ class Nonces(object): MasterpassDiscover = "fake-masterpass-discover-nonce" MasterpassMasterCard = "fake-masterpass-mastercard-nonce" MasterpassVisa = "fake-masterpass-visa-nonce" + MetaCheckoutCard = "fake-meta-checkout-card-nonce" + MetaCheckoutToken = "fake-meta-checkout-token-nonce" VisaCheckoutAmEx = "fake-visa-checkout-amex-nonce" VisaCheckoutDiscover = "fake-visa-checkout-discover-nonce" VisaCheckoutMasterCard = "fake-visa-checkout-mastercard-nonce" @@ -79,3 +81,4 @@ class Nonces(object): SamsungPayMasterCard = "tokensam_fake_mastercard" SamsungPayVisa = "tokensam_fake_visa" SepaDirectDebit = "fake-sepa-direct-debit-nonce" + UsBankAccount = "fake-us-bank-account-nonce" diff --git a/braintree/transaction.py b/braintree/transaction.py index 49adf781..bf7eb360 100644 --- a/braintree/transaction.py +++ b/braintree/transaction.py @@ -22,6 +22,8 @@ from braintree.liability_shift import LiabilityShift from braintree.local_payment import LocalPayment from braintree.masterpass_card import MasterpassCard +from braintree.meta_checkout_card import MetaCheckoutCard +from braintree.meta_checkout_token import MetaCheckoutToken from braintree.payment_instrument_type import PaymentInstrumentType from braintree.paypal_account import PayPalAccount from braintree.paypal_here import PayPalHere @@ -640,7 +642,7 @@ def create_signature(): "data": [ "folio_number", "check_in_date", "check_out_date", "departure_date", "lodging_check_in_date", "lodging_check_out_date", "travel_package", "lodging_name", "room_rate", "passenger_first_name", "passenger_last_name", "passenger_middle_initial", "passenger_title", "issued_date", "travel_agency_name", "travel_agency_code", "ticket_number", - "issuing_carrier_code", "customer_code", "fare_amount", "fee_amount", "room_tax", "tax_amount", "restricted_ticket", "no_show", "advanced_deposit", "fire_safe", "property_phone", + "issuing_carrier_code", "customer_code", "fare_amount", "fee_amount", "room_tax", "tax_amount", "restricted_ticket", "no_show", "advanced_deposit", "fire_safe", "property_phone", "arrival_date", "ticket_issuer_address", "date_of_birth", "country_code", { "legs": [ "conjunction_ticket", "exchange_ticket", "coupon_number", "service_class", "carrier_code", "fare_basis_code", "flight_number", "departure_date", "departure_airport_code", "departure_time", @@ -678,6 +680,29 @@ def submit_for_settlement_signature(): "discount_amount", "shipping_amount", "ships_from_postal_code", + {"industry": + [ + "industry_type", + { + "data": [ + "advanced_deposit", "arrival_date", "check_in_date", "check_out_date", "customer_code", "departure_date", "fare_amount", "fee_amount", "fire_safe", "folio_number", "issued_date", "issuing_carrier_code", + "lodging_check_in_date", "lodging_check_out_date", "lodging_name", "no_show", "passenger_first_name", "passenger_last_name", "passenger_middle_initial", "passenger_title", "property_phone", + "restricted_ticket", "room_rate", "room_tax", "tax_amount", "ticket_issuer_address", "ticket_number", "travel_agency_code", "travel_agency_name", "travel_package", + { + "legs": [ + "arrival_airport_code", "arrival_time", "carrier_code", "conjunction_ticket", "coupon_number", "departure_airport_code", "departure_date", "departure_time", "endorsement_or_restrictions", + "exchange_ticket", "fare_amount", "fare_basis_code", "fee_amount", "flight_number", "service_class", "stopover_permitted", "tax_amount" + ] + }, + { + "additional_charges": [ + "amount", "kind" + ], + } + ] + } + ] + }, {"line_items": [ "quantity", "name", "description", "kind", "unit_amount", "unit_tax_amount", "total_amount", "discount_amount", "tax_amount", "unit_of_measure", "product_code", "commodity_code", "url", @@ -690,6 +715,29 @@ def submit_for_settlement_signature(): "postal_code", "region", "street_address", ] }, + {"industry": + [ + "industry_type", + { + "data": [ + "folio_number", "check_in_date", "check_out_date", "departure_date", "lodging_check_in_date", "lodging_check_out_date", "travel_package", "lodging_name", "room_rate", + "passenger_first_name", "passenger_last_name", "passenger_middle_initial", "passenger_title", "issued_date", "travel_agency_name", "travel_agency_code", "ticket_number", + "issuing_carrier_code", "customer_code", "fare_amount", "fee_amount", "room_tax", "tax_amount", "restricted_ticket", "no_show", "advanced_deposit", "fire_safe", "property_phone", "arrival_date", "ticket_issuer_address", "date_of_birth", "country_code", + { + "legs": [ + "conjunction_ticket", "exchange_ticket", "coupon_number", "service_class", "carrier_code", "fare_basis_code", "flight_number", "departure_date", "departure_airport_code", "departure_time", + "arrival_airport_code", "arrival_time", "stopover_permitted", "fare_amount", "fee_amount", "tax_amount", "endorsement_or_restrictions" + ] + }, + { + "additional_charges": [ + "kind", "amount" + ], + } + ] + } + ] + }, ] @staticmethod @@ -757,6 +805,10 @@ def __init__(self, gateway, attributes): self.masterpass_card_details = MasterpassCard(gateway, attributes.pop("masterpass_card")) if "samsung_pay_card" in attributes: self.samsung_pay_card_details = SamsungPayCard(gateway, attributes.pop("samsung_pay_card")) + if "meta_checkout_card" in attributes: + self.meta_checkout_card_details = MetaCheckoutCard(gateway, attributes.pop("meta_checkout_card")) + if "meta_checkout_token" in attributes: + self.meta_checkout_token_details = MetaCheckoutToken(gateway, attributes.pop("meta_checkout_token")) if "sca_exemption_requested" in attributes: self.sca_exemption_requested = attributes.pop("sca_exemption_requested") else: diff --git a/braintree/us_bank_account_verification.py b/braintree/us_bank_account_verification.py index a727dc97..f94e8ee7 100644 --- a/braintree/us_bank_account_verification.py +++ b/braintree/us_bank_account_verification.py @@ -27,7 +27,7 @@ class Status(object): # NEXT_MAJOR_VERSION this can be an enum! they were added as of python 3.4 and we support 3.5+ class VerificationMethod(object): """ - Constants representing transaction statuses. Available statuses are: + Constants representing verification types. Available types are: * braintree.UsBankAccountVerification.VerificationMethod.NetworkCheck * braintree.UsBankAccountVerification.VerificationMethod.IndependentCheck @@ -40,6 +40,15 @@ class VerificationMethod(object): TokenizedCheck = "tokenized_check" MicroTransfers = "micro_transfers" + class VerificationAddOns(object): + """ + Constants representing verification add on types. Available statuses are: + + * braintree.UsBankAccountVerification.VerificationAddOns.CustomerVerification + """ + + CustomerVerification = "customer_verification" + def __init__(self, gateway, attributes): AttributeGetter.__init__(self, attributes) diff --git a/braintree/version.py b/braintree/version.py index eedc91fa..62b85fce 100644 --- a/braintree/version.py +++ b/braintree/version.py @@ -1 +1 @@ -Version = "4.23.0" +Version = "4.24.0" diff --git a/braintree/webhook_notification.py b/braintree/webhook_notification.py index 7797683e..43b3dad0 100644 --- a/braintree/webhook_notification.py +++ b/braintree/webhook_notification.py @@ -51,6 +51,7 @@ class Kind(object): RecipientUpdatedGrantedPaymentMethod = "recipient_updated_granted_payment_method" SubMerchantAccountApproved = "sub_merchant_account_approved" SubMerchantAccountDeclined = "sub_merchant_account_declined" + SubscriptionBillingSkipped = "subscription_billing_skipped" SubscriptionCanceled = "subscription_canceled" SubscriptionChargedSuccessfully = "subscription_charged_successfully" SubscriptionChargedUnsuccessfully = "subscription_charged_unsuccessfully" diff --git a/braintree/webhook_testing_gateway.py b/braintree/webhook_testing_gateway.py index e0ec4ed8..fefa5672 100644 --- a/braintree/webhook_testing_gateway.py +++ b/braintree/webhook_testing_gateway.py @@ -77,6 +77,8 @@ def __subject_sample_xml(self, kind, id): return self.__dispute_disputed_sample_xml(id) elif kind == WebhookNotification.Kind.DisputeExpired: return self.__dispute_expired_sample_xml(id) + elif kind == WebhookNotification.Kind.SubscriptionBillingSkipped: + return self.__subscription_billing_skipped_sample_xml(id) elif kind == WebhookNotification.Kind.SubscriptionChargedSuccessfully: return self.__subscription_charged_successfully_sample_xml(id) elif kind == WebhookNotification.Kind.SubscriptionChargedUnsuccessfully: @@ -750,6 +752,16 @@ def __subscription_sample_xml(self, id): """ % id + def __subscription_billing_skipped_sample_xml(self, id): + return """ + + %s + + + + + """ % id + def __subscription_charged_successfully_sample_xml(self, id): return """ @@ -984,6 +996,20 @@ def __payment_method_customer_data_updated_sample_xml(self, id): Doe 1231231234 john.doe@paypal.com + + Street Address + Extended Address + Locality + Region + Postal Code + + + Street Address + Extended Address + Locality + Region + Postal Code + diff --git a/setup.py b/setup.py index 84d17fa0..635c71f8 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="braintree", - version="4.23.0", + version="4.24.0", description="Braintree Python Library", long_description=long_description, author="Braintree", diff --git a/tests/integration/test_credit_card.py b/tests/integration/test_credit_card.py index 6017d82e..6efe75a9 100644 --- a/tests/integration/test_credit_card.py +++ b/tests/integration/test_credit_card.py @@ -1207,7 +1207,7 @@ def test_card_without_card_type_indicators(self): self.assertEqual(CreditCard.Healthcare.Unknown, credit_card.healthcare) self.assertEqual(CreditCard.IssuingBank.Unknown, credit_card.issuing_bank) self.assertEqual(CreditCard.CountryOfIssuance.Unknown, credit_card.country_of_issuance) - self.assertEquals(CreditCard.ProductId.Unknown, credit_card.product_id) + self.assertEqual(CreditCard.ProductId.Unknown, credit_card.product_id) def test_card_with_account_type_debit(self): customer = Customer.create().customer diff --git a/tests/integration/test_dispute_search.py b/tests/integration/test_dispute_search.py index b31e11dd..806658bd 100644 --- a/tests/integration/test_dispute_search.py +++ b/tests/integration/test_dispute_search.py @@ -20,7 +20,6 @@ def create_sample_disputed_transaction(self): "expiration_date": "12/2019", }, "customer_id": customer.id, - "merchant_account_id": "14LaddersLLC_instant", "options": { "submit_for_settlement": True, }, @@ -32,7 +31,7 @@ def test_advanced_search_no_results(self): ]) disputes = [dispute for dispute in collection.disputes.items] - self.assertEquals(0, len(disputes)) + self.assertEqual(0, len(disputes)) def test_advanced_search_returns_single_dispute_by_customer_id(self): transaction = self.create_sample_disputed_transaction() @@ -42,12 +41,12 @@ def test_advanced_search_returns_single_dispute_by_customer_id(self): ]) disputes = [dispute for dispute in collection.disputes.items] - self.assertEquals(1, len(disputes)) + self.assertEqual(1, len(disputes)) dispute = disputes[0] - self.assertEquals(dispute.id, transaction.disputes[0].id) - self.assertEquals(dispute.status, Dispute.Status.Open) + self.assertEqual(dispute.id, transaction.disputes[0].id) + self.assertEqual(dispute.status, Dispute.Status.Open) def test_advanced_search_returns_single_dispute_by_id(self): collection = Dispute.search([ @@ -55,12 +54,12 @@ def test_advanced_search_returns_single_dispute_by_id(self): ]) disputes = [dispute for dispute in collection.disputes.items] - self.assertEquals(1, len(disputes)) + self.assertEqual(1, len(disputes)) dispute = disputes[0] - self.assertEquals(dispute.id, "open_dispute") - self.assertEquals(dispute.status, Dispute.Status.Open) + self.assertEqual(dispute.id, "open_dispute") + self.assertEqual(dispute.status, Dispute.Status.Open) def test_advanced_search_returns_disputes_by_multiple_reasons(self): collection = Dispute.search([ @@ -119,7 +118,7 @@ def test_advanced_search_returns_disputes_by_date_range(self): disputes = [dispute for dispute in collection.disputes.items] self.assertGreaterEqual(len(disputes), 1) - self.assertEquals(disputes[0].received_date, date(2014, 3, 4)) + self.assertEqual(disputes[0].received_date, date(2014, 3, 4)) def test_advanced_search_returns_disputes_by_disbursement_date_range(self): transaction = self.create_sample_disputed_transaction() @@ -132,7 +131,7 @@ def test_advanced_search_returns_disputes_by_disbursement_date_range(self): disputes = [dispute for dispute in collection.disputes.items] self.assertGreaterEqual(len(disputes), 1) - self.assertEquals(disputes[0].status_history[0].disbursement_date, disbursement_date) + self.assertEqual(disputes[0].status_history[0].disbursement_date, disbursement_date) def test_advanced_search_returns_disputes_by_effective_date_range(self): transaction = self.create_sample_disputed_transaction() @@ -145,7 +144,7 @@ def test_advanced_search_returns_disputes_by_effective_date_range(self): disputes = [dispute for dispute in collection.disputes.items] self.assertGreaterEqual(len(disputes), 1) - self.assertEquals(disputes[0].status_history[0].effective_date, effective_date) + self.assertEqual(disputes[0].status_history[0].effective_date, effective_date) def test_advanced_search_returns_disputes_by_amount_and_status(self): collection = Dispute.search([ @@ -154,7 +153,7 @@ def test_advanced_search_returns_disputes_by_amount_and_status(self): ]) disputes = [dispute for dispute in collection.disputes.items] - self.assertEquals(1, len(disputes)) + self.assertEqual(1, len(disputes)) def test_advanced_search_can_take_one_criteria(self): collection = Dispute.search( @@ -162,4 +161,4 @@ def test_advanced_search_can_take_one_criteria(self): ) disputes = [dispute for dispute in collection.disputes.items] - self.assertEquals(0, len(disputes)) + self.assertEqual(0, len(disputes)) diff --git a/tests/integration/test_payment_method.py b/tests/integration/test_payment_method.py index 8bc0c813..97b04384 100644 --- a/tests/integration/test_payment_method.py +++ b/tests/integration/test_payment_method.py @@ -396,13 +396,13 @@ def test_create_with_fake_amex_express_checkout_card_nonce(self): self.assertNotEqual(amex_express_checkout_card.token, None) self.assertTrue(amex_express_checkout_card.default) self.assertEqual("American Express", amex_express_checkout_card.card_type) - self.assertRegexpMatches(amex_express_checkout_card.bin, r"\A\d{6}\Z") - self.assertRegexpMatches(amex_express_checkout_card.expiration_month, r"\A\d{2}\Z") - self.assertRegexpMatches(amex_express_checkout_card.expiration_year, r"\A\d{4}\Z") - self.assertRegexpMatches(amex_express_checkout_card.card_member_number, r"\A\d{4}\Z") - self.assertRegexpMatches(amex_express_checkout_card.card_member_expiry_date, r"\A\d{2}/\d{2}\Z") - self.assertRegexpMatches(amex_express_checkout_card.source_description, r"\AAmEx \d{4}\Z") - self.assertRegexpMatches(amex_express_checkout_card.image_url, r"\.png") + self.assertRegex(amex_express_checkout_card.bin, r"\A\d{6}\Z") + self.assertRegex(amex_express_checkout_card.expiration_month, r"\A\d{2}\Z") + self.assertRegex(amex_express_checkout_card.expiration_year, r"\A\d{4}\Z") + self.assertRegex(amex_express_checkout_card.card_member_number, r"\A\d{4}\Z") + self.assertRegex(amex_express_checkout_card.card_member_expiry_date, r"\A\d{2}/\d{2}\Z") + self.assertRegex(amex_express_checkout_card.source_description, r"\AAmEx \d{4}\Z") + self.assertRegex(amex_express_checkout_card.image_url, r"\.png") self.assertEqual(customer_id, amex_express_checkout_card.customer_id) def test_create_with_venmo_account_nonce(self): @@ -421,7 +421,7 @@ def test_create_with_venmo_account_nonce(self): self.assertEqual("venmojoe", venmo_account.username) self.assertEqual("1234567891234567891", venmo_account.venmo_user_id) self.assertEqual("Venmo Account: venmojoe", venmo_account.source_description) - self.assertRegexpMatches(venmo_account.image_url, r"\.png") + self.assertRegex(venmo_account.image_url, r"\.png") self.assertEqual(customer_id, venmo_account.customer_id) def test_create_with_abstract_payment_method_nonce(self): diff --git a/tests/integration/test_payment_method_nonce.py b/tests/integration/test_payment_method_nonce.py index d4be51e5..5b7227a1 100644 --- a/tests/integration/test_payment_method_nonce.py +++ b/tests/integration/test_payment_method_nonce.py @@ -123,17 +123,39 @@ def test_find_nonce_shows_paypal_details(self): def test_find_nonce_shows_venmo_details(self): found_nonce = PaymentMethodNonce.find("fake-venmo-account-nonce") - self.assertEquals("99", found_nonce.details["last_two"]) - self.assertEquals("venmojoe", found_nonce.details["username"]) - self.assertEquals("1234567891234567891", found_nonce.details["venmo_user_id"]) + self.assertEqual("99", found_nonce.details["last_two"]) + self.assertEqual("venmojoe", found_nonce.details["username"]) + self.assertEqual("1234567891234567891", found_nonce.details["venmo_user_id"]) def test_find_nonce_shows_sepa_direct_debit_details(self): found_nonce = PaymentMethodNonce.find(Nonces.SepaDirectDebit) - self.assertEquals("1234", found_nonce.details["last_4"]) - self.assertEquals("RECURRENT", found_nonce.details["mandate_type"]) - self.assertEquals("a-fake-bank-reference-token", found_nonce.details["bank_reference_token"]) - self.assertEquals("a-fake-mp-customer-id", found_nonce.details["merchant_or_partner_customer_id"]) + self.assertEqual("1234", found_nonce.details["last_4"]) + self.assertEqual("RECURRENT", found_nonce.details["mandate_type"]) + self.assertEqual("a-fake-bank-reference-token", found_nonce.details["bank_reference_token"]) + self.assertEqual("a-fake-mp-customer-id", found_nonce.details["merchant_or_partner_customer_id"]) + + def test_find_nonce_shows_meta_checkout_card_details(self): + found_nonce = PaymentMethodNonce.find(Nonces.MetaCheckoutCard) + + self.assertEqual("401288", found_nonce.details["bin"]) + self.assertEqual("81", found_nonce.details["last_two"]) + self.assertEqual("1881", found_nonce.details["last_four"]) + self.assertEqual("Visa", found_nonce.details["card_type"]) + self.assertEqual("Meta Checkout Card Cardholder", found_nonce.details["cardholder_name"]) + self.assertEqual("2024", found_nonce.details["expiration_year"]) + self.assertEqual("12", found_nonce.details["expiration_month"]) + + def test_find_nonce_shows_meta_checkout_token_details(self): + found_nonce = PaymentMethodNonce.find(Nonces.MetaCheckoutToken) + + self.assertEqual("401288", found_nonce.details["bin"]) + self.assertEqual("81", found_nonce.details["last_two"]) + self.assertEqual("1881", found_nonce.details["last_four"]) + self.assertEqual("Visa", found_nonce.details["card_type"]) + self.assertEqual("Meta Checkout Token Cardholder", found_nonce.details["cardholder_name"]) + self.assertEqual("2024", found_nonce.details["expiration_year"]) + self.assertEqual("12", found_nonce.details["expiration_month"]) def test_exposes_null_3ds_info_if_none_exists(self): http = ClientApiHttp.create() diff --git a/tests/integration/test_payment_method_us_bank_account.py b/tests/integration/test_payment_method_us_bank_account.py index ef499aae..368ff703 100644 --- a/tests/integration/test_payment_method_us_bank_account.py +++ b/tests/integration/test_payment_method_us_bank_account.py @@ -36,7 +36,7 @@ def test_create_with_verification(self): customer_id = Customer.create().customer.id result = PaymentMethod.create({ "customer_id": customer_id, - "payment_method_nonce": TestHelper.generate_valid_us_bank_account_nonce("021000021", "1000000000"), + "payment_method_nonce": Nonces.UsBankAccount, "options": { "verification_merchant_account_id": TestHelper.us_bank_merchant_account_id, "us_bank_account_verification_method": UsBankAccountVerification.VerificationMethod.NetworkCheck, @@ -46,13 +46,13 @@ def test_create_with_verification(self): self.assertTrue(result.is_success) us_bank_account = result.payment_method self.assertIsInstance(us_bank_account, UsBankAccount) - self.assertEqual(us_bank_account.routing_number, "021000021") + self.assertEqual(us_bank_account.routing_number, "123456789") self.assertEqual(us_bank_account.last_4, "0000") self.assertEqual(us_bank_account.account_type, "checking") self.assertEqual(us_bank_account.account_holder_name, "Dan Schulman") - self.assertTrue(re.match(r".*CHASE.*", us_bank_account.bank_name)) + self.assertEqual(us_bank_account.bank_name, "Wells Fargo") self.assertEqual(us_bank_account.default, True) - self.assertEqual(us_bank_account.ach_mandate.text, "cl mandate text") + self.assertEqual(us_bank_account.ach_mandate.text, "example mandate text") self.assertIsInstance(us_bank_account.ach_mandate.accepted_at, datetime) self.assertEqual(us_bank_account.verified, True) @@ -62,6 +62,73 @@ def test_create_with_verification(self): self.assertEqual(verification.status, UsBankAccountVerification.Status.Verified) self.assertEqual(verification.verification_method, UsBankAccountVerification.VerificationMethod.NetworkCheck) + self.assertEqual(verification.processor_response_code, "1000") + + def test_create_with_verification_add_ons(self): + customer_id = Customer.create().customer.id + result = PaymentMethod.create({ + "customer_id": customer_id, + "payment_method_nonce": Nonces.UsBankAccount, + "options": { + "verification_merchant_account_id": TestHelper.us_bank_merchant_account_id, + "us_bank_account_verification_method": UsBankAccountVerification.VerificationMethod.NetworkCheck, + "verification_add_ons": UsBankAccountVerification.VerificationAddOns.CustomerVerification, + } + }) + + self.assertTrue(result.is_success) + us_bank_account = result.payment_method + self.assertIsInstance(us_bank_account, UsBankAccount) + self.assertEqual(us_bank_account.routing_number, "123456789") + self.assertEqual(us_bank_account.last_4, "0000") + self.assertEqual(us_bank_account.account_type, "checking") + self.assertEqual(us_bank_account.account_holder_name, "Dan Schulman") + self.assertEqual(us_bank_account.bank_name, "Wells Fargo") + self.assertEqual(us_bank_account.default, True) + self.assertEqual(us_bank_account.ach_mandate.text, "example mandate text") + self.assertIsInstance(us_bank_account.ach_mandate.accepted_at, datetime) + self.assertEqual(us_bank_account.verified, True) + + self.assertEqual(len(us_bank_account.verifications), 1) + + verification = us_bank_account.verifications[0] + + self.assertEqual(verification.status, UsBankAccountVerification.Status.Verified) + self.assertEqual(verification.verification_method, UsBankAccountVerification.VerificationMethod.NetworkCheck) + self.assertEqual(verification.processor_response_code, "1000") + + def test_returns_additional_processor_response_for_failed_verifications(self): + customer_id = Customer.create().customer.id + result = PaymentMethod.create({ + "customer_id": customer_id, + "payment_method_nonce": TestHelper.generate_valid_us_bank_account_nonce("021000021", "1000000005"), + "options": { + "verification_merchant_account_id": TestHelper.us_bank_merchant_account_id, + "us_bank_account_verification_method": UsBankAccountVerification.VerificationMethod.NetworkCheck, + } + }) + + self.assertTrue(result.is_success) + us_bank_account = result.payment_method + self.assertIsInstance(us_bank_account, UsBankAccount) + self.assertEqual(us_bank_account.routing_number, "021000021") + self.assertEqual(us_bank_account.last_4, "0005") + self.assertEqual(us_bank_account.account_type, "checking") + self.assertEqual(us_bank_account.account_holder_name, "Dan Schulman") + self.assertTrue(re.match(r".*CHASE.*", us_bank_account.bank_name)) + self.assertEqual(us_bank_account.default, True) + self.assertEqual(us_bank_account.ach_mandate.text, "cl mandate text") + self.assertIsInstance(us_bank_account.ach_mandate.accepted_at, datetime) + self.assertEqual(us_bank_account.verified, False) + + self.assertEqual(len(us_bank_account.verifications), 1) + + verification = us_bank_account.verifications[0] + + self.assertEqual(verification.status, UsBankAccountVerification.Status.ProcessorDeclined) + self.assertEqual(verification.verification_method, UsBankAccountVerification.VerificationMethod.NetworkCheck) + self.assertEqual(verification.processor_response_code, "2061") + self.assertEqual(verification.additional_processor_response, "Invalid routing number") def test_create_fails_with_invalid_us_bank_account_nonce(self): customer_id = Customer.create().customer.id diff --git a/tests/integration/test_transaction.py b/tests/integration/test_transaction.py index 12c7c4e5..0c28dfb4 100644 --- a/tests/integration/test_transaction.py +++ b/tests/integration/test_transaction.py @@ -3497,6 +3497,164 @@ def test_submit_for_settlement_with_validation_error_on_service_fee(self): result.errors.for_object("transaction").on("amount")[0].code ) + def test_submit_for_settlement_with_industry_data(self): + result = Transaction.sale({ + "amount": TransactionAmounts.Authorize, + "payment_method_nonce": Nonces.PayPalOneTimePayment, + "options": { + "submit_for_settlement": False + } + }) + + self.assertTrue(result.is_success) + + params = { + "industry": { + "industry_type": Transaction.IndustryType.TravelAndFlight, + "data": { + "passenger_first_name": "John", + "passenger_last_name": "Doe", + "passenger_middle_initial": "M", + "passenger_title": "Mr.", + "issued_date": date(2018, 1, 1), + "travel_agency_name": "Expedia", + "travel_agency_code": "12345678", + "ticket_number": "ticket-number", + "issuing_carrier_code": "AA", + "customer_code": "customer-code", + "fare_amount": "70.00", + "fee_amount": "10.00", + "tax_amount": "20.00", + "restricted_ticket": False, + "date_of_birth": "2012-12-12", + "country_code": "US", + "legs": [ + { + "conjunction_ticket": "CJ0001", + "exchange_ticket": "ET0001", + "coupon_number": "1", + "service_class": "Y", + "carrier_code": "AA", + "fare_basis_code": "W", + "flight_number": "AA100", + "departure_date": date(2018, 1, 2), + "departure_airport_code": "MDW", + "departure_time": "08:00", + "arrival_airport_code": "ATX", + "arrival_time": "10:00", + "stopover_permitted": False, + "fare_amount": "35.00", + "fee_amount": "5.00", + "tax_amount": "10.00", + "endorsement_or_restrictions": "NOT REFUNDABLE" + }, + { + "conjunction_ticket": "CJ0002", + "exchange_ticket": "ET0002", + "coupon_number": "1", + "service_class": "Y", + "carrier_code": "AA", + "fare_basis_code": "W", + "flight_number": "AA200", + "departure_date": date(2018, 1, 3), + "departure_airport_code": "ATX", + "departure_time": "12:00", + "arrival_airport_code": "MDW", + "arrival_time": "14:00", + "stopover_permitted": False, + "fare_amount": "35.00", + "fee_amount": "5.00", + "tax_amount": "10.00", + "endorsement_or_restrictions": "NOT REFUNDABLE" + } + ] + } + } + } + + submitted_transaction = Transaction.submit_for_settlement(result.transaction.id, TransactionAmounts.Authorize, params).transaction + + self.assertEqual(Transaction.Status.Settling, submitted_transaction.status) + + def test_submit_for_partial_settlement_with_industry_data(self): + result = Transaction.sale({ + "amount": TransactionAmounts.Authorize, + "payment_method_nonce": Nonces.PayPalOneTimePayment, + "options": { + "submit_for_settlement": False + } + }) + + self.assertTrue(result.is_success) + + params = { + "industry": { + "industry_type": Transaction.IndustryType.TravelAndFlight, + "data": { + "passenger_first_name": "John", + "passenger_last_name": "Doe", + "passenger_middle_initial": "M", + "passenger_title": "Mr.", + "issued_date": date(2018, 1, 1), + "travel_agency_name": "Expedia", + "travel_agency_code": "12345678", + "ticket_number": "ticket-number", + "issuing_carrier_code": "AA", + "customer_code": "customer-code", + "fare_amount": "70.00", + "fee_amount": "10.00", + "tax_amount": "20.00", + "restricted_ticket": False, + "date_of_birth": "2012-12-12", + "country_code": "US", + "legs": [ + { + "conjunction_ticket": "CJ0001", + "exchange_ticket": "ET0001", + "coupon_number": "1", + "service_class": "Y", + "carrier_code": "AA", + "fare_basis_code": "W", + "flight_number": "AA100", + "departure_date": date(2018, 1, 2), + "departure_airport_code": "MDW", + "departure_time": "08:00", + "arrival_airport_code": "ATX", + "arrival_time": "10:00", + "stopover_permitted": False, + "fare_amount": "35.00", + "fee_amount": "5.00", + "tax_amount": "10.00", + "endorsement_or_restrictions": "NOT REFUNDABLE" + }, + { + "conjunction_ticket": "CJ0002", + "exchange_ticket": "ET0002", + "coupon_number": "1", + "service_class": "Y", + "carrier_code": "AA", + "fare_basis_code": "W", + "flight_number": "AA200", + "departure_date": date(2018, 1, 3), + "departure_airport_code": "ATX", + "departure_time": "12:00", + "arrival_airport_code": "MDW", + "arrival_time": "14:00", + "stopover_permitted": False, + "fare_amount": "35.00", + "fee_amount": "5.00", + "tax_amount": "10.00", + "endorsement_or_restrictions": "NOT REFUNDABLE" + } + ] + } + } + } + + submitted_transaction = Transaction.submit_for_partial_settlement(result.transaction.id, Decimal("500.00"), params).transaction + + self.assertEqual(Transaction.Status.Settling, submitted_transaction.status) + def test_update_details_with_valid_params(self): transaction = Transaction.sale({ "amount": "10.00", @@ -4126,6 +4284,10 @@ def test_transactions_accept_travel_flight_industry_data(self): "fee_amount": "10.00", "tax_amount": "20.00", "restricted_ticket": False, + "arrival_date": date(2022, 2, 3), + "ticket_issuer_address": "ti-address", + "date_of_birth": "2012-12-12", + "country_code": "US", "legs": [ { "conjunction_ticket": "CJ0001", @@ -4172,6 +4334,83 @@ def test_transactions_accept_travel_flight_industry_data(self): self.assertTrue(result.is_success) + def test_transaction_submit_for_settlement_with_travel_flight_industry_data(self): + transaction = Transaction.sale({ + "amount": TransactionAmounts.Authorize, + "merchant_account_id": TestHelper.fake_first_data_merchant_account_id, + "credit_card": { + "number": "4111111111111111", + "expiration_date": "05/2009" + } + }).transaction + + params = { + "industry": { + "industry_type": Transaction.IndustryType.TravelAndFlight, + "data": { + "passenger_first_name": "John", + "passenger_last_name": "Doe", + "passenger_middle_initial": "M", + "passenger_title": "Mr.", + "issued_date": date(2018, 1, 1), + "travel_agency_name": "Expedia", + "travel_agency_code": "12345678", + "ticket_number": "ticket-number", + "issuing_carrier_code": "AA", + "customer_code": "customer-code", + "fare_amount": "70.00", + "fee_amount": "10.00", + "tax_amount": "20.00", + "restricted_ticket": False, + "arrival_date": date(2022, 2, 3), + "ticket_issuer_address": "ti-address", + "legs": [ + { + "conjunction_ticket": "CJ0001", + "exchange_ticket": "ET0001", + "coupon_number": "1", + "service_class": "Y", + "carrier_code": "AA", + "fare_basis_code": "W", + "flight_number": "AA100", + "departure_date": date(2018, 1, 2), + "departure_airport_code": "MDW", + "departure_time": "08:00", + "arrival_airport_code": "ATX", + "arrival_time": "10:00", + "stopover_permitted": False, + "fare_amount": "35.00", + "fee_amount": "5.00", + "tax_amount": "10.00", + "endorsement_or_restrictions": "NOT REFUNDABLE" + }, + { + "conjunction_ticket": "CJ0002", + "exchange_ticket": "ET0002", + "coupon_number": "1", + "service_class": "Y", + "carrier_code": "AA", + "fare_basis_code": "W", + "flight_number": "AA200", + "departure_date": date(2018, 1, 3), + "departure_airport_code": "ATX", + "departure_time": "12:00", + "arrival_airport_code": "MDW", + "arrival_time": "14:00", + "stopover_permitted": False, + "fare_amount": "35.00", + "fee_amount": "5.00", + "tax_amount": "10.00", + "endorsement_or_restrictions": "NOT REFUNDABLE" + } + ] + } + } + } + + submitted_transaction = Transaction.submit_for_settlement(transaction.id, Decimal("900"), params).transaction + self.assertEqual(Transaction.Status.SubmittedForSettlement, submitted_transaction.status) + def test_transactions_return_validation_errors_on_travel_flight_industry_data(self): result = Transaction.sale({ "amount": TransactionAmounts.Authorize, @@ -5391,6 +5630,56 @@ def test_creating_sepa_direct_debit_transaction_with_vaulted_fake_nonce(self): self.assertIsNone(sdd_details.refund_from_transaction_fee_currency_iso_code) self.assertIsNone(sdd_details.settlement_type) + def test_creating_meta_checkout_card_transaction_with_fake_nonce(self): + result = Transaction.sale({ + "amount": TransactionAmounts.Authorize, + "payment_method_nonce": Nonces.MetaCheckoutCard, + }) + + self.assertTrue(result.is_success) + transaction = result.transaction + details = transaction.meta_checkout_card_details + + self.assertEqual(details.bin, "401288") + self.assertEqual(details.card_type, "Visa") + self.assertEqual(details.cardholder_name, "Meta Checkout Card Cardholder") + self.assertEqual(details.container_id, "container123") + self.assertEqual(details.customer_location, "US") + self.assertEqual(details.expiration_date, "12/2024") + self.assertEqual(details.expiration_month, "12") + self.assertEqual(details.expiration_year, "2024") + self.assertEqual(details.image_url, "https://assets.braintreegateway.com/payment_method_logo/visa.png?environment=development") + self.assertEqual(details.is_network_tokenized, False) + self.assertEqual(details.last_4, "1881") + self.assertEqual(details.masked_number, "401288******1881") + self.assertEqual(details.prepaid, "No") + + def test_creating_meta_checkout_token_transaction_with_fake_nonce(self): + result = Transaction.sale({ + "amount": TransactionAmounts.Authorize, + "payment_method_nonce": Nonces.MetaCheckoutToken, + }) + + self.assertTrue(result.is_success) + transaction = result.transaction + details = transaction.meta_checkout_token_details + + self.assertEqual(details.bin, "401288") + self.assertEqual(details.card_type, "Visa") + self.assertEqual(details.cardholder_name, "Meta Checkout Token Cardholder") + self.assertEqual(details.container_id, "container123") + self.assertEqual(details.cryptogram, "AlhlvxmN2ZKuAAESNFZ4GoABFA==") + self.assertEqual(details.customer_location, "US") + self.assertEqual(details.ecommerce_indicator, "07") + self.assertEqual(details.expiration_date, "12/2024") + self.assertEqual(details.expiration_month, "12") + self.assertEqual(details.expiration_year, "2024") + self.assertEqual(details.image_url, "https://assets.braintreegateway.com/payment_method_logo/visa.png?environment=development") + self.assertEqual(details.is_network_tokenized, True) + self.assertEqual(details.last_4, "1881") + self.assertEqual(details.masked_number, "401288******1881") + self.assertEqual(details.prepaid, "No") + def test_creating_paypal_transaction_with_vaulted_token(self): customer_id = Customer.create().customer.id @@ -5999,11 +6288,11 @@ def test_installment_transaction(self): self.assertTrue(result.is_success) self.assertEqual("1000", transaction.processor_response_code) self.assertEqual(ProcessorResponseTypes.Approved, transaction.processor_response_type) - self.assertEquals(4, transaction.installment_count) - self.assertEquals(4, len(transaction.installments)) + self.assertEqual(4, transaction.installment_count) + self.assertEqual(4, len(transaction.installments)) for i, t in enumerate(transaction.installments) : - self.assertEquals('250.00', t['amount']) - self.assertEquals('% s_INST_% s'%(transaction.id,i+1), t['id']) + self.assertEqual('250.00', t['amount']) + self.assertEqual('% s_INST_% s'%(transaction.id,i+1), t['id']) result = Transaction.refund(transaction.id,"20.00") self.assertTrue(result.is_success) @@ -6011,8 +6300,8 @@ def test_installment_transaction(self): refund = result.transaction for t in refund.refunded_installments : - self.assertEquals('-5.00', t['adjustments'][0]['amount']) - self.assertEquals("REFUND",t['adjustments'][0]['kind']) + self.assertEqual('-5.00', t['adjustments'][0]['amount']) + self.assertEqual("REFUND",t['adjustments'][0]['kind']) def test_manual_key_entry_transactions_with_valid_card_details(self): result = Transaction.sale({ diff --git a/tests/integration/test_transaction_search.py b/tests/integration/test_transaction_search.py index a81f6ab8..0a3da1c2 100644 --- a/tests/integration/test_transaction_search.py +++ b/tests/integration/test_transaction_search.py @@ -308,6 +308,33 @@ def test_advanced_search_with_sepa_debit_paypal_v2_order_id(self): self.assertEqual(transaction.payment_instrument_type, PaymentInstrumentType.SepaDirectDebitAccount) self.assertEqual(transaction.id, collection.first.id) + def test_advanced_search_with_payment_instrument_type_is_meta_checkout(self): + card_tx = Transaction.sale({ + "amount": TransactionAmounts.Authorize, + "options": { + "submit_for_settlement": True, + }, + "payment_method_nonce": Nonces.MetaCheckoutCard + }).transaction + + token_tx = Transaction.sale({ + "amount": TransactionAmounts.Authorize, + "options": { + "submit_for_settlement": True, + }, + "payment_method_nonce": Nonces.MetaCheckoutToken + }).transaction + + collection = Transaction.search( + TransactionSearch.payment_instrument_type == "MetaCheckout" + ) + + self.assertTrue(collection.maximum_size >= 2) + + tx_id_map = list(map(lambda x: x.id, collection)) + self.assertTrue(card_tx.id in tx_id_map) + self.assertTrue(token_tx.id in tx_id_map) + def test_advanced_search_with_payment_instrument_type_is_apple_pay(self): transaction = Transaction.sale({ "amount": TransactionAmounts.Authorize, diff --git a/tests/integration/test_us_bank_account.py b/tests/integration/test_us_bank_account.py index b1e4a9ce..05987721 100644 --- a/tests/integration/test_us_bank_account.py +++ b/tests/integration/test_us_bank_account.py @@ -17,7 +17,7 @@ def test_find_returns_us_bank_account(self): self.assertEqual(found_account.last_4, "1234") self.assertEqual(found_account.account_type, "checking") self.assertEqual(found_account.account_holder_name, "Dan Schulman") - self.assertRegexpMatches(found_account.bank_name, r".*CHASE.*") + self.assertRegex(found_account.bank_name, r".*CHASE.*") self.assertEqual(found_account.default, True) self.assertEqual(found_account.ach_mandate.text, "cl mandate text") self.assertIsNotNone(found_account.ach_mandate.accepted_at) diff --git a/tests/unit/test_meta_checkout_card.py b/tests/unit/test_meta_checkout_card.py new file mode 100644 index 00000000..fab1a967 --- /dev/null +++ b/tests/unit/test_meta_checkout_card.py @@ -0,0 +1,52 @@ +from tests.test_helper import * +from braintree.meta_checkout_card import MetaCheckoutCard + +class TestMetaCheckoutCard(unittest.TestCase): + def test_initialization(self): + card = MetaCheckoutCard(None, { + "bin": "abc1234", + "card_type": "Visa", + "cardholder_name": "John Doe", + "container_id": "a-container-id", + "expiration_month": "05", + "expiration_year": "2024", + "is_network_tokenized": False, + "last_4": "5678" + }) + + self.assertEqual(card.bin, "abc1234") + self.assertEqual(card.card_type, "Visa") + self.assertEqual(card.cardholder_name, "John Doe") + self.assertEqual(card.container_id, "a-container-id") + self.assertEqual(card.expiration_month, "05") + self.assertEqual(card.expiration_year, "2024") + self.assertEqual(card.is_network_tokenized, False) + self.assertEqual(card.last_4, "5678") + + def test_expiration_date(self): + card = MetaCheckoutCard(None, { + "bin": "abc123", + "card_type": "Visa", + "cardholder_name": "John Doe", + "container_id": "a-container-id", + "expiration_month": "05", + "expiration_year": "2024", + "is_network_tokenized": False, + "last_4": "5678" + }) + + self.assertEqual(card.expiration_date, "05/2024") + + def test_masked_number(self): + card = MetaCheckoutCard(None, { + "bin": "abc123", + "card_type": "Visa", + "cardholder_name": "John Doe", + "container_id": "a-container-id", + "expiration_month": "05", + "expiration_year": "2024", + "is_network_tokenized": False, + "last_4": "5678" + }) + + self.assertEqual(card.masked_number, "abc123******5678") diff --git a/tests/unit/test_meta_checkout_token.py b/tests/unit/test_meta_checkout_token.py new file mode 100644 index 00000000..681956d9 --- /dev/null +++ b/tests/unit/test_meta_checkout_token.py @@ -0,0 +1,56 @@ +from tests.test_helper import * +from braintree.meta_checkout_card import MetaCheckoutCard + +class TestMetaCheckoutToken(unittest.TestCase): + def test_initialization(self): + card = MetaCheckoutCard(None, { + "bin": "abc1234", + "card_type": "Visa", + "cardholder_name": "John Doe", + "container_id": "a-container-id", + "cryptogram": "a-cryptogram", + "ecommerce_indicator": "01", + "expiration_month": "05", + "expiration_year": "2024", + "is_network_tokenized": True, + "last_4": "5678" + }) + + self.assertEqual(card.bin, "abc1234") + self.assertEqual(card.card_type, "Visa") + self.assertEqual(card.cardholder_name, "John Doe") + self.assertEqual(card.container_id, "a-container-id") + self.assertEqual(card.cryptogram, "a-cryptogram") + self.assertEqual(card.ecommerce_indicator, "01") + self.assertEqual(card.expiration_month, "05") + self.assertEqual(card.expiration_year, "2024") + self.assertEqual(card.is_network_tokenized, True) + self.assertEqual(card.last_4, "5678") + + def test_expiration_date(self): + card = MetaCheckoutCard(None, { + "bin": "abc123", + "card_type": "Visa", + "cardholder_name": "John Doe", + "container_id": "a-container-id", + "expiration_month": "05", + "expiration_year": "2024", + "is_network_tokenized": True, + "last_4": "5678" + }) + + self.assertEqual(card.expiration_date, "05/2024") + + def test_masked_number(self): + card = MetaCheckoutCard(None, { + "bin": "abc123", + "card_type": "Visa", + "cardholder_name": "John Doe", + "container_id": "a-container-id", + "expiration_month": "05", + "expiration_year": "2024", + "is_network_tokenized": True, + "last_4": "5678" + }) + + self.assertEqual(card.masked_number, "abc123******5678") diff --git a/tests/unit/test_payment_method_gateway.py b/tests/unit/test_payment_method_gateway.py index 1e328fdc..baad300d 100644 --- a/tests/unit/test_payment_method_gateway.py +++ b/tests/unit/test_payment_method_gateway.py @@ -29,6 +29,7 @@ def test_create_signature(self): "skip_advanced_fraud_checking", "us_bank_account_verification_method", "verification_account_type", + "verification_add_ons", "verification_amount", "verification_merchant_account_id", "verify_card", @@ -104,6 +105,7 @@ def test_update_signature(self): "us_bank_account_verification_method", "venmo_sdk_session", "verification_account_type", + "verification_add_ons", "verification_amount", "verification_merchant_account_id", "verify_card", diff --git a/tests/unit/test_transaction.py b/tests/unit/test_transaction.py index e7bbac96..8fae86df 100644 --- a/tests/unit/test_transaction.py +++ b/tests/unit/test_transaction.py @@ -1,5 +1,7 @@ from tests.test_helper import * from braintree.test.credit_card_numbers import CreditCardNumbers +from braintree.meta_checkout_card import MetaCheckoutCard +from braintree.meta_checkout_token import MetaCheckoutToken from datetime import datetime from datetime import date from braintree.authorization_adjustment import AuthorizationAdjustment @@ -260,3 +262,21 @@ def test_retry_ids_and_retried_transaction_id(self): self.assertEqual(transaction.retry_ids, ['retry_id_1','retry_id2']) self.assertEqual(transaction.retried_transaction_id, "12345") self.assertTrue(transaction.retried) + + def test_transaction_meta_checkout_card_attributes(self): + attributes = { + 'amount': '420', + 'meta_checkout_card': {} + } + + transaction = Transaction(None, attributes) + self.assertIsInstance(transaction.meta_checkout_card_details, MetaCheckoutCard) + + def test_transaction_meta_checkout_token_attributes(self): + attributes = { + 'amount': '69', + 'meta_checkout_token': {} + } + + transaction = Transaction(None, attributes) + self.assertIsInstance(transaction.meta_checkout_token_details, MetaCheckoutToken) diff --git a/tests/unit/test_us_bank_account_verification.py b/tests/unit/test_us_bank_account_verification.py index b85be98e..ce0fcc89 100644 --- a/tests/unit/test_us_bank_account_verification.py +++ b/tests/unit/test_us_bank_account_verification.py @@ -21,7 +21,8 @@ def test_attributes(self): "us_bank_account": { "token": "abc123", "last_4": 9999, - } + }, + "additional_processor_response": "Yikes" } verification = UsBankAccountVerification({}, attributes) @@ -29,6 +30,7 @@ def test_attributes(self): self.assertEqual(verification.id, "my_favorite_id") self.assertEqual(verification.status, UsBankAccountVerification.Status.Verified) self.assertEqual(verification.verification_determined_at, datetime(2018, 11, 11, 23, 59, 59)) + self.assertEqual(verification.additional_processor_response, "Yikes") self.assertEqual( verification.verification_method, UsBankAccountVerification.VerificationMethod.IndependentCheck diff --git a/tests/unit/test_webhooks.py b/tests/unit/test_webhooks.py index 97750b26..635c78a9 100644 --- a/tests/unit/test_webhooks.py +++ b/tests/unit/test_webhooks.py @@ -543,6 +543,20 @@ def test_builds_notification_for_connected_merchant_paypal_status_changed(self): self.assertEqual("my_id", notification.connected_merchant_paypal_status_changed.merchant_id) self.assertEqual("oauth_application_client_id", notification.connected_merchant_paypal_status_changed.oauth_application_client_id) + def test_builds_notification_for_subscription_billing_skipped(self): + sample_notification = WebhookTesting.sample_notification( + WebhookNotification.Kind.SubscriptionBillingSkipped, + "my_id" + ) + + notification = WebhookNotification.parse(sample_notification['bt_signature'], sample_notification['bt_payload']) + + self.assertEqual(WebhookNotification.Kind.SubscriptionBillingSkipped, notification.kind) + self.assertEqual("my_id", notification.subscription.id) + self.assertTrue(len(notification.subscription.transactions) == 0) + self.assertTrue(len(notification.subscription.discounts) == 0) + self.assertTrue(len(notification.subscription.add_ons) == 0) + def test_builds_notification_for_subscription_charged_successfully(self): sample_notification = WebhookTesting.sample_notification( WebhookNotification.Kind.SubscriptionChargedSuccessfully, @@ -834,9 +848,19 @@ def test_payment_method_customer_data_updated_webhook(self): enriched_customer_data = payment_method_customer_data_updated.enriched_customer_data self.assertEqual(enriched_customer_data.fields_updated, ["username"]) + address = { + "street_address": "Street Address", + "extended_address": "Extended Address", + "locality": "Locality", + "region": "Region", + "postal_code":"Postal Code" + } + profile_data = enriched_customer_data.profile_data self.assertEqual(profile_data.first_name, "John") self.assertEqual(profile_data.last_name, "Doe") self.assertEqual(profile_data.username, "venmo_username") self.assertEqual(profile_data.phone_number, "1231231234") self.assertEqual(profile_data.email, "john.doe@paypal.com") + self.assertEqual(profile_data.billing_address, address) + self.assertEqual(profile_data.shipping_address, address)