Skip to content

Commit 3308272

Browse files
ralyodioclaude
andcommitted
fix: handle DB errors in bounty creation and fix payment redirect
The POST /api/bounties handler had no try/catch around the DB insert, so any DB failure (e.g. SQLite Cloud free node paused) returned a non-JSON 500. The browser's res.json() then threw, surfacing the opaque "Something went wrong. Please try again." with no detail. - Wrap the insert + CoinPay payment logic in try/catch, returning a proper JSON 500 { error: 'Failed to create bounty. Please try again.' }. - Fix the new-bounty page redirect: it checked data.checkout_url, but the API returns data.pay_url. Users were never sent to CoinPay to fund the bounty; now they are redirected to pay_url. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent acdee9b commit 3308272

2 files changed

Lines changed: 59 additions & 54 deletions

File tree

apps/web/app/api/bounties/route.ts

Lines changed: 56 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -36,63 +36,68 @@ export async function POST(req: NextRequest) {
3636
if (isNaN(reward) || reward < 0.10) return NextResponse.json({ error: 'Minimum reward is $0.10' }, { status: 400 });
3737
if (!store_id && !store_name?.trim()) return NextResponse.json({ error: 'Store is required' }, { status: 400 });
3838

39-
const db = getDb();
39+
try {
40+
const db = getDb();
4041

41-
// Insert bounty as 'open' first to get an ID
42-
await db.sql`
43-
INSERT INTO bounties (creator_did, store_id, store_name, title, description, reward_usd, status)
44-
VALUES (
45-
${did},
46-
${store_id ?? null},
47-
${store_name?.trim() ?? null},
48-
${title.trim()},
49-
${description?.trim() ?? null},
50-
${reward},
51-
'open'
52-
)
53-
`;
54-
const [{ id: bountyId }] = await db.sql`
55-
SELECT id FROM bounties WHERE creator_did = ${did} ORDER BY id DESC LIMIT 1
56-
`;
42+
// Insert bounty as 'open' first to get an ID
43+
await db.sql`
44+
INSERT INTO bounties (creator_did, store_id, store_name, title, description, reward_usd, status)
45+
VALUES (
46+
${did},
47+
${store_id ?? null},
48+
${store_name?.trim() ?? null},
49+
${title.trim()},
50+
${description?.trim() ?? null},
51+
${reward},
52+
'open'
53+
)
54+
`;
55+
const [{ id: bountyId }] = await db.sql`
56+
SELECT id FROM bounties WHERE creator_did = ${did} ORDER BY id DESC LIMIT 1
57+
`;
5758

58-
// Create a CoinPay payment for the creator to fund the bounty
59-
// Docs: POST /api/payments/create
60-
let paymentAddress: string | null = null;
61-
let paymentId: string | null = null;
59+
// Create a CoinPay payment for the creator to fund the bounty
60+
// Docs: POST /api/payments/create
61+
let paymentAddress: string | null = null;
62+
let paymentId: string | null = null;
6263

63-
try {
64-
const res = await fetch(`${COINPAY_BASE}/api/payments/create`, {
65-
method: 'POST',
66-
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_KEY}` },
67-
body: JSON.stringify({
68-
business_id: MERCHANT_ID,
69-
amount_usd: reward,
70-
currency: 'usdc_pol', // default to USDC on Polygon — low fees
71-
description: `Coupon bounty: ${title.trim()}`,
72-
redirect_url: `${APP_URL}/bounties/${bountyId}?funded=1`,
73-
metadata: { type: 'bounty_fund', bounty_id: bountyId, creator_did: did },
74-
}),
75-
});
64+
try {
65+
const res = await fetch(`${COINPAY_BASE}/api/payments/create`, {
66+
method: 'POST',
67+
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${API_KEY}` },
68+
body: JSON.stringify({
69+
business_id: MERCHANT_ID,
70+
amount_usd: reward,
71+
currency: 'usdc_pol', // default to USDC on Polygon — low fees
72+
description: `Coupon bounty: ${title.trim()}`,
73+
redirect_url: `${APP_URL}/bounties/${bountyId}?funded=1`,
74+
metadata: { type: 'bounty_fund', bounty_id: bountyId, creator_did: did },
75+
}),
76+
});
7677

77-
if (res.ok) {
78-
const data = await res.json();
79-
paymentId = data.payment_id ?? data.id ?? null;
80-
paymentAddress = data.payment_address ?? null;
81-
if (paymentId) {
82-
await db.sql`UPDATE bounties SET payment_id = ${paymentId} WHERE id = ${bountyId}`;
78+
if (res.ok) {
79+
const data = await res.json();
80+
paymentId = data.payment_id ?? data.id ?? null;
81+
paymentAddress = data.payment_address ?? null;
82+
if (paymentId) {
83+
await db.sql`UPDATE bounties SET payment_id = ${paymentId} WHERE id = ${bountyId}`;
84+
}
85+
} else {
86+
const err = await res.text();
87+
console.error('CoinPay payment create error:', err);
8388
}
84-
} else {
85-
const err = await res.text();
86-
console.error('CoinPay payment create error:', err);
89+
} catch (e) {
90+
console.error('CoinPay payment create failed:', e);
8791
}
92+
93+
return NextResponse.json({
94+
id: bountyId,
95+
payment_id: paymentId,
96+
payment_address: paymentAddress,
97+
pay_url: paymentId ? `${COINPAY_BASE}/pay/${paymentId}` : null,
98+
}, { status: 201 });
8899
} catch (e) {
89-
console.error('CoinPay payment create failed:', e);
100+
console.error('Failed to create bounty:', e);
101+
return NextResponse.json({ error: 'Failed to create bounty. Please try again.' }, { status: 500 });
90102
}
91-
92-
return NextResponse.json({
93-
id: bountyId,
94-
payment_id: paymentId,
95-
payment_address: paymentAddress,
96-
pay_url: paymentId ? `${COINPAY_BASE}/pay/${paymentId}` : null,
97-
}, { status: 201 });
98103
}

apps/web/app/bounties/new/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ export default function NewBountyPage() {
5050
});
5151
const data = await res.json();
5252
if (!res.ok) { setError(data.error ?? 'Failed to create bounty'); return; }
53-
// If CoinPay returned a checkout URL, redirect there to fund the bounty
54-
if (data.checkout_url) {
55-
window.location.href = data.checkout_url;
53+
// If CoinPay returned a payment URL, redirect there to fund the bounty
54+
if (data.pay_url) {
55+
window.location.href = data.pay_url;
5656
} else {
5757
router.push(`/bounties/${data.id}`);
5858
}

0 commit comments

Comments
 (0)