Skip to content

Commit e006180

Browse files
authored
Revert "feat: dynamic product checkout buttons (MDK-403)"
1 parent b478884 commit e006180

8 files changed

Lines changed: 249 additions & 413 deletions

File tree

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ coverage
1919
.idea
2020
.vscode
2121
*.tgz
22-
!**/local-packages/*.tgz
2322
.secrets
2423
.envrc
2524
.direnv

mdk-nextjs-demo/Dockerfile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ WORKDIR /app
99
# Install curl and jq for healthchecks and JSON manipulation
1010
RUN apk add --no-cache curl libc6-compat jq
1111

12-
# Copy package files and local packages
12+
# Copy package files
1313
COPY package.json package-lock.json* ./
14-
COPY local-packages ./local-packages
1514

1615
# Copy local tarball dependencies (created by CI when testing PRs from mdk-checkout)
1716
# These files are always created by the e2e-reusable workflow before building this image.
Lines changed: 1 addition & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1 @@
1-
import { NextRequest } from "next/server";
2-
3-
// Webhook secret header
4-
const WEBHOOK_SECRET_HEADER = 'x-moneydevkit-webhook-secret';
5-
6-
// Lazy load the default handler
7-
let defaultHandlerPromise: Promise<(request: Request) => Promise<Response>> | null = null;
8-
function getDefaultHandler() {
9-
if (!defaultHandlerPromise) {
10-
defaultHandlerPromise = import("@moneydevkit/nextjs/server/route").then(m => m.POST);
11-
}
12-
return defaultHandlerPromise;
13-
}
14-
15-
// Helper to sleep for a given number of milliseconds
16-
function sleep(ms: number): Promise<void> {
17-
return new Promise(resolve => setTimeout(resolve, ms));
18-
}
19-
20-
// Custom webhook handler with proper sync and retry logic
21-
async function handleWebhookWithSync(request: NextRequest): Promise<Response> {
22-
const body = await request.json();
23-
24-
// Validate webhook secret
25-
const expectedSecret = process.env.MDK_ACCESS_TOKEN;
26-
if (!expectedSecret) {
27-
console.error('[webhook] MDK_ACCESS_TOKEN not configured');
28-
return new Response(JSON.stringify({ error: 'Webhook secret not configured' }), {
29-
status: 500,
30-
headers: { 'Content-Type': 'application/json' },
31-
});
32-
}
33-
34-
const providedSecret = request.headers.get(WEBHOOK_SECRET_HEADER);
35-
if (!providedSecret || providedSecret !== expectedSecret) {
36-
console.error('[webhook] Unauthorized webhook request. Expected:', expectedSecret.substring(0, 8) + '..., Got:', providedSecret?.substring(0, 8) + '...');
37-
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
38-
status: 401,
39-
headers: { 'Content-Type': 'application/json' },
40-
});
41-
}
42-
43-
if (body.event !== 'incoming-payment') {
44-
console.log('[webhook] Unknown event type:', body.event);
45-
return new Response('OK', { status: 200 });
46-
}
47-
48-
console.log('[webhook] Processing incoming-payment event with node sync and retry');
49-
50-
try {
51-
// Dynamically import to avoid bundling issues
52-
const { createMoneyDevKitNode, createMoneyDevKitClient, markPaymentReceived } = await import("@moneydevkit/core");
53-
54-
const client = createMoneyDevKitClient();
55-
56-
// Retry logic: try up to 5 times with increasing delays
57-
const maxRetries = 5;
58-
const delays = [1000, 2000, 3000, 5000, 8000]; // Total: up to 19 seconds of waiting
59-
60-
let payments: Array<{ paymentHash: string; amount: number }> = [];
61-
62-
for (let attempt = 0; attempt < maxRetries; attempt++) {
63-
// Create a fresh node instance for each attempt
64-
const node = createMoneyDevKitNode();
65-
66-
// CRITICAL: Sync wallets BEFORE checking for payments
67-
console.log(`[webhook] Attempt ${attempt + 1}/${maxRetries}: Syncing wallets...`);
68-
node.syncWallets();
69-
console.log(`[webhook] Attempt ${attempt + 1}/${maxRetries}: Wallet sync complete`);
70-
71-
// Now receive payments with the synced state
72-
console.log(`[webhook] Attempt ${attempt + 1}/${maxRetries}: Checking for received payments...`);
73-
payments = node.receivePayments();
74-
console.log(`[webhook] Attempt ${attempt + 1}/${maxRetries}: Found ${payments.length} payment(s)`);
75-
76-
if (payments.length > 0) {
77-
break; // Found payments, exit retry loop
78-
}
79-
80-
// If no payments found and we have more retries, wait before trying again
81-
if (attempt < maxRetries - 1) {
82-
const delayMs = delays[attempt];
83-
console.log(`[webhook] No payments found, waiting ${delayMs}ms before retry...`);
84-
await sleep(delayMs);
85-
}
86-
}
87-
88-
if (payments.length === 0) {
89-
console.log('[webhook] No payments found after all retries');
90-
return new Response('OK', { status: 200 });
91-
}
92-
93-
// Mark payments as received locally
94-
payments.forEach((payment: { paymentHash: string }) => {
95-
console.log(`[webhook] Marking payment ${payment.paymentHash} as received`);
96-
markPaymentReceived(payment.paymentHash);
97-
});
98-
99-
// Notify MDK API about received payments
100-
try {
101-
console.log('[webhook] Notifying MDK API about payments...');
102-
await client.checkouts.paymentReceived({
103-
payments: payments.map((payment: { paymentHash: string; amount: number }) => ({
104-
paymentHash: payment.paymentHash,
105-
amountSats: payment.amount / 1000,
106-
sandbox: false,
107-
})),
108-
});
109-
console.log('[webhook] MDK API notified successfully');
110-
} catch (error) {
111-
console.error('[webhook] Failed to notify MDK API:', error);
112-
// Don't throw - local state is already marked
113-
}
114-
115-
return new Response('OK', { status: 200 });
116-
} catch (error) {
117-
console.error('[webhook] Error processing webhook:', error);
118-
return new Response(JSON.stringify({ error: 'Internal server error' }), {
119-
status: 500,
120-
headers: { 'Content-Type': 'application/json' },
121-
});
122-
}
123-
}
124-
125-
export async function POST(request: NextRequest): Promise<Response> {
126-
// Clone the request so we can read the body multiple times
127-
const clonedRequest = request.clone();
128-
129-
try {
130-
const body = await clonedRequest.json();
131-
const handler = body?.handler?.toLowerCase?.() ?? body?.route?.toLowerCase?.() ?? body?.target?.toLowerCase?.();
132-
133-
// Handle webhook requests with our custom sync logic
134-
if (handler === 'webhooks' || handler === 'webhook') {
135-
// Create a new request with the parsed body since we already consumed it
136-
return handleWebhookWithSync(request);
137-
}
138-
} catch {
139-
// If JSON parsing fails, let the default handler deal with it
140-
}
141-
142-
// For all other requests, use the default handler
143-
const defaultHandler = await getDefaultHandler();
144-
return defaultHandler(request);
145-
}
1+
export { POST } from "@moneydevkit/nextjs/server/route";

mdk-nextjs-demo/app/page.tsx

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { useCheckout, useProducts } from "@moneydevkit/nextjs";
3+
import { useCheckout } from "@moneydevkit/nextjs";
44
import Link from "next/link";
55
import { useMemo, useState } from "react";
66

@@ -21,7 +21,6 @@ export default function HomePage() {
2121
const [customerName, setCustomerName] = useState("Satoshi Nakamoto");
2222
const [note, setNote] = useState("Fast IBD snapshot with hosted checkout.");
2323
const { navigate, isNavigating } = useCheckout();
24-
const { products, isLoading: productsLoading } = useProducts();
2524

2625
const metadata = useMemo(
2726
() => ({
@@ -44,26 +43,6 @@ export default function HomePage() {
4443
});
4544
};
4645

47-
const handleProductCheckout = () => {
48-
if (products.length === 0) return;
49-
navigate({
50-
// Single product checkout - uses first available product
51-
productId: products[0].id,
52-
metadata,
53-
checkoutPath: "/checkout",
54-
});
55-
};
56-
57-
const handleMultiProductCheckout = () => {
58-
if (products.length < 2) return;
59-
navigate({
60-
// Multiple products checkout - uses first two available products
61-
products: [products[0].id, products[1].id],
62-
metadata,
63-
checkoutPath: "/checkout",
64-
});
65-
};
66-
6746
return (
6847
<main className="page">
6948
<div className="container">
@@ -116,30 +95,8 @@ export default function HomePage() {
11695
disabled={isNavigating}
11796
data-test="start-checkout"
11897
>
119-
{isNavigating ? "Creating checkout…" : "Launch checkout (Amount)"}
98+
{isNavigating ? "Creating checkout…" : "Launch checkout"}
12099
</button>
121-
{products.length >= 1 && (
122-
<button
123-
type="button"
124-
className="button"
125-
onClick={handleProductCheckout}
126-
disabled={isNavigating || productsLoading}
127-
style={{ marginTop: "0.5rem", background: "#2563eb" }}
128-
>
129-
{isNavigating ? "Creating checkout…" : `Launch checkout (${products[0].name})`}
130-
</button>
131-
)}
132-
{products.length >= 2 && (
133-
<button
134-
type="button"
135-
className="button"
136-
onClick={handleMultiProductCheckout}
137-
disabled={isNavigating || productsLoading}
138-
style={{ marginTop: "0.5rem", background: "#7c3aed" }}
139-
>
140-
{isNavigating ? "Creating checkout…" : "Launch checkout (2 Products)"}
141-
</button>
142-
)}
143100
<p className="hint">
144101
We create a checkout session with the values above and redirect to
145102
{" /checkout/[id] "} using <code>useCheckout</code>.
-61.8 KB
Binary file not shown.
-15.9 KB
Binary file not shown.

0 commit comments

Comments
 (0)