Repository: liberapay/liberapay.com

Issue:
Allow different Instruments to be associated with differerent recipients
The credit card I use most adds an extra charge for currency conversion, so I tend to use a different card, which does not add an extra fee, for transactions in Euros or other currencies. I have both cards registered in Liberapay, and can switch default card manually before the payment is taken, but it would be a useful feature to be able to associate an Instrument with payments on the Schedule.
Allow different Instruments to be associated with differerent recipients
The credit card I use most adds an extra charge for currency conversion, so I tend to use a different card, which does not add an extra fee, for transactions in Euros or other currencies. I have both cards registered in Liberapay, and can switch default card manually before the payment is taken, but it would be a useful feature to be able to associate an Instrument with payments on the Schedule.


Discussion:
I think it would be simpler to add the possibility of specifying which currency an instrument should be used with, rather than which donation recipients it should be used for.
I think it would be simpler to add the possibility of specifying which currency an instrument should be used with, rather than which donation recipients it should be used for.

Resolving patch:
diff --git a/liberapay/models/exchange_route.py b/liberapay/models/exchange_route.py
--- a/liberapay/models/exchange_route.py
+++ b/liberapay/models/exchange_route.py
@@ -223,6 +223,22 @@ def set_as_default(self):
                 id=self.id, network=self.network
             ))
 
+    def set_as_default_for(self, currency):
+        with self.db.get_cursor() as cursor:
+            cursor.run("""
+                UPDATE exchange_routes
+                   SET is_default_for = NULL
+                 WHERE participant = %(p_id)s
+                   AND is_default_for = %(currency)s;
+                UPDATE exchange_routes
+                   SET is_default_for = %(currency)s
+                 WHERE participant = %(p_id)s
+                   AND id = %(route_id)s
+            """, dict(p_id=self.participant.id, route_id=self.id, currency=currency))
+            self.participant.add_event(cursor, 'set_default_route_for', dict(
+                id=self.id, network=self.network, currency=currency,
+            ))
+
     def set_mandate(self, mandate_id):
         self.db.run("""
             UPDATE exchange_routes
diff --git a/liberapay/models/participant.py b/liberapay/models/participant.py
--- a/liberapay/models/participant.py
+++ b/liberapay/models/participant.py
@@ -2028,6 +2028,16 @@ def get_currencies_for(tippee, tip):
                 fallback_currency = 'USD'
             return fallback_currency, accepted
 
+    @cached_property
+    def donates_in_multiple_currencies(self):
+        return self.db.one("""
+            SELECT count(DISTINCT amount::currency) > 1
+              FROM current_tips
+             WHERE tipper = %s
+               AND amount > 0
+               AND renewal_mode > 0
+        """, (self.id,))
+
 
     # More Random Stuff
     # =================
diff --git a/liberapay/payin/cron.py b/liberapay/payin/cron.py
--- a/liberapay/payin/cron.py
+++ b/liberapay/payin/cron.py
@@ -264,7 +264,8 @@ def execute_scheduled_payins():
                     AND r.status = 'chargeable'
                     AND r.network::text LIKE 'stripe-%%'
                     AND ( sp.amount::currency = 'EUR' OR r.network <> 'stripe-sdd' )
-               ORDER BY r.is_default NULLS LAST
+               ORDER BY r.is_default_for = sp.amount::currency DESC NULLS LAST
+                      , r.is_default DESC NULLS LAST
                       , r.ctime DESC
                   LIMIT 1
                ) r ON true


Test patch: