feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
|
|
@ -1,95 +1,79 @@
|
|||
// 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'
|
||||
import { verifyCurrentAdminActor } from '../_shared/admin-current-role.ts'
|
||||
import {
|
||||
AdminPublicError,
|
||||
adminErrorResponse,
|
||||
adminJsonResponse,
|
||||
requireUuid,
|
||||
validateQueryKeys,
|
||||
} from '../_shared/admin-contract.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)
|
||||
async function readPaymentHistory(req: Request): Promise<Response> {
|
||||
const admin = await requireManager(req)
|
||||
const url = new URL(req.url)
|
||||
validateQueryKeys(url, ['userId', 'source'])
|
||||
const userId = requireUuid(url.searchParams.get('userId'), 'invalid_userId')
|
||||
const source = url.searchParams.get('source') ?? 'db'
|
||||
if (source !== 'db' && source !== 'payple') throw new AdminPublicError(400, 'invalid_payment_source')
|
||||
if (source === 'payple') {
|
||||
throw new AdminPublicError(501, 'payple_live_history_not_configured')
|
||||
}
|
||||
|
||||
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
|
||||
const serviceClient = createServiceRoleClient()
|
||||
await verifyCurrentAdminActor(serviceClient, admin)
|
||||
const [subscriptionResult, auditResult, eventsResult, operationsResult] = await Promise.all([
|
||||
serviceClient
|
||||
.from('subscriptions')
|
||||
.select('*')
|
||||
.select('id, user_id, tier, status, provider, payment_provider, current_period_start, current_period_end, overage_credits, admin_note, cancel_at, created_at, updated_at')
|
||||
.eq('user_id', userId)
|
||||
.maybeSingle()
|
||||
|
||||
// 감사로그에서 구독 관련 액션만
|
||||
const { data: auditLogs } = await serviceClient
|
||||
.maybeSingle(),
|
||||
serviceClient
|
||||
.from('audit_log')
|
||||
.select('*')
|
||||
.select('id, admin_id, action, target_type, target_id, memo, created_at')
|
||||
.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)
|
||||
.limit(50),
|
||||
serviceClient
|
||||
.from('payment_provider_events')
|
||||
.select('id, provider, event_type, event_created_at, disposition, received_at, processed_at')
|
||||
.eq('user_id', userId)
|
||||
.order('event_created_at', { ascending: false })
|
||||
.limit(50),
|
||||
serviceClient
|
||||
.from('payment_provider_operations')
|
||||
.select('id, provider, operation_type, requested_tier, state, error_code, expires_at, created_at, updated_at')
|
||||
.eq('user_id', userId)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(50),
|
||||
])
|
||||
if (subscriptionResult.error || auditResult.error || eventsResult.error || operationsResult.error) {
|
||||
throw new AdminPublicError(500, 'admin_payment_read_failed')
|
||||
}
|
||||
})
|
||||
|
||||
return adminJsonResponse({
|
||||
subscription: subscriptionResult.data ?? null,
|
||||
auditLogs: auditResult.data ?? [],
|
||||
providerEvents: eventsResult.data ?? [],
|
||||
providerOperations: operationsResult.data ?? [],
|
||||
liveProviderHistoryAvailable: false,
|
||||
}, corsHeaders)
|
||||
}
|
||||
|
||||
export async function handleAdminPayments(req: Request): Promise<Response> {
|
||||
const preflight = handleCorsPreflightRequest(req)
|
||||
if (preflight) return preflight
|
||||
try {
|
||||
if (req.method !== 'GET') {
|
||||
return adminJsonResponse({ error: 'method_not_allowed' }, corsHeaders, 405)
|
||||
}
|
||||
return await readPaymentHistory(req)
|
||||
} catch (error) {
|
||||
console.error('admin-payments request failed', error)
|
||||
return adminErrorResponse(error, corsHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.main) Deno.serve(handleAdminPayments)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue