From 07b107ea7180e8950ef839360c02241eec280217 Mon Sep 17 00:00:00 2001 From: Silviana Ghita <> Date: Tue, 19 Dec 2023 17:27:52 +0200 Subject: [PATCH 1/2] Added CardInfo to 4 entities --- mangopay/fields.py | 30 +++++++- mangopay/resources.py | 15 +++- mangopay/utils.py | 44 +++++++++--- tests/test_payins.py | 122 +++++++++++++++++++++++++++++++- tests/test_preauthorizations.py | 55 +++++++++++++- 5 files changed, 249 insertions(+), 17 deletions(-) diff --git a/mangopay/fields.py b/mangopay/fields.py index 9e5bd68..fa447e9 100644 --- a/mangopay/fields.py +++ b/mangopay/fields.py @@ -10,7 +10,7 @@ Reason, ReportTransactionsFilters, ReportWalletsFilters, \ PlatformCategorization, Billing, SecurityInfo, Birthplace, ApplepayPaymentData, GooglepayPaymentData, \ ScopeBlocked, BrowserInfo, Shipping, CurrentState, FallbackReason, InstantPayout, CountryAuthorizationData, \ - PayinsLinked, ConversionRate + PayinsLinked, ConversionRate, CardInfo class FieldDescriptor(object): @@ -889,3 +889,31 @@ def api_value(self, value): } return value + + +class CardInfoField(Field): + def python_value(self, value): + if value is not None: + return CardInfo( + bin=value['BIN'], + issuing_bank=value['IssuingBank'], + issuer_country_code=value['IssuerCountryCode'], + type=value['Type'], + brand=value['Brand'], + sub_type=value['SubType']) + return value + + def api_value(self, value): + value = super(CardInfoField, self).api_value(value) + + if isinstance(value, CardInfo): + value = { + 'BIN': value.bin, + 'IssuingBank': value.issuing_bank, + 'IssuerCountryCode': value.issuer_country_code, + 'Type': value.type, + 'Brand': value.brand, + 'SubType': value.sub_type, + } + + return value diff --git a/mangopay/resources.py b/mangopay/resources.py index 5f55182..a42a3a3 100644 --- a/mangopay/resources.py +++ b/mangopay/resources.py @@ -14,7 +14,7 @@ ReportWalletsFiltersField, BillingField, SecurityInfoField, PlatformCategorizationField, BirthplaceField, ApplepayPaymentDataField, GooglepayPaymentDataField, ScopeBlockedField, BrowserInfoField, ShippingField, CurrentStateField, FallbackReasonField, InstantPayoutField, - CountryAuthorizationDataField, PayinsLinkedField, ConversionRateField) + CountryAuthorizationDataField, PayinsLinkedField, ConversionRateField, CardInfoField) from .query import InsertQuery, UpdateQuery, SelectQuery, ActionQuery @@ -264,6 +264,7 @@ def get(cls, *args, **kwargs): return ClientWallet.get(*tuple(args[0].split('_')), **kwargs) return super(Wallet, cls).get(*args, **kwargs) + @python_2_unicode_compatible class ConversionRate(BaseModel): debited_currency = CharField(api_name='DebitedCurrency', required=True) @@ -282,9 +283,10 @@ class Meta: verbose_name = 'conversion_rate' verbose_name_plural = 'conversion_rates' url = { - 'GET_CONVERSION_RATE' : '/conversion/rate/%(debited_currency)s/%(credited_currency)s' + 'GET_CONVERSION_RATE': '/conversion/rate/%(debited_currency)s/%(credited_currency)s' } + @python_2_unicode_compatible class InstantConversion(BaseModel): author = ForeignKeyField(User, api_name='AuthorId', required=True) @@ -318,7 +320,7 @@ class Meta: verbose_name = 'instant_conversion' verbose_name_plural = 'instant_conversions' url = { - 'CREATE_INSTANT_CONVERSION' : '/instant-conversion', + 'CREATE_INSTANT_CONVERSION': '/instant-conversion', 'GET_INSTANT_CONVERSION': '/instant-conversion/%(id)s' } @@ -636,6 +638,7 @@ class RecurringPayInCIT(PayIn): secure_mode_redirect_url = CharField(api_name='SecureModeRedirectURL') security_info = SecurityInfoField(api_name='SecurityInfo') shipping = ShippingField(api_name='Shipping') + card_info = CardInfoField(api_name='CardInfo') def get_read_only_properties(self): read_only = ["AuthorId", "Applied3DSVersion", "CardId", "CreationDate", "Culture", "SecureModeNeeded" @@ -672,6 +675,7 @@ class RecurringPayInMIT(PayIn): secure_mode_redirect_url = CharField(api_name='SecureModeRedirectURL') security_info = SecurityInfoField(api_name='SecurityInfo') shipping = ShippingField(api_name='Shipping') + card_info = CardInfoField(api_name='CardInfo') def get_read_only_properties(self): read_only = ["AuthorId", "Applied3DSVersion", "CardId", "CreationDate", "Culture", "SecureModeNeeded" @@ -711,6 +715,7 @@ class DirectPayIn(PayIn): shipping = ShippingField(api_name='Shipping') requested_3ds_version = CharField(api_name='Requested3DSVersion') applied_3ds_version = CharField(api_name='Applied3DSVersion') + card_info = CardInfoField(api_name='CardInfo') class Meta: verbose_name = 'payin' @@ -989,6 +994,7 @@ class Meta: SelectQuery.identifier: '/payins' } + class IdealPayIn(PayIn): author = ForeignKeyField(User, api_name='AuthorId', required=True) credited_wallet = ForeignKeyField(Wallet, api_name='CreditedWalletId', required=True) @@ -1009,6 +1015,7 @@ class Meta: SelectQuery.identifier: '/payins' } + class GiropayPayIn(PayIn): author = ForeignKeyField(User, api_name='AuthorId', required=True) credited_wallet = ForeignKeyField(Wallet, api_name='CreditedWalletId', required=True) @@ -1109,6 +1116,7 @@ class CardPreAuthorizedDepositPayIn(BaseModel): debited_funds = MoneyField(api_name='DebitedFunds') credited_funds = MoneyField(api_name='CreditedFunds') fees = MoneyField(api_name='Fees') + card_info = CardInfoField(api_name='ApiName') class Meta: verbose_name = 'card_preauthorized_deposit_payin' @@ -1146,6 +1154,7 @@ class PreAuthorization(BaseModel): shipping = ShippingField(api_name='Shipping') requested_3ds_version = CharField(api_name='Requested3DSVersion') applied_3ds_version = CharField(api_name='Applied3DSVersion') + card_info = CardInfoField(api_name='CardInfo') def get_transactions(self, *args, **kwargs): kwargs['id'] = self.id diff --git a/mangopay/utils.py b/mangopay/utils.py index b149f9b..186a140 100644 --- a/mangopay/utils.py +++ b/mangopay/utils.py @@ -242,7 +242,7 @@ def __init__(self, first_name=None, last_name=None, address=None): def __str__(self): return 'Billing: %s' % \ - (self.first_name, self.last_name, self.address) + (self.first_name, self.last_name, self.address) @add_camelcase_aliases @@ -253,7 +253,7 @@ def __init__(self, code=None, message=None): def __str__(self): return 'FallbackReason: %s' % \ - (self.code, self.message) + (self.code, self.message) @add_camelcase_aliases @@ -264,7 +264,7 @@ def __init__(self, is_reachable=None, unreachable_reason=None): def __str__(self): return 'InstantPayout: %s' % \ - (self.code, self.message) + (self.code, self.message) @add_camelcase_aliases @@ -289,7 +289,7 @@ def __init__(self, owner_name=None, account_number=None, iban=None, def __str__(self): return 'DebitedBankAccount: %s' % \ - (self.owner_name, self.account_number, self.iban, self.bic, self.type, self.country) + (self.owner_name, self.account_number, self.iban, self.bic, self.type, self.country) def __eq__(self, other): if isinstance(other, DebitedBankAccount): @@ -317,7 +317,7 @@ def __init__(self, address_line_1=None, address_line_2=None, city=None, region=N def __str__(self): return 'Address: %s, %s , %s, %s, %s , %s' % \ - (self.address_line_1, self.address_line_2, self.postal_code, self.city, self.region, self.country) + (self.address_line_1, self.address_line_2, self.postal_code, self.city, self.region, self.country) def __eq__(self, other): if isinstance(other, Address): @@ -542,7 +542,7 @@ def __init__(self, first_name=None, last_name=None, address=None): def __str__(self): return 'Shipping: %s' % \ - (self.first_name, self.last_name, self.address) + (self.first_name, self.last_name, self.address) @add_camelcase_aliases @@ -556,7 +556,7 @@ def __init__(self, payins_linked=None, cumulated_debited_amount=None, cumulated_ def __str__(self): return 'CurrentState: %s' % \ - (self.cumulated_debited_amount, self.cumulated_debited_fees, self.last_payin_id, self.payins_linked) + (self.cumulated_debited_amount, self.cumulated_debited_fees, self.last_payin_id, self.payins_linked) @add_camelcase_aliases @@ -891,7 +891,7 @@ def __init__(self, block_user_creation=None, block_bank_account_creation=None, b def __str__(self): return 'CountryAuthorizationData: %s, %s , %s' % \ - (self.block_user_creation, self.block_bank_account_creation, self.block_payout) + (self.block_user_creation, self.block_bank_account_creation, self.block_payout) def __eq__(self, other): if isinstance(other, CountryAuthorizationData): @@ -916,7 +916,7 @@ def __init__(self, payin_capture_id=None, payin_complement_id=None): def __str__(self): return 'PayinsLinked: %s, %s' % \ - (self.payin_capture_id, self.payin_complement_id) + (self.payin_capture_id, self.payin_complement_id) def to_api_json(self): return { @@ -935,7 +935,7 @@ def __init__(self, name=None, quantity=None, unit_amount=None, tax_amount=None, def __str__(self): return 'LineItem: %s %s %s %s %s' % \ - (self.name, self.quantity, self.unit_amount, self.tax_amount, self.description) + (self.name, self.quantity, self.unit_amount, self.tax_amount, self.description) def to_api_json(self): return { @@ -946,6 +946,7 @@ def to_api_json(self): "Description": self.description } + @add_camelcase_aliases class ConversionRate(object): def __init__(self, client_rate=None, market_rate=None): @@ -954,4 +955,25 @@ def __init__(self, client_rate=None, market_rate=None): def __str__(self): return 'Conversion rate: %s' % \ - (self.client_rate, self.market_rate) \ No newline at end of file + (self.client_rate, self.market_rate) + + +@add_camelcase_aliases +class CardInfo(object): + def __init__(self, + bin=None, + issuing_bank=None, + issuer_country_code=None, + type=None, + brand=None, + sub_type=None): + self.bin = bin + self.issuing_bank = issuing_bank + self.issuer_country_code = issuer_country_code + self.type = type + self.brand = brand + self.sub_type = sub_type + + def __str__(self): + return 'Card info: %s' % \ + (self.bin, self.issuing_bank, self.issuer_country_code, self.type, self.brand, self.sub_type) diff --git a/tests/test_payins.py b/tests/test_payins.py index 4a5e71a..5b198a5 100644 --- a/tests/test_payins.py +++ b/tests/test_payins.py @@ -9,7 +9,7 @@ RecurringPayInCIT, PayInRefund, RecurringPayInMIT, CardPreAuthorizedDepositPayIn, MbwayPayIn, PayPalWebPayIn, \ GooglePayDirectPayIn, MultibancoPayIn, SatispayPayIn, BlikPayIn, KlarnaPayIn, IdealPayIn, GiropayPayIn, CardRegistration from mangopay.utils import (Money, ShippingAddress, Shipping, Billing, Address, SecurityInfo, ApplepayPaymentData, - GooglepayPaymentData, DebitedBankAccount, LineItem) + GooglepayPaymentData, DebitedBankAccount, LineItem, CardInfo) from tests import settings from tests.resources import (Wallet, DirectPayIn, BankWirePayIn, PayPalPayIn, PayconiqPayIn, CardWebPayIn, DirectDebitWebPayIn, constants) @@ -1110,6 +1110,25 @@ def test_card_preauthorized_deposit_payin(self): self.assertEqual("PREAUTHORIZED", pay_in.payment_type) self.assertEqual("PAYIN", pay_in.type) + @unittest.skip("can't be tested yet") + def test_card_preauthorized_deposit_payin_check_card_info(self): + deposit = self.create_new_deposit() + + params = { + "credited_wallet_id": self.get_johns_wallet().id, + "debited_funds": Money(amount=1000, currency='EUR'), + "fees": Money(amount=0, currency='EUR'), + "deposit_id": deposit.id + } + + created = CardPreAuthorizedDepositPayIn(**params).save() + pay_in = CardPreAuthorizedDepositPayIn().get(created.get('id')) + + self.assertIsNotNone(pay_in) + card_info = pay_in['card_info'] + self.assertIsNotNone(card_info) + self.assertIsInstance(card_info, CardInfo) + @unittest.skip('Skip because we cannot generate new payment date in tests') def test_PayIns_GooglePayDirect_Create(self): user = BaseTestLive.get_john(True) @@ -1550,3 +1569,104 @@ def test_create_partial_refund_for_payin(self): self.assertEqual('PAYOUT', refund_result['type']) self.assertEqual('REFUND', refund_result['nature']) + def test_PayIns_CardDirect_CheckCardInfo(self): + user = BaseTestLive.get_john(True) + debited_wallet = BaseTestLive.get_johns_wallet(True) + + # create wallet + credited_wallet = Wallet() + credited_wallet.owners = (user,) + credited_wallet.currency = 'EUR' + credited_wallet.description = 'WALLET IN EUR' + credited_wallet = Wallet(**credited_wallet.save()) + card = BaseTestLive.get_johns_card(True) + + pay_in = DirectPayIn() + pay_in.author = user + pay_in.debited_wallet = debited_wallet + pay_in.credited_wallet = credited_wallet + pay_in.card = card + pay_in.fees = Money() + pay_in.fees.amount = 100 + pay_in.fees.currency = "EUR" + pay_in.debited_funds = Money() + pay_in.debited_funds.amount = 1000 + pay_in.debited_funds.currency = "EUR" + pay_in.secure_mode_return_url = "http://www.example.com/" + pay_in.ip_address = "2001:0620:0000:0000:0211:24FF:FE80:C12C" + pay_in.browser_info = BaseTest.get_browser_info() + + address = Address() + address.address_line_1 = "Big Street" + address.address_line_2 = "no 2 ap 6" + address.country = "FR" + address.city = "Lyon" + address.postal_code = "68400" + pay_in.billing = Billing(first_name="John", last_name="Doe", address=address) + + result = pay_in.save() + + self.assertIsNotNone(result) + card_info = result['card_info'] + self.assertIsNotNone(card_info) + self.assertIsInstance(card_info, CardInfo) + + def test_RecurringPayment_CheckCardInfo(self): + user = self.get_john(True) + wallet = self.get_johns_wallet(True) + card = BaseTestLive.get_johns_card_3dsecure(True) + + recurring = RecurringPayInRegistration() + recurring.author = user + recurring.card = card + recurring.user = user + recurring.credited_wallet = wallet + recurring.first_transaction_fees = Money(1, "EUR") + recurring.first_transaction_debited_funds = Money(12, "EUR") + recurring.free_cycles = 0 + address = Address() + address.address_line_1 = "Big Street" + address.address_line_2 = "no 2 ap 6" + address.country = "FR" + address.city = "Lyon" + address.postal_code = "68400" + recurring.billing = Billing(first_name="John", last_name="Doe", address=address) + recurring.shipping = Shipping(first_name="John", last_name="Doe", address=address) + recurring.end_date = 1768656033 + recurring.migration = True + recurring.next_transaction_fees = Money(1, "EUR") + recurring.next_transaction_debited_funds = Money(12, "EUR") + result = recurring.save() + self.assertIsNotNone(result) + self.assertIsNotNone(result.get('free_cycles')) + + created_recurring = RecurringPayInRegistration.get(result.get('id')) + self.assertIsNotNone(created_recurring) + print(created_recurring.id) + cit = RecurringPayInCIT() + cit.recurring_payin_registration_id = created_recurring.id + cit.tag = "custom meta" + cit.statement_descriptor = "lorem" + cit.secure_mode_return_url = "http://www.my-site.com/returnurl" + cit.ip_address = "2001:0620:0000:0000:0211:24FF:FE80:C12C" + cit.browser_info = BaseTest.get_browser_info() + cit.debited_funds = Money(12, "EUR") + cit.fees = Money(1, "EUR") + + created_cit = cit.save() + self.assertIsNotNone(created_cit) + cit_card_info = created_cit['card_info'] + self.assertIsNotNone(cit_card_info) + self.assertIsInstance(cit_card_info, CardInfo) + + mit = RecurringPayInMIT() + mit.recurring_payin_registration_id = created_recurring.id + mit.statement_descriptor = "lorem" + mit.tag = "custom meta" + mit.debited_funds = Money(10, "EUR") + mit.fees = Money(1, "EUR") + created_mit = mit.save() + self.assertIsNotNone(created_mit) + mit_card_info = created_mit['card_info'] + self.assertIsNotNone(mit_card_info) + self.assertIsInstance(mit_card_info, CardInfo) diff --git a/tests/test_preauthorizations.py b/tests/test_preauthorizations.py index c188c10..e06ae01 100644 --- a/tests/test_preauthorizations.py +++ b/tests/test_preauthorizations.py @@ -8,7 +8,7 @@ except ImportError: import urllib as urlrequest -from mangopay.utils import Money, Billing, Address, SecurityInfo +from mangopay.utils import Money, Billing, Address, SecurityInfo, CardInfo from datetime import date @@ -790,3 +790,56 @@ def test_PreAuthorizations_CreateDirect(self): self.assertEqual(security_info.avs_result, "NO_CHECK") self.assertEqual(payin.status, "SUCCEEDED") self.assertEqual(transactions[0].status, "SUCCEEDED") + + def test_PreAuthorizations_CheckCardInfo(self): + user = BaseTestLive.get_john() + card_registration = CardRegistration() + card_registration.user = user + card_registration.currency = "EUR" + + saved_registration = card_registration.save() + data = { + 'cardNumber': '4970105191923460', + 'cardCvx': '123', + 'cardExpirationDate': '1224', + 'accessKeyRef': card_registration.access_key, + 'data': card_registration.preregistration_data + } + headers = { + 'content-type': 'application/x-www-form-urlencoded' + } + registration_data_response = requests.post(card_registration.card_registration_url, data=data, headers=headers) + saved_registration['registration_data'] = registration_data_response.text + updated_registration = CardRegistration(**saved_registration).save() + + card = Card.get(updated_registration['card_id']) + pre_authorization = PreAuthorization() + pre_authorization.card = card + pre_authorization.author = user + pre_authorization.debited_funds = Money() + pre_authorization.debited_funds.currency = "EUR" + pre_authorization.debited_funds.amount = 100 + pre_authorization.remaining_funds = Money() + pre_authorization.remaining_funds.currency = "EUR" + pre_authorization.remaining_funds.amount = 100 + pre_authorization.secure_mode_return_url = "http://www.example.com/" + pre_authorization.ip_address = "2001:0620:0000:0000:0211:24FF:FE80:C12C" + pre_authorization.browser_info = BaseTest.get_browser_info() + + billing = Billing() + billing.address = Address() + billing.address.address_line_1 = "Main Street" + billing.address.address_line_2 = "no. 5 ap. 6" + billing.address.country = "FR" + billing.address.city = "Lyon" + billing.address.postal_code = "65400" + billing.last_name = "Doe" + billing.first_name = "John" + pre_authorization.billing = billing + + saved_pre_authorization = pre_authorization.save() + + self.assertIsNotNone(saved_pre_authorization) + card_info = saved_pre_authorization['card_info'] + self.assertIsNotNone(card_info) + self.assertIsInstance(card_info, CardInfo) \ No newline at end of file From e03c64e77caef1beb37cb472456bff043511dd92 Mon Sep 17 00:00:00 2001 From: Silviana Ghita <> Date: Thu, 21 Dec 2023 12:35:03 +0200 Subject: [PATCH 2/2] Minor fix --- mangopay/resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mangopay/resources.py b/mangopay/resources.py index a42a3a3..f1bc877 100644 --- a/mangopay/resources.py +++ b/mangopay/resources.py @@ -1116,7 +1116,7 @@ class CardPreAuthorizedDepositPayIn(BaseModel): debited_funds = MoneyField(api_name='DebitedFunds') credited_funds = MoneyField(api_name='CreditedFunds') fees = MoneyField(api_name='Fees') - card_info = CardInfoField(api_name='ApiName') + card_info = CardInfoField(api_name='CardInfo') class Meta: verbose_name = 'card_preauthorized_deposit_payin'