d3ro-voice/server/supabase/functions/admin-payments/index.ts
윤찬 8af75a0a1e feat: V2-7 Admin 콘솔 리디자인 + 3단계 권한 + SaaS 전환 + 코드 정리
- Admin CRM: D3RO Console 스타일 전체 적용 (panelSx/tableSx/filterBtnSx)
- 3단계 권한: manager/admin/super_admin (DB + Edge Functions + Frontend)
- 랜딩 페이지: 1회 결제 → 월간/연간 구독 SaaS 모델 (10개 언어)
- SSE 스트리밍: VoiceConversation Premium LLM 라우팅 + fallback
- Supabase 클라이언트: packages/api-client 공통 추출 (browser+server)
- RPC 함수 타입: 9개 정의 (admin_usage_by_feature 등)
- callAdminApi 401 버그 수정 (getUser() 선행 토큰 갱신)
2026-04-13 01:28:10 +09:00

95 lines
3.2 KiB
TypeScript

// server/supabase/functions/admin-payments/index.ts
// 결제 이력 조회 — DB 기반 + Payple API 직접 조회
import { corsHeaders, handleCorsPreflightRequest } from '../_shared/cors.ts'
import { authErrorResponse, type AuthError } from '../_shared/auth.ts'
import { requireManager } from '../_shared/admin-auth.ts'
import { createServiceRoleClient } from '../_shared/quota.ts'
import { getPaypleConfig, paypleAuth } from '../_shared/payple.ts'
function jsonResponse(body: Record<string, unknown> | Record<string, unknown>[], status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
})
}
// @ts-expect-error — Deno 런타임 전역
Deno.serve(async (req: Request) => {
const preflight = handleCorsPreflightRequest(req)
if (preflight) return preflight
if (req.method !== 'GET') {
return jsonResponse({ error: 'Method not allowed' }, 405)
}
try {
await requireManager(req)
const url = new URL(req.url)
const userId = url.searchParams.get('userId')
const source = url.searchParams.get('source') // 'db' | 'payple' | null(=db)
const serviceClient = createServiceRoleClient()
if (!userId) {
return jsonResponse({ error: 'userId query param required' }, 400)
}
// ── DB 조회 (기본) ──
// 구독 현황
const { data: sub } = await serviceClient
.from('subscriptions')
.select('*')
.eq('user_id', userId)
.maybeSingle()
// 감사로그에서 구독 관련 액션만
const { data: auditLogs } = await serviceClient
.from('audit_log')
.select('*')
.eq('target_id', userId)
.eq('target_type', 'subscription')
.order('created_at', { ascending: false })
.limit(50)
const result: Record<string, unknown> = {
subscription: sub,
auditLogs: auditLogs ?? [],
}
// ── Payple API 직접 조회 (요청 시) ──
if (source === 'payple' && sub?.payple_payer_id) {
try {
const config = getPaypleConfig()
const auth = await paypleAuth(config, { payWork: 'TSRCH' })
// Payple 결제 내역 조회
const paypleResponse = await fetch(`${config.baseUrl}/php/PayCardListAct.php`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
PCD_CST_ID: auth.PCD_CST_ID,
PCD_CUST_KEY: auth.PCD_CUST_KEY,
PCD_AUTH_KEY: auth.PCD_AUTH_KEY,
PCD_PAYER_ID: sub.payple_payer_id,
PCD_PAY_YEAR: new Date().getFullYear().toString(),
PCD_PAY_MONTH: '',
}),
})
const paypleData = await paypleResponse.json()
result.paypleHistory = paypleData
} catch (paypleErr) {
const errMsg = paypleErr instanceof Error ? paypleErr.message : 'Payple API error'
result.paypleError = errMsg
}
}
return jsonResponse(result)
} catch (err) {
if (err && typeof err === 'object' && 'status' in err && 'message' in err) {
return authErrorResponse(err as AuthError, corsHeaders)
}
const message = err instanceof Error ? err.message : 'Unknown error'
return jsonResponse({ error: message }, 500)
}
})