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"