- DB: audit_log 테이블(diff 포함) + subscriptions.admin_note + super_admin role - Edge Functions 4개: admin-users, admin-subscriptions, admin-payments, admin-audit-log - 공유 유틸: admin-auth.ts(권한 검증), audit.ts(감사로그 기록) - Swagger UI: 독립 정적 페이지 + OpenAPI 3.0 spec - CRUD 페이지: 구독 생성/수정/삭제, role 변경, 감사로그 목록/상세 - recharts: feature별 StackedBar + DAU Line + Top Users HorizontalBar - 결제 이력: DB + Payple API 병행 조회 - 권한: super_admin만 위험 작업, admin은 조회 전용 - RLS: admin/super_admin IN 정책 + super_admin 쓰기 정책 - SQL RPC: admin_usage_by_feature, admin_top_users, admin_dau
95 lines
3.2 KiB
TypeScript
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 { requireAdmin } 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 requireAdmin(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)
|
|
}
|
|
})
|