Attempt best-effort on transferwise transactions with no permissions
authorMagnus Hagander <magnus@hagander.net>
Thu, 31 Jul 2025 12:40:49 +0000 (14:40 +0200)
committerMagnus Hagander <magnus@hagander.net>
Thu, 31 Jul 2025 12:40:49 +0000 (14:40 +0200)
If a transferwise transaction is sent from another user of their bank,
the id of the transaction belongs to the *other user*, and the recipient
has no permissions to get to the details. So when this happens, do a
best-effort set of details based on what we can find in the activity
(which generally does not contain payment references for example), and
leave it to the user - rather than just ignoring it.

The same seems to happen for direct debit transactions, so process them
the same.

postgresqleu/transferwise/api.py

index 621e97a4c6d9d62ce186e93288a353241c5bbbca..86661953671abe32130415a744c95a748518f46e 100644 (file)
@@ -129,19 +129,32 @@ class TransferwiseApi(object):
                    (activity['type'] == 'BALANCE_DEPOSIT' and activity['resource']['type'] == 'TRANSFER'):
                     try:
                         details = self.get('transfers/{}'.format(activity['resource']['id']))
+
+                        if details['sourceCurrency'] != settings.CURRENCY_ABBREV:
+                            continue
+
+                        amount = Decimal(details['targetValue']).quantize(Decimal('0.01'))
+                        created = details['created']
+                        reference = details['reference']
+                        fulldescription = details['details']['reference']
                     except requests.exceptions.HTTPError as e:
                         if e.response.status_code == 403:
-                            print("No permissions to access transaction {} from {}, ignoring".format(
+                            # No permissions can mean (1) it's a wise-to-wise transaction, for which we are not allowed to
+                            # see details, or (2) a direct debit transaction.
+                            print("No permissions to access transaction {} from {}, adding placeholder without details".format(
                                 activity['resource']['id'],
                                 activity['updatedOn'],
                             ))
-                            continue
-                        raise
-
-                    if details['sourceCurrency'] != settings.CURRENCY_ABBREV:
-                        continue
 
-                    amount = Decimal(details['targetValue']).quantize(Decimal('0.01'))
+                            amount, currency = self.parse_transferwise_amount(activity['primaryAmount'])
+                            if currency != settings.CURRENCY_ABBREV:
+                                # This is transaction is in a different currency, so ignore it
+                                continue
+                            created = activity['createdOn']
+                            reference = ''
+                            fulldescription = 'Transaction with no permissions on details: {}'.format(self.strip_tw_tags(activity['title']))
+                        else:
+                            raise
 
                     # Yes, the transfer will actually have a positive amount even if it's a withdrawal.
                     # No, this is not indicated anywhere, since the "target account id" that would
@@ -173,12 +186,12 @@ class TransferwiseApi(object):
 
                     yield {
                         'id': 'TRANSFER-{}'.format(activity['resource']['id']),
-                        'datetime': details['created'],
+                        'datetime': created,
                         'amount': amount * negatizer,
                         'feeamount': 0,  # XXX!
                         'transtype': 'TRANSFER',
-                        'paymentref': details['reference'],
-                        'fulldescription': details['details']['reference'],
+                        'paymentref': reference,
+                        'fulldescription': fulldescription,
                     }
                 elif activity['type'] == 'BALANCE_CASHBACK':
                     # No API endpoint to get this so we have to parse it out of