支払いリンク送信
概要説明
支払いリンク送信機能は、カスタムプラン用の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
- Check if subscription has
- 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_idline_itemswithprice_data(custom amount and interval)subscription_data.metadata(custom_contract_id, subscription_slug)metadata(custom_contract_id, subscription_slug)success_urlandcancel_urlfrom request
- Call Stripe API to create checkout session
- Extract session URL from response
- Build session parameters:
- External API Call:
- POST to Stripe
/v1/checkout/sessions
- POST to Stripe
- 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
- Update contract with
- 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 URLcustom_contract_code: Contract codeamount: Contract amountbilling_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(notpayment) - Customer: Must be existing Stripe customer
- Line Items: Uses
price_datafor custom amounts (notpricefrom catalog) - Metadata: Critical for webhook processing
custom_contract_id: Links payment to contractsubscription_slug: Identifies target subscription
Payment Link Lifecycle
- Admin creates contract (status: draft)
- Admin sends payment link (status: offered, session_id saved)
- Customer clicks link and completes payment
- Stripe webhook
invoice.paidactivates contract (status: active) - 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