- subscriptions 테이블 마이그레이션 (기존 paid orders에서 자동 생성) - GET/PATCH /api/subscription: 구독 조회, 해지, 자동갱신 토글 - 마이페이지 구독 관리 탭: D-day, 해지 버튼, 자동갱신 토글 - 해지 시 만료일까지 서비스 계속 이용 가능 - Vercel Cron: 매일 01:00 KST 만료 구독 자동 처리 + 텔레그램 알림 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
88 lines
2.4 KiB
TypeScript
88 lines
2.4 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { createClient } from '@/lib/supabase/server';
|
|
|
|
/**
|
|
* PATCH /api/subscription/[id]
|
|
* action: 'cancel' | 'toggle_autorenew'
|
|
*
|
|
* cancel — 구독 즉시 해지 (status='cancelled', auto_renew=false)
|
|
* toggle_autorenew — 자동갱신 on/off 전환
|
|
*/
|
|
export async function PATCH(
|
|
req: NextRequest,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
const supabase = await createClient();
|
|
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
if (authError || !user) {
|
|
return NextResponse.json({ error: 'UNAUTHORIZED' }, { status: 401 });
|
|
}
|
|
|
|
const { id } = await params;
|
|
let body: { action?: string };
|
|
try {
|
|
body = await req.json();
|
|
} catch {
|
|
return NextResponse.json({ error: 'INVALID_JSON' }, { status: 400 });
|
|
}
|
|
|
|
const { action } = body;
|
|
|
|
// 본인 구독인지 확인
|
|
const { data: sub, error: fetchError } = await supabase
|
|
.from('subscriptions')
|
|
.select('id, status, auto_renew, expires_at')
|
|
.eq('id', id)
|
|
.eq('user_id', user.id)
|
|
.maybeSingle();
|
|
|
|
if (fetchError || !sub) {
|
|
return NextResponse.json({ error: 'NOT_FOUND' }, { status: 404 });
|
|
}
|
|
|
|
if (action === 'cancel') {
|
|
if (sub.status === 'cancelled') {
|
|
return NextResponse.json({ error: 'ALREADY_CANCELLED' }, { status: 400 });
|
|
}
|
|
|
|
const { error } = await supabase
|
|
.from('subscriptions')
|
|
.update({
|
|
status: 'cancelled',
|
|
auto_renew: false,
|
|
cancelled_at: new Date().toISOString(),
|
|
})
|
|
.eq('id', id);
|
|
|
|
if (error) {
|
|
return NextResponse.json({ error: 'DB_ERROR' }, { status: 500 });
|
|
}
|
|
|
|
return NextResponse.json({
|
|
ok: true,
|
|
message: '구독이 해지되었습니다. 만료일까지는 서비스를 계속 이용할 수 있습니다.',
|
|
expires_at: sub.expires_at,
|
|
});
|
|
}
|
|
|
|
if (action === 'toggle_autorenew') {
|
|
if (sub.status === 'cancelled' || sub.status === 'expired') {
|
|
return NextResponse.json({ error: 'SUBSCRIPTION_NOT_ACTIVE' }, { status: 400 });
|
|
}
|
|
|
|
const newValue = !sub.auto_renew;
|
|
const { error } = await supabase
|
|
.from('subscriptions')
|
|
.update({ auto_renew: newValue })
|
|
.eq('id', id);
|
|
|
|
if (error) {
|
|
return NextResponse.json({ error: 'DB_ERROR' }, { status: 500 });
|
|
}
|
|
|
|
return NextResponse.json({ ok: true, auto_renew: newValue });
|
|
}
|
|
|
|
return NextResponse.json({ error: 'INVALID_ACTION' }, { status: 400 });
|
|
}
|