anza-xyz / solana-pay

A new standard for decentralized payments.
https://solanapay.com
Apache License 2.0
1.29k stars 450 forks source link

[feature request] Support for redirect urls on txn success #162

Open uveerma opened 1 year ago

uveerma commented 1 year ago

Wallet providers who support Solana Pay can allow user redirecting on successful txns, just like deep link implementations. This UX will make it familiar with GPay, Apple Pay, etc, and can provide merchants or payment providers a room to support new features.

Motivation: - Recently at Delhi HH it was my first experience seeing people actually mint NFTs IRL by scanning a Solana pay QR Code. While the experience was seamless, post minting, new users who created their first ever wallet had no idea what to further do. Wallet providers take some time to notify users that they got an NFT and further loading of this NFT image takes a lot of time, meanwhile the end user remains clueless.

Implementation: - There's little to no change on the protocol level for this feature, and most work will be on the wallet provider side. As a user, we can pass a redirect_url param in the POST response along with other parameters like message, which can give us an extra space to make Redirect URLs more targeted, e.g. passing mint address for newly minted NFT with an explorer link by which user can see metadata for their minted NFTs instantly.

Excited to hear everyone's feedback and views on this addition. Thanks to @cogoo & @mcintyre94 for listening to this idea initially, and providing their input!

jordaaash commented 1 year ago

I've heard a few requests along these lines but they haven't gotten into implementation details.

Can you provide a proposal here of how you see the spec changing, how this would work with transfer requests, how this would work with transaction requests, how it work in an app-to-app context on the same mobile device, security implications, and a list of use cases for this?

peerwaya commented 1 year ago

This makes sense. Our usecase is using solana pay transaction request to make in app payments. Once the user opens solana pay and the transaction is successful, the user stays on the wallet app. No way to redirect back to the app as seen in the video below.

We can enhance the response of the post request to return a success_url and cancel_url. I believe this is sufficient for wallet providers to redirect based on the outcome of the transaction. If the transaction is successful and a success_url is provided, redirect the user to the success_url. if a transaction is canceled, redirect the user to the cancel_url.

So as @Vampo7152 mentioned, nothing really changes on the protocol but its up to wallet providers to recognize success_url and passing the tx as a query parameter.

POST Request
POST /solana-pay?order=12345 HTTP/1.1
Host: example.com
Connection: close
Accept: application/json
Accept-Encoding: br, gzip, deflate
Content-Type: application/json
Content-Length: 57

{"account":"mvines9iiHiQTysrwkJjGf2gb9Ex9jXJX8ns3qwf2kN"}
POST Response
HTTP/1.1 200 OK
Connection: close
Content-Type: application/json
Content-Length: 298
Content-Encoding: gzip

{"message":"Thanks for all the fish","transaction":"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAECC4JMKqNplIXybGb/GhK1ofdVWeuEjXnQor7gi0Y2hMcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAAMAgAAAAAAAAAAAAAA", "success_url": "https://example.com/thank_you", "cancel_url": "https://example.com/cancel"}

When a transaction is successful or failed redirect the user to success_url and append the tx as a query parameter eg

https://example.com/thank_you?tx=XXXXX

When a transaction is canceled, redirect the user to the cancel_url

https://example.com/cancel

Where success_url is a valid URI. Wallets must check if they can open such url before attempting to open url Where cancel_url is a valid URI. Wallets must check if they can open such url before attempting to open url

Use Case: Using solana pay transaction request to make payment. There is no seamless way to redirect back to the app without the user manually visiting the calling app.

https://user-images.githubusercontent.com/14053870/192168278-b863d3ad-d4a9-46a7-99ad-aadb24d929f6.mp4

Success flow Notice the way the app is redirected when the user taps on done on the Binance app using Binance Pay

https://user-images.githubusercontent.com/14053870/192168606-348f6cee-4f69-4623-bc94-9dd7a044194f.mp4

Cancel flow

https://user-images.githubusercontent.com/14053870/192168677-27579d85-304e-4b14-8a03-d6085aa34122.mp4

uveerma commented 1 year ago

Specification Changes:
The success and cancel redirect URLs should be limited for Transaction Requests and don't hold much usage for Transfer Requests. As @peerwaya shared above, the POST response will change this way,

HTTP/1.1 200 OK
Connection: close
Content-Type: application/json
Content-Length: 298
Content-Encoding: gzip

{"message":"Thanks for all the fish","transaction":"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAECC4JMKqNplIXybGb/GhK1ofdVWeuEjXnQor7gi0Y2hMcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAAMAgAAAAAAAAAAAAAA", "success_url": "https://example.com/thank_you", "cancel_url": "https://example.com/cancel"}

with success_url and cancel_url as two new optional JSON fields. Wallets should verify the validity of these URLs beforehand and should not support HTTP protocol URLs.

Workflow:
As shown in the above reference video of Binanace Pay and how deeplinks of wallet providers for transaction signing works, redirect URls will work in a similar manner for all modes of usage including app to app and website to app -- where mode of payment is online with a single mobile device in action. For POS solutions, there's no direct usage for either of the redirect URLs, but providers may redirect users to a website for downloading e-receipts on each successful purchase

Usecases:

jordaaash commented 1 year ago

Hmm, I don't think we want/need two fields for this. As length of the string increases, so does QR code density and likelihood of scanning problems. A single field (e.g. redirect-url, but I'm not attached to the name yet) with a URL-encoded URL value should be sufficient. That URL could be parameterized with a transaction signature on success, or an error field (https://github.com/solana-labs/solana-pay/issues/150). Cancellation could be represented either by a specific error, or by a lack of a transaction signature or an error.

redirect URLs should be limited for Transaction Requests and don't hold much usage for Transfer Requests

@Vampo7152 can you explain why this is the case?

In the above examples, I've only seen https URLs used. However, several of the use cases you're describing sounds like it requires potentially using deep links, universal links, or app links. Is this accurate? And what implications does that have?

jordaaash commented 1 year ago

@peerwaya

Wallets must check if they can open such url before attempting to open url

What do you mean by this?

For POS solutions, there's no direct usage for either of the redirect URLs, but providers may redirect users to a website for downloading e-receipts on each successful purchase

In general, this feature seems highly likely to be used for all kinds of marketing purposes and information collection (e.g. tying emails to wallet addresses). I'll solicit feedback from wallets on this once we have a practical proposal.

When combined with opening a URL on cancellation, it's also hairy -- someone scans a QR code, it makes a request, they decline to sign, then it automatically opens a URL on their phone (with their wallet address known). I can see wallets having issues with this.

peerwaya commented 1 year ago

on native android and ios apps, an API exist to check if a url can be opened by an existing app eg chrome browser, safari or app. On flutter and react native it's as simple as calling canOpenUrl(url) to check if the url can be opened by an app on the device. if such exists, then the next step will be to call launchUrl(url). This is to ensure there exist an app that can handle such url. Custom url schemes used for deeplinking might be tricky with recent versions of Android and iOS as they require apps to query a known list of scheme that must be registered on the manifest or info.plist file. Therefore, the use of universal links or Web Links might be preferred.

cancelUrl does not have to pass any new information. Though the address was made known during the initial post request

POST /solana-pay?order=12345 HTTP/1.1
Host: example.com
Connection: close
Accept: application/json
Accept-Encoding: br, gzip, deflate
Content-Type: application/json
Content-Length: 57

{"account":"mvines9iiHiQTysrwkJjGf2gb9Ex9jXJX8ns3qwf2kN"}

and the server responded with


{"message":"Thanks for all the fish","transaction":"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAECC4JMKqNplIXybGb/GhK1ofdVWeuEjXnQor7gi0Y2hMcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQECAAAMAgAAAAAAAAAAAAAA", "success_url": "https://example.com/thank_you", "cancel_url": "https://example.com/cancel"}```
uveerma commented 1 year ago

redirect URLs should be limited for Transaction Requests and don't hold much usage for Transfer Requests

The initial idea for redirect URLs was to be unique based on the params generated in the POST response for Transaction requests, it can be added to Transfer requests as well but then the URL will be static and can get too dense if the use case is SPL Token Transfer with a memo added along.

When combined with opening a URL on cancellation, it's also hairy -- someone scans a QR code, it makes a request, they decline to sign, then it automatically opens a URL on their phone (with their wallet address known). I can see wallets having issues with this.

Yeah, cancel URLs don't help much as all transactions which can't be performed by the public key get disabled by the wallet provider already, and canceling the transaction becomes a voluntary action so we can disregard it considering security concerns based on the example you shared above.

jordaaash commented 1 year ago

Transfer requests [...] then the URL will be static

Not sure I follow this. While transfer requests can use static URLs, they are generally designed to use unique URLs because at least one reference key is expected to be unique per transaction, and reference keys can be used for all kinds of things (for example, passing encrypted data around).

If a transfer request can encode a redirect URL field and then will receive a transaction signature as a parameter to it, then the value of the field can be static (e.g. https://example.com/payment?transaction=xxx) or unique (e.g. https://example.com/payment/<reference>?transaction=xxx).

canceling the transaction [...] we can disregard it

I think it's worth fully detailing what pros and cons they might have before we throw the idea out!

peerwaya commented 1 year ago

cancel_url might not be needed if we stick to only redirect_url and append an error_code param. This way, the error_code param can also be used to indicate a specific error eg user_canceled.

https://example.com/payment?error_code=user_canceled

error can be a an enum with the following values:

user_canceled,
transaction_failed,
metadata_error
unknown,

error_message can optionally be appended to describe the error in more details.

jordaaash commented 1 year ago

Let's define errors consistent with #150 (tbd)

But I don't think an error is strictly needed on cancellation since callback with no transaction signature and no error implies the request was declined. Wallet errors can be reserved for connection failures and transaction failures.

samheutmaker commented 1 year ago

I'm glad to see this being discussed. A few thoughts:

When combined with opening a URL on cancellation, it's also hairy -- someone scans a QR code, it makes a request, they decline to sign, then it automatically opens a URL on their phone (with their wallet address known). I can see wallets having issues with this.

I think wallets will be concerned if the spec requires that they automatically redirect users to a URL post-transaction, whether it fails or succeeds. Instead of this, it may be better to make following redirect urls optional even if they are specified in the tx response. This allows wallets to decide if they want to auto-follow links, prompt the user to follow the link, or disallow following links. Going this route also means that these changes can be incrementally adopted by wallets while still remaining compliant with the spec. This also disallows dapp developers from be able to rely on the redirectUrl being visited after a completed transaction, which may not be ideal.

Hmm, I don't think we want/need two fields for this.

I agree that redirectUrl (or something similar) with params passed to indicate state is probably best.

Let's define errors consistent with https://github.com/solana-labs/solana-pay/issues/150 (tbd)

Definitely. This work should be completed by mid-November.

The use-cases mentioned above all seem valid. It may be worth trying to flesh out a few more of these by talking with teams that are building similar tech to make sure that we're covering our bases.

My main concern with this work is that the onus is on wallets to adopt new Solana Pay features. With #150 and #151 scheduled for release in November, and this issue stacked on top, it's beginning to look like a fairly large ask for wallets to upgrade. We should figure out how we can release all these features together so that we can go to wallets with a single request to upgrade rather than releasing changes for the first two issues and then going back one or two months laters with another ask to add support for redirectUrls.

jordaaash commented 1 year ago

We should figure out how we can release all these features together

Generally agreed, though the way I see it is --

Jeffrieh commented 1 year ago

Hi guys, any updates on this ?

jordaaash commented 1 year ago

@Jeffrieh please don't comment on issues like this, it notifies everyone. Instead, just subscribe to the issue.

It hasn't been determined that this is a feature that will be added to the spec, and even if it is, if it will be implemented.

mcintyre94 commented 1 year ago

I've been taking a closer look at the existing deeplink functionality in Phantom + Solflare, with a view to making sure that whatever we come up with is similar in functionality (wrt redirects) and provides the same security.

It is correct that all the deeplink methods take a redirect_link (it's actually required). They don't have separate success/errors, the app can distinguish them by the query params. One thing I've noticed is that the wallets display the redirect URL in full, before doing the connect/transaction/etc.

Deeplinks also return data from the wallet back to the redirect URL, encrypted with a shared secret (details here). This can be things like the connected public key, a transaction signature, and the connect token that needs to be passed into further requests. This is probably not feasible for Solana Pay:

So I think the idea of having anything sent from the wallet on redirect doesn't fit well with Solana Pay.

Because of that, for transaction requests we'd like the redirect URL to be dynamic, so that the API can put the information that it needs into it. This suggests that it should be returned by the POST request (along with the transaction), since that's the only time the API knows about the user's wallet/the transaction etc. This importantly means that the wallet will be unable to display the redirect link before the user interacts, which is a different from deeplinks.

Here's roughly how I think this would work, for transfer and transaction requests

Transfer requests:

Transaction requests:

The redirect link can be any https: or solana: URL, including a universal link, or another Solana pay URL. Since it's defined by the POST API, there's a ton of flexibility here. We're thinking we'd block deeplinks here (except solana) for security.

Allowing a Solana Pay URL would allow chaining transactions, see https://twitter.com/jordaaash/status/1597292945090129921?s=20&t=RN7z3SwXZxcRBzzRub5E4g

But probably means that we need to have the behaviour that cancelling the request means the redirect is not followed, to avoid a malicious infinite request loop

So in summary, the main differences from deeplinks are:

Would love to hear any thoughts on this!

mcintyre94 commented 1 year ago

We're going to be looking to specify this shortly. If anyone has any feedback on the above, please let us know so that we can discuss and incorporate it!

Majidrazaee commented 1 year ago

عالیه