Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# IDE
.idea/*
.idea
!.idea/icon.png
.vscode

Expand Down
9 changes: 9 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ schedule:
user_badge_score_dispatch_interval_seconds: 1
update_user_badges_scores_task_cron: "0 2 * * *"
update_all_badges_task_cron: "0 1 * * *"
expire_stale_payment_orders_cron: "*/1 * * * *" # 扫描超时未付款订单的频率

# Worker
worker:
Expand All @@ -111,3 +112,11 @@ linuxDo:
# OpenTelemetry 配置
otel:
sampling_rate: 0.1 # 采样率 0.0-1.0

# Payment (LDC Credit EasyPay-compatible)
payment:
enabled: false
api_url: "https://credit.linux.do/epay" # 易支付网关地址
notify_base_url: "https://your-domain.com" # 本项目公网基址,用于拼接回调 URL
config_encryption_key: "<32-char-secret-key!!>" # AES-256 密钥,恰好 32 字节,首次部署后不可更改
order_expire_minutes: 10 # 订单未付款超时时间(分钟)
42 changes: 12 additions & 30 deletions docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,36 +589,6 @@ const docTemplate = `{
}
}
},
"/api/v1/projects/{id}/receive": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"project"
],
"parameters": [
{
"type": "string",
"description": "project id",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/project.ProjectResponse"
}
}
}
}
},
"/api/v1/projects/{id}/receivers": {
"get": {
"consumes": [
Expand Down Expand Up @@ -1032,6 +1002,9 @@ const docTemplate = `{
"maxLength": 32,
"minLength": 1
},
"price": {
"type": "number"
},
"project_items": {
"type": "array",
"minItems": 1,
Expand Down Expand Up @@ -1120,6 +1093,9 @@ const docTemplate = `{
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"received_content": {
"type": "string"
},
Expand Down Expand Up @@ -1204,6 +1180,9 @@ const docTemplate = `{
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"risk_level": {
"type": "integer"
},
Expand Down Expand Up @@ -1390,6 +1369,9 @@ const docTemplate = `{
"maxLength": 32,
"minLength": 1
},
"price": {
"type": "number"
},
"project_items": {
"type": "array",
"items": {
Expand Down
42 changes: 12 additions & 30 deletions docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -580,36 +580,6 @@
}
}
},
"/api/v1/projects/{id}/receive": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"project"
],
"parameters": [
{
"type": "string",
"description": "project id",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/project.ProjectResponse"
}
}
}
}
},
"/api/v1/projects/{id}/receivers": {
"get": {
"consumes": [
Expand Down Expand Up @@ -1023,6 +993,9 @@
"maxLength": 32,
"minLength": 1
},
"price": {
"type": "number"
},
"project_items": {
"type": "array",
"minItems": 1,
Expand Down Expand Up @@ -1111,6 +1084,9 @@
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"received_content": {
"type": "string"
},
Expand Down Expand Up @@ -1195,6 +1171,9 @@
"name": {
"type": "string"
},
"price": {
"type": "number"
},
"risk_level": {
"type": "integer"
},
Expand Down Expand Up @@ -1381,6 +1360,9 @@
"maxLength": 32,
"minLength": 1
},
"price": {
"type": "number"
},
"project_items": {
"type": "array",
"items": {
Expand Down
27 changes: 8 additions & 19 deletions docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ definitions:
maxLength: 32
minLength: 1
type: string
price:
type: number
project_items:
items:
type: string
Expand Down Expand Up @@ -260,6 +262,8 @@ definitions:
$ref: '#/definitions/oauth.TrustLevel'
name:
type: string
price:
type: number
received_content:
type: string
report_count:
Expand Down Expand Up @@ -315,6 +319,8 @@ definitions:
$ref: '#/definitions/oauth.TrustLevel'
name:
type: string
price:
type: number
risk_level:
type: integer
start_time:
Expand Down Expand Up @@ -436,6 +442,8 @@ definitions:
maxLength: 32
minLength: 1
type: string
price:
type: number
project_items:
items:
type: string
Expand Down Expand Up @@ -758,25 +766,6 @@ paths:
$ref: '#/definitions/project.ProjectResponse'
tags:
- project
/api/v1/projects/{id}/receive:
post:
consumes:
- application/json
parameters:
- description: project id
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/project.ProjectResponse'
tags:
- project
/api/v1/projects/{id}/receivers:
get:
consumes:
Expand Down
9 changes: 9 additions & 0 deletions frontend/app/(main)/settings/payment/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {PaymentSettingsPage} from '@/components/common/payment';

export default function Page() {
return (
<div className="container max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<PaymentSettingsPage />
</div>
);
}
2 changes: 1 addition & 1 deletion frontend/components/common/dashboard/DataCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function CardList({title, icon, list, type}: Omit<CardListProps, 'iconBg'
<Avatar className="h-6 w-6 rounded-full flex-shrink-0">
<AvatarImage src={item.avatar} />
<AvatarFallback>
{item.name.charAt(0)}
{item.name?.charAt(0)}
</AvatarFallback>
</Avatar>
);
Expand Down
23 changes: 23 additions & 0 deletions frontend/components/common/layout/ManagementBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ExternalLinkIcon,
User,
LogOutIcon,
Wallet,
} from 'lucide-react';
import {useThemeUtils} from '@/hooks/use-theme-utils';
import {useAuth} from '@/hooks/use-auth';
Expand All @@ -27,6 +28,7 @@ import {
} from '@/components/animate-ui/radix/dialog';
import {Avatar, AvatarFallback, AvatarImage} from '@/components/ui/avatar';
import {TrustLevel} from '@/lib/services/core';
import {DialogClose} from '@/components/ui/dialog';

const IconOptions = {
className: 'h-4 w-4',
Expand Down Expand Up @@ -188,6 +190,27 @@ export function ManagementBar() {
</div>
)}

{/* 账户设置 */}
<div>
<h4 className="text-sm font-semibold mb-3 text-muted-foreground">账户设置</h4>
<div className="grid grid-cols-1 gap-2">
<DialogClose asChild>
<Link
href="/settings/payment"
className="flex items-center gap-3 p-2 rounded-md hover:bg-muted/50 transition-colors group"
>
<div className="flex items-center justify-center w-8 h-8 rounded-md bg-indigo-500/10 group-hover:bg-indigo-500/20 transition-colors">
<Wallet className="h-4 w-4 text-indigo-500" />
</div>
<div className="flex flex-col">
<span className="text-sm font-medium">支付设置</span>
<span className="text-xs text-muted-foreground">配置你作为收款商户的 clientID / clientSecret</span>
</div>
</Link>
</DialogClose>
</div>
</div>

{/* 快速链接区域 */}
<div>
<h4 className="text-sm font-semibold mb-3 text-muted-foreground">快速链接</h4>
Expand Down
60 changes: 60 additions & 0 deletions frontend/components/common/payment/CallbackURLHint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client';

import {Button} from '@/components/ui/button';
import {Input} from '@/components/ui/input';
import {Label} from '@/components/ui/label';
import {copyToClipboard} from '@/lib/utils';
import {Copy, ExternalLink, Info} from 'lucide-react';
import {toast} from 'sonner';

interface CallbackURLHintProps {
notifyUrl: string;
returnUrl: string;
}

function URLRow({label, value}: {label: string; value: string}) {
return (
<div className="space-y-1">
<Label className="text-xs text-muted-foreground">{label}</Label>
<div className="flex gap-2">
<Input readOnly value={value} className="bg-muted/40 font-mono text-xs" />
<Button
size="sm"
variant="secondary"
onClick={async () => {
try {
await copyToClipboard(value);
toast.success('已复制');
} catch {
toast.error('复制失败');
}
}}
>
<Copy className="h-4 w-4" />
</Button>
</div>
</div>
);
}

/**
* 醒目的 Callback URL 提示块,指引用户到 LDC 商户后台配置地址
*/
export function CallbackURLHint({notifyUrl, returnUrl}: CallbackURLHintProps) {
return (
<div className="border border-amber-200 bg-amber-50 dark:bg-amber-950/30 dark:border-amber-900 rounded-lg p-4 space-y-3">
<div className="flex items-start gap-2">
<Info className="h-4 w-4 mt-0.5 text-amber-600 dark:text-amber-400 shrink-0" />
<div className="text-sm text-amber-900 dark:text-amber-200 space-y-1">
<p className="font-medium">请先在 LDC 商户后台配置回调地址</p>
<p className="text-xs text-amber-800/80 dark:text-amber-300/80">
登录 <a href="https://credit.linux.do" target="_blank" rel="noopener noreferrer" className="underline inline-flex items-center">credit.linux.do <ExternalLink className="ml-0.5 h-3 w-3" /></a> 进入你的应用设置,把 <code className="text-xs bg-amber-100 dark:bg-amber-900/50 px-1 rounded">notify_url</code> 与 <code className="text-xs bg-amber-100 dark:bg-amber-900/50 px-1 rounded">return_url</code>(或等价字段)填成下方地址
</p>
</div>
</div>

<URLRow label="notify_url(异步通知)" value={notifyUrl} />
<URLRow label="return_url(付款后同步回跳)" value={returnUrl} />
</div>
);
}
Loading
Loading