From 24acc6aaa411acd4cfd56d01bc7788234962b305 Mon Sep 17 00:00:00 2001 From: James Addison Date: Thu, 28 Aug 2025 12:14:37 +0100 Subject: [PATCH 01/26] payments: begin updating Wise implementation to match API updates --- apps/payments/wise.py | 70 +++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 166e5b534..b5bf9b994 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -203,51 +203,51 @@ def wise_business_profile(): return id +def _retrieve_detail(details, requested_type): + """Helper method to retrieve content from attribute-value details recordsets""" + for detail in details: + if detail.type == requested_type: + return detail.body + + def wise_retrieve_accounts(profile_id): - # Wise creates the concept of a multi-currency account by calling normal - # bank accounts "balances". As far as we're concerned, "balances" are bank - # accounts, as that's what people will be sending money to. - for account in wise.balances.list(profile_id=profile_id): - try: - if not account.bankDetails: - continue - if not account.bankDetails.bankAddress: - continue - except AttributeError: - continue + for account in wise.accounts.list(profile_id=profile_id): - address = ", ".join( - [ - account.bankDetails.bankAddress.addressFirstLine, - account.bankDetails.bankAddress.city + " " + (account.bankDetails.bankAddress.postCode or ""), - account.bankDetails.bankAddress.country, - ] - ) + account_holder = bank_name = bank_address = sort_code = account_number = swift = iban = None + + if account.currency.code == "GBP": + + for receive_options in account.receiveOptions: + + account_holder = _retrieve_detail(details, "ACCOUNT_HOLDER") + bank_info = _retrieve_detail(details, "BANK_NAME_AND_ADDRESS") + + if receive_options.type == "LOCAL": + sort_code = _retrieve_detail(details, "BANK_CODE").replace("-", "") + account_number = _retrieve_detail(details, "ACCOUNT_NUMBER") + + elif receive_options.type == "INTERNATIONAL": + swift = _retrieve_detail(details, "SWIFT_CODE") + iban = _retrieve_detail(details, "IBAN") - sort_code = account_number = None + if not bank_info: + continue - if account.bankDetails.currency == "GBP": - # bankCode is the SWIFT code for non-GBP accounts. - sort_code = account.bankDetails.bankCode.replace("-", "") + bank_name, _, bank_address = bank_info.partition("\n") - if len(account.bankDetails.accountNumber) == 8: - account_number = account.bankDetails.accountNumber - else: - # Wise bug: - # accountNumber is sometimes erroneously the IBAN for GBP accounts. - # Extract the account number from the IBAN. - account_number = account.bankDetails.accountNumber.replace(" ", "")[-8:] + if not bank_name or not bank_address: + continue yield BankAccount( sort_code=sort_code, acct_id=account_number, - currency=account.bankDetails.currency, + currency=account.currency.code, active=False, - payee_name=account.bankDetails.get("accountHolderName"), - institution=account.bankDetails.bankName, - address=address, - swift=account.bankDetails.get("swift"), - iban=account.bankDetails.get("iban"), + payee_name=account_holder, + institution=bank_name, + address=bank_address, + swift=swift, + iban=iban, # Webhooks only include the borderlessAccountId wise_balance_id=account.id, ) From 944fe52e8db2f48a4fb78ad1b78e95caffd28039 Mon Sep 17 00:00:00 2001 From: James Addison Date: Sat, 30 Aug 2025 11:23:34 +0100 Subject: [PATCH 02/26] payments: initial functional (sandbox-tested) updated Wise `BankAccount` import --- apps/payments/wise.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index b5bf9b994..eba0413d9 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -211,14 +211,14 @@ def _retrieve_detail(details, requested_type): def wise_retrieve_accounts(profile_id): - for account in wise.accounts.list(profile_id=profile_id): + for account in wise.account_details.list(profile_id=profile_id): account_holder = bank_name = bank_address = sort_code = account_number = swift = iban = None if account.currency.code == "GBP": for receive_options in account.receiveOptions: - + details = receive_options.details account_holder = _retrieve_detail(details, "ACCOUNT_HOLDER") bank_info = _retrieve_detail(details, "BANK_NAME_AND_ADDRESS") @@ -230,13 +230,19 @@ def wise_retrieve_accounts(profile_id): swift = _retrieve_detail(details, "SWIFT_CODE") iban = _retrieve_detail(details, "IBAN") - if not bank_info: - continue + if not bank_info: + continue + + bank_name, _, bank_address = bank_info.partition("\n") - bank_name, _, bank_address = bank_info.partition("\n") + if not bank_name or not bank_address: + continue - if not bank_name or not bank_address: - continue + wise_balance_id = account.id + + # Workaround: the Wise Sandbox API returns a null/empty account ID; populate a value + if account.id is None and app.config.get("TRANSFERWISE_ENVIRONMENT") == "sandbox": + wise_balance_id = 0 yield BankAccount( sort_code=sort_code, @@ -248,8 +254,7 @@ def wise_retrieve_accounts(profile_id): address=bank_address, swift=swift, iban=iban, - # Webhooks only include the borderlessAccountId - wise_balance_id=account.id, + wise_balance_id=wise_balance_id, ) From eb155bd1d1d5b6b7b9b18387a2c9b1194d7bc9e8 Mon Sep 17 00:00:00 2001 From: James Addison Date: Sat, 30 Aug 2025 11:28:46 +0100 Subject: [PATCH 03/26] payments: raise exception when requested detail attribute is not found --- apps/payments/wise.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index eba0413d9..5521f583a 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -208,15 +208,14 @@ def _retrieve_detail(details, requested_type): for detail in details: if detail.type == requested_type: return detail.body + raise AttributeError(f"Failed to find requested {requested_type} attribute in account details") def wise_retrieve_accounts(profile_id): for account in wise.account_details.list(profile_id=profile_id): - account_holder = bank_name = bank_address = sort_code = account_number = swift = iban = None if account.currency.code == "GBP": - for receive_options in account.receiveOptions: details = receive_options.details account_holder = _retrieve_detail(details, "ACCOUNT_HOLDER") From b211ab5cf901d440fc826afa97fb027c8b786236 Mon Sep 17 00:00:00 2001 From: James Addison Date: Mon, 8 Sep 2025 23:28:18 +0100 Subject: [PATCH 04/26] tests: payments: add Wise Sandbox `account-details` VCR recording NB: Redacts a few Cloudflare/Envoy, cookie, etc-related response headers --- apps/payments/wise.py | 6 +- .../test_wise_account_retrieval.yaml | 428 ++++++++++++++++++ tests/test_payment.py | 15 + 3 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 tests/cassettes/test_wise_account_retrieval.yaml diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 5521f583a..ad9420686 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -6,7 +6,7 @@ from pywisetransfer.exceptions import InvalidWebhookSignature from pywisetransfer.webhooks import validate_request -from main import db, wise +from main import db from models import naive_utcnow from models.payment import BankAccount, BankTransaction @@ -115,6 +115,7 @@ def wise_balance_credit(event_type, event): def sync_wise_statement(profile_id, wise_balance_id, currency): + from main import wise # Retrieve an account transaction statement for the past week interval_end = naive_utcnow() interval_start = interval_end - timedelta(days=7) @@ -184,6 +185,7 @@ def sync_wise_statement(profile_id, wise_balance_id, currency): def wise_business_profile(): + from main import wise if app.config.get("TRANSFERWISE_PROFILE_ID"): id = int(app.config["TRANSFERWISE_PROFILE_ID"]) accounts = list(wise.account_details.list(profile_id=id)) @@ -212,6 +214,7 @@ def _retrieve_detail(details, requested_type): def wise_retrieve_accounts(profile_id): + from main import wise for account in wise.account_details.list(profile_id=profile_id): account_holder = bank_name = bank_address = sort_code = account_number = swift = iban = None @@ -258,6 +261,7 @@ def wise_retrieve_accounts(profile_id): def wise_validate(): + from main import wise """Validate that Wise is configured and operational""" result = [] diff --git a/tests/cassettes/test_wise_account_retrieval.yaml b/tests/cassettes/test_wise_account_retrieval.yaml new file mode 100644 index 000000000..a0038a3a5 --- /dev/null +++ b/tests/cassettes/test_wise_account_retrieval.yaml @@ -0,0 +1,428 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate, br + Authorization: + - DUMMY + Connection: + - keep-alive + User-Agent: + - python-requests/2.32.5 + method: GET + uri: https://api.sandbox.transferwise.tech/v1/profiles/123456789/account-details + response: + body: + string: "[{\"id\":null,\"currency\":{\"code\":\"EUR\",\"name\":\"Euro\"},\"balanceId\":-1,\"title\":\"Your + EUR account details\",\"subtitle\":\"IBAN and Swift/BIC\",\"status\":\"AVAILABLE\",\"deprecated\":false,\"migrated\":false,\"receiveOptions\":[{\"type\":\"LOCAL\",\"title\":\"Local\",\"description\":{\"title\":\"Your + EUR account details\",\"body\":\"Receive from a bank in the EurozoneSee how to use EUR account details\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 2 working days to arrive\",\"description\":null},{\"type\":\"INFO\",\"title\":\"SEPA + includes countries in the EU and EEA\",\"description\":{\"title\":\"What\u2019s + SEPA?\",\"body\":\"

SEPA stands for the Single Euro Payments Area. Within + this area, cross-border euro payments must cost the same as a regular, local + one. It\u2019s EU law.

SEPA covers all the countries within the EU. + It also includes other European countries like Monaco, Norway, and Switzerland.

So + if your sender is sending from a euro bank account in one of these countries, + they shouldn\u2019t have to pay much \u2014 if anything \u2014 to send money + to your EUR account details.

Good to know
  • SEPA payments + to your EUR account details usually take 1\u20132 working days.
  • Just + because a country uses euros, doesn\u2019t mean it\u2019s in SEPA.\_Please + check the list below if you\u2019re unsure.

See the full list of SEPA countries

\",\"cta\":null}},{\"type\":\"SAFETY\",\"title\":\"Learn + how Wise keeps your money safe\",\"description\":{\"title\":\"How Wise keeps + your money safe\",\"body\":\"
  • We follow strict regulations everywhere + we work
  • We safeguard 100% of your cash
  • We work round the clock + to keep your money protected

Your Wise account, and the money + in it, are managed and protected by Wise in your region.

Learn more

\",\"cta\":null}}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"SWIFT_CODE\",\"title\":\"BIC\",\"body\":\"TRWIBxxxxxx\",\"description\":{\"title\":\"What\u2019s + BIC?\",\"body\":\"

A BIC code identifies banks and financial institutions + globally. It says who and where they are \u2014 a sort of international bank + code or ID.

\",\"cta\":null},\"hidden\":false},{\"type\":\"IBAN\",\"title\":\"IBAN\",\"body\":\"BE29 + 9670 xxxx xxxx\",\"description\":{\"title\":\"What is an IBAN?\",\"body\":\"

IBAN + stands for International Bank Account Number. Your EUR account details include + a Belgian IBAN, which begins with BE.

You only have one Wise account, + no matter how many account details you have. Your Wise account is managed + and protected by Wise in your region. Learn more.

Do you + need an account number?

If your sender specifically asks for an + account number, use: 0000001

\",\"cta\":{\"label\":\"IBAN\",\"content\":\"BE29 + 9670 xxxx xxxx\"}},\"hidden\":false},{\"type\":\"BANK_NAME_AND_ADDRESS\",\"title\":\"Bank + name and address\",\"body\":\"TransferWise Europe SA\\nSquare de Mee\xFBs + 38 bte 40\\nBrussels\\n1000\\nBelgium\",\"description\":{\"title\":\"Did they + ask for a bank address?\",\"body\":\"

Although Wise isn\u2019t a bank, you + can use these details if your sender needs a bank name and address and we\u2019ll + process the transfer.

\",\"cta\":null},\"hidden\":false}],\"alert\":null,\"shareText\":null},{\"type\":\"INTERNATIONAL\",\"title\":\"Global + \xB7 Swift\",\"description\":{\"title\":\"Your EUR account details\",\"body\":\"Receive + from a bank outside the EurozoneSee how to use EUR account details\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 5 working days to arrive\",\"description\":null},{\"type\":\"SAFETY\",\"title\":\"Learn + how Wise keeps your money safe\",\"description\":{\"title\":\"How Wise keeps + your money safe\",\"body\":\"
  • We follow strict regulations everywhere + we work
  • We safeguard 100% of your cash
  • We work round the clock + to keep your money protected

Your Wise account, and the money + in it, are managed and protected by Wise in your region.

Learn more

\",\"cta\":null}}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"SWIFT_CODE\",\"title\":\"Swift/BIC\",\"body\":\"TRWIBxxxxxx\",\"description\":{\"title\":\"What\u2019s + Swift/BIC?\",\"body\":\"

A Swift/BIC code identifies banks and financial + institutions globally. It says who and where they are \u2014 a sort of international + bank code or ID.

\",\"cta\":null},\"hidden\":false},{\"type\":\"IBAN\",\"title\":\"IBAN\",\"body\":\"BE29 + 9670 xxxx xxxx\",\"description\":{\"title\":\"What is an IBAN?\",\"body\":\"

IBAN + stands for International Bank Account Number. Your EUR account details include + a Belgian IBAN, which begins with BE.

You only have one Wise account, + no matter how many account details you have. Your Wise account is managed + and protected by Wise in your region. Learn more.

Do you + need an account number?

If your sender specifically asks for an + account number, use: 0000001

\",\"cta\":{\"label\":\"IBAN\",\"content\":\"BE29 + 9670 xxxx xxxx\"}},\"hidden\":false},{\"type\":\"BANK_NAME_AND_ADDRESS\",\"title\":\"Bank + name and address\",\"body\":\"TransferWise Europe SA\\nSquare de Mee\xFBs + 38 bte 40\\nBrussels\\n1000\\nBelgium\",\"description\":{\"title\":\"Did they + ask for a bank address?\",\"body\":\"

Although Wise isn\u2019t a bank, you + can use these details if your sender needs a bank name and address and we\u2019ll + process the transfer.

\",\"cta\":null},\"hidden\":false}],\"alert\":null,\"shareText\":null}],\"bankFeatures\":[{\"key\":\"LOCAL_RECEIVE\",\"title\":\"Receive + locally\",\"supported\":true},{\"key\":\"SWIFT\",\"title\":\"Receive internationally + (Swift)\",\"supported\":true},{\"key\":\"DIRECT_DEBITS\",\"title\":\"Set up + Direct Debits\",\"supported\":true},{\"key\":\"PLATFORM_RECEIVE\",\"title\":\"Receive + from PayPal and Stripe\",\"supported\":true}]},{\"id\":null,\"currency\":{\"code\":\"GBP\",\"name\":\"British + pound (accepts US dollar)\"},\"balanceId\":-1,\"title\":\"Your GBP account + details\",\"subtitle\":\"UK sort code, account number and IBAN to receive + US dollars + 22 other currencies\",\"status\":\"AVAILABLE\",\"deprecated\":false,\"migrated\":false,\"receiveOptions\":[{\"type\":\"LOCAL\",\"title\":\"Local\",\"description\":{\"title\":\"Your + GBP account details\",\"body\":\"Receive from a bank in the UKSee how to use GBP account details\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 1 working day to arrive\",\"description\":{\"title\":\"How long + does it take to receive GBP?\",\"body\":\"

Most GBP payments arrive in a + few minutes. But sometimes, it can take longer \u2014 it depends on the kind + of payment.

Waiting for a payment?

Ask your sender what kind + of payment they sent you. The payment confirmation from their bank should + show this.

Then, check the table below.

Here\u2019s how long different + payments take in the UK:

  • Faster payments \u2013 + less than 2 hours.
  • Bacs \u2013 2\u20133 working days.
  • CHAPS + \u2013 1 working day.

If you\u2019re waiting for longer than this, + we can help. Just get in touch with a copy of the payment confirmation.

\",\"cta\":null}}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"BANK_CODE\",\"title\":\"Sort + code\",\"body\":\"23-1x-xx\",\"description\":null,\"hidden\":false},{\"type\":\"ACCOUNT_NUMBER\",\"title\":\"Account + number\",\"body\":\"1000xxxx\",\"description\":null,\"hidden\":false},{\"type\":\"IBAN\",\"title\":\"IBAN\",\"body\":\"GB77 + TRWI 2314 7010 0000 00\",\"description\":null,\"hidden\":false},{\"type\":\"BANK_NAME_AND_ADDRESS\",\"title\":\"Bank + name and address\",\"body\":\"TransferWise\\n56 Shoreditch High Street\\nLondon\\nE1 + 6JJ\\nUnited Kingdom\",\"description\":{\"title\":\"Did they ask for a bank + address?\",\"body\":\"

Although Wise isn\u2019t a bank, you can use these + details if your sender needs a bank name and address and we\u2019ll process + the transfer.

Our address changed in March 2025. Payments made using + the previous address will still arrive.

\",\"cta\":null},\"hidden\":false}],\"alert\":null,\"shareText\":null},{\"type\":\"INTERNATIONAL\",\"title\":\"Global + \xB7 Swift\",\"description\":{\"title\":\"Your GBP account details\",\"body\":\"Receive + from a bank outside the UKSee how to use GBP account details\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 5 working days to arrive\",\"description\":null},{\"type\":\"INFO\",\"title\":\"Receive + payments in 22 other currencies with these details. Fees apply\",\"description\":{\"title\":\"Receive + payments in 22 other currencies\",\"body\":\"

Share these account details + to receive payments in:

  • AED, AUD, BGN, CAD, CHF, CNY, CZK, DKK, + EUR, HKD, HUF, ILS, JPY, NOK, NZD, PLN, RON, SEK, SGD, UGX, USD and ZAR

You + can view the latest fees for receiving Swift payments on our pricing page.

Ask your + sender to check about Swift fees

Your sender\u2019s bank may need to + use correspondent banks within + the Swift network to complete the payment.

Correspondent banks can charge + fees that are outside of our control and are deducted before the money reaches + us. These fees are usually between 15 and 50 USD but can be higher. This means + the amount that you receive can be less than was sent.

It\u2019s best + to ask your sender to check with their bank if there are any fees before they + make the payment.

\",\"cta\":null}}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"SWIFT_CODE\",\"title\":\"Swift/BIC\",\"body\":\"TRWIGB2L\",\"description\":{\"title\":\"What\u2019s + Swift/BIC?\",\"body\":\"

A Swift/BIC code identifies banks and financial + institutions globally. It says who and where they are \u2014 a sort of international + bank code or ID.

\",\"cta\":null},\"hidden\":false},{\"type\":\"IBAN\",\"title\":\"IBAN\",\"body\":\"GB77 + TRWI 2314 7010 0000 00\",\"description\":null,\"hidden\":false},{\"type\":\"BANK_NAME_AND_ADDRESS\",\"title\":\"Bank + name and address\",\"body\":\"TransferWise\\n56 Shoreditch High Street\\nLondon\\nE1 + 6JJ\\nUnited Kingdom\",\"description\":{\"title\":\"Did they ask for a bank + address?\",\"body\":\"

Although Wise isn\u2019t a bank, you can use these + details if your sender needs a bank name and address and we\u2019ll process + the transfer.

Our address changed in March 2025. Payments made using + the previous address will still arrive.

\",\"cta\":null},\"hidden\":false}],\"alert\":null,\"shareText\":null}],\"bankFeatures\":[{\"key\":\"LOCAL_RECEIVE\",\"title\":\"Receive + locally\",\"supported\":true},{\"key\":\"SWIFT\",\"title\":\"Receive internationally + (Swift)\",\"supported\":true},{\"key\":\"DIRECT_DEBITS\",\"title\":\"Set up + Direct Debits\",\"supported\":true},{\"key\":\"PLATFORM_RECEIVE\",\"title\":\"Receive + from PayPal and Stripe\",\"supported\":true}]},{\"id\":null,\"currency\":{\"code\":\"USD\",\"name\":\"US + dollar\"},\"balanceId\":-1,\"title\":\"Your USD account details\",\"subtitle\":\"Routing + number (ACH or ABA), account number and Swift/BIC\",\"status\":\"AVAILABLE\",\"deprecated\":false,\"migrated\":false,\"receiveOptions\":[{\"type\":\"LOCAL\",\"title\":\"Local\",\"description\":{\"title\":\"Your + USD account details\",\"body\":\"Receive from a bank in the USreceive-option.usd.description.body.links\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 3 working days to arrive\",\"description\":null}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"ROUTING_NUMBER\",\"title\":\"Wire + routing number\",\"body\":\"026073008\",\"description\":null,\"hidden\":false},{\"type\":\"ACCOUNT_NUMBER\",\"title\":\"Account + number\",\"body\":\"8xx00xxxxx\",\"description\":null,\"hidden\":false},{\"type\":\"ACCOUNT_TYPE\",\"title\":\"Account + type\",\"body\":\"Checking\",\"description\":null,\"hidden\":false},{\"type\":\"BANK_NAME_AND_ADDRESS\",\"title\":\"Bank + name and address\",\"body\":\"TransferWise\\n19 W 24th Street\\nNew York NY + 10010\\nUnited States\",\"description\":{\"title\":\"Did they ask for a bank + address?\",\"body\":\"

Community Federal Savings Bank is our partner bank + in the US. If your sender asks for a bank name and address, give them these + details and we\u2019ll process the transfer.

Keep in mind that if you + have questions or need help, you\u2019ll need to contact us and not our partner + bank. Get help

\",\"cta\":null},\"hidden\":false}],\"alert\":null,\"shareText\":null},{\"type\":\"INTERNATIONAL\",\"title\":\"Global + \xB7 Swift\",\"description\":{\"title\":\"Your USD account details\",\"body\":\"Receive + from a bank outside the USreceive-option.usd.description.body.links\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 5 working days to arrive\",\"description\":null}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"ROUTING_NUMBER\",\"title\":\"Routing + number\",\"body\":\"026073008\",\"description\":{\"title\":\"Did they ask + for a routing number?\",\"body\":\"

A routing number is not required to + add or receive USD from bank accounts outside the US. But if they specifically + ask for a routing number, you can give them this:

026073008

\",\"cta\":{\"label\":\"Routing + number\",\"content\":\"026073008\"}},\"hidden\":false},{\"type\":\"SWIFT_CODE\",\"title\":\"Swift/BIC\",\"body\":\"CMFGxxxx\",\"description\":{\"title\":\"Swift/BIC\",\"body\":\"

We\u2019ll + return payments from countries on this list to the sender. This can take 3\u201310 + working days.

Africa

Burundi, Central African Republic, Chad, + Democratic Republic of the Congo, Eritrea, Guinea-Bissau, Libya, Somalia, + South Sudan, Sudan.

Americas

Cuba, Venezuela.

Asia

Democratic + People\u2019s Republic of Korea (North Korea).

Europe

Belarus, + Crimea, Russian Federation, Serbia.

Middle East

Afghanistan, + Iran, Iraq, Syria, Yemen.

\",\"cta\":{\"label\":\"Swift/BIC\",\"content\":\"CMFGxxxx\"}},\"hidden\":true},{\"type\":\"ACCOUNT_NUMBER\",\"title\":\"Account + number\",\"body\":\"8xx00xxxxx\",\"description\":null,\"hidden\":false},{\"type\":\"BANK_NAME_AND_ADDRESS\",\"title\":\"Bank + name and address\",\"body\":\"TransferWise\\n19 W 24th Street\\nNew York NY + 10010\\nUnited States\",\"description\":{\"title\":\"Did they ask for a bank + address?\",\"body\":\"

Community Federal Savings Bank is our partner bank + in the US. If your sender asks for a bank name and address, give them these + details and we\u2019ll process the transfer.

Keep in mind that if you + have questions or need help, you\u2019ll need to contact us and not our partner + bank. Get help

\",\"cta\":null},\"hidden\":false}],\"alert\":null,\"shareText\":null}],\"bankFeatures\":[{\"key\":\"LOCAL_RECEIVE\",\"title\":\"Receive + locally\",\"supported\":true},{\"key\":\"SWIFT\",\"title\":\"Receive internationally + (Swift)\",\"supported\":true},{\"key\":\"DIRECT_DEBITS\",\"title\":\"Set up + Direct Debits\",\"supported\":true},{\"key\":\"PLATFORM_RECEIVE\",\"title\":\"Receive + from PayPal and Stripe\",\"supported\":true}]},{\"id\":null,\"currency\":{\"code\":\"AUD\",\"name\":\"Australian + dollar\"},\"balanceId\":-1,\"title\":\"Your AUD account details\",\"subtitle\":\"BSB + code, account number and Swift/BIC\",\"status\":\"AVAILABLE\",\"deprecated\":false,\"migrated\":false,\"receiveOptions\":[{\"type\":\"LOCAL\",\"title\":\"Local\",\"description\":{\"title\":\"Your + AUD account details\",\"body\":\"Receive from a bank in AustraliaSee how to use AUD account details\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 1 working day to arrive\",\"description\":{\"title\":\"How long + does it take to receive AUD?\",\"body\":\"

Most AUD payments arrive in a + few minutes. But sometimes, it can take longer \u2014 it depends on the kind + of payment.

Waiting for a payment?

Ask your sender what kind + of payment they sent you. The payment confirmation from their bank should + show this.

Then, check the table below.

Here\u2019s how long different + payments take in Australia:

  • Instant-BSB, POLI, and Pay-ID + payments \u2013 almost instantly.
  • Traditional bank + transfers (BECS) \u2013 1 working day.

If you\u2019re + waiting for longer than this, we can help. Just get in touch with a copy of + the payment confirmation.\",\"cta\":null}}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"BANK_CODE\",\"title\":\"BSB + code\",\"body\":\"774xxx\",\"description\":null,\"hidden\":false},{\"type\":\"ACCOUNT_NUMBER\",\"title\":\"Account + number\",\"body\":\"6129xxxxx\",\"description\":null,\"hidden\":false}],\"alert\":null,\"shareText\":null},{\"type\":\"INTERNATIONAL\",\"title\":\"Global + \xB7 Swift\",\"description\":{\"title\":\"Your AUD account details\",\"body\":\"Sorry, + you can\u2019t get account details to receive international AUD payments yet.\",\"cta\":null},\"summaries\":[],\"details\":[],\"alert\":null,\"shareText\":null}],\"bankFeatures\":[{\"key\":\"LOCAL_RECEIVE\",\"title\":\"Receive + locally\",\"supported\":false},{\"key\":\"SWIFT\",\"title\":\"Receive internationally + (Swift)\",\"supported\":true},{\"key\":\"DIRECT_DEBITS\",\"title\":\"Set up + Direct Debits\",\"supported\":false},{\"key\":\"PLATFORM_RECEIVE\",\"title\":\"Receive + from PayPal and Stripe\",\"supported\":false}]},{\"id\":null,\"currency\":{\"code\":\"NZD\",\"name\":\"New + Zealand dollar\"},\"balanceId\":-1,\"title\":\"Your NZD account details\",\"subtitle\":\"Account + number and Swift/BIC\",\"status\":\"AVAILABLE\",\"deprecated\":false,\"migrated\":false,\"receiveOptions\":[{\"type\":\"LOCAL\",\"title\":\"Local\",\"description\":{\"title\":\"Your + NZD account details\",\"body\":\"Receive from a bank in New ZealandSee how to use NZD account details\",\"cta\":null},\"summaries\":[{\"type\":\"INFO\",\"title\":\"Direct + Debits aren\u2019t supported\",\"description\":{\"title\":\"We don\u2019t + support NZD Direct Debits yet\",\"body\":\"

We hope to change this soon + \u2014 and we\u2019ll let you know when we do.

Until then, please don\u2019t + try to set up any Direct Debits with your NZD account details. The payment + will be rejected and the merchant may charge you a fee.

\",\"cta\":null}},{\"type\":\"TIME\",\"title\":\"Payments + take up to 2 working days to arrive\",\"description\":null}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"ACCOUNT_NUMBER\",\"title\":\"Account + number\",\"body\":\"02-1290-0xxxxxx-xxx\",\"description\":null,\"hidden\":false}],\"alert\":null,\"shareText\":null},{\"type\":\"INTERNATIONAL\",\"title\":\"Global + \xB7 Swift\",\"description\":{\"title\":\"Your NZD account details\",\"body\":\"Sorry, + you can\u2019t get account details to receive international NZD payments yet.\",\"cta\":null},\"summaries\":[],\"details\":[],\"alert\":null,\"shareText\":null}],\"bankFeatures\":[{\"key\":\"LOCAL_RECEIVE\",\"title\":\"Receive + locally\",\"supported\":true},{\"key\":\"SWIFT\",\"title\":\"Receive internationally + (Swift)\",\"supported\":false},{\"key\":\"DIRECT_DEBITS\",\"title\":\"Set + up Direct Debits\",\"supported\":false},{\"key\":\"PLATFORM_RECEIVE\",\"title\":\"Receive + from PayPal and Stripe\",\"supported\":false}]},{\"id\":null,\"currency\":{\"code\":\"CAD\",\"name\":\"Canadian + dollar\"},\"balanceId\":-1,\"title\":\"Your CAD account details\",\"subtitle\":\"Institution + number, transit number, account number and Swift/BIC\",\"status\":\"AVAILABLE\",\"deprecated\":false,\"migrated\":false,\"receiveOptions\":[{\"type\":\"LOCAL\",\"title\":\"Local\",\"description\":{\"title\":\"Your + CAD account details\",\"body\":\"Receive from a bank in CanadaSee how to use CAD account details\",\"cta\":null},\"summaries\":[{\"type\":\"INFO\",\"title\":\"Domestic + wire transfers and Online Bill Transfers (OBTs) are not yet supported\",\"description\":{\"title\":\"We + support EFTs, Direct Deposits, Interac and Swift\",\"body\":\"

You can always + receive CAD into your local details via EFTs (electronic fund transfers) + and Direct Deposits, or to your global details via Swift. + Learn more here.

You + can also receive CAD to your email via Interac. Learn more here.

We don\u2019t + yet support the following payment methods:

  • Domestic wire transfers
  • Online + Bill Transfers (OBT)

If a domestic wire is sent to this currency, + it will be returned to the originating bank with $25 CAD deducted to cover + return processing fees.

\",\"cta\":null}},{\"type\":\"TIME\",\"title\":\"Payments + take up to 2 working days to arrive\",\"description\":null}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"ROUTING_NUMBER\",\"title\":\"Institution + number\",\"body\":\"6xx\",\"description\":null,\"hidden\":false},{\"type\":\"ACCOUNT_NUMBER\",\"title\":\"Account + number\",\"body\":\"200110xxxxxx\",\"description\":null,\"hidden\":false},{\"type\":\"BANK_NAME_AND_ADDRESS\",\"title\":\"Bank + name and address\",\"body\":\"Peoples Trust\\n595 Burrard Street\\nVancouver + BC V7X 1L7\\nCanada\",\"description\":{\"title\":\"Did they ask for a bank + address?\",\"body\":\"

Peoples Trust is our partner bank in Canada. If your + sender asks for a bank name and address, give them these details and we\u2019ll + process the transfer.

Keep in mind that if you have questions or need + help, you\u2019ll need to contact us and not our partner bank. Get help

\",\"cta\":null},\"hidden\":false}],\"alert\":null,\"shareText\":null},{\"type\":\"INTERNATIONAL\",\"title\":\"Global + \xB7 Swift\",\"description\":{\"title\":\"Your CAD account details\",\"body\":\"Sorry, + your Wise CAD account details do not yet support receiving CAD from banks + via Swift transfers. If a Swift transfer is sent to this account, it will + be returned to the originating bank with $50 CAD deducted to cover return + processing fees.\",\"cta\":null},\"summaries\":[],\"details\":[],\"alert\":null,\"shareText\":null}],\"bankFeatures\":[{\"key\":\"LOCAL_RECEIVE\",\"title\":\"Receive + locally\",\"supported\":true},{\"key\":\"SWIFT\",\"title\":\"Receive internationally + (Swift)\",\"supported\":false},{\"key\":\"DIRECT_DEBITS\",\"title\":\"Set + up Direct Debits\",\"supported\":true},{\"key\":\"PLATFORM_RECEIVE\",\"title\":\"Receive + from PayPal and Stripe\",\"supported\":false}]},{\"id\":null,\"currency\":{\"code\":\"HUF\",\"name\":\"Hungarian + forint\"},\"balanceId\":-1,\"title\":\"Your HUF account details\",\"subtitle\":\"Bank + code, account number, IBAN and Swift/BIC\",\"status\":\"AVAILABLE\",\"deprecated\":false,\"migrated\":false,\"receiveOptions\":[{\"type\":\"LOCAL\",\"title\":\"Local\",\"description\":{\"title\":\"Your + HUF account details\",\"body\":\"Receive from a bank in HungarySee how to use HUF account details\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 1 working day to arrive\",\"description\":null}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"ACCOUNT_NUMBER\",\"title\":\"Account + number\",\"body\":\"12600016-0000xxxx-xxxxxxxx\",\"description\":{\"title\":\"Did + they ask for an IBAN?\",\"body\":\"

For most payments in Hungary, you can + use your account number. But if your sender specifically asked for an IBAN, + you can give them this:

HU68 1260 0016 0000 0000 0000 0000

Just + remember that this IBAN only works for HUF payments. So we recommend asking + the sender which currency they\u2019re sending you first.

\",\"cta\":{\"label\":\"IBAN\",\"content\":\"HU68 + 1260 0016 0000 0000 0000 0000\"}},\"hidden\":false},{\"type\":\"BANK_NAME_AND_ADDRESS\",\"title\":\"Bank + name and address\",\"body\":\"TransferWise Europe SA\\nSquare de Mee\xFBs + 38 bte 40\\nBrussels\\n1000\\nBelgium\",\"description\":{\"title\":\"Did they + ask for a bank address?\",\"body\":\"

Although Wise isn\u2019t a bank, you + can use these details if your sender needs a bank name and address and we\u2019ll + process the transfer.

\",\"cta\":null},\"hidden\":false}],\"alert\":null,\"shareText\":null},{\"type\":\"INTERNATIONAL\",\"title\":\"Global + \xB7 Swift\",\"description\":{\"title\":\"Your HUF account details\",\"body\":\"Receive + from a bank outside HungarySee how to use HUF account details\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 5 working days to arrive\",\"description\":null},{\"type\":\"FEE\",\"title\":\"Fees + can apply to receive Swift payments\",\"description\":{\"title\":\"What are + the fees to receive HUF?\",\"body\":\"

Your sender\u2019s bank may need + to use correspondent banks within + the Swift network to complete the payment.

Correspondent banks can charge + fees that are outside of our control and are deducted before the money reaches + us. These fees are usually between 15 and 50 USD but can be higher. This means + the amount that you receive can be less than was sent.

It\u2019s best + to ask your sender to check with their bank if there are any fees before they + make the payment.

\",\"cta\":null}}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"SWIFT_CODE\",\"title\":\"Swift/BIC\",\"body\":\"TRWIBxxxxxx\",\"description\":{\"title\":\"What\u2019s + Swift/BIC?\",\"body\":\"

A Swift/BIC code identifies banks and financial + institutions globally. It says who and where they are \u2014 a sort of international + bank code or ID.

\",\"cta\":{\"label\":\"Swift/BIC\",\"content\":\"TRWIBxxxxxx\"}},\"hidden\":false},{\"type\":\"IBAN\",\"title\":\"IBAN\",\"body\":\"HU68 + 1260 0016 0000 0000 0000 0000\",\"description\":{\"title\":\"How to use your + IBAN\",\"body\":\"

Your IBAN is a code that helps banks identify your account + for international payments. You can use your Hungarian IBAN to receive HUF + payments.

If you have problems using this IBAN

Some banks may + not be able to process payments using your Hungarian IBAN. If your sender + has problems sending to this IBAN, you can receive HUF using GBP account details + instead, and the payment will arrive as HUF into your account.

Learn more about using GBP account + details

\",\"cta\":null},\"hidden\":false},{\"type\":\"BANK_NAME_AND_ADDRESS\",\"title\":\"Bank + name and address\",\"body\":\"TransferWise Europe SA\\nSquare de Mee\xFBs + 38 bte 40\\nBrussels\\n1000\\nBelgium\",\"description\":{\"title\":\"Did they + ask for a bank address?\",\"body\":\"

Although Wise isn\u2019t a bank, you + can use these details if your sender needs a bank name and address and we\u2019ll + process the transfer.

\",\"cta\":null},\"hidden\":false}],\"alert\":null,\"shareText\":null}],\"bankFeatures\":[{\"key\":\"LOCAL_RECEIVE\",\"title\":\"Receive + locally\",\"supported\":true},{\"key\":\"SWIFT\",\"title\":\"Receive internationally + (Swift)\",\"supported\":false},{\"key\":\"DIRECT_DEBITS\",\"title\":\"Set + up Direct Debits\",\"supported\":false},{\"key\":\"PLATFORM_RECEIVE\",\"title\":\"Receive + from PayPal and Stripe\",\"supported\":false}]},{\"id\":null,\"currency\":{\"code\":\"MYR\",\"name\":\"Malaysian + ringgit\"},\"balanceId\":-1,\"title\":\"Your MYR account details\",\"subtitle\":\"Account + number\",\"status\":\"AVAILABLE\",\"deprecated\":false,\"migrated\":false,\"receiveOptions\":[{\"type\":\"LOCAL\",\"title\":\"Local\",\"description\":{\"title\":\"Your + MYR account details\",\"body\":\"Receive from a bank in MalaysiaSee how to use MYR account details\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 1 working day to arrive\",\"description\":null}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"PARTNER_BANK_NAME\",\"title\":\"Partner + bank name\",\"body\":\"JPMorgan Chase Bank Berhad\",\"description\":null,\"hidden\":false},{\"type\":\"ACCOUNT_NUMBER\",\"title\":\"Account + number\",\"body\":\"3120000xxxxxxxx\",\"description\":null,\"hidden\":false}],\"alert\":null,\"shareText\":null},{\"type\":\"INTERNATIONAL\",\"title\":\"Global + \xB7 Swift\",\"description\":{\"title\":\"Your MYR account details\",\"body\":\"Sorry, + you can\u2019t get account details to receive international MYR payments yet.\",\"cta\":null},\"summaries\":[],\"details\":[],\"alert\":null,\"shareText\":null}],\"bankFeatures\":[{\"key\":\"LOCAL_RECEIVE\",\"title\":\"Receive + locally\",\"supported\":false},{\"key\":\"SWIFT\",\"title\":\"Receive internationally + (Swift)\",\"supported\":false},{\"key\":\"DIRECT_DEBITS\",\"title\":\"Set + up Direct Debits\",\"supported\":false},{\"key\":\"PLATFORM_RECEIVE\",\"title\":\"Receive + from PayPal and Stripe\",\"supported\":false}]},{\"id\":null,\"currency\":{\"code\":\"RON\",\"name\":\"Romanian + leu\"},\"balanceId\":-1,\"title\":\"Your RON account details\",\"subtitle\":\"Bank + code, account number and IBAN\",\"status\":\"AVAILABLE\",\"deprecated\":false,\"migrated\":false,\"receiveOptions\":[{\"type\":\"LOCAL\",\"title\":\"Local\",\"description\":{\"title\":\"Your + RON account details\",\"body\":\"Receive from a bank in RomaniaSee how to use RON account details\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 1 working day to arrive\",\"description\":null}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"SWIFT_CODE\",\"title\":\"Bank + code\",\"body\":\"BRELRxxxxxx\",\"description\":null,\"hidden\":false},{\"type\":\"ACCOUNT_NUMBER\",\"title\":\"Account + number\",\"body\":\"RO83 BREL 0005 xxxx xxxx xxxx\",\"description\":null,\"hidden\":false},{\"type\":\"BANK_NAME_AND_ADDRESS\",\"title\":\"Bank + name and address\",\"body\":\"Libra Internet Bank\\nCalea Vitan Nr. 6-6A\\nBucuresti\\n031296\\nRomania\",\"description\":{\"title\":\"Did + they ask for a bank address?\",\"body\":\"

Libra Internet Bank is our partner + bank in Romania. If your sender asks for a bank name and address, give them + these details and we\u2019ll process the transfer.

Keep in mind that + if you have questions or need help, you\u2019ll need to contact us and not + our partner bank. Get help

\",\"cta\":null},\"hidden\":false}],\"alert\":null,\"shareText\":null},{\"type\":\"INTERNATIONAL\",\"title\":\"Global + \xB7 Swift\",\"description\":{\"title\":\"Your Currency(code=RON, name=Romanian + leu) account details\",\"body\":\"Use your Global Swift GBP details to receive + RON from outside Romania.\",\"cta\":null},\"summaries\":[],\"details\":[],\"alert\":null,\"shareText\":null}],\"bankFeatures\":[{\"key\":\"LOCAL_RECEIVE\",\"title\":\"Receive + locally\",\"supported\":true},{\"key\":\"SWIFT\",\"title\":\"Receive internationally + (Swift)\",\"supported\":false},{\"key\":\"DIRECT_DEBITS\",\"title\":\"Set + up Direct Debits\",\"supported\":false},{\"key\":\"PLATFORM_RECEIVE\",\"title\":\"Receive + from PayPal and Stripe\",\"supported\":false}]},{\"id\":null,\"currency\":{\"code\":\"SGD\",\"name\":\"Singapore + dollar\"},\"balanceId\":-1,\"title\":\"Your SGD account details\",\"subtitle\":\"Bank + code, account number and Swift/BIC\",\"status\":\"AVAILABLE\",\"deprecated\":false,\"migrated\":false,\"receiveOptions\":[{\"type\":\"LOCAL\",\"title\":\"Local\",\"description\":{\"title\":\"Your + SGD account details\",\"body\":\"Receive SGD payments instantly through the + FAST networkSee how to use SGD account details\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 1 working day to arrive\",\"description\":null},{\"type\":\"LIMIT\",\"title\":\"200,000 + SGD limit per payment\",\"description\":null}],\"details\":[{\"type\":\"SUPPORTED_PAYMENT_NETWORK\",\"title\":\"Payment + network\",\"body\":\"FAST\",\"description\":{\"title\":\"What is FAST?\",\"body\":\"

FAST + (Fast and Secure Transfer) is the most popular payment network in Singapore + that allows payments to arrive instantly, 24/7.

When to use your FAST + account details
  • When you want to receive payments instantly\u2014 + it\u2019s speedy and available 24/7
  • To receive payments under 200,000 + SGD

If you want to receive payments from platforms like Google + or Stripe, use your GIRO/MEPS account details instead.

Useful to know

You + can only receive SGD from people or business that have a SGD bank account + in Singapore.

\",\"cta\":null},\"hidden\":false},{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"BANK_NAME\",\"title\":\"Bank + name\",\"body\":\"DBS Bank Ltd\",\"description\":null,\"hidden\":false},{\"type\":\"BANK_CODE\",\"title\":\"Bank + code\",\"body\":\"71xx\",\"description\":null,\"hidden\":false},{\"type\":\"ACCOUNT_NUMBER\",\"title\":\"Account + number\",\"body\":\"885-074-xxx-xxx\",\"description\":null,\"hidden\":false},{\"type\":\"ADDRESS\",\"title\":\"Wise\u2019s + address\",\"body\":\"56 Shoreditch High Street\\nLondon E1 6JJ\",\"description\":{\"title\":\"Did + they ask for a bank address?\",\"body\":\"

Although Wise isn\u2019t a bank, + you can use these details if your sender needs a bank name and address and + we\u2019ll process the transfer.

\",\"cta\":null},\"hidden\":false}],\"alert\":null,\"shareText\":null},{\"type\":\"INTERNATIONAL\",\"title\":\"Global + \xB7 Swift\",\"description\":{\"title\":\"Your SGD account details\",\"body\":\"Sorry, + you can\u2019t get account details to receive international SGD payments yet.\",\"cta\":null},\"summaries\":[],\"details\":[],\"alert\":null,\"shareText\":null}],\"bankFeatures\":[{\"key\":\"LOCAL_RECEIVE\",\"title\":\"Receive + locally\",\"supported\":true},{\"key\":\"SWIFT\",\"title\":\"Receive internationally + (Swift)\",\"supported\":false},{\"key\":\"DIRECT_DEBITS\",\"title\":\"Set + up Direct Debits\",\"supported\":false},{\"key\":\"PLATFORM_RECEIVE\",\"title\":\"Receive + from PayPal and Stripe\",\"supported\":false}]},{\"id\":null,\"currency\":{\"code\":\"TRY\",\"name\":\"Turkish + lira\"},\"balanceId\":-1,\"title\":\"Your TRY account details\",\"subtitle\":\"IBAN\",\"status\":\"AVAILABLE\",\"deprecated\":false,\"migrated\":false,\"receiveOptions\":[{\"type\":\"LOCAL\",\"title\":\"Local\",\"description\":{\"title\":\"Your + TRY account details\",\"body\":\"Receive from a bank in TurkeySee how to use TRY account details\",\"cta\":null},\"summaries\":[{\"type\":\"TIME\",\"title\":\"Payments + take up to 1 working day to arrive\",\"description\":null}],\"details\":[{\"type\":\"ACCOUNT_HOLDER\",\"title\":\"Account + holder\",\"body\":\"Willis and Daughters 4056\",\"description\":null,\"hidden\":false},{\"type\":\"IBAN\",\"title\":\"IBAN\",\"body\":\"TR29 + 0010 3000 0xxx xxxx xxxx xx\",\"description\":null,\"hidden\":false}],\"alert\":null,\"shareText\":null},{\"type\":\"INTERNATIONAL\",\"title\":\"Global + \xB7 Swift\",\"description\":{\"title\":\"Your TRY account details\",\"body\":\"Sorry, + you can\u2019t get account details to receive international TRY payments yet.\",\"cta\":null},\"summaries\":[],\"details\":[],\"alert\":null,\"shareText\":null}],\"bankFeatures\":[{\"key\":\"LOCAL_RECEIVE\",\"title\":\"Receive + locally\",\"supported\":true},{\"key\":\"SWIFT\",\"title\":\"Receive internationally + (Swift)\",\"supported\":false},{\"key\":\"DIRECT_DEBITS\",\"title\":\"Set + up Direct Debits\",\"supported\":false},{\"key\":\"PLATFORM_RECEIVE\",\"title\":\"Receive + from PayPal and Stripe\",\"supported\":false}]}]" + headers: + Connection: + - keep-alive + Content-Type: + - application/json + Date: + - Mon, 08 Sep 2025 22:19:49 GMT + Transfer-Encoding: + - chunked + cache-control: + - no-cache, no-store, max-age=0, must-revalidate + content-encoding: + - gzip + expires: + - '0' + pragma: + - no-cache + vary: + - origin,access-control-request-method,access-control-request-headers,accept-encoding + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_payment.py b/tests/test_payment.py index c8f35591e..be2906a10 100644 --- a/tests/test_payment.py +++ b/tests/test_payment.py @@ -1,8 +1,23 @@ import pytest +from apps.payments.wise import wise_retrieve_accounts from models.payment import BankTransaction +@pytest.fixture(scope="module") +def vcr_config(): + return { + # Replace the Authorization request header with "DUMMY" in cassettes + "filter_headers": [("authorization", "DUMMY")], + } + + +@pytest.mark.vcr() +def test_wise_account_retrieval(app): + accounts = list(wise_retrieve_accounts(profile_id=123456789)) + assert len(accounts) == 1 # FIXME + + @pytest.mark.parametrize( "payee, bankref", [ From 7b7166e0d3a65408e7f293ca234ca752de661dfe Mon Sep 17 00:00:00 2001 From: James Addison Date: Mon, 8 Sep 2025 23:36:11 +0100 Subject: [PATCH 05/26] payments: apply `ruff format` --- apps/payments/wise.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index ad9420686..e135412ef 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -116,6 +116,7 @@ def wise_balance_credit(event_type, event): def sync_wise_statement(profile_id, wise_balance_id, currency): from main import wise + # Retrieve an account transaction statement for the past week interval_end = naive_utcnow() interval_start = interval_end - timedelta(days=7) @@ -186,6 +187,7 @@ def sync_wise_statement(profile_id, wise_balance_id, currency): def wise_business_profile(): from main import wise + if app.config.get("TRANSFERWISE_PROFILE_ID"): id = int(app.config["TRANSFERWISE_PROFILE_ID"]) accounts = list(wise.account_details.list(profile_id=id)) @@ -215,6 +217,7 @@ def _retrieve_detail(details, requested_type): def wise_retrieve_accounts(profile_id): from main import wise + for account in wise.account_details.list(profile_id=profile_id): account_holder = bank_name = bank_address = sort_code = account_number = swift = iban = None @@ -262,6 +265,7 @@ def wise_retrieve_accounts(profile_id): def wise_validate(): from main import wise + """Validate that Wise is configured and operational""" result = [] From 23e74910b22358a2dc03d402157c4459478a98d5 Mon Sep 17 00:00:00 2001 From: James Addison Date: Mon, 8 Sep 2025 23:43:45 +0100 Subject: [PATCH 06/26] payments: refactor preparation: add basic account-details test coverage --- tests/test_payment.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_payment.py b/tests/test_payment.py index be2906a10..69abbb0c2 100644 --- a/tests/test_payment.py +++ b/tests/test_payment.py @@ -17,6 +17,12 @@ def test_wise_account_retrieval(app): accounts = list(wise_retrieve_accounts(profile_id=123456789)) assert len(accounts) == 1 # FIXME + primary_account = accounts[0] + assert primary_account.currency == "GBP" + assert primary_account.institution == "TransferWise" + assert primary_account.sort_code.startswith("231") + assert primary_account.acct_id.startswith("1000") + @pytest.mark.parametrize( "payee, bankref", From ede71c614f4d4e3da4467e78e9a2cfbe7c2407d6 Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 00:05:39 +0100 Subject: [PATCH 07/26] payments: naive refactor for Wise account detail aggregation --- apps/payments/wise.py | 45 ++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index e135412ef..44aabcf20 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -215,6 +215,32 @@ def _retrieve_detail(details, requested_type): raise AttributeError(f"Failed to find requested {requested_type} attribute in account details") +def _aggregate_account_recipient_details(account): + account_holder = bank_name = bank_address = sort_code = account_number = swift = iban = None + for receive_options in account.receiveOptions: + details = receive_options.details + + account_holder = _retrieve_detail(details, "ACCOUNT_HOLDER") + bank_info = _retrieve_detail(details, "BANK_NAME_AND_ADDRESS") + + if receive_options.type == "LOCAL": + sort_code = _retrieve_detail(details, "BANK_CODE").replace("-", "") + account_number = _retrieve_detail(details, "ACCOUNT_NUMBER") + + elif receive_options.type == "INTERNATIONAL": + swift = _retrieve_detail(details, "SWIFT_CODE") + iban = _retrieve_detail(details, "IBAN") + + if not bank_info: + raise ValueError("Bank info field is empty") + + bank_name, _, bank_address = bank_info.partition("\n") + if not bank_name or not bank_address: + raise ValueError("Could not extract bank name and address from bank info") + + return account_holder, bank_name, bank_address, sort_code, account_number, swift, iban + + def wise_retrieve_accounts(profile_id): from main import wise @@ -222,24 +248,11 @@ def wise_retrieve_accounts(profile_id): account_holder = bank_name = bank_address = sort_code = account_number = swift = iban = None if account.currency.code == "GBP": - for receive_options in account.receiveOptions: - details = receive_options.details - account_holder = _retrieve_detail(details, "ACCOUNT_HOLDER") - bank_info = _retrieve_detail(details, "BANK_NAME_AND_ADDRESS") - - if receive_options.type == "LOCAL": - sort_code = _retrieve_detail(details, "BANK_CODE").replace("-", "") - account_number = _retrieve_detail(details, "ACCOUNT_NUMBER") - - elif receive_options.type == "INTERNATIONAL": - swift = _retrieve_detail(details, "SWIFT_CODE") - iban = _retrieve_detail(details, "IBAN") - - if not bank_info: + try: + account_holder, bank_name, bank_address, sort_code, account_number, swift, iban = _aggregate_account_recipient_details(account) + except ValueError: continue - bank_name, _, bank_address = bank_info.partition("\n") - if not bank_name or not bank_address: continue From 02ba15357ae75afe38468ebaab0da0ae143bcd7a Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 00:06:41 +0100 Subject: [PATCH 08/26] payments: preparation: declare (as-yet-unused) `RecipientDetails` dataclass --- apps/payments/wise.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 44aabcf20..e3741433a 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -1,4 +1,5 @@ import logging +from dataclasses import dataclass from datetime import timedelta from flask import abort, request @@ -215,6 +216,17 @@ def _retrieve_detail(details, requested_type): raise AttributeError(f"Failed to find requested {requested_type} attribute in account details") +@dataclass +class RecipientDetails: + account_holder: str + bank_name: str + bank_address: str + sort_code: str + account_number: str + swift: str + iban: str + + def _aggregate_account_recipient_details(account): account_holder = bank_name = bank_address = sort_code = account_number = swift = iban = None for receive_options in account.receiveOptions: From d5a0937fa7c095a9d1e75222e1b253db5256cbd3 Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 00:10:35 +0100 Subject: [PATCH 09/26] payments: preparation: consolidate bank name/address fields in dataclass Relates-to commit df1196e1396bee6f308e0be24774cdc77d9e5e11. --- apps/payments/wise.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index e3741433a..28e13fcdc 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -219,8 +219,7 @@ def _retrieve_detail(details, requested_type): @dataclass class RecipientDetails: account_holder: str - bank_name: str - bank_address: str + bank_name_and_address: str sort_code: str account_number: str swift: str From fd91cdfcc5fe00bb915140064806f7692a21383c Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 00:14:20 +0100 Subject: [PATCH 10/26] payments: run `ruff format` --- apps/payments/wise.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 28e13fcdc..b08cdacad 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -260,7 +260,9 @@ def wise_retrieve_accounts(profile_id): if account.currency.code == "GBP": try: - account_holder, bank_name, bank_address, sort_code, account_number, swift, iban = _aggregate_account_recipient_details(account) + account_holder, bank_name, bank_address, sort_code, account_number, swift, iban = ( + _aggregate_account_recipient_details(account) + ) except ValueError: continue From 71df64d73a68427712b49a7575a92200807178e8 Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 00:16:03 +0100 Subject: [PATCH 11/26] payments: add some `TODO` notes for pending refactoring --- apps/payments/wise.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index b08cdacad..37e632c77 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -210,6 +210,7 @@ def wise_business_profile(): def _retrieve_detail(details, requested_type): """Helper method to retrieve content from attribute-value details recordsets""" + # TODO: eliminate this loop, by iterating over the keys/attributes from the details object for detail in details: if detail.type == requested_type: return detail.body @@ -249,6 +250,7 @@ def _aggregate_account_recipient_details(account): if not bank_name or not bank_address: raise ValueError("Could not extract bank name and address from bank info") + # TODO: return a RecipientDetails instance here instead return account_holder, bank_name, bank_address, sort_code, account_number, swift, iban @@ -260,6 +262,7 @@ def wise_retrieve_accounts(profile_id): if account.currency.code == "GBP": try: + # TODO: retrieve a RecipientDetails instance here instead account_holder, bank_name, bank_address, sort_code, account_number, swift, iban = ( _aggregate_account_recipient_details(account) ) From 04e36bcb31b232c62e4f60fb64a198fa2c74938a Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 10:13:15 +0100 Subject: [PATCH 12/26] payments: refactor: single-pass bank detail aggregation --- apps/payments/wise.py | 71 +++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 37e632c77..1da624659 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -208,15 +208,6 @@ def wise_business_profile(): return id -def _retrieve_detail(details, requested_type): - """Helper method to retrieve content from attribute-value details recordsets""" - # TODO: eliminate this loop, by iterating over the keys/attributes from the details object - for detail in details: - if detail.type == requested_type: - return detail.body - raise AttributeError(f"Failed to find requested {requested_type} attribute in account details") - - @dataclass class RecipientDetails: account_holder: str @@ -227,31 +218,39 @@ class RecipientDetails: iban: str -def _aggregate_account_recipient_details(account): - account_holder = bank_name = bank_address = sort_code = account_number = swift = iban = None - for receive_options in account.receiveOptions: - details = receive_options.details - - account_holder = _retrieve_detail(details, "ACCOUNT_HOLDER") - bank_info = _retrieve_detail(details, "BANK_NAME_AND_ADDRESS") +def _translate_recipient_details(receive_options): + """Helper method to translate Wise receive options into a local RecipientDetails instance""" + field_mappings = { + "ACCOUNT_HOLDER": "account_holder", + "BANK_NAME_AND_ADDRESS": "name_and_address", + "BANK_CODE": "sort_code", + "ACCOUNT_NUMBER": "account_number", + "SWIFT_CODE": "swift", + "IBAN": "iban", + } + return RecipientDetails(**{ + field_mappings.get(detail.type): detail.body + for detail in details + if detail.type in field_mappings + }) - if receive_options.type == "LOCAL": - sort_code = _retrieve_detail(details, "BANK_CODE").replace("-", "") - account_number = _retrieve_detail(details, "ACCOUNT_NUMBER") - elif receive_options.type == "INTERNATIONAL": - swift = _retrieve_detail(details, "SWIFT_CODE") - iban = _retrieve_detail(details, "IBAN") +def _aggregate_account_recipient_details(account): + existing_details = None + for receive_options in account.receiveOptions: + recipient_details = _translate_recipient_details(receive_options) - if not bank_info: - raise ValueError("Bank info field is empty") + if existing_details: + # coalesce translated account info into the existing bank details + existing_details.sort_code = existing_details.sort_code or recipient_details.sort_code + existing_details.account_number = existing_details.account_number or recipient_details.account_number + existing_details.swift = existing_details.swift or recipient_details.swift + existing_details.iban = existing_details.iban or recipient_details.iban - bank_name, _, bank_address = bank_info.partition("\n") - if not bank_name or not bank_address: - raise ValueError("Could not extract bank name and address from bank info") + else: + existing_details = recipient_details - # TODO: return a RecipientDetails instance here instead - return account_holder, bank_name, bank_address, sort_code, account_number, swift, iban + return existing_details def wise_retrieve_accounts(profile_id): @@ -261,15 +260,15 @@ def wise_retrieve_accounts(profile_id): account_holder = bank_name = bank_address = sort_code = account_number = swift = iban = None if account.currency.code == "GBP": - try: - # TODO: retrieve a RecipientDetails instance here instead - account_holder, bank_name, bank_address, sort_code, account_number, swift, iban = ( - _aggregate_account_recipient_details(account) - ) - except ValueError: - continue + bank_details = _aggregate_account_recipient_details(account) + + if not bank_details.name_and_address: + # TODO: add an error or warning about this condition + continue + bank_name, _, bank_address = bank_details.name_and_address.partition("\n") if not bank_name or not bank_address: + # TODO: add an error or warning about this condition continue wise_balance_id = account.id From bff957e281f9661fa644582df3a1d2545cb99161 Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 10:28:42 +0100 Subject: [PATCH 13/26] payments: fixup: construct `BankAccount` instance using correct variables/attributes Relates-to commit 559cc5dfb39e805561d033d56092081c8daee0ca. --- apps/payments/wise.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 1da624659..470fd992d 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -257,8 +257,6 @@ def wise_retrieve_accounts(profile_id): from main import wise for account in wise.account_details.list(profile_id=profile_id): - account_holder = bank_name = bank_address = sort_code = account_number = swift = iban = None - if account.currency.code == "GBP": bank_details = _aggregate_account_recipient_details(account) @@ -278,15 +276,15 @@ def wise_retrieve_accounts(profile_id): wise_balance_id = 0 yield BankAccount( - sort_code=sort_code, - acct_id=account_number, + sort_code=bank_details.sort_code, + acct_id=bank_details.account_number, currency=account.currency.code, active=False, - payee_name=account_holder, + payee_name=bank_details.account_holder, institution=bank_name, address=bank_address, - swift=swift, - iban=iban, + swift=bank_details.swift, + iban=bank_details.iban, wise_balance_id=wise_balance_id, ) From 902cb3594fb9e9597eed8f97751ad4fecedad39f Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 10:34:49 +0100 Subject: [PATCH 14/26] payments: rectify some naming, and refactor to reduce line length --- apps/payments/wise.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 470fd992d..3d0c633d9 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -235,18 +235,18 @@ def _translate_recipient_details(receive_options): }) -def _aggregate_account_recipient_details(account): +def _combine_account_recipient_details(account): existing_details = None for receive_options in account.receiveOptions: recipient_details = _translate_recipient_details(receive_options) if existing_details: # coalesce translated account info into the existing bank details - existing_details.sort_code = existing_details.sort_code or recipient_details.sort_code - existing_details.account_number = existing_details.account_number or recipient_details.account_number - existing_details.swift = existing_details.swift or recipient_details.swift - existing_details.iban = existing_details.iban or recipient_details.iban - + for field in ("sort_code", "account_number", "swift", "iban"): + existing_value = getattr(existing_details, field) + updated_value = getattr(recipient_details, field) + combined_value = existing_value if existing_value is not None else updated_value + setattr(existing_details, field, combined_value) else: existing_details = recipient_details @@ -258,7 +258,7 @@ def wise_retrieve_accounts(profile_id): for account in wise.account_details.list(profile_id=profile_id): if account.currency.code == "GBP": - bank_details = _aggregate_account_recipient_details(account) + bank_details = _combine_account_recipient_details(account) if not bank_details.name_and_address: # TODO: add an error or warning about this condition From 98850c3aefc68873fc0ba533e720f557d785f531 Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 10:40:13 +0100 Subject: [PATCH 15/26] payments: fixup: ensure `bank_details` variable is assigned prior to access --- apps/payments/wise.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 3d0c633d9..0bcf3a597 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -257,9 +257,11 @@ def wise_retrieve_accounts(profile_id): from main import wise for account in wise.account_details.list(profile_id=profile_id): - if account.currency.code == "GBP": - bank_details = _combine_account_recipient_details(account) + if account.currency.code != "GBP": + # TODO: support other host currencies + continue + bank_details = _combine_account_recipient_details(account) if not bank_details.name_and_address: # TODO: add an error or warning about this condition continue From 69b0844f9ca00745ef034927f92d30fc834efb46 Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 10:47:46 +0100 Subject: [PATCH 16/26] payments: fixup: variable/attribute names --- apps/payments/wise.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 0bcf3a597..284759106 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -211,7 +211,7 @@ def wise_business_profile(): @dataclass class RecipientDetails: account_holder: str - bank_name_and_address: str + name_and_address: str sort_code: str account_number: str swift: str @@ -230,7 +230,7 @@ def _translate_recipient_details(receive_options): } return RecipientDetails(**{ field_mappings.get(detail.type): detail.body - for detail in details + for detail in receive_options.details if detail.type in field_mappings }) From 1cc942cbc9f504a02b663267fe8309ff2a5f73e5 Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 10:48:02 +0100 Subject: [PATCH 17/26] payments: fixup: default `RecipientDetails` attribute values --- apps/payments/wise.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 284759106..5a1a30537 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -212,10 +212,10 @@ def wise_business_profile(): class RecipientDetails: account_holder: str name_and_address: str - sort_code: str - account_number: str - swift: str - iban: str + sort_code: str | None = None + account_number: str | None = None + swift: str | None = None + iban: str | None = None def _translate_recipient_details(receive_options): From 76ed50e08a76cc41d98a19ebffd846a002f57c46 Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 11:16:46 +0100 Subject: [PATCH 18/26] payments: fixup: parse/clean sort code and account number --- apps/payments/wise.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 5a1a30537..4ec78ffc6 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -217,6 +217,14 @@ class RecipientDetails: swift: str | None = None iban: str | None = None + @property + def parsed_account_number(self): + return self.account_number.replace(" ", "")[-8:] + + @property + def parsed_sort_code(self): + return self.sort_code.replace("-", "") + def _translate_recipient_details(receive_options): """Helper method to translate Wise receive options into a local RecipientDetails instance""" @@ -278,8 +286,8 @@ def wise_retrieve_accounts(profile_id): wise_balance_id = 0 yield BankAccount( - sort_code=bank_details.sort_code, - acct_id=bank_details.account_number, + sort_code=bank_details.parsed_sort_code, + acct_id=bank_details.parsed_account_number, currency=account.currency.code, active=False, payee_name=bank_details.account_holder, From 3269de1ce255c4a6579b80bc0c3eeb5f8b199853 Mon Sep 17 00:00:00 2001 From: James Addison Date: Tue, 9 Sep 2025 11:17:14 +0100 Subject: [PATCH 19/26] payments: apply `ruff format` --- apps/payments/wise.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 4ec78ffc6..691962ff0 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -236,11 +236,13 @@ def _translate_recipient_details(receive_options): "SWIFT_CODE": "swift", "IBAN": "iban", } - return RecipientDetails(**{ - field_mappings.get(detail.type): detail.body - for detail in receive_options.details - if detail.type in field_mappings - }) + return RecipientDetails( + **{ + field_mappings.get(detail.type): detail.body + for detail in receive_options.details + if detail.type in field_mappings + } + ) def _combine_account_recipient_details(account): From 579129e04e0ef0d4e63ed69dac112ae70f6891bf Mon Sep 17 00:00:00 2001 From: James Addison Date: Wed, 10 Sep 2025 00:16:42 +0100 Subject: [PATCH 20/26] payments: rename method for brevity --- apps/payments/wise.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 691962ff0..65d3dc433 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -245,7 +245,7 @@ def _translate_recipient_details(receive_options): ) -def _combine_account_recipient_details(account): +def _merge_recipient_details(account): existing_details = None for receive_options in account.receiveOptions: recipient_details = _translate_recipient_details(receive_options) @@ -271,7 +271,7 @@ def wise_retrieve_accounts(profile_id): # TODO: support other host currencies continue - bank_details = _combine_account_recipient_details(account) + bank_details = _merge_recipient_details(account) if not bank_details.name_and_address: # TODO: add an error or warning about this condition continue From a616d9e615b7da3a5b70643f8acfc28c20e3a87f Mon Sep 17 00:00:00 2001 From: James Addison Date: Wed, 10 Sep 2025 00:27:26 +0100 Subject: [PATCH 21/26] payments: refactor / relocate validation logic --- apps/payments/wise.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 65d3dc433..f144da350 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -217,6 +217,26 @@ class RecipientDetails: swift: str | None = None iban: str | None = None + @property + def bank_info(self): + if not self.name_and_address: + raise ValueError("Bank name/address information not found") + return self.name_and_address + + @property + def name(self): + bank_name, _, _ = self.bank_info.partition("\n") + if not bank_name: + raise ValueError("Bank name is empty") + return bank_name + + @property + def address(self): + _, _, bank_address = self.bank_info.partition("\n") + if not bank_address: + raise ValueError("Bank address is empty") + return bank_address + @property def parsed_account_number(self): return self.account_number.replace(" ", "")[-8:] @@ -272,15 +292,6 @@ def wise_retrieve_accounts(profile_id): continue bank_details = _merge_recipient_details(account) - if not bank_details.name_and_address: - # TODO: add an error or warning about this condition - continue - - bank_name, _, bank_address = bank_details.name_and_address.partition("\n") - if not bank_name or not bank_address: - # TODO: add an error or warning about this condition - continue - wise_balance_id = account.id # Workaround: the Wise Sandbox API returns a null/empty account ID; populate a value @@ -293,8 +304,8 @@ def wise_retrieve_accounts(profile_id): currency=account.currency.code, active=False, payee_name=bank_details.account_holder, - institution=bank_name, - address=bank_address, + institution=bank_details.name, + address=bank_details.address, swift=bank_details.swift, iban=bank_details.iban, wise_balance_id=wise_balance_id, From 4ce979d0fd6f820fc22a85d65380f3e995837d9c Mon Sep 17 00:00:00 2001 From: James Addison Date: Wed, 10 Sep 2025 00:28:48 +0100 Subject: [PATCH 22/26] payments: use assertions for brevity --- apps/payments/wise.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index f144da350..513a19ca9 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -219,22 +219,19 @@ class RecipientDetails: @property def bank_info(self): - if not self.name_and_address: - raise ValueError("Bank name/address information not found") + assert self.name_and_address, "Bank name/address information not found" return self.name_and_address @property def name(self): bank_name, _, _ = self.bank_info.partition("\n") - if not bank_name: - raise ValueError("Bank name is empty") + assert bank_name, "Bank name is empty" return bank_name @property def address(self): _, _, bank_address = self.bank_info.partition("\n") - if not bank_address: - raise ValueError("Bank address is empty") + assert bank_address, "Bank address is empty" return bank_address @property From 4a0a2b1b7be0e0261f82ad37a70136e0222cc8e0 Mon Sep 17 00:00:00 2001 From: James Addison Date: Wed, 10 Sep 2025 00:31:13 +0100 Subject: [PATCH 23/26] payments: factor-out `wise_balance_id` variable --- apps/payments/wise.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 513a19ca9..671e027c7 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -289,11 +289,10 @@ def wise_retrieve_accounts(profile_id): continue bank_details = _merge_recipient_details(account) - wise_balance_id = account.id # Workaround: the Wise Sandbox API returns a null/empty account ID; populate a value if account.id is None and app.config.get("TRANSFERWISE_ENVIRONMENT") == "sandbox": - wise_balance_id = 0 + account.id = 0 yield BankAccount( sort_code=bank_details.parsed_sort_code, @@ -305,7 +304,7 @@ def wise_retrieve_accounts(profile_id): address=bank_details.address, swift=bank_details.swift, iban=bank_details.iban, - wise_balance_id=wise_balance_id, + wise_balance_id=account.id, ) From 2f11c07918a71545aeb0bb19e03303f4c083b728 Mon Sep 17 00:00:00 2001 From: James Addison Date: Wed, 10 Sep 2025 00:50:43 +0100 Subject: [PATCH 24/26] payments: cleanup: remove a few temporary/workaround `import` statements Relates-to commit 0c469bd67eb0f385c322517dcc2273e1fa29a014. --- apps/payments/wise.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 671e027c7..4290f0b33 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -7,7 +7,7 @@ from pywisetransfer.exceptions import InvalidWebhookSignature from pywisetransfer.webhooks import validate_request -from main import db +from main import db, wise from models import naive_utcnow from models.payment import BankAccount, BankTransaction @@ -116,8 +116,6 @@ def wise_balance_credit(event_type, event): def sync_wise_statement(profile_id, wise_balance_id, currency): - from main import wise - # Retrieve an account transaction statement for the past week interval_end = naive_utcnow() interval_start = interval_end - timedelta(days=7) @@ -187,8 +185,6 @@ def sync_wise_statement(profile_id, wise_balance_id, currency): def wise_business_profile(): - from main import wise - if app.config.get("TRANSFERWISE_PROFILE_ID"): id = int(app.config["TRANSFERWISE_PROFILE_ID"]) accounts = list(wise.account_details.list(profile_id=id)) @@ -209,7 +205,7 @@ def wise_business_profile(): @dataclass -class RecipientDetails: +class WiseRecipient: account_holder: str name_and_address: str sort_code: str | None = None @@ -244,7 +240,7 @@ def parsed_sort_code(self): def _translate_recipient_details(receive_options): - """Helper method to translate Wise receive options into a local RecipientDetails instance""" + """Helper method to translate Wise receive options into a WiseRecipient instance""" field_mappings = { "ACCOUNT_HOLDER": "account_holder", "BANK_NAME_AND_ADDRESS": "name_and_address", @@ -253,7 +249,7 @@ def _translate_recipient_details(receive_options): "SWIFT_CODE": "swift", "IBAN": "iban", } - return RecipientDetails( + return WiseRecipient( **{ field_mappings.get(detail.type): detail.body for detail in receive_options.details @@ -309,8 +305,6 @@ def wise_retrieve_accounts(profile_id): def wise_validate(): - from main import wise - """Validate that Wise is configured and operational""" result = [] From 9632cb96c68f572b42535138f39c64758f53939c Mon Sep 17 00:00:00 2001 From: James Addison Date: Wed, 10 Sep 2025 10:55:54 +0100 Subject: [PATCH 25/26] payments: nitpick / rectify a method name --- apps/payments/wise.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/payments/wise.py b/apps/payments/wise.py index 4290f0b33..84c11ba97 100644 --- a/apps/payments/wise.py +++ b/apps/payments/wise.py @@ -239,8 +239,8 @@ def parsed_sort_code(self): return self.sort_code.replace("-", "") -def _translate_recipient_details(receive_options): - """Helper method to translate Wise receive options into a WiseRecipient instance""" +def _recipient_details_adapter(receive_options): + """Helper method to adapt Wise receive options response data into a WiseRecipient instance""" field_mappings = { "ACCOUNT_HOLDER": "account_holder", "BANK_NAME_AND_ADDRESS": "name_and_address", @@ -261,7 +261,7 @@ def _translate_recipient_details(receive_options): def _merge_recipient_details(account): existing_details = None for receive_options in account.receiveOptions: - recipient_details = _translate_recipient_details(receive_options) + recipient_details = _recipient_details_adapter(receive_options) if existing_details: # coalesce translated account info into the existing bank details From 4bba914efd2826373eda3cdceb1222c24d66f3e6 Mon Sep 17 00:00:00 2001 From: James Addison Date: Wed, 10 Sep 2025 11:01:24 +0100 Subject: [PATCH 26/26] tests: payments: resolve Wise `FIXME` item --- tests/test_payment.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_payment.py b/tests/test_payment.py index 69abbb0c2..6bb23d52c 100644 --- a/tests/test_payment.py +++ b/tests/test_payment.py @@ -15,7 +15,9 @@ def vcr_config(): @pytest.mark.vcr() def test_wise_account_retrieval(app): accounts = list(wise_retrieve_accounts(profile_id=123456789)) - assert len(accounts) == 1 # FIXME + + # we merge Wise's local and international details for our GBP account into a single record + assert len(accounts) == 1 primary_account = accounts[0] assert primary_account.currency == "GBP"