Chrome 확장 프로그램에 Stripe Payments를 통합하는 방법 (단계별)

발행: (2025년 12월 14일 오후 05:05 GMT+9)
6 min read
원문: Dev.to

Source: Dev.to

Overview

결제 흐름은 다음과 같습니다:

  1. 사용자가 확장 프로그램 팝업에서 **“Upgrade to Premium”**을 클릭합니다.
  2. 확장 프로그램이 서버리스 함수에 호출하여 Stripe Checkout URL을 생성합니다.
  3. 백그라운드 스크립트가 사용자를 Stripe의 보안 Checkout 페이지로 리디렉션합니다.
  4. 사용자가 결제를 완료합니다.
  5. Stripe가 웹훅을 서버리스 함수에 전송합니다.
  6. 웹훅 핸들러가 구독 데이터를 데이터베이스에 저장합니다.
  7. 사용자가 프리미엄 기능에 접근할 때 확장 프로그램이 프리미엄 상태를 확인합니다.

Part 1: Setting Up Stripe

Step 1 – Create a Stripe Sandbox Account

개발 및 테스트를 위해 Stripe 샌드박스로 시작합니다. 공식 문서를 참고하세요.

Step 2 – Create Your Product

  1. Stripe 대시보드에서 ProductsAdd Product 로 이동합니다.

  2. 세부 정보를 입력합니다:

    • Name: “Premium Subscription”
    • Description: “Monthly premium access”
    • Pricing: $9.99 / month (또는 원하는 가격)
  3. Price ID(price_ 로 시작)를 복사합니다. 나중에 필요합니다.

Step 3 – Get Your API Keys

Developers → API Keys 로 이동하거나 방문하세요.

  • Secret Key(샌드박스의 경우 sk_test_ 로 시작)를 복사합니다.
  • 안전하게 보관하세요—소스 컨트롤에 절대 커밋하지 마세요.

Reference: Stripe API Keys Documentation.

Part 2: Creating Serverless Functions

두 개의 서버리스 엔드포인트를 만들겠습니다:

  • Generate Checkout URLs
  • Handle Stripe Webhooks

Setting Up Your Project

Stripe SDK를 설치합니다:

npm install stripe --save

Reference: Stripe SDK Docs.

Initialize Stripe Client

Stripe 클라이언트를 설정할 파일을 생성합니다:

// lib/stripe/stripeClient.ts
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2025-11-17.clover',
  typescript: true,
});

export default stripe;

STRIPE_SECRET_KEY.env 파일에 정의되어 있어야 합니다.

이 서버리스 함수는 개인화된 Checkout URL(이메일 미리 채워짐)을 생성합니다.

// app/api/checkout/route.ts
import stripe from '@/lib/stripe/stripeClient';

interface CreateCheckoutParams {
  user?: {
    customerId?: string;
    email?: string;
  };
  mode: 'payment' | 'subscription';
  clientReferenceId?: string;
  priceId: string;
  couponId?: string | null;
}

export const createCheckoutStripe = async ({
  user,
  mode,
  clientReferenceId,
  priceId,
  couponId,
}: CreateCheckoutParams): Promise => {
  try {
    const extraParams: {
      customer?: string;
      customer_creation?: 'always';
      customer_email?: string;
      invoice_creation?: { enabled: boolean };
      payment_intent_data?: { setup_future_usage: 'on_session' };
      tax_id_collection?: { enabled: boolean };
    } = {};

    // Existing vs. new customers
    if (user?.customerId) {
      extraParams.customer = user.customerId;
    } else {
      if (mode === 'payment') {
        extraParams.customer_creation = 'always';
        extraParams.invoice_creation = { enabled: true };
        extraParams.payment_intent_data = { setup_future_usage: 'on_session' };
      }
      if (user?.email) extraParams.customer_email = user.email;
      extraParams.tax_id_collection = { enabled: true };
    }

    const stripeSession = await stripe.checkout.sessions.create({
      mode,
      allow_promotion_codes: true,
      client_reference_id: clientReferenceId,
      line_items: [{ price: priceId, quantity: 1 }],
      discounts: couponId ? [{ coupon: couponId }] : [],
      success_url: 'https://example.com/checkout-success',
      cancel_url: 'https://example.com/home',
      locale: 'auto',
      ...extraParams,
    });

    return stripeSession.url;
  } catch (e) {
    console.error('Error creating checkout session:', e);
    return null;
  }
};

export async function POST(request: Request) {
  try {
    const { id, userEmail, mode } = await request.json();

    if (!id) {
      return new Response(JSON.stringify({ error: 'Price ID is missing' }), {
        status: 400,
        headers: { 'Content-Type': 'application/json' },
      });
    }
    if (!mode) {
      return new Response(
        JSON.stringify({
          error: "Mode is missing (choose 'payment' or 'subscription')",
        }),
        { status: 400, headers: { 'Content-Type': 'application/json' } }
      );
    }

    const checkoutUrl = await createCheckoutStripe({
      user: { email: userEmail },
      mode,
      priceId: id,
    });

    if (!checkoutUrl) {
      return new Response(JSON.stringify({ error: 'Failed to create URL' }), {
        status: 500,
        headers: { 'Content-Type': 'application/json' },
      });
    }

    return new Response(JSON.stringify({ checkout_url: checkoutUrl }), {
      status: 200,
      headers: { 'Content-Type': 'application/json' },
    });
  } catch (error) {
    console.error('Checkout error:', error);
    return new Response(JSON.stringify({ error: 'Internal server error' }), {
      status: 500,
      headers: { 'Content-Type': 'application/json' },
    });
  }
}

Alternative: 사용자의 이메일을 미리 채울 필요가 없으면 Stripe 대시보드(Products → Checkout Links)에서 고정 Checkout 링크를 직접 만들고 이 함수를 건너뛸 수 있습니다.

Function 2 – Handle Stripe Webhooks

(구현은 간략히 생략했습니다; Stripe 서명을 검증하고 이벤트를 파싱한 뒤 데이터베이스에 구독 상태를 업데이트하는 엔드포인트를 만들면 됩니다.)

Part 3: Building the Chrome Extension

Extension File Structure

your-extension/
├── manifest.json
├── popup/
│   ├── popup.html
│   └── popup.tsx
├── background/
│   └── background.ts
├── utils/
│   └── handleCheckout.ts
└── components/
    └── PremiumCard.tsx

Step 1 – Checkout Handler

서버리스 Checkout 엔드포인트를 호출하고 URL을 반환하는 유틸리티입니다.

// utils/handleCheckout.ts
/**
 * Generates a Stripe checkout URL by calling your serverless function.
 * The email is pre‑filled on the Stripe Checkout page.
 */
export async function createCheckoutURL(
  email: string | null,
  priceId: string,
  mode: 'payment' | 'subscription' = 'subscription'
): Promise {
  const CHECKOUT_ENDPOINT = 'https://your-app.vercel.app/api/checkout';

  try {
    const response = await fetch(CHECKOUT_ENDPOINT, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        id: priceId,
        userEmail: email,
        mode,
      }),
    });

    if (!response.ok) {
      console.error('Failed to create checkout URL:', response.statusText);
      return null;
    }

    const { checkout_url } = await response.json();
    return checkout_url;
  } catch (error) {
    console.error('Error creating checkout URL:', error);
    return null;
  }
}

이제 팝업이나 백그라운드 스크립트에서 createCheckoutURL을 호출하고 반환된 URL을 새 탭으로 열면 됩니다. 웹훅을 통해 사용자의 프리미엄 상태를 업데이트하면 완료됩니다.

Back to Blog

관련 글

더 보기 »

입력 요소용 Number mask

!Forem 로고https://media2.dev.to/dynamic/image/width=65,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%...