구독 비즈니스를 위한 최고의 Stripe 설정: 우리가 운영하는 4가지 모델
Stripe 통합을 지난 몇 년간 아마도 십여 번은 설정했는데, 여기 제가 배운 것들입니다: 시작 튜토리얼은 쉬운 부분입니다. 어려운 부분은 같은 조직에서 완전히 다른 네 가지 가격 책정 모델을 실행하고, 30개 이상의 국가 통화를 처리하면서 반올림 오류를 피하고, 토요일 오전 3시에 웹훅 핸들러가 조용히 실패하지 않도록 하는 것입니다.
이것은 "5분 안에 체크아웃 세션 생성" 같은 글이 아닙니다. 우리가 구축하고 운영한 네 가지 프로덕션 가격 책정 모델을 살펴볼 것입니다 -- 지역 가격 책정이 있는 계층형 구독, Stripe Connect를 통한 마켓플레이스 수수료, 특정 엔티티에 연결된 반복 기부, 일회성 서비스 결제. 각각의 고유한 함정이 있으며, 저는 고통스러운 버그로부터 우리를 구한 특정 코드 패턴과 구성 결정을 공유할 것입니다.
목차
- Why One Stripe Setup Doesn't Fit All
- Model 1: Tiered Subscriptions with Regional Pricing
- Model 2: Marketplace Commissions with Stripe Connect
- Model 3: Recurring Donations Tied to Entities
- Model 4: One-Time Service Payments
- Webhook Architecture That Actually Works
- Failed Payment Retry Logic and Dunning
- The Zero-Decimal Currency Bug That Cost Us Money
- Comparing the Four Models
- FAQ

왜 하나의 Stripe 설정이 모두에 맞지는 않는가
Stripe의 문서는 단일 모델 비즈니스에 탁월합니다. 구독 또는 일회성 결제를 선택하고 가이드를 따르고 라이브로 갑니다. 하지만 대부분의 실제 비즈니스는 오래도록 그렇게 단순하게 유지되지 않습니다.
우리는 여러 제품 전반에서 운영합니다: 계층형 구독이 있는 SaaS 플랫폼, 제공자로부터 수수료를 가져가는 마켓플레이스, 반복 스폰서십이 있는 자선 이니셔티브, 일회성 결제가 있는 상담 예약 시스템. 이 모든 것들은 동일한 Stripe 계정 아래에서 운영되지만 제품, 가격 책정, 웹훅 및 고객 관리에 대해 근본적으로 다른 구성이 필요합니다.
제가 팀들이 저지르는 가장 큰 실수는 모든 청구를 하나의 모델로 강제하려고 하는 것입니다. 구독 우선 아키텍처는 일회성 결제가 필요할 때 깨집니다. 체크아웃 세션 전용 접근 방식은 배분이 있는 반복 청구가 필요할 때 무너집니다. Stripe 설정을 청구 패턴의 포트폴리오로 생각해야 합니다.
비슷한 것을 구축 중이라면 -- 특히 프론트엔드에서 Next.js 또는 Astro가 있는 헤드리스 아키텍처에서 -- 여기의 패턴들은 수주일의 디버깅을 절약할 것입니다.
모델 1: 지역 가격 책정이 있는 계층형 구독
이것은 우리가 실행하는 가장 복잡한 모델이며, 가장 많은 고통스러운 교훈을 준 모델입니다. 설정: 30개 이상의 국가에서 다양한 가격 책정이 있는 네 가지 계층(Free, Basic, Pro, Premium).
Stripe의 제품 구조
Stripe에서 각 계층은 별도의 제품입니다. 각 제품에는 여러 가격이 있습니다 -- 통화/지역 조합당 하나씩. 이것이 중요합니다: 단일 가격을 사용하고 스스로 통화 변환을 시도하지 마십시오. Stripe의 다중 통화 가격 책정은 이 목적을 위해 특별히 만들어졌습니다.
// 지역 가격 책정 구성
const REGIONAL_PRICING = {
pro: {
USD: { monthly: 2900, yearly: 29000 }, // $29/mo, $290/yr
EUR: { monthly: 2700, yearly: 27000 }, // €27/mo, €270/yr
GBP: { monthly: 2300, yearly: 23000 }, // £23/mo, £230/yr
JPY: { monthly: 4200, yearly: 42000 }, // ¥4,200/mo -- NOT ¥42.00!
KRW: { monthly: 38000, yearly: 380000 }, // ₩38,000/mo
INR: { monthly: 190000, yearly: 1900000 }, // ₹1,900/mo
BRL: { monthly: 14900, yearly: 149000 }, // R$149/mo
},
// ... basic, premium에 대해 반복
};
저 JPY와 KRW 값들을 주목하십시오? 나중에 그 버그에 대해 자세히 알아볼 것이지만, 짧은 버전: 이들은 소수점 없는 통화입니다. JPY에 4200을 전달하면, Stripe는 이를 ¥4,200으로 해석합니다 -- ¥42.00이 아닙니다. 만약 USD에 대해 하듯이 100을 곱하면, 당신은 누군가에게 ¥420,000($2,800) 청구를 한 것입니다 ¥4,200($28) 대신에. 어떻게 알았는지 물어보세요.
지역 평가판 로직
모든 지역이 무료 평가판을 받지는 않습니다. 우리는 특정 동남아시아 시장에서 이를 배웠는데, 평가판 남용이 다른 지역보다 훨씬 높았습니다. 우리의 구성은 다음과 같습니다:
const TRIAL_CONFIG = {
default_trial_days: 14,
excluded_regions: ['VN', 'PH', 'ID', 'TH', 'MM', 'KH', 'LA'],
reduced_trial_regions: {
IN: 7,
BR: 7,
},
};
function getTrialDays(countryCode) {
if (TRIAL_CONFIG.excluded_regions.includes(countryCode)) {
return 0;
}
return TRIAL_CONFIG.reduced_trial_regions[countryCode]
?? TRIAL_CONFIG.default_trial_days;
}
이것은 구독 생성으로 전달됩니다:
const subscription = await stripe.subscriptions.create({
customer: customerId,
items: [{ price: regionalPriceId }],
trial_period_days: getTrialDays(customer.address.country),
payment_behavior: 'default_incomplete',
payment_settings: {
save_default_payment_method: 'on_subscription',
},
expand: ['latest_invoice.payment_intent'],
});
배분 동작
누군가가 월 중순에 Basic에서 Pro로 업그레이드할 때, 당신은 결정해야 합니다: 그들은 즉시 차액을 지불하나요, 아니면 다음 청구 주기에 지불하나요? 우리는 즉시 결제로 create_prorations을 사용합니다:
const updatedSubscription = await stripe.subscriptions.update(subscriptionId, {
items: [{
id: existingItemId,
price: newPriceId,
}],
proration_behavior: 'create_prorations',
payment_behavior: 'pending_if_incomplete',
});
다운그레이드의 경우, 우리는 청구 주기 끝을 위한 변경을 예약합니다. 아무도 청구서에서 놀라운 크레딧 계산을 원하지 않습니다.
고객 포털
Stripe의 고객 포털은 과소평가됩니다. 자신의 구독 관리 UI를 구축하는 대신, 포털을 구성하고 사용자를 거기로 리다이렉트합니다:
const portalSession = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${process.env.APP_URL}/settings/billing`,
});
대시보드에서 포털을 구성하여 계획 변경, 취소(취소 이유 조사와 함께 -- 데이터는 금이 됩니다), 그리고 결제 방법 업데이트를 허용하도록 합니다. 이것 하나만 해도 아마도 40시간의 프론트엔드 개발을 절약했습니다.
모델 2: Stripe Connect가 있는 마켓플레이스 수수료
우리의 마켓플레이스 모델은 Stripe Connect를 사용하여 고객과 서비스 제공자 간의 결제를 용이하게 합니다. 플랫폼은 모든 거래에 수수료를 가져갑니다. 이것은 대부분의 튜토리얼이 간과하는 Stripe Connect 설정입니다.
제공자 온보딩
마켓플레이스의 모든 제공자는 Stripe Express 계정이 필요합니다. 온보딩 흐름은 계정을 생성하고 Stripe의 호스팅 온보딩으로 리다이렉트합니다:
const account = await stripe.accounts.create({
type: 'express',
country: provider.country,
email: provider.email,
capabilities: {
card_payments: { requested: true },
transfers: { requested: true },
},
business_type: 'individual',
metadata: {
provider_id: provider.id,
platform: 'fme',
},
});
const accountLink = await stripe.accountLinks.create({
account: account.id,
refresh_url: `${process.env.APP_URL}/provider/onboarding/refresh`,
return_url: `${process.env.APP_URL}/provider/onboarding/complete`,
type: 'account_onboarding',
});
핵심 세부 사항: refresh_url은 링크가 만료된 경우 Stripe가 사용자를 보내는 곳입니다. 이것은 생각보다 자주 일어납니다 -- 누군가가 휴대폰에서 온보딩을 시작하고, 산만해지고, 나중에 돌아온다면. 항상 새 링크를 생성하여 우아하게 처리하십시오.
수수료 구조
고객이 서비스를 예약할 때, 우리는 application_fee_amount로 PaymentIntent를 생성합니다:
const paymentIntent = await stripe.paymentIntents.create({
amount: bookingAmountInCents,
currency: 'usd',
application_fee_amount: Math.round(bookingAmountInCents * 0.15), // 15% 플랫폼 수수료
transfer_data: {
destination: providerStripeAccountId,
},
metadata: {
booking_id: booking.id,
provider_id: provider.id,
customer_id: customer.id,
},
});
15% 수수료는 플랫폼으로 갑니다. 나머지 85%(Stripe의 처리 수수료 제외)는 제공자의 Express 계정으로 갑니다.
지급 일정 예약
기본적으로, Stripe는 Express 계정을 순환 기준으로 지급합니다. 우리는 이것을 주간 지급으로 변경하는데, 이는 환불과 분쟁에 대한 완충을 제공합니다:
await stripe.accounts.update(providerStripeAccountId, {
settings: {
payouts: {
schedule: {
interval: 'weekly',
weekly_anchor: 'friday',
},
},
},
});
금요일 지급은 제공자가 월요일까지 자신의 은행 계좌에서 돈을 본다는 의미입니다. 작은 일이지만 제공자 만족도와 유지에 엄청나게 중요합니다.
Connect 특정 웹훅
Stripe Connect를 사용하면, 당신의 플랫폼과 연결된 계정 모두에 대한 웹훅을 받습니다. Connect 이벤트에 대한 별도의 웹훅 끝점이 필요합니다:
// 일반 웹훅 끝점
app.post('/webhooks/stripe', handlePlatformWebhooks);
// Connect 웹훅 끝점
app.post('/webhooks/stripe-connect', handleConnectWebhooks);
Connect 웹훅 핸들러는 다르게 이벤트를 확인하고 account 필드를 확인해야 합니다:
async function handleConnectWebhooks(req, res) {
const event = stripe.webhooks.constructEvent(
req.body,
req.headers['stripe-signature'],
process.env.STRIPE_CONNECT_WEBHOOK_SECRET // 다른 비밀!
);
const connectedAccountId = event.account;
// 이제 연결된 계정의 맥락에서 이벤트를 처리합니다
}

모델 3: 엔티티에 연결된 반복 기부
이것은 우리가 구축 중인 동물 자선 이니셔티브를 위한 것입니다 -- "특정 동물 스폰서"를 월별 반복 기부로 생각하십시오. 기부자는 동물을 선택하고, 월간 금액을 설정하고, 사진 업데이트를 얻습니다.
엔티티 연결 구독
여기의 트릭은 Stripe 구독을 당신의 데이터베이스의 특정 엔티티(동물)에 연결하는 것입니다. 우리는 전적으로 메타데이터를 통해 이것을 합니다:
const subscription = await stripe.subscriptions.create({
customer: donorCustomerId,
items: [{
price_data: {
currency: 'usd',
product: sponsorshipProductId,
unit_amount: donorChosenAmount, // 기부자가 자신의 금액을 선택합니다
recurring: {
interval: 'month',
},
},
}],
metadata: {
entity_id: animal.id,
entity_type: 'animal',
entity_name: animal.name,
sponsor_email: donor.email,
},
});
미리 생성한 가격 대신 price_data를 사용하면 기부자들이 자신의 월간 금액을 선택할 수 있습니다. 이것은 수백 개의 가격 객체를 생성하는 것보다 깔끔합니다.
월간 업데이트 이메일
스폰서십 구독에 대해 invoice.paid가 발생하면, 우리는 월간 업데이트 흐름을 트리거합니다:
async function handleSponsorshipInvoicePaid(invoice) {
const subscription = await stripe.subscriptions.retrieve(invoice.subscription);
const entityId = subscription.metadata.entity_id;
// 최신 사진과 함께 월간 업데이트 이메일을 큐에 넣습니다
await emailQueue.add('sponsorship-update', {
donorEmail: subscription.metadata.sponsor_email,
entityId,
invoiceAmount: invoice.amount_paid,
invoicePdf: invoice.invoice_pdf,
});
}
이메일은 청구서 PDF(Stripe가 자동으로 생성함), 스폰서된 동물의 최신 사진, 그리고 관리 업데이트를 포함합니다. 반복 기부에 대한 이탈을 극적으로 감소시키는 작은 터치입니다.
기부 취소 처리
누군가가 그들의 스폰서십을 취소할 때, 당신은 SaaS 취소와 다르게 처리해야 합니다. "다운그레이드"가 없습니다 -- 취소하거나 아무것도 아닙니다. 하지만 당신은 나중에 다시 구독하기 쉽게 만들고 싶습니다:
async function handleSponsorshipCancellation(subscription) {
const entityId = subscription.metadata.entity_id;
// 스폰서십을 삭제하지 않고 비활성으로 표시합니다
await db.sponsorships.update({
where: { stripeSubscriptionId: subscription.id },
data: {
status: 'inactive',
cancelledAt: new Date(),
},
});
// "당신이 그리울 거예요" 이메일을 쉬운 재구독 링크와 함께 보냅니다
await sendCancellationEmail(subscription.metadata.sponsor_email, entityId);
}
모델 4: 일회성 서비스 결제
가장 단순한 모델이지만 여전히 중요한 세부 사항이 있습니다. 이 패턴은 누군가가 한 번 지불하고 서비스를 받는 상담 예약용입니다 -- 반복 청구 없이.
예약 데이터가 있는 체크아웃 세션
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{
price: consultationPriceId,
quantity: 1,
}],
customer_email: customer.email,
metadata: {
booking_id: booking.id,
service_type: 'consultation',
appointment_date: booking.date.toISOString(),
practitioner_id: booking.practitionerId,
},
success_url: `${process.env.APP_URL}/booking/confirmed?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.APP_URL}/booking/${booking.id}`,
expires_after: 1800, // 30 분
payment_intent_data: {
metadata: {
booking_id: booking.id,
},
},
});
주목할 두 가지가 있습니다. 첫째: expires_after는 버려진 체크아웃 세션이 남아 있는 것을 방지합니다. 예약 슬롯은 영원히 보류되어서는 안 됩니다. 둘째: 우리는 booking_id를 payment_intent_data.metadata에 복제합니다. PaymentIntent 메타데이터가 체크아웃 세션 메타데이터와 별개이기 때문입니다. 당신이 payment_intent.succeeded 웹훅을 받을 때, 당신은 바로 거기에 그 예약 ID를 원할 것입니다.
결제 + 예약 확인
checkout.session.completed에서, 우리는 예약을 확인하고 한 번에 모든 것을 보냅니다:
async function handleCheckoutComplete(session) {
const bookingId = session.metadata.booking_id;
// 예약을 확인합니다
const booking = await db.bookings.update({
where: { id: bookingId },
data: {
status: 'confirmed',
paymentSessionId: session.id,
paidAt: new Date(),
},
});
// 고객에게 확인을 보냅니다
await sendBookingConfirmation(session.customer_email, booking);
// 서비스 제공자에게 알립니다
await notifyPractitioner(booking.practitionerId, booking);
}
실제로 작동하는 웹훅 아키텍처
모든 네 가지 모델 전반에서, 웹훅은 백본입니다. 여기 우리가 너무 많은 디버깅 세션 후에 정착한 아키텍처입니다:
const WEBHOOK_HANDLERS = {
'checkout.session.completed': handleCheckoutComplete,
'invoice.paid': handleInvoicePaid,
'invoice.payment_failed': handlePaymentFailed,
'customer.subscription.created': handleSubscriptionCreated,
'customer.subscription.updated': handleSubscriptionUpdated,
'customer.subscription.deleted': handleSubscriptionDeleted,
'account.updated': handleConnectAccountUpdated,
'payment_intent.succeeded': handlePaymentSucceeded,
};
async function webhookHandler(req, res) {
let event;
try {
event = stripe.webhooks.constructEvent(
req.rawBody, // 당신은 원본 바디가 필요합니다, JSON을 파싱하지 않습니다
req.headers['stripe-signature'],
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.error('웹훅 서명 확인 실패:', err.message);
return res.status(400).send();
}
// 멱등성: 우리가 이미 이 이벤트를 처리했는지 확인합니다
const processed = await db.webhookEvents.findUnique({
where: { stripeEventId: event.id },
});
if (processed) {
return res.status(200).json({ received: true, duplicate: true });
}
const handler = WEBHOOK_HANDLERS[event.type];
if (handler) {
try {
await handler(event.data.object, event);
await db.webhookEvents.create({
data: { stripeEventId: event.id, type: event.type, processedAt: new Date() },
});
} catch (err) {
console.error(`${event.type} 처리 중 오류:`, err);
return res.status(500).send(); // Stripe는 재시도합니다
}
}
res.status(200).json({ received: true });
}
멱등성 확인이 중요합니다. Stripe는 실패한 웹훅을 재시도할 것이고, 당신은 절대로 같은 이벤트를 두 번 처리하고 싶지 않습니다 -- 특히 예약 생성이나 지급 트리거 같은 것들을 위해.
실패한 결제 재시도 로직 및 던닝
Stripe는 내장 Smart Retries를 가지고 있지만, 그 위에 자신의 던닝 로직을 레이어링해야 합니다:
async function handlePaymentFailed(invoice) {
const attemptCount = invoice.attempt_count;
const subscription = await stripe.subscriptions.retrieve(invoice.subscription);
if (attemptCount === 1) {
// 첫 번째 실패: 부드러운 닷지
await sendEmail(invoice.customer_email, 'payment-failed-soft', {
updatePaymentUrl: await createPortalLink(invoice.customer),
});
} else if (attemptCount === 2) {
// 두 번째 실패: 더 긴급함
await sendEmail(invoice.customer_email, 'payment-failed-urgent', {
updatePaymentUrl: await createPortalLink(invoice.customer),
daysUntilCancellation: 7,
});
} else if (attemptCount >= 3) {
// 최종 경고
await sendEmail(invoice.customer_email, 'payment-failed-final', {
updatePaymentUrl: await createPortalLink(invoice.customer),
});
}
}
Stripe의 재시도 일정을 대시보드 → 설정 → 구독 및 이메일 → 실패한 결제 관리에서 구성합니다. 우리는 취소 전에 14일 동안 3회 재시도를 사용합니다.
우리에게 돈을 준 소수점 없는 통화 버그
이것은 자신의 섹션이 필요합니다. 결국 모두를 물어뜯는 버그이기 때문입니다. Stripe는 대부분의 통화에 센트(가장 작은 통화 단위)를 사용합니다. $29.00는 2900이 됩니다. 하지만 일부 통화에는 소수점 자리가 없습니다.
여기는 중요한 소수점 없는 통화들입니다:
| 통화 | 코드 | 예: "$29에 해당하는 것" | Stripe에 전달할 것 |
|---|---|---|---|
| 일본 엔 | JPY | ¥4,200 | 4200 (NOT 420000) |
| 한국 원 | KRW | ₩38,000 | 38000 (NOT 3800000) |
| 베트남 동 | VND | ₫700,000 | 700000 |
| 칠레 페소 | CLP | $25,000 | 25000 |
| 파라과이 과라니 | PYG | ₲200,000 | 200000 |
여기는 우리가 어디에나 사용하는 유틸리티 함수입니다:
const ZERO_DECIMAL_CURRENCIES = [
'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW',
'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF',
'XOF', 'XPF',
];
function toStripeAmount(amount, currency) {
const curr = currency.toUpperCase();
if (ZERO_DECIMAL_CURRENCIES.includes(curr)) {
return Math.round(amount); // 이미 가장 작은 단위에 있습니다
}
return Math.round(amount * 100);
}
function fromStripeAmount(stripeAmount, currency) {
const curr = currency.toUpperCase();
if (ZERO_DECIMAL_CURRENCIES.includes(curr)) {
return stripeAmount;
}
return stripeAmount / 100;
}
어디에나 이것을 사용하십시오. 당신의 체크아웃 생성에서, 웹훅 핸들러에서, 당신의 대시보드 표시에서. 어디에나. 당신이 한 번 잊어버린 시간이 당신이 누군가에게 예상한 것의 100배를 청구하는 시간입니다.
네 가지 모델 비교
| 측면 | 계층형 서브 | 마켓플레이스 | 반복 기부 | 일회성 결제 |
|---|---|---|---|---|
| Stripe 제품 | 여러 제품, 제품당 여러 가격 | 서비스 유형당 단일 제품 | 단일 제품, 동적 가격 책정 | 단일 제품, 고정 가격 |
| 청구 모드 | subscription |
payment (Connect 포함) |
subscription |
payment |
| 웹훅 복잡성 | 높음 (생명주기 이벤트) | 높음 (Connect 이벤트) | 중간 | 낮음 |
| 통화 처리 | 지역 가격 책정 매트릭스 | 제공자의 통화 | 기부자의 통화 | 단일 통화 |
| 평가판 지원 | 예, 지역 의존 | 해당 없음 | 해당 없음 | 해당 없음 |
| 배분 | 예, 업그레이드시 | 해당 없음 | 해당 없음 | 해당 없음 |
| 환불 복잡성 | 배분된 계산 | 플랫폼 수수료 역전 | 간단한 전체 환불 | 간단한 전체 환불 |
| 고객 포털 | 필수 | 필요 없음 | 유용한 것 | 필요 없음 |
| Stripe 수수료 (2025) | 2.9% + 30¢ | 2.9% + 30¢ + 0.5% Connect | 2.9% + 30¢ | 2.9% + 30¢ |
FAQ
계층형 가격 책정을 위해 얼마나 많은 Stripe 제품을 만들어야 합니까?
계층당 하나의 제품. 따라서 Free, Basic, Pro, 그리고 Premium이 있다면, 그것은 네 개의 제품입니다. 그러면 각 제품은 여러 가격을 가집니다 -- 통화와 청구 간격 조합당 하나씩. 월간 및 연간 청구가 있는 Pro 계층을 10개의 통화로 나누면, 그 단일 제품에 20개의 가격 객체를 의미합니다. 많은 것처럼 들리지만, Stripe는 이것을 잘 처리하고 카탈로그를 구성한 상태로 유지합니다.
지역 가격 책정이 있는 구독에 Stripe Checkout을 사용할 수 있습니까?
예, 하지만 체크아웃 세션을 생성하기 전에 고객의 지역을 결정해야 합니다. 이렇게 하면 올바른 가격 ID를 전달할 수 있습니다. 우리는 IP 지리위치(Cloudflare 헤더를 통해)를 사용하여 통화를 미리 선택한 다음, 고객이 확인하거나 변경하도록 합니다. 체크아웃의 자동 통화에 의존하지 마십시오 -- 당신은 어느 가격을 봤는지에 대한 제어를 원합니다.
Stripe Connect Express와 Custom 계정의 차이점은 무엇입니까?
Express 계정은 Stripe가 제공자를 위한 온보딩, 신원 확인, 그리고 대시보드를 처리하게 합니다. Custom 계정은 당신에게 모든 제어 권한을 제공하지만 이 모든 것을 자신이 구축하도록 요구합니다. 대부분의 마켓플레이스의 경우, Express가 올바른 선택입니다. 우리는 제어 손실이 엔지니어링 비용을 정당화하는 경우를 본 적이 없습니다. Express 계정은 또한 세금 보고(미국의 1099)를 자동으로 처리하는데, 이것은 엄청난 규정 준수 승리입니다.
고객을 잃지 않으면서 실패한 구독 결제를 처리하려면 어떻게 합니까?
세 가지를 레이어링하십시오: Stripe의 Smart Retries(대시보드에서 활성화됨), invoice.payment_failed 웹훅에 의해 트리거된 사용자 정의 던닝 이메일, 그리고 취소 전의 유예 기간. 우리는 14일 동안 3회 재시도 시도를 제공합니다. 첫 번째 이메일은 친절합니다("이봐요, 당신의 카드가 만료되었을 수도 있습니다"), 두 번째는 긴급하고, 세 번째는 최종 경고입니다. 고객 포털의 결제 방법을 업데이트할 수 있는 직접 링크를 포함합니다. 이것 하나만으로도 약 30-40%의 실패한 결제를 복구합니다.
Stripe Connect에 대해 별도의 웹훅 끝점이 필요합니까?
예. 플랫폼 이벤트와 Connect 계정 이벤트는 다른 웹훅 비밀과 다른 이벤트 구조를 사용합니다. Connect 이벤트는 이벤트가 어느 연결된 계정과 관련이 있는지 식별하는 account 필드를 포함합니다. Stripe 대시보드에 두 개의 끝점을 등록하십시오: 플랫폼 이벤트용 하나, Connect 이벤트용 하나. 이 분리는 또한 디버깅을 훨씬 쉽게 만듭니다.
소수점 없는 통화가 무엇이며 왜 신경 써야 합니까?
소수점 없는 통화는 JPY(일본 엔) 및 KRW(한국 원)와 같이 분수 단위를 사용하지 않습니다. Stripe가 "가장 작은 통화 단위의 금액"을 말할 때, USD의 경우 센트(2900 = $29.00)이지만 JPY의 경우 엔(4200 = ¥4,200)입니다. 만약 USD에 대해 하듯이 100을 곱하면, 당신은 ¥420,000 대신 ¥4,200을 청구합니다. 항상 통화를 변환하기 전에 확인하는 헬퍼 함수를 사용하십시오. Stripe는 그들의 문서에서 소수점 없는 통화의 공식 목록을 유지합니다.
구독 관리를 위해 Stripe Billing의 고객 포털을 사용해야 합니까, 아니면 자신의 것을 구축해야 합니까?
매우 구체적인 UI 요구 사항이 없는 한 고객 포털을 사용하십시오. 그것은 계획 변경, 취소, 결제 방법 업데이트, 그리고 청구서 기록을 바로 처리합니다. 브랜딩을 사용자 정의하고 어떤 조치를 허용할지 구성할 수 있습니다. 자신의 포털을 구축하는 것은 배분 계산, 결제 방법 토큰화, 그리고 SCA/3D Secure 흐름을 스스로 처리하는 것을 의미합니다. 포털은 무료입니다 -- Stripe 구독 수수료에 포함되어 있습니다.
지역 가격 책정과 통화 처리를 로컬에서 테스트하려면 어떻게 합니까?
Stripe의 테스트 모드는 모든 통화를 지원합니다. 각 통화에서 테스트 가격을 생성한 다음, Stripe CLI를 사용하여 웹훅을 로컬 서버로 전달합니다: stripe listen --forward-to localhost:3000/webhooks/stripe. 소수점 없는 통화 테스트의 경우, JPY 가격을 생성하고 웹훅 핸들러 로그에서 금액을 확인하고 라이브로 가기 전에 금액을 확인합니다. 우리는 또한 모든 지원되는 통화에 대해 toStripeAmount와 fromStripeAmount를 실행하는 테스트 스위트를 유지합니다 -- 그것이 한 번 이상 문제를 잡았습니다.
구독 기반 제품을 구축 중이고 청구 아키텍처에 대한 도움이 필요하거나, Stripe를 헤드리스 설정과 통합하는 경우, 연락하십시오. 우리는 여러 헤드리스 CMS 프로젝트에서 이러한 패턴을 구축했으며 비용이 드는 실수를 건너뛰도록 도울 수 있습니다. 참여 모델에 대해 가격 페이지를 확인하십시오 -- 우리는 프로젝트 기반 빌드와 진행 중인 자문을 모두 수행합니다.