메인 콘텐츠로 건너뛰기

구독 업그레이드 또는 다운그레이드란 무엇인가요?

계획을 변경하면 고객을 구독 계층 또는 수량 간에 이동할 수 있습니다. 이를 사용하여:
  • 사용량 또는 기능에 따라 가격을 조정
  • 월간에서 연간으로 이동 (또는 그 반대)
  • 좌석 기반 제품의 수량 조정
플랜 변경은 선택한 비례 배분 모드에 따라 즉각적인 요금을 발생시킬 수 있습니다.

플랜 변경을 사용할 때

  • 고객이 더 많은 기능, 사용량 또는 좌석이 필요할 때 업그레이드
  • 사용량이 감소할 때 다운그레이드
  • 구독을 취소하지 않고 새로운 제품이나 가격으로 사용자 마이그레이션

전제 조건

구독 계획 변경을 구현하기 전에 다음을 확인하세요:
  • 활성 구독 제품이 있는 Dodo Payments 상인 계정
  • 대시보드에서 API 자격 증명 (API 키 및 웹훅 비밀 키)
  • 수정할 기존 활성 구독
  • 구독 이벤트를 처리할 웹훅 엔드포인트 구성
자세한 설정 지침은 통합 가이드를 참조하세요.

단계별 구현 가이드

애플리케이션에서 구독 계획 변경을 구현하기 위한 포괄적인 가이드를 따르세요:
1

플랜 변경 요구 사항 이해하기

구현하기 전에 다음을 결정하세요:
  • 어떤 구독 제품을 어떤 다른 제품으로 변경할 수 있는지
  • 어떤 비례 배분 모드가 비즈니스 모델에 적합한지
  • 실패한 플랜 변경을 어떻게 우아하게 처리할 것인지
  • 상태 관리를 위해 어떤 웹훅 이벤트를 추적할 것인지
생산 환경에 구현하기 전에 테스트 모드에서 플랜 변경을 철저히 테스트하세요.
2

비례 배분 전략 선택하기

비즈니스 요구에 맞는 청구 방식을 선택하세요:
최고의 경우: 사용하지 않은 시간에 대해 공정하게 요금을 부과하고자 하는 SaaS 애플리케이션
  • 남은 주기 시간에 따라 정확한 비례 배분 금액을 계산
  • 주기 내 남은 사용하지 않은 시간에 따라 비례 배분 금액을 부과
  • 고객에게 투명한 청구 제공
3

변경 플랜 API 구현하기

변경 플랜 API를 사용하여 구독 세부정보를 수정하세요:
subscription_id
string
required
수정할 활성 구독의 ID입니다.
product_id
string
required
구독을 변경할 새로운 제품 ID입니다.
quantity
integer
default:"1"
새로운 플랜의 단위 수 (좌석 기반 제품의 경우).
proration_billing_mode
string
required
즉각적인 청구를 처리하는 방법: prorated_immediately, full_immediately, 또는 difference_immediately.
addons
array
새로운 플랜에 대한 선택적 애드온. 이 필드를 비워두면 기존 애드온이 제거됩니다.
4

웹훅 이벤트 처리하기

플랜 변경 결과를 추적하기 위해 웹훅 처리를 설정하세요:
  • subscription.active: 플랜 변경 성공, 구독 업데이트됨
  • subscription.plan_changed: 구독 플랜 변경됨 (업그레이드/다운그레이드/애드온 업데이트)
  • subscription.on_hold: 플랜 변경 요금 실패, 구독 일시 중지됨
  • payment.succeeded: 플랜 변경에 대한 즉각적인 요금 성공
  • payment.failed: 즉각적인 요금 실패
웹훅 서명을 항상 확인하고 멱등성 이벤트 처리를 구현하세요.
5

애플리케이션 상태 업데이트하기

웹훅 이벤트에 따라 애플리케이션을 업데이트하세요:
  • 새로운 플랜에 따라 기능 부여/회수
  • 고객 대시보드에 새로운 플랜 세부정보 업데이트
  • 플랜 변경에 대한 확인 이메일 전송
  • 감사 목적으로 청구 변경 사항 기록
6

테스트 및 모니터링

구현을 철저히 테스트하세요:
  • 다양한 시나리오로 모든 비례 배분 모드를 테스트하세요
  • 웹훅 처리 기능이 올바르게 작동하는지 확인하세요
  • 플랜 변경 성공률 모니터링
  • 실패한 플랜 변경에 대한 알림 설정
구독 플랜 변경 구현이 이제 생산 사용을 위해 준비되었습니다.

플랜 변경 미리보기

플랜 변경을 확정하기 전에 미리보기 API를 사용하여 고객에게 정확히 얼마가 청구될지를 보여주세요:
const preview = await client.subscriptions.previewChangePlan('sub_123', {
  product_id: 'prod_pro',
  quantity: 1,
  proration_billing_mode: 'prorated_immediately'
});

// Show customer the charge before confirming
console.log('Immediate charge:', preview.immediate_charge.summary);
console.log('New plan details:', preview.new_plan);
미리보기 API를 사용하여 고객이 플랜 변경을 확인하기 전에 정확한 금액을 보여주는 확인 대화 상자를 구축하세요.

변경 플랜 API

변경 플랜 API를 사용하여 활성 구독의 제품, 수량 및 비례 배분 동작을 수정하세요.

빠른 시작 예제

import DodoPayments from 'dodopayments';

const client = new DodoPayments({
  bearerToken: process.env.DODO_PAYMENTS_API_KEY,
  environment: 'test_mode', // defaults to 'live_mode'
});

async function changePlan() {
  const result = await client.subscriptions.changePlan('sub_123', {
    product_id: 'prod_new',
    quantity: 3,
    proration_billing_mode: 'prorated_immediately',
  });
  console.log(result.status, result.invoice_id, result.payment_id);
}

changePlan();
Success
{
  "status": "processing",
  "subscription_id": "sub_123",
  "invoice_id": "inv_789",
  "payment_id": "pay_456",
  "proration_billing_mode": "prorated_immediately"
}
invoice_idpayment_id와 같은 필드는 플랜 변경 중 즉각적인 요금 및/또는 인보이스가 생성될 때만 반환됩니다. 항상 웹훅 이벤트 (예: payment.succeeded, subscription.plan_changed)에 의존하여 결과를 확인하세요.
즉각적인 요금이 실패하면 구독이 subscription.on_hold로 이동할 수 있습니다. 결제가 성공할 때까지.

애드온 관리

구독 계획을 변경할 때 애드온도 수정할 수 있습니다:
// Add addons to the new plan
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_new',
  quantity: 1,
  proration_billing_mode: 'difference_immediately',
  addons: [
    { addon_id: 'addon_123', quantity: 2 }
  ]
});

// Remove all existing addons
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_new',
  quantity: 1,
  proration_billing_mode: 'difference_immediately',
  addons: [] // Empty array removes all existing addons
});
애드온은 비례 배분 계산에 포함되며 선택한 비례 배분 모드에 따라 청구됩니다.

비례 배분 모드

계획을 변경할 때 고객에게 청구하는 방법을 선택하세요:

prorated_immediately

  • 현재 주기의 부분 차이에 대해 요금을 부과합니다.
  • 시험 중인 경우 즉시 요금을 부과하고 지금 새로운 플랜으로 전환합니다.
  • 다운그레이드: 향후 갱신에 적용되는 비례 배분 크레딧을 생성할 수 있습니다.

full_immediately

  • 새로운 플랜의 전체 금액을 즉시 부과합니다.
  • 이전 플랜의 남은 시간을 무시합니다.
difference_immediately를 사용하여 생성된 크레딧은 구독 범위에 국한되며 고객 크레딧과는 다릅니다. 동일한 구독의 향후 갱신에 자동으로 적용되며 구독 간에 이전할 수 없습니다.

difference_immediately

  • 업그레이드: 이전 및 새로운 플랜 간의 가격 차이를 즉시 부과합니다.
  • 다운그레이드: 남은 가치를 구독에 내부 크레딧으로 추가하고 갱신 시 자동 적용합니다.

예제 시나리오

await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_pro',
  quantity: 1,
  proration_billing_mode: 'difference_immediately'
})
// Immediate charge: $50
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_starter',
  quantity: 1,
  proration_billing_mode: 'difference_immediately'
})
// Credit added: $30 (auto-applied to future renewals for this subscription)
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_new',
  quantity: 1,
  proration_billing_mode: 'prorated_immediately'
})
// Immediate prorated charge based on remaining days in cycle
await client.subscriptions.changePlan('sub_123', {
  product_id: 'prod_new',
  quantity: 1,
  proration_billing_mode: 'full_immediately'
})
// Immediate full charge for new plan; no credits calculated
공정한 시간 회계를 위해 prorated_immediately를 선택하세요; 청구를 재시작하려면 full_immediately를 선택하세요; 간단한 업그레이드 및 다운그레이드 시 자동 크레딧을 위해 difference_immediately를 사용하세요.

웹훅 처리

웹훅을 통해 구독 상태를 추적하여 플랜 변경 및 결제를 확인하세요.

처리할 이벤트 유형

  • subscription.active: 구독 활성화됨
  • subscription.plan_changed: 구독 플랜 변경됨 (업그레이드/다운그레이드/애드온 변경)
  • subscription.on_hold: 요금 실패, 구독 일시 중지됨
  • subscription.renewed: 갱신 성공
  • payment.succeeded: 플랜 변경 또는 갱신에 대한 결제 성공
  • payment.failed: 결제 실패
비즈니스 로직을 구독 이벤트에서 유도하고 결제 이벤트를 확인 및 조정에 사용하기를 권장합니다.

서명 확인 및 의도 처리

import { NextRequest, NextResponse } from 'next/server';

export async function POST(req) {
  const webhookId = req.headers.get('webhook-id');
  const webhookSignature = req.headers.get('webhook-signature');
  const webhookTimestamp = req.headers.get('webhook-timestamp');
  const secret = process.env.DODO_WEBHOOK_SECRET;

  const payload = await req.text();
  // verifySignature is a placeholder – in production, use a Standard Webhooks library
  const { valid, event } = await verifySignature(
    payload,
    { id: webhookId, signature: webhookSignature, timestamp: webhookTimestamp },
    secret
  );
  if (!valid) return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });

  switch (event.type) {
    case 'subscription.active':
      // mark subscription active in your DB
      break;
    case 'subscription.plan_changed':
      // refresh entitlements and reflect the new plan in your UI
      break;
    case 'subscription.on_hold':
      // notify user to update payment method
      break;
    case 'subscription.renewed':
      // extend access window
      break;
    case 'payment.succeeded':
      // reconcile payment for plan change
      break;
    case 'payment.failed':
      // log and alert
      break;
    default:
      // ignore unknown events
      break;
  }

  return NextResponse.json({ received: true });
}
자세한 페이로드 스키마는 구독 웹훅 페이로드결제 웹훅 페이로드를 참조하세요.

모범 사례

신뢰할 수 있는 구독 계획 변경을 위한 권장 사항을 따르세요:

플랜 변경 전략

  • 철저히 테스트: 항상 생산 전에 테스트 모드에서 플랜 변경을 테스트하세요.
  • 비례 배분 신중히 선택: 비즈니스 모델에 맞는 비례 배분 모드를 선택하세요.
  • 실패를 우아하게 처리: 적절한 오류 처리 및 재시도 로직을 구현하세요.
  • 성공률 모니터링: 플랜 변경 성공/실패 비율을 추적하고 문제를 조사하세요.

웹훅 구현

  • 서명 확인: 항상 웹훅 서명을 검증하여 진위를 확인하세요.
  • 멱등성 구현: 중복 웹훅 이벤트를 우아하게 처리하세요.
  • 비동기 처리: 무거운 작업으로 웹훅 응답을 차단하지 마세요.
  • 모든 것을 기록: 디버깅 및 감사 목적으로 자세한 로그를 유지하세요.

사용자 경험

  • 명확하게 소통: 고객에게 청구 변경 및 타이밍에 대해 알리세요.
  • 확인 제공: 성공적인 플랜 변경에 대한 이메일 확인을 전송하세요.
  • 엣지 케이스 처리: 시험 기간, 비례 배분 및 실패한 결제를 고려하세요.
  • UI 즉시 업데이트: 애플리케이션 인터페이스에 플랜 변경을 반영하세요.

일반적인 문제 및 해결책

구독 계획 변경 중 발생하는 일반적인 문제를 해결하세요:
증상: API 호출이 성공하지만 구독이 이전 플랜에 남아 있음일반적인 원인:
  • 웹훅 처리 실패 또는 지연
  • 웹훅 수신 후 애플리케이션 상태가 업데이트되지 않음
  • 상태 업데이트 중 데이터베이스 트랜잭션 문제
해결책:
  • 재시도 로직이 포함된 강력한 웹훅 처리를 구현하세요.
  • 상태 업데이트에 대해 멱등성 작업을 사용하세요.
  • 놓친 웹훅 이벤트를 감지하고 알림을 설정하세요.
  • 웹훅 엔드포인트가 접근 가능하고 올바르게 응답하는지 확인하세요.
증상: 고객이 다운그레이드했지만 크레딧 잔액이 보이지 않음일반적인 원인:
  • 비례 배분 모드 기대: 다운그레이드는 difference_immediately로 전체 플랜 가격 차이를 크레딧으로 부여하고, prorated_immediately는 주기 내 남은 시간에 따라 비례 배분 크레딧을 생성합니다.
  • 크레딧은 구독별로 특정되며 구독 간에 이전되지 않음
  • 고객 대시보드에서 크레딧 잔액이 보이지 않음
해결책:
  • 자동 크레딧을 원할 경우 다운그레이드 시 difference_immediately를 사용하세요.
  • 크레딧이 동일한 구독의 향후 갱신에 적용된다는 것을 고객에게 설명하세요.
  • 크레딧 잔액을 보여주는 고객 포털을 구현하세요.
  • 다음 인보이스 미리보기를 확인하여 적용된 크레딧을 확인하세요.
증상: 잘못된 서명으로 인해 웹훅 이벤트가 거부됨일반적인 원인:
  • 잘못된 웹훅 비밀 키
  • 서명 검증 전에 원시 요청 본문이 수정됨
  • 잘못된 서명 검증 알고리즘
해결책:
  • 대시보드에서 올바른 DODO_WEBHOOK_SECRET를 사용하고 있는지 확인하세요.
  • JSON 파싱 미들웨어 전에 원시 요청 본문을 읽으세요.
  • 플랫폼에 맞는 표준 웹훅 검증 라이브러리를 사용하세요.
  • 개발 환경에서 웹훅 서명 검증을 테스트하세요.
증상: API가 422 Unprocessable Entity 오류를 반환함일반적인 원인:
  • 잘못된 구독 ID 또는 제품 ID
  • 구독이 활성 상태가 아님
  • 필수 매개변수가 누락됨
  • 플랜 변경을 위한 제품이 없음
해결책:
  • 구독이 존재하고 활성 상태인지 확인하세요.
  • 제품 ID가 유효하고 사용 가능한지 확인하세요.
  • 모든 필수 매개변수가 제공되었는지 확인하세요.
  • 매개변수 요구 사항에 대한 API 문서를 검토하세요.
증상: 플랜 변경이 시작되었지만 즉각적인 요금이 실패함일반적인 원인:
  • 고객의 결제 수단에 자금 부족
  • 결제 수단이 만료되었거나 유효하지 않음
  • 은행이 거래를 거부함
  • 사기 탐지가 요금을 차단함
해결책:
  • payment.failed 웹훅 이벤트를 적절히 처리하세요.
  • 고객에게 결제 수단을 업데이트하라고 알리세요.
  • 일시적인 실패에 대한 재시도 로직을 구현하세요.
  • 즉각적인 요금이 실패한 경우 플랜 변경을 허용하는 것을 고려하세요.
증상: 플랜 변경 요금이 실패하고 구독이 on_hold 상태로 이동함무슨 일이 발생하나요: 플랜 변경 요금이 실패하면 구독이 자동으로 on_hold 상태로 전환됩니다. 결제 수단이 업데이트될 때까지 구독은 자동으로 갱신되지 않습니다.해결책: 결제 수단을 업데이트하여 구독을 재활성화하세요.실패한 플랜 변경 후 on_hold 상태에서 구독을 재활성화하려면:
  1. 결제 수단 업데이트: 업데이트 결제 수단 API를 사용하세요.
  2. 자동 요금 생성: API가 남은 요금에 대한 요금을 자동으로 생성합니다.
  3. 인보이스 생성: 요금에 대한 인보이스가 생성됩니다.
  4. 결제 처리: 새로운 결제 수단을 사용하여 결제가 처리됩니다.
  5. 재활성화: 결제가 성공하면 구독이 active 상태로 재활성화됩니다.
// Reactivate subscription from on_hold after failed plan change
async function reactivateAfterFailedPlanChange(subscriptionId) {
  // Update payment method - automatically creates charge for remaining dues
  const response = await client.subscriptions.updatePaymentMethod(subscriptionId, {
    type: 'new',
    return_url: 'https://example.com/return'
  });
  
  if (response.payment_id) {
    console.log('Charge created for remaining dues:', response.payment_id);
    console.log('Payment link:', response.payment_link);
    
    // Redirect customer to payment_link to complete payment
    // Monitor webhooks for:
    // 1. payment.succeeded - charge succeeded
    // 2. subscription.active - subscription reactivated
  }
  
  return response;
}

// Or use existing payment method if available
async function reactivateWithExistingPaymentMethod(subscriptionId, paymentMethodId) {
  const response = await client.subscriptions.updatePaymentMethod(subscriptionId, {
    type: 'existing',
    payment_method_id: paymentMethodId
  });
  
  // Monitor webhooks for payment.succeeded and subscription.active
  return response;
}
모니터링할 웹훅 이벤트:
  • subscription.on_hold: 구독이 보류됨 (플랜 변경 요금 실패 시 수신됨)
  • payment.succeeded: 남은 요금에 대한 결제 성공 (결제 수단 업데이트 후)
  • subscription.active: 성공적인 결제 후 구독 재활성화
모범 사례:
  • 플랜 변경 요금이 실패할 경우 고객에게 즉시 알리세요.
  • 결제 수단 업데이트 방법에 대한 명확한 지침을 제공하세요.
  • 재활성화 상태를 추적하기 위해 웹훅 이벤트를 모니터링하세요.
  • 일시적인 결제 실패에 대한 자동 재시도 로직을 구현하는 것을 고려하세요.

결제 수단 업데이트 API 참조

결제 수단 업데이트 및 구독 재활성화를 위한 전체 API 문서를 확인하세요.

구현 테스트

구독 플랜 변경 구현을 철저히 테스트하기 위해 다음 단계를 따르세요:
1

테스트 환경 설정

  • 테스트 API 키 및 테스트 제품 사용
  • 다양한 플랜 유형으로 테스트 구독 생성
  • 테스트 웹훅 엔드포인트 구성
  • 모니터링 및 로깅 설정
2

다양한 비례 배분 모드 테스트

  • 다양한 청구 주기 위치에서 prorated_immediately 테스트
  • 업그레이드 및 다운그레이드에 대해 difference_immediately 테스트
  • 청구 주기를 재설정하기 위해 full_immediately 테스트
  • 크레딧 계산이 올바른지 확인
3

웹훅 처리 테스트

  • 모든 관련 웹훅 이벤트가 수신되는지 확인
  • 웹훅 서명 검증 테스트
  • 중복 웹훅 이벤트를 우아하게 처리
  • 웹훅 처리 실패 시나리오 테스트
4

오류 시나리오 테스트

  • 잘못된 구독 ID로 테스트
  • 만료된 결제 수단으로 테스트
  • 네트워크 실패 및 타임아웃 테스트
  • 자금 부족으로 테스트
5

생산 환경에서 모니터링

  • 실패한 플랜 변경에 대한 알림 설정
  • 웹훅 처리 시간 모니터링
  • 플랜 변경 성공률 추적
  • 플랜 변경 문제에 대한 고객 지원 티켓 검토

오류 처리

구현에서 일반적인 API 오류를 우아하게 처리하세요:

HTTP 상태 코드

플랜 변경 요청이 성공적으로 처리되었습니다. 구독이 업데이트되고 결제 처리가 시작되었습니다.
잘못된 요청 매개변수입니다. 모든 필수 필드가 제공되고 올바르게 형식화되었는지 확인하세요.
잘못되었거나 누락된 API 키입니다. DODO_PAYMENTS_API_KEY가 올바르고 적절한 권한이 있는지 확인하세요.
구독 ID를 찾을 수 없거나 귀하의 계정에 속하지 않습니다.
구독을 변경할 수 없습니다 (예: 이미 취소됨, 제품이 사용 불가능함 등).
서버 오류가 발생했습니다. 잠시 후 요청을 재시도하세요.

오류 응답 형식

{
  "error": {
    "code": "subscription_not_found",
    "message": "The subscription with ID 'sub_123' was not found",
    "details": {
      "subscription_id": "sub_123"
    }
  }
}

다음 단계