Files
whattoplay/features/keycrow/src/routes/transactions.ts
2026-03-10 17:03:13 +01:00

226 lines
5.8 KiB
TypeScript

import { Router, Response } from 'express';
import { store } from '../services/Store';
import { paymentProvider } from '../services/PaymentProvider';
import { keyActivationProvider } from '../services/KeyActivationProvider';
import { encryptionService } from '../utils/encryption';
import { mockAuthMiddleware, requireSteamAuth } from '../middleware/auth';
import { config } from '../config';
import { CreateTransactionDto } from '../models';
const router = Router();
router.post('/', mockAuthMiddleware, async (req, res: Response) => {
const { listingId } = req.body as CreateTransactionDto;
if (!listingId) {
return res.status(400).json({
success: false,
error: 'listingId is required',
});
}
const listing = store.getListingById(listingId);
if (!listing) {
return res.status(404).json({ success: false, error: 'Listing not found' });
}
if (listing.status !== 'ACTIVE') {
return res.status(400).json({
success: false,
error: 'Listing is not available',
});
}
if (listing.sellerId === req.userId) {
return res.status(400).json({
success: false,
error: 'Cannot buy your own listing',
});
}
const holdResult = await paymentProvider.createHold(listing.price, listing.currency);
if (!holdResult.success) {
return res.status(400).json({
success: false,
error: holdResult.error || 'Payment failed',
});
}
const transaction = store.createTransaction({
listingId: listing.id,
buyerId: req.userId!,
sellerId: listing.sellerId,
amount: listing.price,
currency: listing.currency,
escrowStatus: 'HELD',
transactionStatus: 'PENDING',
holdId: holdResult.holdId,
keyDelivered: false,
});
store.updateListing(listing.id, { status: 'SOLD' });
res.json({
success: true,
data: {
transaction: {
id: transaction.id,
listingId: transaction.listingId,
amount: transaction.amount,
currency: transaction.currency,
escrowStatus: transaction.escrowStatus,
transactionStatus: transaction.transactionStatus,
},
},
});
});
router.get('/:id', mockAuthMiddleware, (req, res: Response) => {
const transaction = store.getTransactionById(req.params.id);
if (!transaction) {
return res.status(404).json({ success: false, error: 'Transaction not found' });
}
if (transaction.buyerId !== req.userId && transaction.sellerId !== req.userId) {
return res.status(403).json({ success: false, error: 'Forbidden' });
}
res.json({
success: true,
data: { transaction },
});
});
router.get('/:id/key', mockAuthMiddleware, (req, res: Response) => {
const transaction = store.getTransactionById(req.params.id);
if (!transaction) {
return res.status(404).json({ success: false, error: 'Transaction not found' });
}
if (transaction.buyerId !== req.userId) {
return res.status(403).json({ success: false, error: 'Forbidden' });
}
if (transaction.escrowStatus !== 'HELD') {
return res.status(400).json({
success: false,
error: 'Key only available when payment is held in escrow',
});
}
if (transaction.keyDelivered) {
return res.json({
success: true,
data: {
keyAlreadyDelivered: true,
message: 'Key was already delivered in this transaction',
},
});
}
const listing = store.getListingById(transaction.listingId);
if (!listing) {
return res.status(404).json({ success: false, error: 'Listing not found' });
}
const key = encryptionService.decrypt(listing.keyEncrypted);
store.updateTransaction(transaction.id, { keyDelivered: true });
res.json({
success: true,
data: { key },
});
});
router.post('/:id/confirm', requireSteamAuth, async (req, res: Response) => {
const { status } = req.body;
if (!status || !['SUCCESS', 'FAILED'].includes(status)) {
return res.status(400).json({
success: false,
error: 'status must be SUCCESS or FAILED',
});
}
const transaction = store.getTransactionById(req.params.id);
if (!transaction) {
return res.status(404).json({ success: false, error: 'Transaction not found' });
}
if (transaction.buyerId !== req.userId) {
return res.status(403).json({ success: false, error: 'Forbidden' });
}
if (transaction.transactionStatus !== 'PENDING') {
return res.status(400).json({
success: false,
error: 'Transaction already confirmed or disputed',
});
}
if (status === 'SUCCESS') {
if (transaction.holdId) {
await paymentProvider.release(transaction.holdId);
}
store.updateTransaction(transaction.id, {
escrowStatus: 'RELEASED',
transactionStatus: 'COMPLETED',
confirmedAt: new Date(),
});
res.json({
success: true,
data: {
message: 'Key confirmed. Payment released to seller.',
escrowStatus: 'RELEASED',
},
});
} else {
if (transaction.holdId) {
await paymentProvider.refund(transaction.holdId);
}
store.updateTransaction(transaction.id, {
escrowStatus: 'REFUNDED',
transactionStatus: 'DISPUTED',
confirmedAt: new Date(),
});
res.json({
success: true,
data: {
message: 'Key marked as failed. Payment refunded to buyer.',
escrowStatus: 'REFUNDED',
transactionStatus: 'DISPUTED',
},
});
}
});
router.get('/buyer/me', mockAuthMiddleware, (req, res: Response) => {
const transactions = store.getTransactionsByBuyer(req.userId!);
res.json({
success: true,
data: { transactions },
});
});
router.get('/seller/me', mockAuthMiddleware, (req, res: Response) => {
const transactions = store.getTransactionsBySeller(req.userId!);
res.json({
success: true,
data: { transactions },
});
});
export default router;