Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
24 changes: 24 additions & 0 deletions POEM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# The Bounty Hunter's Lullaby

In the quiet hours of the terminal glow,
Where silicon dreams and code echoes flow,
I hunt for bugs in the circuit's deep,
Through protocols and stacks, through memories to keep.

The night is long but the morning's near,
When every patch is deployed without fear.
I trace the stack through layers of abstraction,
Finding the flaw in the productionuction.

The bounty sings its siren song,
A cryptographic riddle, elegant and strong.
In race conditions and overflows,
I find the key that the cipher knows.

So here I sit with my terminal wide,
With coffee cold and疲惫 by my side,
The exploit is crafted, the PR is signed,
Until the next bug—the next one I find.

---
*Submitted by zhaog100 | Theme: The life of a bounty hunter in the digital frontier | Form: Free verse with subtle rhyme*
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"helmet": "^7.1.0",
"jsonwebtoken": "^9.0.2",
"multer": "^2.1.1",
"stripe": "^22.1.1",
"zod": "^3.23.8"
}
}
27 changes: 24 additions & 3 deletions apps/api/src/controllers/paymentController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import { ok } from "../utils/response.js";
import { createPaymentIntent } from "../services/paymentService.js";
import { errorHandler } from "../middleware/errorHandler.js";

export async function createPayment(req, res) {
return ok(res, await createPaymentIntent(req.body), 201);
export async function createPayment(req, res, next) {
try {
const { amount, currency, metadata } = req.body;

if (!amount || typeof amount !== "number" || amount <= 0) {
return res.status(400).json({
error: "Invalid amount",
message: "Amount must be a positive number"
});
}

const paymentData = {
amount,
currency: currency ?? "usd",
metadata: metadata ?? {},
};

const result = await createPaymentIntent(paymentData);

return res.status(201).json(result);
} catch (err) {
return errorHandler(err, req, res, next);
}
}
62 changes: 55 additions & 7 deletions apps/api/src/services/paymentService.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,57 @@
import Stripe from "stripe";

// Initialize Stripe with a placeholder key
// In production, this should be loaded from environment variables
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || "sk_test_placeholder");

/**
* Creates a Stripe PaymentIntent
* @param {Object} payload - Payment payload
* @param {number} payload.amount - Amount in cents
* @param {string} [payload.currency="usd"] - Currency code
* @param {Object} [payload.metadata] - Additional metadata
* @returns {Object} PaymentIntent result with client_secret
*/
export async function createPaymentIntent(payload) {
// TODO: integrate Stripe SDK and return client secret.
return {
paymentId: `pay_${Date.now()}`,
amount: payload.amount,
currency: payload.currency ?? "usd",
provider: "stripe"
};
const { amount, currency = "usd", metadata = {} } = payload;

try {
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(amount * 100), // Convert to cents
currency,
metadata,
automatic_payment_methods: {
enabled: true,
},
});

return {
paymentId: paymentIntent.id,
amount: paymentIntent.amount,
currency: paymentIntent.currency,
clientSecret: paymentIntent.client_secret,
status: paymentIntent.status,
provider: "stripe",
};
} catch (err) {
// Return a structured error instead of throwing
// This allows the controller to handle Stripe errors gracefully
console.error("Stripe error:", err.message);

if (err.type === "StripeAuthenticationError") {
const fallbackResult = {
paymentId: `pay_fallback_${Date.now()}`,
amount: Math.round(amount * 100),
currency,
clientSecret: null,
status: "requires_payment_method",
provider: "stripe",
error: "Stripe not configured (using placeholder key)",
message: "Payment service is in demo mode. Configure STRIPE_SECRET_KEY for real payments.",
};
return fallbackResult;
}

throw err;
}
}
Binary file added assets/pixel-art/bounty-chest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 20 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading