Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
"@stripe/stripe-js": "^8.9.0",
"@vercel/blob": "^2.3.1",
"mermaid": "^11.12.2",
"mppx": "https://pkg.pr.new/mppx@231",
"mppx": "https://pkg.pr.new/mppx@283",
"react": "^19",
"react-dom": "^19",
"stripe": "^20.4.1",
"tailwindcss": "^4.1.18",
"viem": "^2.46.2",
"viem": "^2.47.5",
"vocs": "https://pkg.pr.new/wevm/vocs@319c55c",
"wagmi": "^3.4.2",
"waku": "^1.0.0-alpha.4"
Expand All @@ -54,6 +54,7 @@
"typescript": "^5",
"unplugin-icons": "^23.0.1",
"vite": "^7",
"vite-plugin-mkcert": "^1.17.10",
"vitest": "^4.0.18"
},
"packageManager": "pnpm@10.22.0",
Expand Down
206 changes: 155 additions & 51 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions src/components/PaymentLinkDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use client";

const PAYMENT_LINK_URL = "/api/payment-link/photo";

export function PaymentLinkDemo() {
return (
<div className="not-prose">
<div
className="rounded-xl overflow-hidden"
style={{
border: "1px solid light-dark(#e5e5e5, #262626)",
height: 420,
}}
>
<iframe
src={PAYMENT_LINK_URL}
title="Payment link demo"
style={{
border: "none",
display: "block",
height: 560,
transform: "scale(0.75)",
transformOrigin: "top left",
width: "133.33%",
}}
/>
</div>
</div>
);
}
11 changes: 11 additions & 0 deletions src/components/cards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ export function PayAsYouGoCard() {
);
}

export function PaymentLinksCard() {
return (
<Card
description="Create a link. Get paid."
icon="lucide:link"
title="Create a payment link"
to="/guides/payment-links"
/>
);
}

export function ProxyExistingServiceCard() {
return (
<Card
Expand Down
57 changes: 57 additions & 0 deletions src/mppx-payment-link.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Mppx, tempo } from "mppx/server";
import { createClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { tempoModerato } from "viem/chains";

const realm = process.env.REALM ?? "mpp.tempo.xyz";
const account = privateKeyToAccount(
(process.env.FEE_PAYER_PRIVATE_KEY ??
"0x0000000000000000000000000000000000000000000000000000000000000001") as `0x${string}`,
);

export const mppx = Mppx.create({
methods: [
tempo({
account,
currency: import.meta.env.VITE_DEFAULT_CURRENCY!,
feePayer: true,
getClient() {
return createClient({
chain: tempoModerato,
transport: http(
import.meta.env.RPC_URL ?? "https://rpc.moderato.tempo.xyz",
),
});
},
html: {
theme: {
accent: ["#000000", "#ffffff"],
background: ["#ffffff", "#0a0a0a"],
border: ["#e5e5e5", "#262626"],
colorScheme: "light dark",
fontFamily: "'Geist', system-ui, sans-serif",
fontSizeBase: "16px",
foreground: ["#0a0a0a", "#fafafa"],
logo: {
dark: "/logo-light.svg",
light: "/logo-dark.svg",
},
muted: ["#737373", "#a3a3a3"],
negative: ["#ef4444", "#f87171"],
positive: ["#22c55e", "#4ade80"],
radius: "8px",
spacingUnit: "4px",
surface: ["#f5f5f5", "#171717"],
},
text: {
paymentRequired: "Payment Required",
title: "MPP — Payment Required",
},
},
sse: true,
testnet: true,
}),
],
realm,
secretKey: "demo",
});
1 change: 1 addition & 0 deletions src/pages.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ type Page =
| { path: '/guides/multiple-payment-methods'; render: 'static' }
| { path: '/guides/one-time-payments'; render: 'static' }
| { path: '/guides/pay-as-you-go'; render: 'static' }
| { path: '/guides/payment-links'; render: 'static' }
| { path: '/guides/proxy-existing-service'; render: 'static' }
| { path: '/guides/split-payments'; render: 'static' }
| { path: '/guides/streamed-payments'; render: 'static' }
Expand Down
8 changes: 4 additions & 4 deletions src/pages/_api/api/demo/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,12 @@ export default async function handler(request: Request) {
const text = content.replaceAll("\n", "\t");
const tokens = tokenize(text);

return result.withReceipt(async function* (stream) {
return result.withReceipt(async function* (stream: any) {
for (const token of tokens) {
await stream.charge();
yield token;
}
});
} as any);
}
console.warn(
"[demo/chat] OpenAI response did not contain message content",
Expand Down Expand Up @@ -151,10 +151,10 @@ export default async function handler(request: Request) {
].join("\t");
const tokens = tokenize(text);

return result.withReceipt(async function* (stream) {
return result.withReceipt(async function* (stream: any) {
for (const token of tokens) {
await stream.charge();
yield token;
}
});
} as any);
}
4 changes: 2 additions & 2 deletions src/pages/_api/api/demo/poem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ export default async function handler(request: Request) {
const text = poem.join("\t");
const tokens = tokenize(text);

return result.withReceipt(async function* (stream) {
return result.withReceipt(async function* (stream: any) {
for (const token of tokens) {
await stream.charge();
yield token;
}
});
} as any);
}
61 changes: 61 additions & 0 deletions src/pages/_api/api/payment-link/photo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { mppx } from "../../../../mppx-payment-link.server";

export async function GET(request: Request) {
const result = await mppx.charge({
amount: "0.01",
description: "A random unique image",
})(request);

if (result.status === 402) return result.challenge;

const res = await fetch("https://picsum.photos/1024/1024");
const imageUrl = res.url;

const html = `<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Photo — MPP Demo</title>
<style>
:root { color-scheme: dark light; }
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: 32px;
font-family: system-ui, -apple-system, sans-serif;
background: light-dark(#fafafa, #0a0a0a);
color: light-dark(#111, #eee);
}
img {
max-width: 480px;
width: 100%;
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0,0,0,0.15);
}
p {
font-size: 13px;
color: light-dark(#666, #888);
}
</style>
</head>
<body>
<img src="${imageUrl}" alt="Random photo from Picsum" />
<p>Paid via MPP — $0.01</p>
</body>
</html>`;

return result.withReceipt(
new Response(html, {
headers: {
"Cache-Control": "no-store",
"Content-Type": "text/html; charset=utf-8",
},
}),
);
}
4 changes: 2 additions & 2 deletions src/pages/_api/api/sessions/poem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ export default async function handler(request: Request) {
const poem = poems[Math.floor(Math.random() * poems.length)];
const words = poem.lines.flatMap((line) => [...line.split(" "), "\\n"]);

return result.withReceipt(async function* (stream) {
return result.withReceipt(async function* (stream: any) {
yield JSON.stringify({ title: poem.title, author: poem.author });
for (const word of words) {
await stream.charge();
yield word;
}
});
} as any);
}
Loading
Loading