支払いリンク送信

概要説明

支払いリンク送信機能は、カスタムプラン用のStripe Checkoutセッションを生成し、メールで顧客に支払いリンクを送信します。これにより、顧客はStripeを通じて支払いプロセスを完了することで、カスタムプランを有効化できます。

前提条件

  • ユーザーは管理者として認証されている必要があります(SuperAdminまたはAdminStaffロール)
  • カスタムプランがステータス'draft'または'offered'で存在する必要があります
  • 契約は有効なサブスクリプションにリンクされている必要があります
  • サブスクリプションはpricing_type = 'custom'であるか、キャンセルされている必要があります
  • パッケージプランはStripe製品IDで設定されている必要があります
  • サブスクリプションは有効なStripe顧客IDを持っている必要があります(または作成されます)

依存関係

  • Stripe Checkoutサービス(支払いセッション作成用)
  • SendGridメールサービス(支払いリンクメール送信用)
  • ユーザーサービス(Stripe顧客同期用)
  • パッケージからプロバイダーへのマッピング(Stripe製品設定用)

Swaggerリンク

API: 支払いリンク送信

ケースドキュメント

ケース1: 支払いリンク生成とメール送信成功

説明

管理者がカスタムプラン用のStripe支払いリンクを正常に生成し、メールで顧客に送信します。契約ステータスは'draft'から'offered'に更新されます。

Sequence Diagram

sequenceDiagram
    participant Admin
    participant Controller as CustomContractController
    participant Request as SendPaymentLinkRequest
    participant Service as CustomContractService
    participant ContractRepo as CustomContractRepository
    participant StripeService as CheckoutStripeService
    participant EmailService as SendGridEmailService
    participant Stripe((Stripe API))
    participant Database
    
    Note over Admin,Database: POST /api/v1/admin/custom-contracts/{id}/send-payment-link
    
    rect rgb(200, 255, 200)
    Note right of Admin: Happy Case Flow
    
    Admin->>Controller: POST request with success_url and cancel_url
    
    rect rgb(200, 230, 255)
    Note right of Controller: Input Validation
    Controller->>Request: validate(data)
    Request->>Request: Check required URLs
    Request->>Request: Validate URL format
    Request-->>Controller: Validation passed
    end
    
    rect rgb(200, 255, 255)
    Note right of Controller: Business Logic Processing
    Controller->>Service: sendPaymentLink(contractId, data)
    
    Service->>ContractRepo: findById(contractId)
    ContractRepo->>Database: SELECT * FROM custom_contracts WHERE id = ?
    Database-->>ContractRepo: Return contract data
    ContractRepo-->>Service: Return contract with relations
    
    Service->>Service: Validate contract status (draft/offered)
    Service->>Service: Validate subscription pricing_type
    Service->>Service: Get package plan and Stripe product ID
    Service->>Service: Ensure Stripe customer ID exists
    
    rect rgb(255, 230, 200)
    Note right of Service: Stripe Checkout Session Creation
    Service->>StripeService: createCheckoutSession(params)
    StripeService->>Stripe: POST /v1/checkout/sessions
    Stripe-->>StripeService: Return session object with URL
    StripeService-->>Service: Return session data
    end
    
    Service->>ContractRepo: update provider_checkout_session_id
    ContractRepo->>Database: UPDATE custom_contracts SET provider_checkout_session_id
    Database-->>ContractRepo: Update confirmed
    
    Service->>Service: Update status from draft to offered
    Service->>ContractRepo: update status
    ContractRepo->>Database: UPDATE custom_contracts SET status = 'offered'
    Database-->>ContractRepo: Update confirmed
    
    rect rgb(255, 230, 200)
    Note right of Service: Email Notification
    Service->>EmailService: sendNotification(email, subject, template_data)
    EmailService->>EmailService: Compose email with payment link
    EmailService-->>Service: Email sent confirmation
    end
    
    Service-->>Controller: Return payment_link URL
    end
    
    Controller-->>Admin: 200 OK (payment_link)
    end
    
    rect rgb(255, 200, 200)
    Note right of Admin: Error Scenarios
    rect rgb(255, 230, 230)
    alt Validation Error
        Request-->>Controller: Validation failed
        Controller-->>Admin: 422 Unprocessable Entity
    else Contract Not Found
        ContractRepo-->>Service: Contract not found
        Service-->>Controller: Error: Contract not found
        Controller-->>Admin: 400 Bad Request
    else Invalid Contract Status
        Service-->>Service: Status not draft/offered
        Service-->>Controller: Error: Invalid status
        Controller-->>Admin: 400 Bad Request
    else Package Plan Not Configured
        Service-->>Service: Stripe product ID missing
        Service-->>Controller: Error: Price not configured
        Controller-->>Admin: 400 Bad Request
    else Stripe API Error
        Stripe-->>StripeService: 4xx/5xx Error
        StripeService-->>Service: Stripe error
        Service-->>Controller: Error: Payment link failed
        Controller-->>Admin: 400 Bad Request
    else Email Missing
        Service-->>Service: No email address
        Service-->>Controller: Error: Email missing
        Controller-->>Admin: 400 Bad Request
    end
    end
    end

ステップ

ステップ1: リクエスト検証

  • 説明: 支払いリンクリクエストパラメータを検証します
  • リクエスト: POST /api/v1/admin/custom-contracts/{id}/send-payment-link
  • 認証: ユーザーはSuperAdminまたはAdminStaffロールを持つ必要があります
  • 検証ルール:
    • success_url: 必須、有効なURL形式
    • cancel_url: 必須、有効なURL形式
    • email: オプション、有効なメール形式(デフォルトはサブスクリプションのメール)

Step 2: Contract Validation

  • Description: Verify contract exists and is in valid state
  • Action:
    • Load contract with subscription, group, user, and package plan relations
    • Verify contract status is 'draft' or 'offered'
    • Verify subscription has pricing_type = 'custom' or is cancelled
    • Get package_plan_id from contract or subscription
  • Business Rules:
    • Only contracts in 'draft' or 'offered' status can have payment links sent
    • Subscription must support custom pricing
  • Potential Errors:
    • Contract not found (404)
    • Invalid contract status (400)
    • Subscription type not allowed (400)

Step 3: Stripe Product Configuration

  • Description: Retrieve Stripe product ID for the package plan
  • Action:
    • Get payment provider ID (Stripe)
    • Query package_to_providers table for product mapping
    • Verify provider_product_id exists
  • Business Rules:
    • Package plan must be configured in Stripe
    • Product ID is required for creating checkout session
  • Potential Errors:
    • Package plan not configured (400)
    • Price not found in Stripe (400)

Step 4: Stripe Customer Synchronization

  • Description: Ensure subscription has valid Stripe customer ID
  • Action:
    • Check if subscription has payment_provider_customer_id
    • If missing, get user from contract or subscription
    • Sync Stripe customer using UserService
    • Update subscription with customer ID
  • Database Operations:
    • UPDATE subscriptions SET payment_provider_customer_id

Step 5: Create Stripe Checkout Session

  • Description: Generate Stripe payment session with custom price
  • Action:
    • Build session parameters:
      • mode = 'subscription'
      • customer = stripe_customer_id
      • line_items with price_data (custom amount and interval)
      • subscription_data.metadata (custom_contract_id, subscription_slug)
      • metadata (custom_contract_id, subscription_slug)
      • success_url and cancel_url from request
    • Call Stripe API to create checkout session
    • Extract session URL from response
  • External API Call:
    • POST to Stripe /v1/checkout/sessions
  • Potential Errors:
    • Stripe API error (network, validation, etc.)

Step 6: Save Session ID and Update Status

  • Description: Store Stripe session ID and update contract status
  • Action:
    • Update contract with provider_checkout_session_id
    • If status is 'draft', update to 'offered'
    • Log session creation with contract and subscription IDs
  • Database Operations:
    • UPDATE custom_contracts SET provider_checkout_session_id, status

Step 7: Send Email Notification

  • Description: Send payment link via email to customer
  • Action:
    • Get recipient email (from request or subscription)
    • Build email subject with contract code
    • Send email using SendGrid with template data:
      • payment_link: Session URL
      • custom_contract_code: Contract code
      • amount: Contract amount
      • billing_interval: Billing interval
    • Log email sending
  • External API Call:
    • SendGrid email API
  • Potential Errors:
    • Email address missing (400)
    • Email sending failed (logged but not blocking)

ステップ8: レスポンス返却

  • 説明: 管理者に支払いリンクURLを返却します
  • レスポンスデータ:
    • payment_link: Stripe checkoutセッションURL
  • 成功メッセージ: "支払いリンクが送信されました"

Database Related Tables & Fields

erDiagram
    custom_contracts {
        bigint id PK
        bigint subscription_id FK
        bigint package_plan_id FK
        string code "Contract identifier"
        string billing_interval "month or year"
        string currency "Currency code"
        integer amount "Amount in minor unit"
        string status "draft, offered, active, expired, cancelled"
        string provider_checkout_session_id "Stripe session ID (updated in this API)"
        string provider_price_id "Stripe price ID (nullable)"
        string provider_subscription_item_id "Stripe subscription item ID (nullable)"
        timestamp created_at
        timestamp updated_at
    }
    subscriptions {
        bigint id PK
        string slug "Subscription identifier"
        bigint package_plan_id FK
        bigint group_id FK
        bigint user_id FK
        string pricing_type "standard or custom"
        bigint custom_contract_id FK
        string payment_provider_customer_id "Stripe customer ID"
        string email "Subscription owner email"
        timestamp created_at
        timestamp updated_at
    }
    package_plans {
        bigint id PK
        bigint package_id FK
        string name "Plan name"
        timestamp created_at
        timestamp updated_at
    }
    package_to_providers {
        bigint id PK
        bigint package_id FK
        bigint provider_id FK
        string provider_product_id "Stripe product ID (required)"
        timestamp created_at
        timestamp updated_at
    }
    
    custom_contracts ||--|| subscriptions : has
    custom_contracts ||--|| package_plans : based_on
    package_plans ||--o{ package_to_providers : has_mapping

Error Handling

  • Log: Stripe operations are logged via logStripe() method
  • Error Detail:
Status Code Error Message Description
422 Validation error messages When URLs are invalid or missing
400 "カスタムプランが見つかりませんでした" When contract doesn't exist
400 "サブスクリプションが見つかりませんでした" When subscription not linked to contract
400 "サブスクリプションのタイプ切り替えは許可されていません" When subscription pricing_type invalid
400 "無効なステータスです" When contract status not draft/offered
400 "パッケージプランが必要です" When package_plan_id missing
400 "パッケージプランが見つかりませんでした" When package plan doesn't exist
400 "価格が設定されていません" When Stripe product not configured
400 "支払いリンクの作成に失敗しました" When Stripe API fails
400 "メールアドレスが見つかりません" When email address missing
400 Generic error with exception message For unexpected errors

Additional Notes

Stripe Checkout Session Configuration
  • Mode: subscription (not payment)
  • Customer: Must be existing Stripe customer
  • Line Items: Uses price_data for custom amounts (not price from catalog)
  • Metadata: Critical for webhook processing
    • custom_contract_id: Links payment to contract
    • subscription_slug: Identifies target subscription
Payment Link Lifecycle
  1. Admin creates contract (status: draft)
  2. Admin sends payment link (status: offered, session_id saved)
  3. Customer clicks link and completes payment
  4. Stripe webhook invoice.paid activates contract (status: active)
  5. Contract remains active until cancelled or expired
Email Template
  • Subject includes contract code for easy identification
  • Template includes:
    • Payment link button
    • Contract details (amount, interval)
    • Instructions for completing payment
    • Support contact information
Idempotency
  • Can send payment link multiple times for same contract
  • Each call creates new Stripe session (old ones expire automatically)
  • Status remains 'offered' if already sent before
  • Latest session ID overwrites previous one
Security Considerations
  • Payment links are time-limited by Stripe (24 hours default)
  • Links are single-use (expire after successful payment)
  • Customer email must match subscription email for security
  • Admin cannot see or modify payment data (handled by Stripe)
Performance Considerations
  • External API calls to Stripe (average 500-1000ms)
  • Email sending is synchronous but fast
  • Database updates are minimal (single record)
  • Consider adding queue for email sending in high-volume scenarios