956 lines
16 KiB
Markdown
956 lines
16 KiB
Markdown
# Mixvideo Workflow API Documentation
|
|
|
|
## Overview
|
|
This document describes the REST API endpoints for the Mixvideo Workflow service, which provides video generation capabilities with Google OAuth integration, payment processing, and template management.
|
|
|
|
## Base URL
|
|
```
|
|
https://mixvideo-workflow.bowong.cc/
|
|
```
|
|
|
|
## Authentication
|
|
Most endpoints require authentication via OAuth tokens or API keys. Include tokens in the Authorization header:
|
|
```
|
|
Authorization: Bearer <access_token>
|
|
```
|
|
|
|
---
|
|
|
|
## Auth Controller (`/auth/google`)
|
|
|
|
### Google OAuth Authentication
|
|
|
|
#### `GET /auth/google/authorize`
|
|
Initiates Google OAuth authorization flow.
|
|
|
|
**Query Parameters:**
|
|
- `redirect_url` (optional): URL to redirect after authorization
|
|
- `scopes` (optional): Comma-separated OAuth scopes (default: openid,email,profile)
|
|
|
|
**Response:**
|
|
- Redirects to Google OAuth consent screen
|
|
|
|
---
|
|
|
|
#### `GET /auth/google/callback`
|
|
Handles OAuth callback from Google.
|
|
|
|
**Query Parameters:**
|
|
- `code`: Authorization code from Google
|
|
- `state`: State parameter for CSRF protection
|
|
- `error` (optional): Error code if authorization failed
|
|
|
|
**Success Response (200):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"code": 200,
|
|
"message": "OAuth authorization successful",
|
|
"data": {
|
|
"user": {
|
|
"id": "user_id",
|
|
"email": "user@example.com",
|
|
"name": "User Name"
|
|
},
|
|
"access_token": "ya29.xxx",
|
|
"expires_in": 3600
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /auth/google/refresh`
|
|
Refreshes OAuth access token.
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"refresh_token": "refresh_token_here",
|
|
"user_id": "optional_user_id"
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"code": 200,
|
|
"message": "Token refreshed successfully",
|
|
"data": {
|
|
"access_token": "new_access_token",
|
|
"expires_in": 3600,
|
|
"scope": "openid email profile"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /auth/google/userinfo`
|
|
Retrieves user information from Google.
|
|
|
|
**Headers:**
|
|
- `Authorization: Bearer <access_token>` OR
|
|
- Query parameter: `access_token=<token>`
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"code": 200,
|
|
"message": "User info retrieved successfully",
|
|
"data": {
|
|
"id": "user_id",
|
|
"email": "user@example.com",
|
|
"name": "User Name",
|
|
"picture": "https://..."
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /auth/google/revoke`
|
|
Revokes OAuth token.
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"token": "token_to_revoke",
|
|
"user_id": "optional_user_id"
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"code": 200,
|
|
"message": "Token revoked successfully"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Payment Controller (`/payment`)
|
|
|
|
### Stripe Payment Integration
|
|
|
|
#### `POST /payment/checkout/:templateCode`
|
|
Creates Stripe checkout session for a template.
|
|
|
|
**Path Parameters:**
|
|
- `templateCode`: Template identifier
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"userId": "user_123",
|
|
"metadata": {
|
|
"imageUrl": "https://example.com/image.jpg"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Checkout session created successfully",
|
|
"data": {
|
|
"sessionId": "cs_xxx",
|
|
"url": "https://checkout.stripe.com/pay/cs_xxx",
|
|
"paymentId": "payment_123"
|
|
},
|
|
"timestamp": "2024-01-01T00:00:00Z",
|
|
"traceId": "trace_123"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /payment/webhook`
|
|
Handles Stripe webhook events.
|
|
|
|
**Headers:**
|
|
- `stripe-signature`: Stripe webhook signature
|
|
|
|
**Request Body:** Raw webhook payload from Stripe
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Webhook processed successfully"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /payment/success`
|
|
Handles successful payment callback.
|
|
|
|
**Query Parameters:**
|
|
- `session_id`: Stripe session ID
|
|
- `payment_id`: Internal payment ID
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Payment successful, template execution started",
|
|
"data": {
|
|
"payment": {
|
|
"id": "payment_123",
|
|
"status": "completed",
|
|
"templateCode": "template_001"
|
|
},
|
|
"execution": {
|
|
"executionId": "exec_123",
|
|
"taskId": "task_123"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /payment/cancel`
|
|
Handles payment cancellation.
|
|
|
|
**Query Parameters:**
|
|
- `session_id`: Stripe session ID
|
|
- `payment_id`: Internal payment ID
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Payment cancelled",
|
|
"data": {
|
|
"payment": {
|
|
"id": "payment_123",
|
|
"status": "cancelled"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /payment/history`
|
|
Retrieves user payment history.
|
|
|
|
**Query Parameters:**
|
|
- `userId`: User identifier
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Payment history retrieved successfully",
|
|
"data": {
|
|
"payments": [
|
|
{
|
|
"id": "payment_123",
|
|
"templateCode": "template_001",
|
|
"status": "completed",
|
|
"amount": 1000,
|
|
"currency": "usd",
|
|
"createdAt": "2024-01-01T00:00:00Z"
|
|
}
|
|
],
|
|
"total": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /payment/refund/:paymentId`
|
|
Processes refund for a payment.
|
|
|
|
**Path Parameters:**
|
|
- `paymentId`: Payment identifier
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"reason": "user_request",
|
|
"amount": 1000,
|
|
"metadata": {
|
|
"note": "Customer requested refund"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Refund processed successfully",
|
|
"data": {
|
|
"refundId": "re_xxx",
|
|
"amount": 1000,
|
|
"status": "succeeded"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /payment/:paymentId`
|
|
Retrieves payment details.
|
|
|
|
**Path Parameters:**
|
|
- `paymentId`: Payment identifier
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Payment details retrieved successfully",
|
|
"data": {
|
|
"id": "payment_123",
|
|
"templateCode": "template_001",
|
|
"userId": "user_123",
|
|
"status": "completed",
|
|
"amount": 1000,
|
|
"currency": "usd",
|
|
"stripeSessionId": "cs_xxx",
|
|
"executionId": "exec_123",
|
|
"createdAt": "2024-01-01T00:00:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /payment/health`
|
|
Payment service health check.
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Payment service is healthy",
|
|
"data": {
|
|
"timestamp": "2024-01-01T00:00:00Z",
|
|
"service": "PaymentController"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Task Controller (`/task`)
|
|
|
|
### Task Management
|
|
|
|
#### `GET /task/:taskId`
|
|
Retrieves task status and details.
|
|
|
|
**Path Parameters:**
|
|
- `taskId`: Task identifier
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Task status retrieved",
|
|
"data": {
|
|
"id": "task_123",
|
|
"status": "completed",
|
|
"progress": 100,
|
|
"result": {
|
|
"videoUrl": "https://example.com/output.mp4"
|
|
},
|
|
"createdAt": "2024-01-01T00:00:00Z",
|
|
"completedAt": "2024-01-01T00:05:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /task`
|
|
Creates a new task.
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"type": "video_generation",
|
|
"templateCode": "template_001",
|
|
"userId": "user_123",
|
|
"parameters": {
|
|
"imageUrl": "https://example.com/input.jpg"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Sent message to the queue",
|
|
"data": "task_123"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /task/:taskId`
|
|
Creates task with specific ID.
|
|
|
|
**Path Parameters:**
|
|
- `taskId`: Desired task identifier
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"type": "video_generation",
|
|
"templateCode": "template_001",
|
|
"parameters": {
|
|
"imageUrl": "https://example.com/input.jpg"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Sent message to the queue",
|
|
"data": "task_123"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Template Controller (`/templates`)
|
|
|
|
### Template Management
|
|
|
|
#### `GET /templates`
|
|
Retrieves all active templates.
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Templates retrieved successfully",
|
|
"data": {
|
|
"templates": [
|
|
{
|
|
"code": "template_001",
|
|
"name": "AI Video Generator",
|
|
"description": "Generate videos from images using AI",
|
|
"creditCost": 10,
|
|
"templateType": "VIDEO",
|
|
"isActive": true,
|
|
"tags": ["ai", "video", "generation"]
|
|
}
|
|
],
|
|
"total": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /templates/get/:code`
|
|
Retrieves specific template by code.
|
|
|
|
**Path Parameters:**
|
|
- `code`: Template code
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Template retrieved successfully",
|
|
"data": {
|
|
"code": "template_001",
|
|
"name": "AI Video Generator",
|
|
"description": "Generate videos from images using AI",
|
|
"creditCost": 10,
|
|
"templateType": "VIDEO",
|
|
"workflow": "video-generation-workflow",
|
|
"isActive": true,
|
|
"stripeProductId": "prod_xxx",
|
|
"createdAt": "2024-01-01T00:00:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /templates`
|
|
Creates a new template.
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"code": "template_002",
|
|
"name": "New Template",
|
|
"description": "Template description",
|
|
"workflow": "workflow-name",
|
|
"creditCost": 15,
|
|
"version": "1.0.0",
|
|
"templateType": "IMAGE",
|
|
"tags": ["new", "template"],
|
|
"imageModel": "dall-e-3",
|
|
"imagePrompt": "Generate image based on: {prompt}"
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Template created successfully",
|
|
"data": {
|
|
"code": "template_002",
|
|
"stripeProductId": "prod_yyy",
|
|
"stripePriceId": "price_yyy"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `PUT /templates/update/:code`
|
|
Updates existing template.
|
|
|
|
**Path Parameters:**
|
|
- `code`: Template code
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"name": "Updated Template Name",
|
|
"description": "Updated description",
|
|
"creditCost": 20,
|
|
"isActive": true
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Template updated successfully",
|
|
"data": {
|
|
"updated": true,
|
|
"stripeSynced": true
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `DELETE /templates/delete/:code`
|
|
Deletes template.
|
|
|
|
**Path Parameters:**
|
|
- `code`: Template code
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Template deleted successfully",
|
|
"data": {
|
|
"deleted": true
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /templates/execute/:code`
|
|
Executes template workflow.
|
|
|
|
**Path Parameters:**
|
|
- `code`: Template code
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"userId": "user_123",
|
|
"imageUrl": "https://example.com/input.jpg"
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Template execution started",
|
|
"data": {
|
|
"executionId": "exec_123",
|
|
"taskId": "task_123",
|
|
"status": "running"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /templates/activate/:code`
|
|
Activates template and syncs to Stripe.
|
|
|
|
**Path Parameters:**
|
|
- `code`: Template code
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Template activated successfully",
|
|
"data": {
|
|
"isActive": true,
|
|
"stripeSynced": true
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /templates/deactivate/:code`
|
|
Deactivates template.
|
|
|
|
**Path Parameters:**
|
|
- `code`: Template code
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Template deactivated successfully",
|
|
"data": {
|
|
"isActive": false,
|
|
"stripeSynced": true
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /templates/sync/:code`
|
|
Syncs template to Stripe.
|
|
|
|
**Path Parameters:**
|
|
- `code`: Template code
|
|
|
|
**Request Body (optional):**
|
|
```json
|
|
{
|
|
"forceUpdate": true
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Template sync completed",
|
|
"data": {
|
|
"synced": true,
|
|
"stripeProductId": "prod_xxx",
|
|
"stripePriceId": "price_xxx"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /templates/execution/:taskId/progress`
|
|
Gets execution progress.
|
|
|
|
**Path Parameters:**
|
|
- `taskId`: Task identifier
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Execution progress retrieved",
|
|
"data": {
|
|
"taskId": "task_123",
|
|
"executionId": "exec_123",
|
|
"status": "completed",
|
|
"progress": 100,
|
|
"result": {
|
|
"videoUrl": "https://example.com/output.mp4"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /templates/executions/user/:userId`
|
|
Gets user execution history.
|
|
|
|
**Path Parameters:**
|
|
- `userId`: User identifier
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "User executions retrieved successfully",
|
|
"data": {
|
|
"executions": [
|
|
{
|
|
"executionId": "exec_123",
|
|
"templateCode": "template_001",
|
|
"status": "completed",
|
|
"createdAt": "2024-01-01T00:00:00Z"
|
|
}
|
|
],
|
|
"total": 1
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /templates/bulk`
|
|
Performs bulk operations on templates.
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"templateCodes": ["template_001", "template_002"],
|
|
"operation": "sync_stripe"
|
|
}
|
|
```
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Bulk operation completed",
|
|
"data": {
|
|
"successful": ["template_001", "template_002"],
|
|
"failed": [],
|
|
"total": 2
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /templates/sync-all-stripe`
|
|
Syncs all templates to Stripe.
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "All templates synced to Stripe",
|
|
"data": {
|
|
"successful": 10,
|
|
"failed": 0,
|
|
"total": 10
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /templates/sync-status`
|
|
Checks template-Stripe sync status.
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Sync status retrieved",
|
|
"data": {
|
|
"totalTemplates": 10,
|
|
"withStripeProduct": 8,
|
|
"activeTemplates": 9,
|
|
"needsSync": 1,
|
|
"templates": [
|
|
{
|
|
"code": "template_001",
|
|
"name": "Template Name",
|
|
"isActive": true,
|
|
"hasStripeProduct": true,
|
|
"lastStripeSyncAt": "2024-01-01T00:00:00Z"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /templates/verify-sync/:code`
|
|
Verifies template Stripe sync status.
|
|
|
|
**Path Parameters:**
|
|
- `code`: Template code
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Template sync verified successfully",
|
|
"data": {
|
|
"templateCode": "template_001",
|
|
"synced": true,
|
|
"issues": [],
|
|
"stripeProductId": "prod_xxx",
|
|
"stripePriceId": "price_xxx",
|
|
"recommendations": ["Template is properly synced with Stripe"]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /templates/diagnose-stripe`
|
|
Diagnoses Stripe environment and configuration.
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Stripe environment is healthy",
|
|
"data": {
|
|
"environment": "test",
|
|
"apiKeyValid": true,
|
|
"issues": [],
|
|
"accountInfo": {
|
|
"chargesEnabled": true,
|
|
"country": "US"
|
|
},
|
|
"recommendations": ["Stripe configuration appears healthy"]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /templates/health`
|
|
Template service health check.
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Template service is healthy",
|
|
"data": {
|
|
"totalTemplates": 10,
|
|
"timestamp": "2024-01-01T00:00:00Z",
|
|
"service": "TemplateService"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `GET /templates/init`
|
|
Initializes templates from JSON data.
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Templates initialization completed",
|
|
"data": {
|
|
"total": 10,
|
|
"imported": 9,
|
|
"failed": 1,
|
|
"details": [
|
|
{
|
|
"code": "template_001",
|
|
"status": "success"
|
|
},
|
|
{
|
|
"code": "template_002",
|
|
"status": "error",
|
|
"error": "Template already exists"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
#### `POST /templates/test/create`
|
|
Creates a test template for development.
|
|
|
|
**Response (200):**
|
|
```json
|
|
{
|
|
"code": 200,
|
|
"message": "Test template created successfully",
|
|
"data": {
|
|
"code": "test-image-generation",
|
|
"stripeProductId": "prod_test",
|
|
"stripePriceId": "price_test"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Responses
|
|
|
|
All endpoints return standardized error responses:
|
|
|
|
**Error Response Format:**
|
|
```json
|
|
{
|
|
"code": 400,
|
|
"message": "Error description",
|
|
"timestamp": "2024-01-01T00:00:00Z",
|
|
"traceId": "trace_123"
|
|
}
|
|
```
|
|
|
|
**Common HTTP Status Codes:**
|
|
- `200` - Success
|
|
- `400` - Bad Request (validation errors, missing parameters)
|
|
- `401` - Unauthorized (missing or invalid authentication)
|
|
- `404` - Not Found (resource not found)
|
|
- `500` - Internal Server Error (server-side errors)
|
|
|
|
## Rate Limiting
|
|
|
|
API endpoints may be subject to rate limiting. Check response headers for rate limit information:
|
|
- `X-RateLimit-Limit`: Requests per time window
|
|
- `X-RateLimit-Remaining`: Remaining requests in current window
|
|
- `X-RateLimit-Reset`: Time when rate limit resets
|
|
|
|
## Data Types
|
|
|
|
### Template Types
|
|
- `IMAGE` - Image generation templates
|
|
- `VIDEO` - Video generation templates
|
|
|
|
### Payment Status
|
|
- `pending` - Payment initiated but not completed
|
|
- `completed` - Payment successfully processed
|
|
- `failed` - Payment failed
|
|
- `cancelled` - Payment cancelled by user
|
|
- `refunded` - Payment refunded
|
|
|
|
### Task Status
|
|
- `pending` - Task queued but not started
|
|
- `running` - Task currently executing
|
|
- `completed` - Task finished successfully
|
|
- `failed` - Task failed with error
|
|
|
|
### Refund Reasons
|
|
- `user_request` - User requested refund
|
|
- `technical_issue` - Technical problem occurred
|
|
- `duplicate` - Duplicate payment
|
|
- `fraudulent` - Fraudulent transaction |