TBD54566975 / tbdex

56 stars 25 forks source link

Consider how payment issues such as refunds, insufficient pay-in, etc. should be handled #319

Open frankhinek opened 3 months ago

frankhinek commented 3 months ago

Context

On a recent tbDEX Ecosystem Partners workstream call, one of the partners building a PFI raised the question of how to handle cases such as:

The tbDEX protocol is not prescriptive on the payment process. However, rather than coming up with their own bespoke solutions to handle the above scenarios via refunds, etc., the team wanted to explore whether there are ways that this could be done in a more standardized way.

Discussion

Explore whether there can/should be accommodations in the tbDEX protocol to make handling the scenarios raised in a more standardized manner.

For example, one suggestion was to allow for the inclusion of a refund URL to be returned to the tbDEX client app so that the user could be directed through a refund process flow.

Another question was whether a new tbDEX exchange should be initiated by the PFI to handle the additional pay-in or a refund. If so, how does a PFI initiate a new exchange with an end-user?

Desired Outcome

Document TBD's guidance and suggestions on this topic and whether there are any specific protocol enhancements that could/should be adopted.

frankhinek commented 3 months ago

In the event that the PFI cannot automatically handle the refund, it seems that a Close message with a success boolean and arbitrary reason text would be insufficient to notify the client app to direct the end user through a refund flow.

Example 1

Scenario:

Example 2

Scenario:

KendallWeihe commented 3 months ago

Here's how I'm thinking about this... tbdex is not a transaction settlement protocol, it's an exchange protocol which is composed of 2 sequential transactions -- pay-in and pay-out -- both of which occur outside the scope of the protocol. The issue at hand here is an issue wherein the first transaction (the pay-in) settles -- as in, the funds successfully transfer -- but whereafter some error occurs. The two failure cases being discussed:

  1. The pay-out is unable to succeed
  2. The pay-in amount was an incorrect amount

We could generalize both of these failure cases to the same outcome, wherein they both are considered to be unsuccessful exchanges and the full pay-in is returned to the client. Or, we could consider them differently, because (1) the exchange is guaranteed to be unsuccessful, but with (2) the exchange could still be considered successful (as in, the pay-out is settled), but the difference in the incorrect pay-in would need to be reconciled, i.e. sent back to the sender. So the question here is, should we generalize both of these two to the same case? The simple solution is yes, let's consider both of these to be unsuccessful exchanges wherein the full pay-in funds must be sent back to the sender.

So then we have the concern that not all pay-in payment rails will support "return-to-sender." @frankhinek as you point out above, if a user pays-in via Lightning or via an exchange wallet, neither situations can the PFI automatically return the funds back to the sending address (in the case of Lightning, that doesn't exist b/c LN uses invoices, in the case of an exchange wallet the exchange has to associate the funds with a given user account so if the PFI sent the funds back then they may go to the exchange business but not to Alice).

I can think of a few potential ways forward if we want to add this "return-to-sender" at the protocol level.

  1. We could include a "refund" payment method in the RFQ. If the PFI needs to return the pay-in funds then they would send the funds to this "refund" location (in the case of Lightning this could be an additional Lightning invoice, and in the case of an exchange wallet the user would have to enter their exchange wallet address).
  2. We could introduce new message types which are sent after an Order which would inform the PFI where to return the pay-in funds.
  3. We could string multiple exchanges together. The first exchange would have a Close.success of false but whereafter there could be a second exchange which is somehow cryptographically linked to the first exchange which exchanges the original pay-in back to the client.

An alternative would be to not support this at the protocol level but as I see it, that would require bespoke remediation for the given applications (client & PFI). The PFI could submit a Close with success of false whereafter somehow the wallet and the PFI would need to agree to create a subsequent exchange where the funds are moved back to the wallet.

mistermoe commented 3 months ago

Thanks for providing examples @frankhinek. Thinking through what you've surfaced out loud:

User pays in too much or pays in too little

One thought to note here is that a PFI could choose to payout relative to what's been payed in. Specifically:

Even in the scenario where the alice didn't pay in enough, the resulting payout could deterministically fail due to incorrect information (e.g. bank account info etc.) which would necessitate a refund. Given that the scenarios you've surfaced in addition to likely several more being resolvable via refunds, I think it's important for us to spend some time forming a perspective on how to how to support them

Suggestions

Collecting refund details upfront

Without changing anything about the protocol, technically a PFI could include any necessary refund details as part of the required payment details submitted with an RFQ. We could take this a step further and first-class requiredRefundDetails adjacent to requiredPaymentDetails in Offering.

Refund Instructions / URL

one suggestion was to allow for the inclusion of a refund URL to be returned to the tbDEX client app so that the user could be directed through a refund process flow.

I think this is a great idea. Instinctively, I think it would be best to include a refund or remediation property in Close whose value is an object that closely resembles the PaymentInstruction type used within payin and payout in Quote. Concretely, refund would be an object that can include a url or instructions property. close.refund would only be present if success is false.

PENDING_ACTION Order Status or Message Kind

Another idea that came to mind is introducing a reserved Order Status or message kind: PENDING_ACTION that contains an action property whose value is an object that includes either a JSON Schema (similar to offering.requiredPaymentDetails) or a URL. the JSON Schema would contain fields that need to be collected from Alice in order to proceed. The URL would be a link to a page where Alice can provide the necessary information. This would allow for information correction and refunds to be handled in a more standardized way.

This idea is tempting but i'm a bit hesitant given that it would make the state machine for a given exchange more complex and we've erred in favor of keeping it as simple as possible. I think it's worth considering though. Instinctively moreso in favor of collecting refund details upfront or including a refund property in Close as mentioned above.

General Information

I think it would be beneficial to include a refundPolicy property in Offering whose value is a structured object that includes information on how refunds are handled for that specific offering. An alternative to including the policy in the as part of the offering itself would be to introduce an optional GET /refunds or GET /refunds/:offering_id endpoint to the PFI HTTP API that returns a structured object detailing that PFIs refund policy.

Related to ^ I think it would be beneficial to introduce a GET /support or GET /contact or GET /details endpoint to the PFI HTTP API that returns a structured object containing:


Would be curious to hear others thoughts on the above. I'll be opening an issue to track GET /support or GET /details as it's a bit of a tangent from the current discussion.

frankhinek commented 3 months ago

Another related but slightly different scenario to consider:

What happens if the payin method is one time use only and Alice only sends a partial payment in the future transaction?

An example could be a pre-staged transaction through payment platforms like Stripe or a BOLT 11 Lightning invoice.

How can a PFI notify the client app that Alice is using (assume there are many apps from different developers) in a consistent way that there's a new payment link that they need to use to submit the remaining payin amount due?

mistermoe commented 3 months ago

@frankhinek do those payment links typically allow you to pay in a partial amount? In my experience with both stripe and square links, the full amount is required

frankhinek commented 3 months ago

@frankhinek do those payment links typically allow you to pay in a partial amount? In my experience with both stripe and square links, the full amount is required

Fair point -- I used two bad examples ;-)

For the purposes of the scenario, let's assume that its a payment method that DOES allow you to pay less than the total payin due. Some examples include:

michaelneale commented 3 months ago

anecdata: when I get a quote via an FX service it is for an approximate amount, I believe if "close" it will pay out relative to pay in (I know as sometimes has been a bit over or under what I put in to get the quote, and it worked) but never strayed far from quoted amount. The agreements I have seen (this is USD to AUD) imply it is exact amount (and sometimes fees may apply out of their control) but I think can flex a little bit (it may require an additional confirmation however). Not sure that helps!

angiejones commented 2 months ago

The tbDEX whitepaper alludes to smart contracts being used for these types of scenarios. Are we steering away from that as a possibility?

michaelneale commented 2 months ago

@angiejones yeah - I have assumed that things have moved away from smart contracts for that especially when a lot of the connective tissue is to fiat systems and remittances are for fiat to fiat but curious what others may think.