solana-pay
solana-pay copied to clipboard
[feature request] Support for redirect urls on txn success
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.
- Similarly, there can be a case where a user paid to buy something with Solana pay on web apps (not taking pos reference here) but is confused if their user was actually marked paid or not.
- Businesses accepting Solana pay will expect their user to come back to their site and keep surfing more post a successful order, instead of remaining on the wallet provider apps, without a specific action on what to do next
- NFT and Payment Providers would like to show users with actual NFT minted and/or the invoice of payment made respectively as soon as the txn is confirmed, instead of having them wait and figure things out themselves
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!
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?
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
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:
-
Payment Providers:
- Recently we have been working on a Checkout SDK which takes in success and cancel redirect URLs from merchants while creating sessions, the ux is seamless for desktop transactions done with wallet extensions but for mobile, user remains stagnant on the wallet apps once payment is success/cancelled meanwhile the checkout site redirects them back to the provided URLs directly in the browser, so its a broken UX currently but can become flawless with this feature addition.
- POS providers can redirect users to download e-receipts for their purchases or to some website where they can claim special offers based on their purchase metrics e.g. purchase above $500 and claim special coupon/NFT
-
Businesses:
- Businesses using payment providers or having their in-house solution, would prefer user get redirected back to their website/app post purchase/payment to surf more products and keep user engaged without this broken link post payment. SASS services would like to have user start using services instantly after purchase and so on
-
Users:
-
success
andcancel
redirect URLs are important in terms of user experience to make it familiar with other web2 solutions like GPay, Apple Pay, etc. to provide a seamless user flow - Whether it's NFT minting, Payments, Token swap or any usecase we are missing here, instant result showcase based on the action taken by user will make them feel engaged and rest assured
-
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?
@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.
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"}```
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.
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!
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.
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.
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.
We should figure out how we can release all these features together
Generally agreed, though the way I see it is --
- #152 is binary -- either it works or it doesn't. Wallets definitely have to do something here.
- #150 is optional -- if an HTTP error response is returned, the extra context of a standard error body is nice but the wallet doesn't strictly need to do anything with it.
- #162 is optional -- iff redirects aren't required to be followed. A wallet can choose to ignore the field.
Hi guys, any updates on this ?
@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.
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:
- We don't want apps to have to trust the wallet. In Solana Pay (unlike deeplinks), the app doesn't control or know which wallet is being used, and can't trust what it receives from the wallet.
- For transaction requests, you'd need to have a private key of a keypair generated by the API returning the transaction, and then used to decrypt at the redirect URL. I think the data flow here is just too different to deeplinks to rely on the same sort of encryption.
- I don't think wallets would want to be sending things like connected wallets and transaction signatures in plaintext, and we wouldn't want to encourage that
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:
- A new optional query parameter that takes a URL-encoded redirect URL. In practice the app would probably want this tov include an encrypted form of the
reference
, but that's outside the spec. - Before the transfer, the wallet can show this redirect link as it currently does with deeplinks
- After the transfer, the wallet can redirect the user to that link, as-is
Transaction requests:
- A new optional field in the POST response that takes a redirect URL. In practice the app would probably want to include an encrypted form of account keys, signatures, internal identifiers, etc.
- Before the transaction, the wallet cannot show this redirect link because it won't know it
- After the transaction, the wallet should show this redirect and allow the user to follow it, as-is
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:
- In Solana Pay we will never send data from the wallet to the redirect URL, it's just a redirect. Everything the app needs to know will need to be in this redirect link.
- In transaction requests the redirect link is dynamic and not known before sending the transaction
Would love to hear any thoughts on this!
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!
عالیه