feat(api): auto-generated Open Graph images for links + invoices (#165) - #166
Draft
rahXephonz wants to merge 1 commit into
Draft
feat(api): auto-generated Open Graph images for links + invoices (#165)#166rahXephonz wants to merge 1 commit into
rahXephonz wants to merge 1 commit into
Conversation
Render a per-document 1200x630 PNG preview card for payment links and invoices so a shared pay-host URL unfurls with the merchant name and the human-form amount instead of the generic landing image. - apps/api/src/services/og-image.ts: satori (element tree -> SVG) + @resvg/resvg-js (SVG -> PNG). One template, brand palette from checkout-ui. Money via formatMoneyLocale (id-ID human form), never a number. Static Geist font instances bundled in apps/api/assets (the variable TTF chokes satori's opentype parser). - /checkout/:id/og.png and /invoices/:id/og.png routes; missing/disabled/ voided or render failure -> cached generic card, 200, never 500. - checkout-shell injects per-document og:image/twitter:image meta via a new <!--__OG__--> placeholder; link + invoice pages build it from the document, pay page keeps the generic landing image (intent id is unguessable, not shared). - pay-proxy allowlist forwards /invoices/:id/og.png (/checkout/:id/og.png already covered by the /checkout prefix). - og:image url is same-origin via requestOrigin, so pay-host links point at the pay host through the proxy. 232 api tests + pay-proxy allowlist tests green; typecheck + format clean.
rahXephonz
marked this pull request as draft
August 18, 2026 06:27
This was referenced Aug 19, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements RFC #165.
What
Render a per-document 1200×630 PNG preview card for payment links and
invoices, and inject the matching
og:image/twitter:imagemeta tags intothe checkout and invoice page shells. A
pay-testnet.mayarin.xyz/checkout/:idor
/invoices/:id/viewlink shared in WhatsApp / Telegram / X now unfurls withthe merchant name and the human-form amount instead of the generic landing
image.
How
apps/api/src/services/og-image.ts—satori(element tree → SVG) +@resvg/resvg-js(SVG → PNG). One template, brand palette lifted fromapps/checkout-ui/src/styles.css. Money is rendered withformatMoneyLocale(the
id-IDhuman form) — nevertoDecimalString, never anumber.geist-regular/semibold/bold.ttf) bundledin
apps/api/assets/. The variable TTF chokes satori's bundled opentypeparser on the
fvaraxes, so static instances are required. OFL licenseincluded.
GET /checkout/:id/og.pngandGET /invoices/:id/og.png.Missing / disabled / voided document or any render failure → cached generic
card, 200, never 500 (a shared link never loses its preview card).
Cache-Control: public, max-age=86400(link content is immutable aftercreate).
checkout-shell.tsinjects per-document OG meta via a new<!--__OG__-->placeholder (alongside<!--__BOOTSTRAP__-->). Link + invoicepages build it from the document; the pay page keeps the generic landing image
(its intent id is unguessable, not a shared URL).
og:imageURL — same-origin viarequestOrigin(RFC RFC: Dedicated checkout host — pay.mayarin.xyz (Phase 4) #163), so a pay-hostlink points at
pay-testnet.../checkout/:id/og.pngthrough the proxy./invoices/:id/og.png(the/checkout/:id/og.pngis already covered by the/checkoutprefix).Cards
formatMoneyLocale(total)+ asset code.Pay {merchant}when it has none.Invoice {number}+ outstanding total.Settledstate label + total. Voided →Voidedlabel.Verification
bun run typecheck— green.bun test— 1329 pass, 0 fail. New tests:og-image.test.ts(card + metabuilders, pure) and
checkout-shell.test.ts(/checkout/:id/og.pngrenders areal PNG, missing link → 200 generic, page shells carry the right
og:image).apps/checkout-uibuild preserves both placeholders indist/index.html.Out of scope (per RFC #165 non-goals)
AI image generation, per-merchant branded backgrounds, merchant logo, video
previews, mainnet hosting, OG for landing/dashboard/demo.
Closes #165.