anza-xyz / solana-pay

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

Be able to create the QR image Server side, stick it in an <img> and send the html to client #145

Closed ranka23 closed 2 years ago

ranka23 commented 2 years ago

Currently, one can't create the QR image on the backend, stick it in an <img src={...}> and send the entire html across the wire to the client to render the html directly. This is because the lib qr-code-styling.js requires document to work. document is undefined on NodeJS. Since it's related to payments, I would rather want to send the entire html across the wire rather than just the URL and tinker with it on the client. Else the client seems a bit smart taking up some of the server's responsibility. Any idea how we can achieve this? Also, if I'm wrong and this is possible please do correct me. Thank you.

ranka23 commented 2 years ago

As usual, found the answer after posting the issue.

What you can do, if you want to generate the QR Code on the server and send the QR code to the client, instead of sending the encoded url and then doing more work on the front-end to actually generate the QR code.

...

const createSolanaOrder = async (_req: Request, res: Response) => {

// get the order details (The encoded url) const url = getOrderDetails()

// Replace the below line from the docs example, which uses the createQR(url) from @solana/pay // -> const qrCode = createQR(url); with the line below

// Where the magic happens. The .toString() is required else it won't work. const qr = await QRCode.toDataURL(url.toString())

// create an html string. You can also use ejs or another template engine const html = <img src=${qr} >

// send the html string in the response return res.send({ __html: html }) }


Now, I'm a React guy, so on the client, you can do something like this.

import { useEffect, useState } from "react"

const SolanaPay: React.FC = () => { const [html, setHTML] = useState({ __html: "" })

useEffect(() => { const getSolQR = async () => { const res = await axios.get<{ __html: "" }>(/checkout/solana) setHTML(res.data) } getSolQR() }, [])

return <>{html.__html ?

: }</> }

export default SolanaPay



I've tested this with the Phantom wallet. Seems to be working for me.

Personally, I found this to be a much simpler implementation without manipulating the DOM directly or creating refs to access elements. 

More importantly, the client is merely painting what data the server returns and not doing more work. Ideally, it should not.

Then again I may be wrong and I've perhaps overlooked something. Maybe the `.toString()` in the controller messes something up in the encoded url? or it's an unsafe implementation. I am not too sure. I'll leave this issue open for now, so others can have a look and pitch in. If this implementation seems acceptable, please feel free to close the issue. 

Wish you a pleasant day reader.  😊