135 lines
5.3 KiB
TypeScript
135 lines
5.3 KiB
TypeScript
import { Router } from 'express'
|
|
import { requireCompanyAuth } from '../../middleware/requireCompanyAuth'
|
|
import { requireTenant } from '../../middleware/requireTenant'
|
|
import { requireSubscriptionRead, requireSubscriptionFull } from '../../middleware/requireSubscription'
|
|
import { requireRole } from '../../middleware/requireRole'
|
|
import { parseBody } from '../../http/validate'
|
|
import { ok } from '../../http/respond'
|
|
import { getRawBodyString, parseRawJsonBody } from '../../http/webhooks'
|
|
import * as amanpay from '../../services/amanpayService'
|
|
import * as paypal from '../../services/paypalService'
|
|
import * as service from './subscription.service'
|
|
import {
|
|
checkoutSchema,
|
|
changePlanSchema,
|
|
capturePaypalSchema,
|
|
startTrialSchema,
|
|
cancelSchema,
|
|
reactivateSchema,
|
|
} from './subscription.schemas'
|
|
|
|
const publicRouter = Router()
|
|
const webhookRouter = Router()
|
|
const router = Router()
|
|
|
|
// ─── Public ────────────────────────────────────────────────────
|
|
|
|
publicRouter.get('/plans', (_req, res, next) => {
|
|
service.getPlans().then((d: any) => ok(res, d)).catch(next)
|
|
})
|
|
|
|
publicRouter.get('/providers', (_req, res) => {
|
|
ok(res, service.getProviders())
|
|
})
|
|
|
|
publicRouter.get('/features', (_req, res, next) => {
|
|
service.getPlanFeatures().then((d: any) => ok(res, d)).catch(next)
|
|
})
|
|
|
|
// ─── Webhooks (no auth) ────────────────────────────────────────
|
|
|
|
webhookRouter.post('/webhooks/amanpay', async (req, res, next) => {
|
|
try {
|
|
const rawBody = getRawBodyString(req)
|
|
const payload = parseRawJsonBody(req)
|
|
const signature = (req.headers['x-amanpay-signature'] as string) ?? ''
|
|
if (!amanpay.isConfigured() || !amanpay.verifyWebhookSignature(rawBody, signature)) {
|
|
return res.status(401).json({ error: 'invalid_signature' })
|
|
}
|
|
await service.handleAmanpayWebhook(payload, rawBody)
|
|
res.json({ received: true })
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
webhookRouter.post('/webhooks/paypal', async (req, res, next) => {
|
|
try {
|
|
const rawBody = getRawBodyString(req)
|
|
const payload = parseRawJsonBody(req)
|
|
const isValid = await paypal.verifyWebhookEvent(req.headers as Record<string, string>, rawBody)
|
|
if (!paypal.isConfigured() || !isValid) return res.status(401).json({ error: 'invalid_signature' })
|
|
await service.handlePaypalWebhook(payload, rawBody)
|
|
res.json({ received: true })
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
// ─── PayPal capture (auth but no subscription check) ──────────
|
|
|
|
router.post('/capture-paypal', requireCompanyAuth, requireTenant, requireSubscriptionFull, requireRole('OWNER'), async (req, res, next) => {
|
|
try {
|
|
const { paypalOrderId } = parseBody(capturePaypalSchema, req)
|
|
ok(res, await service.capturePaypal(req.companyId, paypalOrderId))
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
// ─── Authenticated ─────────────────────────────────────────────
|
|
|
|
router.use(requireCompanyAuth, requireTenant, requireSubscriptionRead)
|
|
|
|
router.get('/me', async (req, res, next) => {
|
|
try { ok(res, await service.getSubscription(req.companyId)) } catch (err) { next(err) }
|
|
})
|
|
|
|
router.get('/invoices', async (req, res, next) => {
|
|
try { ok(res, await service.getInvoices(req.companyId)) } catch (err) { next(err) }
|
|
})
|
|
|
|
router.get('/events', async (req, res, next) => {
|
|
try { ok(res, await service.getEvents(req.companyId)) } catch (err) { next(err) }
|
|
})
|
|
|
|
router.get('/entitlement', async (req, res, next) => {
|
|
try { ok(res, await service.getEntitlement(req.companyId)) } catch (err) { next(err) }
|
|
})
|
|
|
|
router.post('/trial', requireSubscriptionFull, requireRole('OWNER'), async (req, res, next) => {
|
|
try {
|
|
const body = parseBody(startTrialSchema, req)
|
|
ok(res, await service.startTrial(req.companyId, body.plan, body.billingPeriod, body.currency))
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
router.post('/checkout', requireSubscriptionFull, requireRole('OWNER'), async (req, res, next) => {
|
|
try {
|
|
const body = parseBody(checkoutSchema, req)
|
|
ok(res, await service.checkout(req.companyId, body))
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
router.post('/reactivate', requireSubscriptionFull, requireRole('OWNER'), async (req, res, next) => {
|
|
try {
|
|
const body = parseBody(reactivateSchema, req)
|
|
ok(res, await service.reactivate(req.companyId, body))
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
router.post('/change-plan', requireSubscriptionFull, requireRole('OWNER'), async (req, res, next) => {
|
|
try {
|
|
const body = parseBody(changePlanSchema, req)
|
|
ok(res, await service.changePlan(req.companyId, body))
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
router.post('/cancel', requireSubscriptionFull, requireRole('OWNER'), async (req, res, next) => {
|
|
try {
|
|
const { mode, reason } = parseBody(cancelSchema, req)
|
|
ok(res, await service.cancel(req.companyId, mode, reason))
|
|
} catch (err) { next(err) }
|
|
})
|
|
|
|
router.post('/resume', requireSubscriptionFull, requireRole('OWNER'), async (req, res, next) => {
|
|
try { ok(res, await service.resume(req.companyId)) } catch (err) { next(err) }
|
|
})
|
|
|
|
export default router
|
|
export { publicRouter as subscriptionPublicRouter, webhookRouter as subscriptionWebhookRouter }
|