Send Payment Link

Overview Description

The Send Payment Link feature generates a Stripe Checkout session for a custom contract and sends the payment link via email to the customer. This allows customers to activate their custom contract by completing the payment process through Stripe.

Pre-conditions

  • User must be authenticated as admin (SuperAdmin or AdminStaff role)
  • Custom contract must exist with status 'draft' or 'offered'
  • Contract must be linked to a valid subscription
  • Subscription must have pricing_type = 'custom' or be cancelled
  • Package plan must be configured with Stripe product ID
  • Subscription must have valid Stripe customer ID (or will be created)

Dependencies

  • Stripe Checkout Service (for creating payment session)
  • SendGrid Email Service (for sending payment link email)
  • User Service (for Stripe customer synchronization)
  • Package To Provider mapping (for Stripe product configuration)

Swagger Link

API: Send Payment Link

Case Documentation

Case 1: Successful Payment Link Generation and Email Sending

Description

Administrator successfully generates a Stripe payment link for a custom contract and sends it via email to the customer. The contract status is updated from 'draft' to '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

Steps

Step 1: Request Validation

  • Description: Validate payment link request parameters
  • Request: POST /api/v1/admin/custom-contracts/{id}/send-payment-link
  • Authorization: User must have SuperAdmin or AdminStaff role
  • Validation Rules:
    • success_url: Required, valid URL format
    • cancel_url: Required, valid URL format
    • email: Optional, valid email format (defaults to subscription 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)

Step 8: Return Response

  • Description: Return payment link URL to admin
  • Response Data:
    • payment_link: Stripe checkout session URL
  • Success Message: "支払いリンクが送信されました" (Payment link sent)

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