@cogito.ai/cli 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +29 -22
  2. package/dist/index.js +9 -15
  3. package/dist/templates/web-nextjs/.github/copilot-instructions.md +5 -6
  4. package/dist/templates/web-nextjs/README.md +25 -24
  5. package/dist/templates/web-nextjs/apps/docs/.source/browser.ts +1 -1
  6. package/dist/templates/web-nextjs/apps/docs/.source/server.ts +6 -6
  7. package/dist/templates/web-nextjs/apps/docs/app/docs/[[...slug]]/page.tsx +1 -6
  8. package/dist/templates/web-nextjs/apps/docs/app/docs/layout.tsx +1 -4
  9. package/dist/templates/web-nextjs/apps/docs/app/llms-full.txt/route.ts +3 -1
  10. package/dist/templates/web-nextjs/apps/docs/next-env.d.ts +1 -1
  11. package/dist/templates/web-nextjs/apps/web/.env.example +35 -0
  12. package/dist/templates/web-nextjs/apps/web/.github/copilot-instructions.md +53 -6
  13. package/dist/templates/web-nextjs/apps/web/.github/skills/impeccable/SKILL.md +55 -0
  14. package/dist/templates/web-nextjs/apps/web/DESIGN.md +65 -0
  15. package/dist/templates/web-nextjs/apps/web/messages/en.json +151 -5
  16. package/dist/templates/web-nextjs/apps/web/messages/zh.json +151 -5
  17. package/dist/templates/web-nextjs/apps/web/next-env.d.ts +1 -1
  18. package/dist/templates/web-nextjs/apps/web/next.config.ts +3 -3
  19. package/dist/templates/web-nextjs/apps/web/package.json +4 -0
  20. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(auth)/forgot-password/page.tsx +167 -38
  21. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(auth)/login/page.tsx +13 -3
  22. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(auth)/reset-password/page.tsx +4 -1
  23. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(auth)/signup/page.tsx +18 -17
  24. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(protected)/dashboard/page.tsx +1 -5
  25. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(protected)/payment/[paymentId]/page.tsx +88 -0
  26. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(protected)/payment/checkout/page.tsx +170 -0
  27. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(protected)/pricing/page.tsx +120 -0
  28. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(protected)/settings/layout.tsx +45 -2
  29. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(protected)/settings/profile/page.tsx +2 -8
  30. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/(protected)/settings/subscription/page.tsx +128 -0
  31. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/about/page.tsx +3 -6
  32. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/globals.css +17 -5
  33. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/help/page.tsx +1 -5
  34. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/layout.tsx +10 -8
  35. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/page.tsx +22 -6
  36. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/privacy/page.tsx +3 -6
  37. package/dist/templates/web-nextjs/apps/web/src/app/[locale]/terms/page.tsx +1 -5
  38. package/dist/templates/web-nextjs/apps/web/src/app/api/payments/alipay/create/route.ts +43 -0
  39. package/dist/templates/web-nextjs/apps/web/src/app/api/payments/alipay/notify/route.ts +105 -0
  40. package/dist/templates/web-nextjs/apps/web/src/app/api/payments/alipay/query/route.ts +54 -0
  41. package/dist/templates/web-nextjs/apps/web/src/app/api/payments/alipay/return/route.ts +20 -0
  42. package/dist/templates/web-nextjs/apps/web/src/app/api/payments/create/route.ts +52 -0
  43. package/dist/templates/web-nextjs/apps/web/src/app/api/payments/wechat/create/route.ts +58 -0
  44. package/dist/templates/web-nextjs/apps/web/src/app/api/payments/wechat/notify/route.ts +92 -0
  45. package/dist/templates/web-nextjs/apps/web/src/app/api/payments/wechat/query/route.ts +54 -0
  46. package/dist/templates/web-nextjs/apps/web/src/app/auth/callback/route.ts +2 -3
  47. package/dist/templates/web-nextjs/apps/web/src/components/dashboard/app-sidebar.tsx +13 -16
  48. package/dist/templates/web-nextjs/apps/web/src/components/dashboard/chart-area-interactive.tsx +122 -146
  49. package/dist/templates/web-nextjs/apps/web/src/components/dashboard/data-table.tsx +84 -149
  50. package/dist/templates/web-nextjs/apps/web/src/components/dashboard/nav-documents.tsx +7 -16
  51. package/dist/templates/web-nextjs/apps/web/src/components/dashboard/nav-main.tsx +4 -4
  52. package/dist/templates/web-nextjs/apps/web/src/components/dashboard/nav-secondary.tsx +4 -4
  53. package/dist/templates/web-nextjs/apps/web/src/components/dashboard/nav-user.tsx +12 -21
  54. package/dist/templates/web-nextjs/apps/web/src/components/dashboard/page.tsx +10 -13
  55. package/dist/templates/web-nextjs/apps/web/src/components/dashboard/section-cards.tsx +5 -9
  56. package/dist/templates/web-nextjs/apps/web/src/components/dashboard/site-header.tsx +6 -7
  57. package/dist/templates/web-nextjs/apps/web/src/components/landing/features.tsx +63 -0
  58. package/dist/templates/web-nextjs/apps/web/src/components/landing/footer.tsx +48 -0
  59. package/dist/templates/web-nextjs/apps/web/src/components/landing/header.tsx +97 -0
  60. package/dist/templates/web-nextjs/apps/web/src/components/landing/hero.tsx +45 -0
  61. package/dist/templates/web-nextjs/apps/web/src/components/landing/how-it-works.tsx +35 -0
  62. package/dist/templates/web-nextjs/apps/web/src/components/landing/pricing-teaser.tsx +23 -0
  63. package/dist/templates/web-nextjs/apps/web/src/components/profile/profile-form.tsx +6 -4
  64. package/dist/templates/web-nextjs/apps/web/src/components/providers/theme-provider.tsx +16 -0
  65. package/dist/templates/web-nextjs/apps/web/src/components/ui/alert-dialog.tsx +32 -49
  66. package/dist/templates/web-nextjs/apps/web/src/components/ui/alert.tsx +16 -23
  67. package/dist/templates/web-nextjs/apps/web/src/components/ui/avatar.tsx +25 -38
  68. package/dist/templates/web-nextjs/apps/web/src/components/ui/badge.tsx +16 -18
  69. package/dist/templates/web-nextjs/apps/web/src/components/ui/breadcrumb.tsx +19 -26
  70. package/dist/templates/web-nextjs/apps/web/src/components/ui/button.tsx +23 -24
  71. package/dist/templates/web-nextjs/apps/web/src/components/ui/card.tsx +19 -36
  72. package/dist/templates/web-nextjs/apps/web/src/components/ui/chart.tsx +60 -94
  73. package/dist/templates/web-nextjs/apps/web/src/components/ui/checkbox.tsx +8 -11
  74. package/dist/templates/web-nextjs/apps/web/src/components/ui/collapsible.tsx +5 -17
  75. package/dist/templates/web-nextjs/apps/web/src/components/ui/command.tsx +25 -48
  76. package/dist/templates/web-nextjs/apps/web/src/components/ui/dialog.tsx +21 -35
  77. package/dist/templates/web-nextjs/apps/web/src/components/ui/drawer.tsx +24 -35
  78. package/dist/templates/web-nextjs/apps/web/src/components/ui/dropdown-menu.tsx +26 -55
  79. package/dist/templates/web-nextjs/apps/web/src/components/ui/field.tsx +62 -76
  80. package/dist/templates/web-nextjs/apps/web/src/components/ui/form.tsx +19 -34
  81. package/dist/templates/web-nextjs/apps/web/src/components/ui/input-otp.tsx +13 -20
  82. package/dist/templates/web-nextjs/apps/web/src/components/ui/input.tsx +6 -6
  83. package/dist/templates/web-nextjs/apps/web/src/components/ui/label.tsx +6 -6
  84. package/dist/templates/web-nextjs/apps/web/src/components/ui/pagination.tsx +21 -42
  85. package/dist/templates/web-nextjs/apps/web/src/components/ui/popover.tsx +16 -31
  86. package/dist/templates/web-nextjs/apps/web/src/components/ui/progress.tsx +5 -8
  87. package/dist/templates/web-nextjs/apps/web/src/components/ui/radio-group.tsx +8 -8
  88. package/dist/templates/web-nextjs/apps/web/src/components/ui/scroll-area.tsx +10 -12
  89. package/dist/templates/web-nextjs/apps/web/src/components/ui/select.tsx +26 -41
  90. package/dist/templates/web-nextjs/apps/web/src/components/ui/separator.tsx +7 -7
  91. package/dist/templates/web-nextjs/apps/web/src/components/ui/sheet.tsx +29 -38
  92. package/dist/templates/web-nextjs/apps/web/src/components/ui/sidebar.tsx +157 -189
  93. package/dist/templates/web-nextjs/apps/web/src/components/ui/skeleton.tsx +3 -3
  94. package/dist/templates/web-nextjs/apps/web/src/components/ui/slider.tsx +10 -15
  95. package/dist/templates/web-nextjs/apps/web/src/components/ui/sonner.tsx +13 -7
  96. package/dist/templates/web-nextjs/apps/web/src/components/ui/switch.tsx +9 -9
  97. package/dist/templates/web-nextjs/apps/web/src/components/ui/table.tsx +24 -48
  98. package/dist/templates/web-nextjs/apps/web/src/components/ui/tabs.tsx +21 -31
  99. package/dist/templates/web-nextjs/apps/web/src/components/ui/textarea.tsx +5 -5
  100. package/dist/templates/web-nextjs/apps/web/src/components/ui/theme-toggle.tsx +23 -0
  101. package/dist/templates/web-nextjs/apps/web/src/components/ui/toggle-group.tsx +15 -16
  102. package/dist/templates/web-nextjs/apps/web/src/components/ui/toggle.tsx +14 -15
  103. package/dist/templates/web-nextjs/apps/web/src/components/ui/tooltip.tsx +8 -12
  104. package/dist/templates/web-nextjs/apps/web/src/core/repositories/IAuthRepository.ts +2 -0
  105. package/dist/templates/web-nextjs/apps/web/src/core/types/auth.ts +1 -3
  106. package/dist/templates/web-nextjs/apps/web/src/features/auth/actions.ts +57 -1
  107. package/dist/templates/web-nextjs/apps/web/src/features/auth/index.ts +2 -0
  108. package/dist/templates/web-nextjs/apps/web/src/features/subscription/alipay/client.ts +36 -0
  109. package/dist/templates/web-nextjs/apps/web/src/features/subscription/alipay/server.ts +83 -0
  110. package/dist/templates/web-nextjs/apps/web/src/features/subscription/components/index.ts +4 -0
  111. package/dist/templates/web-nextjs/apps/web/src/features/subscription/components/pro-badge.tsx +9 -0
  112. package/dist/templates/web-nextjs/apps/web/src/features/subscription/components/pro-feature-comparison.tsx +70 -0
  113. package/dist/templates/web-nextjs/apps/web/src/features/subscription/components/quota-warning-banner.tsx +37 -0
  114. package/dist/templates/web-nextjs/apps/web/src/features/subscription/components/upgrade-button.tsx +42 -0
  115. package/dist/templates/web-nextjs/apps/web/src/features/subscription/hooks.ts +141 -0
  116. package/dist/templates/web-nextjs/apps/web/src/features/subscription/payment-helpers.ts +27 -0
  117. package/dist/templates/web-nextjs/apps/web/src/features/subscription/payment-service.ts +56 -0
  118. package/dist/templates/web-nextjs/apps/web/src/features/subscription/service.ts +55 -0
  119. package/dist/templates/web-nextjs/apps/web/src/features/subscription/types.ts +73 -0
  120. package/dist/templates/web-nextjs/apps/web/src/features/subscription/wechat/client.ts +33 -0
  121. package/dist/templates/web-nextjs/apps/web/src/features/subscription/wechat/server.ts +147 -0
  122. package/dist/templates/web-nextjs/apps/web/src/hooks/use-mobile.ts +4 -4
  123. package/dist/templates/web-nextjs/apps/web/src/i18n/config.ts +1 -1
  124. package/dist/templates/web-nextjs/apps/web/src/infra/db/SupabaseAuthRepository.ts +48 -4
  125. package/dist/templates/web-nextjs/apps/web/src/infra/db/client.ts +1 -4
  126. package/dist/templates/web-nextjs/apps/web/src/lib/supabase/admin.ts +23 -0
  127. package/dist/templates/web-nextjs/apps/web/src/lib/utils.ts +2 -2
  128. package/dist/templates/web-nextjs/apps/web/src/lib/validations/auth.ts +13 -0
  129. package/dist/templates/web-nextjs/apps/web/src/lib/wechat-pay/client.ts +66 -0
  130. package/dist/templates/web-nextjs/apps/web/src/lib/wechat-pay/crypto.ts +99 -0
  131. package/dist/templates/web-nextjs/apps/web/src/lib/wechat-pay/types.ts +10 -0
  132. package/dist/templates/web-nextjs/apps/web/src/styles/tokens.css +58 -0
  133. package/dist/templates/web-nextjs/pnpm-lock.yaml +319 -0
  134. package/dist/templates/web-nextjs/supabase/migrations/20250608_add_subscription_tables.sql +125 -0
  135. package/package.json +1 -1
@@ -0,0 +1,54 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { queryAlipayOrder } from '@/features/subscription/alipay/server'
3
+ import { getPaymentByOrderNo } from '@/features/subscription/payment-service'
4
+ import { getServerClient } from '@/infra/db/client'
5
+
6
+ function normalizeAlipayStatus(tradeStatus: string): string {
7
+ switch (tradeStatus) {
8
+ case 'TRADE_SUCCESS':
9
+ case 'TRADE_FINISHED':
10
+ return 'paid'
11
+ case 'TRADE_CLOSED':
12
+ return 'failed'
13
+ case 'WAIT_BUYER_PAY':
14
+ default:
15
+ return 'pending'
16
+ }
17
+ }
18
+
19
+ export async function POST(req: NextRequest) {
20
+ try {
21
+ const supabase = await getServerClient()
22
+ const {
23
+ data: { user },
24
+ } = await supabase.auth.getUser()
25
+
26
+ if (!user) {
27
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
28
+ }
29
+
30
+ const { outTradeNo } = (await req.json()) as { outTradeNo: string }
31
+
32
+ // Verify order ownership
33
+ const payment = await getPaymentByOrderNo(outTradeNo)
34
+ if (!payment) {
35
+ return NextResponse.json({ error: 'Payment not found' }, { status: 404 })
36
+ }
37
+
38
+ if (payment.user_id !== user.id) {
39
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
40
+ }
41
+
42
+ const result = await queryAlipayOrder(outTradeNo)
43
+ const normalizedStatus = normalizeAlipayStatus(result.tradeStatus)
44
+
45
+ return NextResponse.json({
46
+ status: normalizedStatus,
47
+ tradeStatus: result.tradeStatus,
48
+ code: result.code,
49
+ })
50
+ } catch (error) {
51
+ console.error('Alipay query error:', error)
52
+ return NextResponse.json({ error: 'Failed to query Alipay order' }, { status: 500 })
53
+ }
54
+ }
@@ -0,0 +1,20 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { getPaymentByOrderNo } from '@/features/subscription/payment-service'
3
+
4
+ export async function GET(req: NextRequest) {
5
+ const url = new URL(req.url)
6
+ const outTradeNo = url.searchParams.get('out_trade_no')
7
+
8
+ if (!outTradeNo) {
9
+ return NextResponse.redirect(new URL('/pricing', req.url))
10
+ }
11
+
12
+ // Lookup payment by order_no to get the numeric ID for the status page
13
+ const payment = await getPaymentByOrderNo(outTradeNo)
14
+ if (!payment) {
15
+ return NextResponse.redirect(new URL('/pricing', req.url))
16
+ }
17
+
18
+ // Redirect to payment status page using the numeric payment ID
19
+ return NextResponse.redirect(new URL(`/payment/${payment.id}`, req.url))
20
+ }
@@ -0,0 +1,52 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { createPaymentRecord, getPaymentByOrderNo } from '@/features/subscription/payment-service'
3
+ import { getServerClient } from '@/infra/db/client'
4
+ import { generateOrderNumber } from '@/features/subscription/payment-helpers'
5
+
6
+ export async function POST(req: NextRequest) {
7
+ try {
8
+ const supabase = await getServerClient()
9
+ const {
10
+ data: { user },
11
+ } = await supabase.auth.getUser()
12
+
13
+ if (!user) {
14
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
15
+ }
16
+
17
+ const body = (await req.json()) as {
18
+ planId: number
19
+ amount: number
20
+ paymentMethod: string
21
+ billingCycle: string
22
+ }
23
+
24
+ if (!body.planId || !body.amount || !body.paymentMethod || !body.billingCycle) {
25
+ return NextResponse.json({ error: 'Missing required fields' }, { status: 400 })
26
+ }
27
+
28
+ const orderNo = generateOrderNumber()
29
+
30
+ const payment = await createPaymentRecord({
31
+ user_id: user.id,
32
+ order_no: orderNo,
33
+ amount: body.amount,
34
+ currency: 'CNY',
35
+ payment_method: body.paymentMethod as 'alipay' | 'wechat',
36
+ metadata: {
37
+ plan_id: body.planId,
38
+ billing_cycle: body.billingCycle,
39
+ },
40
+ })
41
+
42
+ return NextResponse.json({
43
+ id: payment.id,
44
+ orderNo: payment.order_no,
45
+ amount: payment.amount,
46
+ status: payment.status,
47
+ })
48
+ } catch (error) {
49
+ console.error('Payment creation error:', error)
50
+ return NextResponse.json({ error: 'Failed to create payment' }, { status: 500 })
51
+ }
52
+ }
@@ -0,0 +1,58 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { createWechatNativePay, createWechatH5Pay } from '@/features/subscription/wechat/server'
3
+ import { getPaymentById } from '@/features/subscription/payment-service'
4
+
5
+ export async function POST(req: NextRequest) {
6
+ try {
7
+ const { paymentId } = (await req.json()) as { paymentId: number }
8
+
9
+ const payment = await getPaymentById(paymentId)
10
+ if (!payment) {
11
+ return NextResponse.json({ error: 'Payment not found' }, { status: 404 })
12
+ }
13
+
14
+ const userAgent = req.headers.get('user-agent') || ''
15
+ const isMobile = /Mobile|Android|iPhone/i.test(userAgent)
16
+
17
+ // In development, always use native (qrcode) even on mobile (D3)
18
+ const forceNative = process.env.NODE_ENV === 'development' && isMobile
19
+
20
+ const notifyUrl = `${process.env.NEXT_PUBLIC_APP_URL}/api/payments/wechat/notify`
21
+
22
+ if (!isMobile || forceNative) {
23
+ // Native (PC) - return QR code URL
24
+ const result = await createWechatNativePay({
25
+ description: 'Subscription',
26
+ outTradeNo: payment.order_no,
27
+ notifyUrl,
28
+ amount: { total: payment.amount, currency: 'CNY' },
29
+ })
30
+
31
+ if (forceNative) {
32
+ return NextResponse.json({
33
+ type: 'qrcode',
34
+ codeUrl: result.code_url,
35
+ warning: 'H5 disabled in dev',
36
+ })
37
+ }
38
+
39
+ return NextResponse.json({ type: 'qrcode', codeUrl: result.code_url })
40
+ } else {
41
+ // H5 (mobile)
42
+ const result = await createWechatH5Pay({
43
+ description: 'Subscription',
44
+ outTradeNo: payment.order_no,
45
+ notifyUrl,
46
+ amount: { total: payment.amount, currency: 'CNY' },
47
+ sceneInfo: {
48
+ payer_client_ip: req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || '127.0.0.1',
49
+ },
50
+ })
51
+
52
+ return NextResponse.json({ type: 'redirect', h5Url: result.h5_url })
53
+ }
54
+ } catch (error) {
55
+ console.error('WeChat create error:', error)
56
+ return NextResponse.json({ error: 'Failed to create WeChat payment' }, { status: 500 })
57
+ }
58
+ }
@@ -0,0 +1,92 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { verifyAndDecryptNotify } from '@/features/subscription/wechat/server'
3
+ import { createAdminClient } from '@/lib/supabase/admin'
4
+ import { getPaymentByOrderNo } from '@/features/subscription/payment-service'
5
+
6
+ export async function POST(req: NextRequest) {
7
+ try {
8
+ const rawBody = await req.text()
9
+ const headers: Record<string, string | undefined> = {
10
+ 'wechatpay-timestamp': req.headers.get('wechatpay-timestamp') || undefined,
11
+ 'wechatpay-nonce': req.headers.get('wechatpay-nonce') || undefined,
12
+ 'wechatpay-signature': req.headers.get('wechatpay-signature') || undefined,
13
+ 'wechatpay-serial': req.headers.get('wechatpay-serial') || undefined,
14
+ }
15
+
16
+ const result = await verifyAndDecryptNotify(headers, rawBody)
17
+
18
+ if (result.tradeState !== 'SUCCESS') {
19
+ return NextResponse.json({ code: 'SUCCESS', message: '成功' })
20
+ }
21
+
22
+ const payment = await getPaymentByOrderNo(result.outTradeNo)
23
+ if (!payment) {
24
+ console.warn(`WeChat notify: payment not found for order ${result.outTradeNo}`)
25
+ return NextResponse.json({ code: 'SUCCESS', message: '成功' })
26
+ }
27
+
28
+ // Validate mch_id matches our configured merchant
29
+ if (result.mchId !== process.env.WECHAT_PAY_MCH_ID) {
30
+ console.warn(`WeChat notify: mch_id mismatch. received=${result.mchId}`)
31
+ return NextResponse.json({ code: 'FAIL', message: 'mch_id mismatch' }, { status: 400 })
32
+ }
33
+
34
+ // Validate appid matches our configured app
35
+ if (result.appId !== process.env.WECHAT_PAY_APP_ID) {
36
+ console.warn(`WeChat notify: appid mismatch. received=${result.appId}`)
37
+ return NextResponse.json({ code: 'FAIL', message: 'appid mismatch' }, { status: 400 })
38
+ }
39
+
40
+ // Validate amount matches the local order
41
+ if (result.amount !== payment.amount) {
42
+ console.warn(
43
+ `WeChat notify: amount mismatch. order=${result.outTradeNo}, expected=${payment.amount}, received=${result.amount}`,
44
+ )
45
+ return NextResponse.json({ code: 'FAIL', message: 'amount mismatch' }, { status: 400 })
46
+ }
47
+
48
+ // Idempotency
49
+ if (payment.status === 'paid') {
50
+ return NextResponse.json({ code: 'SUCCESS', message: '成功' })
51
+ }
52
+
53
+ const supabase = createAdminClient()
54
+
55
+ // Update payment with provider_trade_no and conditional update
56
+ const { error: updateError } = await supabase
57
+ .from('payments')
58
+ .update({
59
+ status: 'paid',
60
+ provider_trade_no: result.transactionId,
61
+ paid_at: new Date().toISOString(),
62
+ })
63
+ .eq('order_no', result.outTradeNo)
64
+ .eq('status', 'pending')
65
+
66
+ if (updateError) {
67
+ console.error('WeChat notify: failed to update payment', updateError)
68
+ return NextResponse.json({ code: 'FAIL', message: 'update failed' }, { status: 500 })
69
+ }
70
+
71
+ // Update subscription
72
+ if (payment.subscription_id) {
73
+ const now = new Date()
74
+ const nextMonth = new Date(now)
75
+ nextMonth.setMonth(nextMonth.getMonth() + 1)
76
+
77
+ await supabase
78
+ .from('user_subscriptions')
79
+ .update({
80
+ status: 'active',
81
+ current_period_start: now.toISOString(),
82
+ current_period_end: nextMonth.toISOString(),
83
+ })
84
+ .eq('id', payment.subscription_id)
85
+ }
86
+
87
+ return NextResponse.json({ code: 'SUCCESS', message: '成功' })
88
+ } catch (error) {
89
+ console.error('WeChat notify error:', error)
90
+ return NextResponse.json({ code: 'FAIL', message: (error as Error).message }, { status: 500 })
91
+ }
92
+ }
@@ -0,0 +1,54 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+ import { queryWechatOrder } from '@/features/subscription/wechat/server'
3
+ import { getPaymentByOrderNo } from '@/features/subscription/payment-service'
4
+ import { getServerClient } from '@/infra/db/client'
5
+
6
+ function normalizeWechatStatus(tradeState: string): string {
7
+ switch (tradeState) {
8
+ case 'SUCCESS':
9
+ return 'paid'
10
+ case 'CLOSED':
11
+ case 'REVOKED':
12
+ return 'failed'
13
+ case 'NOTPAY':
14
+ case 'USERPAYING':
15
+ default:
16
+ return 'pending'
17
+ }
18
+ }
19
+
20
+ export async function POST(req: NextRequest) {
21
+ try {
22
+ const supabase = await getServerClient()
23
+ const {
24
+ data: { user },
25
+ } = await supabase.auth.getUser()
26
+
27
+ if (!user) {
28
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
29
+ }
30
+
31
+ const { outTradeNo } = (await req.json()) as { outTradeNo: string }
32
+
33
+ // Verify order ownership
34
+ const payment = await getPaymentByOrderNo(outTradeNo)
35
+ if (!payment) {
36
+ return NextResponse.json({ error: 'Payment not found' }, { status: 404 })
37
+ }
38
+
39
+ if (payment.user_id !== user.id) {
40
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
41
+ }
42
+
43
+ const result = await queryWechatOrder(outTradeNo)
44
+ const normalizedStatus = normalizeWechatStatus(result.trade_state)
45
+
46
+ return NextResponse.json({
47
+ status: normalizedStatus,
48
+ tradeState: result.trade_state,
49
+ })
50
+ } catch (error) {
51
+ console.error('WeChat query error:', error)
52
+ return NextResponse.json({ error: 'Failed to query WeChat order' }, { status: 500 })
53
+ }
54
+ }
@@ -10,9 +10,8 @@ export async function GET(request: NextRequest) {
10
10
  const nextParam = searchParams.get('next')
11
11
 
12
12
  // Whitelist check to prevent open redirect attacks
13
- const safePath = nextParam && ALLOWED_NEXT.includes(nextParam)
14
- ? nextParam
15
- : `/${defaultLocale}/dashboard`
13
+ const safePath =
14
+ nextParam && ALLOWED_NEXT.includes(nextParam) ? nextParam : `/${defaultLocale}/dashboard`
16
15
 
17
16
  if (code) {
18
17
  const supabase = await getServerClient()
@@ -1,6 +1,6 @@
1
- "use client"
1
+ 'use client'
2
2
 
3
- import * as React from "react"
3
+ import * as React from 'react'
4
4
  import {
5
5
  IconDashboard,
6
6
  IconHelp,
@@ -8,11 +8,11 @@ import {
8
8
  IconInnerShadowTop,
9
9
  IconSettings,
10
10
  IconShield,
11
- } from "@tabler/icons-react"
11
+ } from '@tabler/icons-react'
12
12
 
13
- import { NavMain } from "@/components/dashboard/nav-main"
14
- import { NavSecondary } from "@/components/dashboard/nav-secondary"
15
- import { NavUser } from "@/components/dashboard/nav-user"
13
+ import { NavMain } from '@/components/dashboard/nav-main'
14
+ import { NavSecondary } from '@/components/dashboard/nav-secondary'
15
+ import { NavUser } from '@/components/dashboard/nav-user'
16
16
  import {
17
17
  Sidebar,
18
18
  SidebarContent,
@@ -21,7 +21,7 @@ import {
21
21
  SidebarMenu,
22
22
  SidebarMenuButton,
23
23
  SidebarMenuItem,
24
- } from "@/components/ui/sidebar"
24
+ } from '@/components/ui/sidebar'
25
25
 
26
26
  export function AppSidebar({
27
27
  user,
@@ -33,12 +33,12 @@ export function AppSidebar({
33
33
  }) {
34
34
  const navMain = [
35
35
  {
36
- title: "Dashboard",
36
+ title: 'Dashboard',
37
37
  url: `/${locale}/dashboard`,
38
38
  icon: IconDashboard,
39
39
  },
40
40
  {
41
- title: "Settings",
41
+ title: 'Settings',
42
42
  url: `/${locale}/settings/profile`,
43
43
  icon: IconSettings,
44
44
  },
@@ -46,17 +46,17 @@ export function AppSidebar({
46
46
 
47
47
  const navSecondary = [
48
48
  {
49
- title: "Help",
49
+ title: 'Help',
50
50
  url: `/${locale}/help`,
51
51
  icon: IconHelp,
52
52
  },
53
53
  {
54
- title: "Privacy Policy",
54
+ title: 'Privacy Policy',
55
55
  url: `/${locale}/privacy`,
56
56
  icon: IconShield,
57
57
  },
58
58
  {
59
- title: "About",
59
+ title: 'About',
60
60
  url: `/${locale}/about`,
61
61
  icon: IconInfoCircle,
62
62
  },
@@ -67,10 +67,7 @@ export function AppSidebar({
67
67
  <SidebarHeader>
68
68
  <SidebarMenu>
69
69
  <SidebarMenuItem>
70
- <SidebarMenuButton
71
- asChild
72
- className="data-[slot=sidebar-menu-button]:p-1.5!"
73
- >
70
+ <SidebarMenuButton asChild className="data-[slot=sidebar-menu-button]:p-1.5!">
74
71
  <a href={`/${locale}/dashboard`}>
75
72
  <IconInnerShadowTop className="size-5!" />
76
73
  <span className="text-base font-semibold">AgentDock</span>