Jelajahi Sumber

feat: 更新微信支付证书配置

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
MyFramework User 3 bulan lalu
induk
melakukan
36e4f4c524

+ 3 - 0
alipay-test.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cc6889b79c3943b9f0f1da93901f7afa5dcb0475e729b1a3b19b9eff11a73477
+size 60828

+ 3 - 0
cert/wx/1113216359_20260527_cert.zip

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5e9cace868b924f9ca5422baddfa39332ef852135adbe585d9a457d20e89731f
+size 6624

+ 0 - 3
cert/wx/1710495274_20250403_cert.zip

@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:339b3f7766d4fd3cd9a8eb3989371a4c19dbf43869b33636dce44ec3bb2d96bb
-size 5784

TEMPAT SAMPAH
cert/wx/apiclient_cert.p12


+ 18 - 18
cert/wx/apiclient_cert.pem

@@ -1,26 +1,26 @@
 -----BEGIN CERTIFICATE-----
-MIIETzCCAzegAwIBAgIUHP+03q/85glPaT4GCBRAF7gvJxMwDQYJKoZIhvcNAQEL
+MIIETzCCAzegAwIBAgIUNTdDxMivaNtDNGaHBo1xczFPJmUwDQYJKoZIhvcNAQEL
 BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
 FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
-Q0EwHhcNMjUwNDAzMDI1NDQ1WhcNMzAwNDAyMDI1NDQ1WjCBqDETMBEGA1UEAwwK
-MTcxMDQ5NTI3NDEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMVQwUgYDVQQL
-DEvljZfkuqznu4/mtY7mioDmnK/lvIDlj5HljLrot6/lrrnlv4PnkIblkqjor6Ll
+Q0EwHhcNMjYwNTI3MTUyMzExWhcNMzEwNTI2MTUyMzExWjCBqDETMBEGA1UEAwwK
+MTExMzIxNjM1OTEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMVQwUgYDVQQL
+DEvljZfkuqznu4/mtY7mioDmnK/lvIDlj5HljLrot6/lrrnmlofljJbliJvmhI/l
 t6XkvZzlrqTvvIjkuKrkvZPlt6XllYbmiLfvvIkxCzAJBgNVBAYTAkNOMREwDwYD
-VQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJoB
-yTwJovtXuxdHof0aVe+ddglE0EmMa3UJip2KoBevfKhbeoRJpLsSk2KgOOiHOztF
-N+8d8227LTNdEbNpboz+OIrqRM+bGhu5JSEocSUYBwrP/6BeQv7TujOfIz8pqX/G
-RP31m/qbOR0e0nq4q9UGS2tBDcem0sFd0n6Ey7F2ZjJEajzyAwCH2kvnxhVkUT5b
-V4eysIZ7eMn6UXreoL1lyApwRhyOf/d+uDpmNDLJf5w+iU/w7PevDl+/prLSFmW/
-XiAvlaJ65GFIqIRYG357IYfD8TCQdemLUKp5s9MDwJa+YvjdB7LTMBA42lbVgxIH
-tDuvBsB2bk8YG/SNXNsCAwEAAaOBuTCBtjAJBgNVHRMEAjAAMAsGA1UdDwQEAwID
+VQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALw9
+v1T3oex2z7c0o75O2pZhsjLt/u4ziAF2ZEHqzLG5WiFT5xLNtur6jSJaJIDA/xZK
+I8SCPpm0GNViWqtWpSxlURM/QUM+CoXI+35dGYiJWtttLXMxDirB6Un3FyfK+FaS
+db6vhVRnGbm65ShOWzlCc1OrXJkis0lWNLfwORdjQ0+px6hEnQDxD9dklyuWwOCE
+7pZbGXG1AJHonXAjOOddE1sBBCIXD2Q/MxhCMcC2S99prsnjWyb/ezyGRvgxVe5Z
+K/mwCFX+n0eyjawt8j1ukW5q+8NFSmThEZjyi4oSVm1Vm3Umw4E+nHGrzn2dp5Jm
+mf/12PhvirR0zaAKfE0CAwEAAaOBuTCBtjAJBgNVHRMEAjAAMAsGA1UdDwQEAwID
 +DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2Y2EuaXRydXMuY29t
 LmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJDMDRCMDZBRDM5NzU0
 OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJCMjdBOUQzM0E4N0FE
-MUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQC2SIz7L0SHhpF68lEY
-LR7bbEeLqGfcyEWB7XLLSTtIiGJcfFHxGUiR8OB/+gJDs/P7aglqJCHNJN2MZ5zM
-7X6Nn8/GZ2ePWOBP3xcBR19njQxQNBmbgrGPabksYnSCpWcCKClkDOaee9AM9rBj
-wxNoRFWYNZ2bLT25t4L6yPKYj97aGGbEBkPeYNrcgBeHlQwAyB4m1NdydGFnHiV9
-7F2SMhqLxSft3+g5uhENGKQtNZkUCtt+7juyy9LEm6OtGvYpql3y0hkMtK5AzVxi
-YXDJAU2GEQ/3axJqFRztY6HNmRhWT5w4DGdJxYHZ3kGSIAH6TMuiXpKzihy9CXpV
-Je0O
+MUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQBTe10Zlqov05zqTZCY
+ZKD3ZA6rR6Xi1uar+5lmvtkymSVHCL4Ri2UEigIqdCMq6A9rHCRiER2vey1UvQ92
++G2iXWHqnEsRM5GNbaA/1Ki8k5YSvowiZDvVuLLJ3bTY35UeBEtDSA7q/D4xsnUz
+vU2kvFlR01EjfWTQnyN3y9ZDpXpbHGo1ctyMqXrBcO0vaKOhS3zcrQzGgbOtswNh
+W/cSR1zwR4xHKpkElTqWZgjkaxHTL5mLgHZ5DRJLf8WFPWmhtfXffPnoLcA2bqcz
+GWlKHhmWRtF+Jzd3p++Yn3pvK4c405LnGW8/fEsaEgP/ycTdEPd+AtNccD9HM0sv
+gj+F
 -----END CERTIFICATE-----

+ 26 - 26
cert/wx/apiclient_key.pem

@@ -1,28 +1,28 @@
 -----BEGIN PRIVATE KEY-----
-MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCaAck8CaL7V7sX
-R6H9GlXvnXYJRNBJjGt1CYqdiqAXr3yoW3qESaS7EpNioDjohzs7RTfvHfNtuy0z
-XRGzaW6M/jiK6kTPmxobuSUhKHElGAcKz/+gXkL+07oznyM/Kal/xkT99Zv6mzkd
-HtJ6uKvVBktrQQ3HptLBXdJ+hMuxdmYyRGo88gMAh9pL58YVZFE+W1eHsrCGe3jJ
-+lF63qC9ZcgKcEYcjn/3frg6ZjQyyX+cPolP8Oz3rw5fv6ay0hZlv14gL5WieuRh
-SKiEWBt+eyGHw/EwkHXpi1CqebPTA8CWvmL43Qey0zAQONpW1YMSB7Q7rwbAdm5P
-GBv0jVzbAgMBAAECggEBAJlFfclKrfIHdiPNHuKO+0fmJjtDSShpn+hy/omcFYh9
-FktX/674vAGSkyxs2TZSoBh04x4PaN2kk2+zCJLOM0APIqbeUG1tp6Y/tTwyy6hn
-KVsRdw9bwMFsZrxJfQK3rAGGidNWRU48fbMFoeSRNjJkeVrc+6Hf3ZCkl4FjGHYc
-HIL/as0Qiksbvo8QlSa/qdqq2zAQ1aD13ykdQiP0uK8+7mmv48XRD4B46NGuCUKV
-DZQyPzZJksMCP3KYqCYsOrtjgunWOfmLp7CwPEC/idbH/hPoD2w5lndq0m1FAovN
-Jab7yQm0fg9pR+R+AdJ1JaN/5fCJeLpr2HigBHwOwkECgYEAyawvZNcFWDoTo+v+
-30WZ085rWLELTgrD7wr/ntik5faZqQm3nBk+x0lmdOLviTagpZYaUtxmmeAd5RDc
-As3nYhJxufPbZBgHMrxd55CP0bIPcLNJ/xjxSGQZbbDyJIAmmTmna5A8ixkdtdEx
-YIoDiQ19Nr25LQjZSOKPgTNFK6cCgYEAw3530ABdjP6CfhyHpA8a1sJ33pRqosA/
-j2bi6mT4NklboD2cklC5O/SNuTSFOcmU5YruOT5oySrjs3Jm0QUVh50xc4OtgpVz
-eCJ73NzQgTmm/Dx6j4MxRODoESm63gn8pEk1TFqp8FrhkKyXGog/b1LcZ+Mx0YnA
-rdZRCeyx260CgYBYBv5Q58H/dqon/NStJDY2y3zzo6Oplu1bI9Pbw/iGIBOGQyWP
-zz0lw5YjIwMnQ17vqAspa+ak97xUxf0ihDmhByf44MY8dHh/3tSmROVhxHUU3k1I
-Odznmjm3NWscH4u5if7X5odmt7nOKNeCqftNEUQ8ZqezTo95TtsQrA5fNQKBgD2Y
-VlXCF7e55RGpGY+2dqVd5njJ2INpAV3EdBCYOaCZInZtkyyCpKpgVlsZUjSv7+sX
-CBIfONLRYacDj6Oh73OoMpGL1pqDTjt22gHvzP46VQO6Zn9bPaXPGy91L3yQNvro
-WhOxqUOiztlPl9hzP1FrESjma6byuTPMiAIRgsSNAoGBAKnw0pUuwHMdbZzBtS1k
-j1qeq+9rQzXMI83zaeciD1+GlnENm+17QyCnV/ZwajWSqmk6mz688VB9M4wLAtIw
-P8xfytUQ6vvtU4RCJg9s5hBqmKPQcOTyCn8soOY/32l9rudhDC25x9AAEqBMQuEj
-3OVeODeGTvCMN1JoUjUKzuvr
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8Pb9U96Hsds+3
+NKO+TtqWYbIy7f7uM4gBdmRB6syxuVohU+cSzbbq+o0iWiSAwP8WSiPEgj6ZtBjV
+YlqrVqUsZVETP0FDPgqFyPt+XRmIiVrbbS1zMQ4qwelJ9xcnyvhWknW+r4VUZxm5
+uuUoTls5QnNTq1yZIrNJVjS38DkXY0NPqceoRJ0A8Q/XZJcrlsDghO6WWxlxtQCR
+6J1wIzjnXRNbAQQiFw9kPzMYQjHAtkvfaa7J41sm/3s8hkb4MVXuWSv5sAhV/p9H
+so2sLfI9bpFuavvDRUpk4RGY8ouKElZtVZt1JsOBPpxxq859naeSZpn/9dj4b4q0
+dM2gCnxNAgMBAAECggEBAJdP6ySZiYdSWdWicpzfNcX78goOVQWUVpcWW7DA1Jln
+rJNtpijvQ0HO9PPQEdeNS37XSbkEGN3kbrHXxZpEo7D0IB+gkQ1EKaEw1TfWDlg6
+UNZhBE+79u9Xv/a8r9hExyqPwCXvYPVfisZI/TtRAPMd/yYacOyHgCZLLUVNzBaH
+SAq8SS/bGj0NEz0xmDFaBj8Vlas7iVGZnmLMbTmyXdBS2/m2Hnyxrts8clmJc1qK
+NzF524nc/o9/M1h7HIQ41JyvgHu5XQ4GkaBPgeVVGbeZm0mPkevVr4HxElQCjr1t
+o7TInTUrV131AxyruhRr7FjYOYHBbkNY/Q3HiCLYOgECgYEA+d6azGpdyuuyra+3
+M+/RBSOxIPj1ims1EpZGmCRTvRnztLqkpxhNcEpNWQfJygv4AYiFGrwf5Isa1RRn
+86KWSTx23HYssJrtbu492NMSOw5/TwJU4BuySDtLWXc8HxUlv5PmDAuf+eA3Ei6I
+v89Lyu45eypxJh9Xw1e8xMKjgN0CgYEAwNwQUrF1uDDPbeJ0QJSYBFBr+wSvXM+r
+govjkN71gca4u+g7lv0w3AkOcAUPphU+9llMUIvqELs9ADwH1K3KM9EA/2m6qOhD
+OWm9UQd+E7mNFTuOECtnzPtHymD6t0Eyj5tKYoVb9nxjArtFKGqRdayDLlYHZoLA
+ncVGD0ST+jECgYArSe+TnpHlR/RKtviMXO3fcBGN3tPbSTuGntZYIWMIjeh01SkV
+cdZor9a6p+jDwlMTIO2+df0ogRkC9WgVA4P56CnHhAm2BwE/iLLaT6cG6DoSHBtM
+QEAg13T0aP3XfLhvelYOnCz5A2lHExzJ7kX5QFBhEDdOHXv88cRYakgdWQKBgFhH
+o5UmtDsW5Kxo/0vceOVoprTlhRwX12tPe2CCHqFEBKXG/Rf1V9swrhIqzun4SbTc
+VWl9UixMePTVhcQdtTQY1LsVe6L7hmIOe+FOa7C9u83fT/0o0T/sOgVydPFTf8Bq
+lhcY8mqWr3+9lz+T4Heb+wRHzuVmrqLJfXNZsd7RAoGBAPU53izlCy1mgS1h/jQE
+kcj/+f1eE8WbW5++HNyPnsyT42zVrAqW5ZiNo3FLjIiK3QaMxiGcI3jwbq5RxiqX
+mTtNK5ItL7N1Roqa/WdE+qApDd/oXR6rJCzCBqPsN0GsH0W0mNqW+v/pg5dmS5UK
+nHeMo5HTv2HCwU4olrlIAuAN
 -----END PRIVATE KEY-----

+ 0 - 9
cert/wx/pub_key.pem

@@ -1,9 +0,0 @@
------BEGIN PUBLIC KEY-----
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmZ9zqIsyL46h+e1k3M1e
-sVU2pD4bVCfisms3p2eUzlCAr5Ye74sjZN29Z32zpBESWQr31ROmAhaMC0lH3yx4
-OfOw6pxLQyndiNRCGC3yzFJ9iu1PxtRDFFM0pfL6AiNU5QZlDuHcRgZzTg4Z+RDq
-nrGbOKa+OOHeiHc9wAA3XSpkmVVzMdmn5rlvTcA5aHi951BCgfkaYzTfefaanqAq
-zeb0Zi5SAN/c5y8byYek69zZ8yE5aRapGwYSxQkA5T10YNCcRCQpGG/zgvUFB/va
-gURtYKDSzzW3uMYSRv816vsRd+5cM/IhfYyQ0EkRrhlE0Sazr/QKjUhjKuLe61dj
-CQIDAQAB
------END PUBLIC KEY-----

+ 18 - 0
cert/wx/证书使用说明.txt

@@ -0,0 +1,18 @@
+欢迎使用微信支付!
+附件中的三份文件(证书pkcs12格式、证书pem格式、证书密钥pem格式),为接口中强制要求时需携带的证书文件。
+证书属于敏感信息,请妥善保管不要泄露和被他人复制。
+不同开发语言下的证书格式不同,以下为说明指引:
+    证书pkcs12格式(apiclient_cert.p12)
+        包含了私钥信息的证书文件,为p12(pfx)格式,由微信支付签发给您用来标识和界定您的身份
+        部分安全性要求较高的API需要使用该证书来确认您的调用身份
+        windows上可以直接双击导入系统,导入过程中会提示输入证书密码,证书密码默认为您的商户号(如:1900006031)
+    证书pem格式(apiclient_cert.pem)
+        从apiclient_cert.p12中导出证书部分的文件,为pem格式,请妥善保管不要泄漏和被他人复制
+        部分开发语言和环境,不能直接使用p12文件,而需要使用pem,所以为了方便您使用,已为您直接提供
+        您也可以使用openssl命令来自己导出:openssl pkcs12 -clcerts -nokeys -in apiclient_cert.p12 -out apiclient_cert.pem
+    证书密钥pem格式(apiclient_key.pem)
+        从apiclient_cert.p12中导出密钥部分的文件,为pem格式
+        部分开发语言和环境,不能直接使用p12文件,而需要使用pem,所以为了方便您使用,已为您直接提供
+        您也可以使用openssl命令来自己导出:openssl pkcs12 -nocerts -in apiclient_cert.p12 -out apiclient_key.pem
+备注说明:  
+        由于绝大部分操作系统已内置了微信支付服务器证书的根CA证书,  2018年3月6日后, 不再提供CA证书文件(rootca.pem)下载 

+ 27 - 0
docker-nginx/dev-nginx.conf

@@ -0,0 +1,27 @@
+server {
+    listen 80;
+    server_name book.rrbrr.com;
+
+    # 前端 & API(Vite 已代理 /api → localhost:3000)
+    location / {
+        proxy_pass http://host.docker.internal:5173;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+    }
+}
+
+server {
+    listen 80;
+    server_name bookapi.rrbrr.com;
+
+    # 后端 API 直连
+    location / {
+        proxy_pass http://host.docker.internal:3000;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+    }
+}

+ 15 - 0
docker-nginx/dev-nginx.yml

@@ -0,0 +1,15 @@
+# 本地开发 nginx 配置
+# 用法: docker compose -f docker-nginx/dev-nginx.yml up -d
+# 前提: hosts 文件已添加 127.0.0.1 book.rrbrr.com
+
+services:
+  nginx:
+    image: nginx:latest
+    container_name: nginx-local-dev
+    ports:
+      - "80:80"
+    volumes:
+      - ./dev-nginx.conf:/etc/nginx/conf.d/default.conf:ro
+    extra_hosts:
+      - "host.docker.internal:host-gateway"
+    restart: unless-stopped

+ 160 - 29
my-uniapp-vue3/package-lock.json

@@ -16,6 +16,7 @@
         "katex": "^0.16.45",
         "marked": "^4.3.0",
         "pinia": "^2.1.7",
+        "qrcode": "^1.5.4",
         "vconsole": "^3.15.1",
         "vue": "^3.4.21",
         "vue-i18n": "^9.1.9"
@@ -5487,9 +5488,7 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
       "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
-      "dev": true,
       "license": "MIT",
-      "peer": true,
       "engines": {
         "node": ">=8"
       }
@@ -6140,9 +6139,7 @@
       "version": "5.3.1",
       "resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz",
       "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
-      "dev": true,
       "license": "MIT",
-      "peer": true,
       "engines": {
         "node": ">=6"
       }
@@ -6600,6 +6597,15 @@
         }
       }
     },
+    "node_modules/decamelize": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz",
+      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/decimal.js": {
       "version": "10.6.0",
       "resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.6.0.tgz",
@@ -6694,6 +6700,12 @@
         "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
       }
     },
+    "node_modules/dijkstrajs": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+      "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
+      "license": "MIT"
+    },
     "node_modules/dom-walk": {
       "version": "0.1.2",
       "resolved": "https://registry.npmmirror.com/dom-walk/-/dom-walk-0.1.2.tgz",
@@ -6771,9 +6783,7 @@
       "version": "8.0.0",
       "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
-      "dev": true,
-      "license": "MIT",
-      "peer": true
+      "license": "MIT"
     },
     "node_modules/encodeurl": {
       "version": "2.0.0",
@@ -7239,9 +7249,7 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz",
       "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
-      "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "locate-path": "^5.0.0",
         "path-exists": "^4.0.0"
@@ -7388,9 +7396,7 @@
       "version": "2.0.5",
       "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
       "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
-      "dev": true,
       "license": "ISC",
-      "peer": true,
       "engines": {
         "node": "6.* || 8.* || >= 10.*"
       }
@@ -7868,9 +7874,7 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
       "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
-      "dev": true,
       "license": "MIT",
-      "peer": true,
       "engines": {
         "node": ">=8"
       }
@@ -10331,9 +10335,7 @@
       "version": "5.0.0",
       "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz",
       "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
-      "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "p-locate": "^4.1.0"
       },
@@ -10814,9 +10816,7 @@
       "version": "2.3.0",
       "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz",
       "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
-      "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "p-try": "^2.0.0"
       },
@@ -10831,9 +10831,7 @@
       "version": "4.1.0",
       "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz",
       "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
-      "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "p-limit": "^2.2.0"
       },
@@ -10845,9 +10843,7 @@
       "version": "2.2.0",
       "resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz",
       "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
-      "dev": true,
       "license": "MIT",
-      "peer": true,
       "engines": {
         "node": ">=6"
       }
@@ -10958,9 +10954,7 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
       "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
-      "dev": true,
       "license": "MIT",
-      "peer": true,
       "engines": {
         "node": ">=8"
       }
@@ -11408,6 +11402,23 @@
         "node": ">=6"
       }
     },
+    "node_modules/qrcode": {
+      "version": "1.5.4",
+      "resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz",
+      "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+      "license": "MIT",
+      "dependencies": {
+        "dijkstrajs": "^1.0.1",
+        "pngjs": "^5.0.0",
+        "yargs": "^15.3.1"
+      },
+      "bin": {
+        "qrcode": "bin/qrcode"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
     "node_modules/qrcode-reader": {
       "version": "1.0.4",
       "resolved": "https://registry.npmmirror.com/qrcode-reader/-/qrcode-reader-1.0.4.tgz",
@@ -11422,6 +11433,114 @@
         "qrcode-terminal": "bin/qrcode-terminal.js"
       }
     },
+    "node_modules/qrcode/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/qrcode/node_modules/cliui": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz",
+      "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.0",
+        "wrap-ansi": "^6.2.0"
+      }
+    },
+    "node_modules/qrcode/node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/qrcode/node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "license": "MIT"
+    },
+    "node_modules/qrcode/node_modules/pngjs": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz",
+      "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/qrcode/node_modules/wrap-ansi": {
+      "version": "6.2.0",
+      "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+      "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/y18n": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz",
+      "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+      "license": "ISC"
+    },
+    "node_modules/qrcode/node_modules/yargs": {
+      "version": "15.4.1",
+      "resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz",
+      "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+      "license": "MIT",
+      "dependencies": {
+        "cliui": "^6.0.0",
+        "decamelize": "^1.2.0",
+        "find-up": "^4.1.0",
+        "get-caller-file": "^2.0.1",
+        "require-directory": "^2.1.1",
+        "require-main-filename": "^2.0.0",
+        "set-blocking": "^2.0.0",
+        "string-width": "^4.2.0",
+        "which-module": "^2.0.0",
+        "y18n": "^4.0.0",
+        "yargs-parser": "^18.1.2"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/qrcode/node_modules/yargs-parser": {
+      "version": "18.1.3",
+      "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz",
+      "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+      "license": "ISC",
+      "dependencies": {
+        "camelcase": "^5.0.0",
+        "decamelize": "^1.2.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/qs": {
       "version": "6.11.0",
       "resolved": "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz",
@@ -11606,13 +11725,17 @@
       "version": "2.1.1",
       "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
       "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
-      "dev": true,
       "license": "MIT",
-      "peer": true,
       "engines": {
         "node": ">=0.10.0"
       }
     },
+    "node_modules/require-main-filename": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz",
+      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+      "license": "ISC"
+    },
     "node_modules/requires-port": {
       "version": "1.0.0",
       "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz",
@@ -11987,6 +12110,12 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/set-blocking": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz",
+      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+      "license": "ISC"
+    },
     "node_modules/setprototypeof": {
       "version": "1.2.0",
       "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -12216,9 +12345,7 @@
       "version": "4.2.3",
       "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-      "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "emoji-regex": "^8.0.0",
         "is-fullwidth-code-point": "^3.0.0",
@@ -12232,9 +12359,7 @@
       "version": "6.0.1",
       "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
       "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-      "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "ansi-regex": "^5.0.1"
       },
@@ -13494,6 +13619,12 @@
         "node": ">= 8"
       }
     },
+    "node_modules/which-module": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz",
+      "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+      "license": "ISC"
+    },
     "node_modules/wrap-ansi": {
       "version": "7.0.0",
       "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",

+ 1 - 0
my-uniapp-vue3/package.json

@@ -45,6 +45,7 @@
     "katex": "^0.16.45",
     "marked": "^4.3.0",
     "pinia": "^2.1.7",
+    "qrcode": "^1.5.4",
     "vconsole": "^3.15.1",
     "vue": "^3.4.21",
     "vue-i18n": "^9.1.9"

+ 20 - 6
my-uniapp-vue3/src/pages/alipay-qrcode/index.vue

@@ -68,8 +68,9 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, computed } from 'vue';
+import { ref, onMounted, watch, nextTick } from 'vue';
 import { post, get } from '../../utils/request';
+import QRCode from 'qrcode';
 
 const orderInfo = ref<{
   orderNo: string;
@@ -82,16 +83,29 @@ const orderInfo = ref<{
 });
 
 const qrcodeUrl = ref('');
+const qrcodeImageUrl = ref('');
 const isWechatPay = ref(false);
 const loading = ref(false);
 const error = ref('');
 let checkTimer: ReturnType<typeof setInterval> | null = null;
 
-// 生成二维码图片URL(使用在线二维码生成服务)
-const qrcodeImageUrl = computed(() => {
-  if (!qrcodeUrl.value) return '';
-  // 使用 API 转换为图片
-  return `https://api.powered.by/qrcode?text=${encodeURIComponent(qrcodeUrl.value)}&size=200`;
+// 本地生成二维码
+async function generateQRCodeImage(text: string) {
+  try {
+    qrcodeImageUrl.value = await QRCode.toDataURL(text, {
+      width: 300,
+      margin: 2,
+      color: { dark: '#000000', light: '#ffffff' }
+    });
+  } catch (e) {
+    console.error('生成二维码失败:', e);
+  }
+}
+
+watch(qrcodeUrl, (url) => {
+  if (url) {
+    generateQRCodeImage(url);
+  }
 });
 
 onMounted(async () => {

+ 82 - 26
my-uniapp-vue3/src/pages/player/index.vue

@@ -6,15 +6,18 @@
         <text class="nav-icon">↓</text>
       </view>
       <view class="nav-center">
-        <text class="nav-title">正在播放</text>
-        <!-- 睡眠定时显示 -->
+        <text class="nav-title">{{ audio?.title || '正在播放' }}</text>
         <view v-if="sleepTimer" class="sleep-timer-badge" @click="showSleepTimerPicker = true">
-          <text class="sleep-timer-icon">⏰</text>
           <text class="sleep-timer-text">{{ formatSleepTimer(sleepTimer.remaining) }}</text>
         </view>
       </view>
-      <view class="nav-btn" @click="showSleepTimerPicker = true">
-        <text class="nav-icon">⏰</text>
+      <view class="nav-actions">
+        <view class="nav-btn" @click="showSleepTimerPicker = true">
+          <text class="nav-icon">⏰</text>
+        </view>
+        <view class="nav-btn" @click="showLyrics = !showLyrics">
+          <text class="nav-icon">{{ showLyrics ? '📖' : '📕' }}</text>
+        </view>
       </view>
     </view>
 
@@ -30,19 +33,19 @@
     <template v-else>
 
     <!-- 封面和标题 -->
-    <view class="cover-section">
+    <view class="cover-section" v-show="showLyrics">
       <view class="cover">
         <text class="cover-icon">🎵</text>
       </view>
       <text class="title">{{ audio?.title || '未知标题' }}</text>
-      <text class="meta">{{ formatDuration(duration) }} · {{ audio?.wordCount || 0 }}字</text>
     </view>
 
     <!-- 文本预览(歌词同步) -->
     <scroll-view
+      v-show="showLyrics"
       class="text-preview"
       scroll-y
-      :style="{ maxHeight: '300rpx' }"
+      :style="{ maxHeight: '550rpx' }"
       :scroll-into-view="scrollIntoViewId"
       scroll-with-animation
     >
@@ -131,7 +134,7 @@
           <text class="playlist-mode-text">{{ getPlayModeText() }}</text>
         </view>
       </view>
-      <scroll-view class="playlist-list-mobile" scroll-y :style="{ maxHeight: '35vh' }">
+      <scroll-view class="playlist-list-mobile" scroll-y>
         <view
           v-for="(item, index) in playlist"
           :key="item.id"
@@ -191,7 +194,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, computed, onMounted, onUnmounted } from 'vue';
+import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
 import { onShow } from '@dcloudio/uni-app';
 import { onLoad } from '@dcloudio/uni-app';
 import { useAudioStore } from '../../store/audio';
@@ -219,6 +222,7 @@ const audioId = ref('');
 const audio = ref<AudioItem | null>(null);
 const showRatePicker = ref(false);
 const showSleepTimerPicker = ref(false);
+const showLyrics = ref(true);
 const lyrics = ref<{ text: string; start: number; end: number }[]>([]);
 const scrollIntoViewId = ref('');
 
@@ -403,6 +407,14 @@ onShow(() => {
   }
 });
 
+// 监听 store 当前音频变化(自动切歌时同步标题和歌词)
+watch(() => audioStore.currentAudio, (newAudio) => {
+  if (newAudio && newAudio.id !== audio.value?.id) {
+    audio.value = newAudio;
+    updateLyricsForAudio(newAudio);
+  }
+});
+
 // 页面卸载
 onUnmounted(() => {
   // 清除睡眠定时器
@@ -665,11 +677,36 @@ function getPlayModeText() {
   return texts[audioStore.playMode] || '顺序';
 }
 
+// 更新歌词文本(切换章节时调用)
+async function updateLyricsForAudio(audioItem: AudioItem) {
+  let text = audioItem?.text || '';
+  let lrc = (audioItem as any)?.lrcLyrics || '';
+  const ad = audioItem?.audioDuration || audioStore.duration || 60;
+
+  if (!text && !lrc) {
+    try {
+      const detail = await get<any>(`/book-generator/books/chapters/${audioItem.id}`);
+      text = detail?.content || '';
+      lrc = detail?.lrcLyrics || '';
+    } catch (e) {
+      console.warn('获取章节文本失败:', e);
+    }
+  }
+
+  if (lrc) {
+    lyrics.value = parseLrc(lrc);
+  } else {
+    lyrics.value = generateLyricsTimeline(text, ad);
+  }
+  lyrics.value = [{ text: '', start: 0, end: 0 }, ...lyrics.value];
+}
+
 // 上一首
 function playPrev() {
   if (hasPrev.value) {
     audioStore.playPrev();
     audio.value = audioStore.currentAudio;
+    if (audio.value) updateLyricsForAudio(audio.value);
   }
 }
 
@@ -678,6 +715,7 @@ function playNext() {
   if (hasNext.value) {
     audioStore.playNext();
     audio.value = audioStore.currentAudio;
+    if (audio.value) updateLyricsForAudio(audio.value);
   }
 }
 
@@ -685,6 +723,7 @@ function playNext() {
 function playFromPlaylist(index: number) {
   audioStore.play(playlist.value[index]);
   audio.value = playlist.value[index];
+  if (audio.value) updateLyricsForAudio(audio.value);
 }
 
 // 设置播放速度
@@ -796,6 +835,8 @@ function copyShareLink(): string {
   width: 100%;
   box-sizing: border-box;
   overflow-x: hidden;
+  display: flex;
+  flex-direction: column;
 }
 
 /* 空状态 */
@@ -840,7 +881,7 @@ function copyShareLink(): string {
   display: flex;
   align-items: center;
   justify-content: space-between;
-  padding: 44rpx 32rpx 24rpx;
+  padding: 20rpx 32rpx 12rpx;
 }
 
 .nav-center {
@@ -849,6 +890,12 @@ function copyShareLink(): string {
   align-items: center;
 }
 
+.nav-actions {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+}
+
 .nav-btn {
   width: 64rpx;
   height: 64rpx;
@@ -897,32 +944,35 @@ function copyShareLink(): string {
   display: flex;
   flex-direction: column;
   align-items: center;
-  padding: 48rpx 0;
+  padding: 24rpx 0 16rpx;
 }
 
 .cover {
-  width: 320rpx;
-  height: 320rpx;
+  width: 180rpx;
+  height: 180rpx;
   background: linear-gradient(135deg, #4f46e5 0%, #818cf8 100%);
-  border-radius: 32rpx;
+  border-radius: 24rpx;
   display: flex;
   align-items: center;
   justify-content: center;
-  margin-bottom: 32rpx;
-  box-shadow: 0 16rpx 48rpx rgba(79, 70, 229, 0.4);
+  margin-bottom: 16rpx;
+  box-shadow: 0 8rpx 24rpx rgba(79, 70, 229, 0.4);
 }
 
 .cover-icon {
-  font-size: 96rpx;
+  font-size: 64rpx;
 }
 
 .title {
-  font-size: 36rpx;
+  font-size: 32rpx;
   font-weight: 600;
   color: #ffffff;
   text-align: center;
-  margin-bottom: 12rpx;
+  margin-bottom: 8rpx;
   max-width: 600rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
 .meta {
@@ -937,6 +987,7 @@ function copyShareLink(): string {
   border-radius: 16rpx;
   width: calc(100% - 64rpx);
   box-sizing: border-box;
+  overflow: hidden;
 }
 
 .lyrics-container {
@@ -947,12 +998,12 @@ function copyShareLink(): string {
 }
 
 .lyric-line {
-  padding: 12rpx 16rpx;
+  padding: 8rpx 16rpx;
   border-radius: 12rpx;
   opacity: 0.5;
   width: 100%;
   box-sizing: border-box;
-  margin-bottom: 16rpx;
+  margin-bottom: 8rpx;
 }
 
 .lyric-line.active {
@@ -961,9 +1012,9 @@ function copyShareLink(): string {
 }
 
 .lyric-text {
-  font-size: 28rpx;
+  font-size: 24rpx;
   color: #d1d5db;
-  line-height: 1.8;
+  line-height: 1.6;
   white-space: pre-wrap;
   word-break: break-word;
   width: 100%;
@@ -1174,8 +1225,11 @@ function copyShareLink(): string {
 
 /* 移动端播放列表设计 - 直接嵌入界面 */
 .playlist-section-mobile {
-  margin-top: 20rpx;
-  background: rgba(31, 41, 55, 0.8); /* 半透明白色背景 */
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+  background: rgba(31, 41, 55, 0.8);
   border-top-left-radius: 32rpx;
   border-top-right-radius: 32rpx;
   padding: 20rpx 0 0;
@@ -1231,6 +1285,8 @@ function copyShareLink(): string {
 }
 
 .playlist-list-mobile {
+  flex: 1;
+  min-height: 0;
   padding: 0 16rpx 16rpx;
 }
 

+ 62 - 0
server/_test_alipay_sdk.js

@@ -0,0 +1,62 @@
+// 测试 Alipay SDK 修复后是否能正确签名
+async function test() {
+  const { AlipaySdk } = await import('alipay-sdk');
+
+  const rawKey = 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC798Z+2//v0KPswSiczFy/oGv7M99FCgQ4nh4i/F8OtWDs37+Ts/5HD0Jo4wxgrlKHLtUmsEDH2hXn2/08wj0V2cDNgUe2IrOOrpmaiVnBczjilmheUZDtnG26I38WyE3viZ7zQ5ZfNTcVxRH1gbpSC512922Vi9lFRJycIlptvKzsov11RtE29NDoKcPxTo6gMa94hLEVsDRoJroGBlGjw27Pbynwdgcjb4URacUO4T8ZFTnuYZeTyZHqyJF41WUIwRfzRSrlFUb7oRfF4LC23eXELATtpL+28Y5k4CyA6uIOPKJ02CsmCg+VjYxvV/JEw8slm/IUJiLAfjoy8vf3AgMBAAECggEAL6OyuUBE86DrivvBsUvSTfb4pygUdEj/nOHmPXO9G9s4f5CN23+I+LWTXUX+Qxqra/ugpjeRk5Vga+1Dl3ybQsh3px4W8MCGxajW4WXiS7dKOZNpTE5b7vzwinaopTuLFSTKFlmQ6Pxxw6C41oJUmKq89APt0DMrk/S5xJCoXps86xp6zbXjlPm8HtLwuL0OP0WfgnwfXm8kOkaCswS4NXrz83DcR/tUrsdNmcOUmXXuXx+hwu/x0hgMOnSZYlrea/el6deM/giU+JPbM9BMrM8IeMW0MkLSBTikSsav+hetosxuPUrCebcK4QSUrTJ+mwqWNvFFeJuM/P6HbvfTQQKBgQD8GQlwpaQBRe2VXoapjB4ShL6ml+vVNwsjhR0WiwJHavGRS/hKIe/YLRqgfpHUR0/bUOFFxpkRApBT0LevL74c1HriXzHmGhC7DLmYQD+MZmTEbHNbEgVUsJeB04Cn+teWIsKYe80YYbvpwNvsX/Eu5FutOoZCNLrR5SAR7iZNZwKBgQC+4J39gO35W1Kc91tpX5UsmuGPdO6F89BFqvydkcYAYsEi3P92lrTXiplhNnGBDe7KoM8M7gWrnjvTr9S3ZLylKK7cgKn1VUYIqdAqyikiI7hVCfKZZeMa4TKZ0Os0PnGf6PqX3Rm/3yFZpXqD48QqVq2zLePWQGDXdd6BVFDW8QKBgEpBda5gm/ZkCp88wJ2gUBtUTnPzu9uJ4xYLV44UVA+FSLJrli7/4A3k+zrbDUlh1/vZzwEmhl+kSa7mg7Ruc6k6TRfwwlnsw5DVJrXZ9t38KyJQb4obvMY9XeM30WWpVWc7xeX0UW0lHJy12hG+EOSnDvnviyxlK8I3o0Kky1NxAoGAFsyVknYX9rTlxUH2dTupgc5uKsh4xR8+PVQTIG8NC3bAQv3KtcuChkxTzbzHCfc0Epfqo0y6+KjJ1Jsn+U/bCI83c7calec9mkNOTg4fo6hImD+77n6xBcDoXl8J+Q+MWuFlLUaHkNeCEEcnvyExiS0ZSlGs6SZ/arz1etUrYsECgYEA3D2gaibcyTAYrJ/mk5pu2M6g+Ga7A4TSwUCAwUIN0pYVCgFLvl6Faii5n9AzHRd9/H5CQKl2pqEw44tBYPNAyv8zGivyUAO2uDWpQYm7enZjJ/4eSKAJgoF2tGQEjOyORQub6i78P4vnW0yQLpfR7O73JGF9C+PohmfyDpSkbXY=';
+  const pubKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg2H/H1UoYUUSr3hdtgrMQqoFvHpWaz9nSOC9+rM2Csci9iRWW/K9mk4Vfawh9qwaRAv4OPB82arS7NSPUQirUraeGw3JbZ/HUhBY0blkES9GznhQzeht/IES2WccNC+nCprcXcvaLCqEx0gPTb1ujhKDZOkoc9H2JZCyPOiOsMlKdFJjk7jlR/Vn5aA+M9TaK8hOwCz02VCg7d/NfXZFwdg+88k9e4GLbdHI0MnmQ2Mg7DmuSM0TjSk6oPh3OlePMIPcvO/Wt/k2x6Fl2aVvH3FabtqZzNRtseKiTSzKCiNb7mJktkSsoE7lySXsUEP4X+hlesjNyIGRgf/yTCDlNwIDAQAB';
+
+  // 使用 PKCS#8 头(修复后的方式)
+  const privateKey = '-----BEGIN PRIVATE KEY-----\n' + rawKey + '\n-----END PRIVATE KEY-----';
+  const alipayPublicKey = '-----BEGIN PUBLIC KEY-----\n' + pubKey + '\n-----END PUBLIC KEY-----';
+
+  console.log('=== 测试1: 修复后 SDK 初始化 ===');
+  const sdk = new AlipaySdk({
+    appId: '2021006154659461',
+    privateKey,
+    keyType: 'PKCS8',
+    alipayPublicKey,
+    gateway: 'https://openapi.alipay.com/gateway.do',
+    signType: 'RSA2',
+  });
+  console.log('✅ SDK 初始化成功\n');
+
+  console.log('=== 测试2: pageExecute 生成支付表单 ===');
+  try {
+    const form = sdk.pageExecute('alipay.trade.wap.pay', 'POST', {
+      bizContent: {
+        out_trade_no: 'TEST' + Date.now(),
+        total_amount: '0.01',
+        subject: 'AI有声书订阅-测试',
+        product_code: 'QUICK_WAP_WAY',
+      },
+      returnUrl: 'https://book.rrbrr.com/#/pages/payment-result/index',
+      notifyUrl: 'https://book.rrbrr.com/api/payment/alipay/notify',
+    });
+
+    const actionMatch = String(form).match(/action="([^"]+)"/);
+    const paymentUrl = actionMatch ? actionMatch[1] : '未能提取action';
+    console.log('✅ pageExecute 调用成功');
+    console.log('支付网关地址:', paymentUrl);
+    console.log('表单前200字符:', String(form).substring(0, 200));
+  } catch (e) {
+    console.error('❌ pageExecute 失败:', e.message);
+  }
+
+  console.log('\n=== 测试3: 错误格式对比 (PKCS#1 包装) ===');
+  try {
+    const wrongKey = '-----BEGIN RSA PRIVATE KEY-----\n' + rawKey + '\n-----END RSA PRIVATE KEY-----';
+    const sdkWrong = new AlipaySdk({
+      appId: '2021006154659461',
+      privateKey: wrongKey,
+      keyType: 'PKCS1',
+      alipayPublicKey,
+      gateway: 'https://openapi.alipay.com/gateway.do',
+    });
+    // 能初始化但签名会是错的
+    console.log('SDK 未报错(但签名会是错的,支付宝会返回 invalid-signature)');
+  } catch (e) {
+    console.log('SDK 报错:', e.message);
+  }
+}
+
+test().catch(e => console.error('脚本异常:', e));

TEMPAT SAMPAH
server/cert/wx/apiclient_cert.p12


+ 18 - 18
server/cert/wx/apiclient_cert.pem

@@ -1,26 +1,26 @@
 -----BEGIN CERTIFICATE-----
-MIIETzCCAzegAwIBAgIUHP+03q/85glPaT4GCBRAF7gvJxMwDQYJKoZIhvcNAQEL
+MIIETzCCAzegAwIBAgIUNTdDxMivaNtDNGaHBo1xczFPJmUwDQYJKoZIhvcNAQEL
 BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
 FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
-Q0EwHhcNMjUwNDAzMDI1NDQ1WhcNMzAwNDAyMDI1NDQ1WjCBqDETMBEGA1UEAwwK
-MTcxMDQ5NTI3NDEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMVQwUgYDVQQL
-DEvljZfkuqznu4/mtY7mioDmnK/lvIDlj5HljLrot6/lrrnlv4PnkIblkqjor6Ll
+Q0EwHhcNMjYwNTI3MTUyMzExWhcNMzEwNTI2MTUyMzExWjCBqDETMBEGA1UEAwwK
+MTExMzIxNjM1OTEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMVQwUgYDVQQL
+DEvljZfkuqznu4/mtY7mioDmnK/lvIDlj5HljLrot6/lrrnmlofljJbliJvmhI/l
 t6XkvZzlrqTvvIjkuKrkvZPlt6XllYbmiLfvvIkxCzAJBgNVBAYTAkNOMREwDwYD
-VQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJoB
-yTwJovtXuxdHof0aVe+ddglE0EmMa3UJip2KoBevfKhbeoRJpLsSk2KgOOiHOztF
-N+8d8227LTNdEbNpboz+OIrqRM+bGhu5JSEocSUYBwrP/6BeQv7TujOfIz8pqX/G
-RP31m/qbOR0e0nq4q9UGS2tBDcem0sFd0n6Ey7F2ZjJEajzyAwCH2kvnxhVkUT5b
-V4eysIZ7eMn6UXreoL1lyApwRhyOf/d+uDpmNDLJf5w+iU/w7PevDl+/prLSFmW/
-XiAvlaJ65GFIqIRYG357IYfD8TCQdemLUKp5s9MDwJa+YvjdB7LTMBA42lbVgxIH
-tDuvBsB2bk8YG/SNXNsCAwEAAaOBuTCBtjAJBgNVHRMEAjAAMAsGA1UdDwQEAwID
+VQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALw9
+v1T3oex2z7c0o75O2pZhsjLt/u4ziAF2ZEHqzLG5WiFT5xLNtur6jSJaJIDA/xZK
+I8SCPpm0GNViWqtWpSxlURM/QUM+CoXI+35dGYiJWtttLXMxDirB6Un3FyfK+FaS
+db6vhVRnGbm65ShOWzlCc1OrXJkis0lWNLfwORdjQ0+px6hEnQDxD9dklyuWwOCE
+7pZbGXG1AJHonXAjOOddE1sBBCIXD2Q/MxhCMcC2S99prsnjWyb/ezyGRvgxVe5Z
+K/mwCFX+n0eyjawt8j1ukW5q+8NFSmThEZjyi4oSVm1Vm3Umw4E+nHGrzn2dp5Jm
+mf/12PhvirR0zaAKfE0CAwEAAaOBuTCBtjAJBgNVHRMEAjAAMAsGA1UdDwQEAwID
 +DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDovL2V2Y2EuaXRydXMuY29t
 LmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUwREJDMDRCMDZBRDM5NzU0
 OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJFMTJCMjdBOUQzM0E4N0FE
-MUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQC2SIz7L0SHhpF68lEY
-LR7bbEeLqGfcyEWB7XLLSTtIiGJcfFHxGUiR8OB/+gJDs/P7aglqJCHNJN2MZ5zM
-7X6Nn8/GZ2ePWOBP3xcBR19njQxQNBmbgrGPabksYnSCpWcCKClkDOaee9AM9rBj
-wxNoRFWYNZ2bLT25t4L6yPKYj97aGGbEBkPeYNrcgBeHlQwAyB4m1NdydGFnHiV9
-7F2SMhqLxSft3+g5uhENGKQtNZkUCtt+7juyy9LEm6OtGvYpql3y0hkMtK5AzVxi
-YXDJAU2GEQ/3axJqFRztY6HNmRhWT5w4DGdJxYHZ3kGSIAH6TMuiXpKzihy9CXpV
-Je0O
+MUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IBAQBTe10Zlqov05zqTZCY
+ZKD3ZA6rR6Xi1uar+5lmvtkymSVHCL4Ri2UEigIqdCMq6A9rHCRiER2vey1UvQ92
++G2iXWHqnEsRM5GNbaA/1Ki8k5YSvowiZDvVuLLJ3bTY35UeBEtDSA7q/D4xsnUz
+vU2kvFlR01EjfWTQnyN3y9ZDpXpbHGo1ctyMqXrBcO0vaKOhS3zcrQzGgbOtswNh
+W/cSR1zwR4xHKpkElTqWZgjkaxHTL5mLgHZ5DRJLf8WFPWmhtfXffPnoLcA2bqcz
+GWlKHhmWRtF+Jzd3p++Yn3pvK4c405LnGW8/fEsaEgP/ycTdEPd+AtNccD9HM0sv
+gj+F
 -----END CERTIFICATE-----

+ 26 - 26
server/cert/wx/apiclient_key.pem

@@ -1,28 +1,28 @@
 -----BEGIN PRIVATE KEY-----
-MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCaAck8CaL7V7sX
-R6H9GlXvnXYJRNBJjGt1CYqdiqAXr3yoW3qESaS7EpNioDjohzs7RTfvHfNtuy0z
-XRGzaW6M/jiK6kTPmxobuSUhKHElGAcKz/+gXkL+07oznyM/Kal/xkT99Zv6mzkd
-HtJ6uKvVBktrQQ3HptLBXdJ+hMuxdmYyRGo88gMAh9pL58YVZFE+W1eHsrCGe3jJ
-+lF63qC9ZcgKcEYcjn/3frg6ZjQyyX+cPolP8Oz3rw5fv6ay0hZlv14gL5WieuRh
-SKiEWBt+eyGHw/EwkHXpi1CqebPTA8CWvmL43Qey0zAQONpW1YMSB7Q7rwbAdm5P
-GBv0jVzbAgMBAAECggEBAJlFfclKrfIHdiPNHuKO+0fmJjtDSShpn+hy/omcFYh9
-FktX/674vAGSkyxs2TZSoBh04x4PaN2kk2+zCJLOM0APIqbeUG1tp6Y/tTwyy6hn
-KVsRdw9bwMFsZrxJfQK3rAGGidNWRU48fbMFoeSRNjJkeVrc+6Hf3ZCkl4FjGHYc
-HIL/as0Qiksbvo8QlSa/qdqq2zAQ1aD13ykdQiP0uK8+7mmv48XRD4B46NGuCUKV
-DZQyPzZJksMCP3KYqCYsOrtjgunWOfmLp7CwPEC/idbH/hPoD2w5lndq0m1FAovN
-Jab7yQm0fg9pR+R+AdJ1JaN/5fCJeLpr2HigBHwOwkECgYEAyawvZNcFWDoTo+v+
-30WZ085rWLELTgrD7wr/ntik5faZqQm3nBk+x0lmdOLviTagpZYaUtxmmeAd5RDc
-As3nYhJxufPbZBgHMrxd55CP0bIPcLNJ/xjxSGQZbbDyJIAmmTmna5A8ixkdtdEx
-YIoDiQ19Nr25LQjZSOKPgTNFK6cCgYEAw3530ABdjP6CfhyHpA8a1sJ33pRqosA/
-j2bi6mT4NklboD2cklC5O/SNuTSFOcmU5YruOT5oySrjs3Jm0QUVh50xc4OtgpVz
-eCJ73NzQgTmm/Dx6j4MxRODoESm63gn8pEk1TFqp8FrhkKyXGog/b1LcZ+Mx0YnA
-rdZRCeyx260CgYBYBv5Q58H/dqon/NStJDY2y3zzo6Oplu1bI9Pbw/iGIBOGQyWP
-zz0lw5YjIwMnQ17vqAspa+ak97xUxf0ihDmhByf44MY8dHh/3tSmROVhxHUU3k1I
-Odznmjm3NWscH4u5if7X5odmt7nOKNeCqftNEUQ8ZqezTo95TtsQrA5fNQKBgD2Y
-VlXCF7e55RGpGY+2dqVd5njJ2INpAV3EdBCYOaCZInZtkyyCpKpgVlsZUjSv7+sX
-CBIfONLRYacDj6Oh73OoMpGL1pqDTjt22gHvzP46VQO6Zn9bPaXPGy91L3yQNvro
-WhOxqUOiztlPl9hzP1FrESjma6byuTPMiAIRgsSNAoGBAKnw0pUuwHMdbZzBtS1k
-j1qeq+9rQzXMI83zaeciD1+GlnENm+17QyCnV/ZwajWSqmk6mz688VB9M4wLAtIw
-P8xfytUQ6vvtU4RCJg9s5hBqmKPQcOTyCn8soOY/32l9rudhDC25x9AAEqBMQuEj
-3OVeODeGTvCMN1JoUjUKzuvr
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8Pb9U96Hsds+3
+NKO+TtqWYbIy7f7uM4gBdmRB6syxuVohU+cSzbbq+o0iWiSAwP8WSiPEgj6ZtBjV
+YlqrVqUsZVETP0FDPgqFyPt+XRmIiVrbbS1zMQ4qwelJ9xcnyvhWknW+r4VUZxm5
+uuUoTls5QnNTq1yZIrNJVjS38DkXY0NPqceoRJ0A8Q/XZJcrlsDghO6WWxlxtQCR
+6J1wIzjnXRNbAQQiFw9kPzMYQjHAtkvfaa7J41sm/3s8hkb4MVXuWSv5sAhV/p9H
+so2sLfI9bpFuavvDRUpk4RGY8ouKElZtVZt1JsOBPpxxq859naeSZpn/9dj4b4q0
+dM2gCnxNAgMBAAECggEBAJdP6ySZiYdSWdWicpzfNcX78goOVQWUVpcWW7DA1Jln
+rJNtpijvQ0HO9PPQEdeNS37XSbkEGN3kbrHXxZpEo7D0IB+gkQ1EKaEw1TfWDlg6
+UNZhBE+79u9Xv/a8r9hExyqPwCXvYPVfisZI/TtRAPMd/yYacOyHgCZLLUVNzBaH
+SAq8SS/bGj0NEz0xmDFaBj8Vlas7iVGZnmLMbTmyXdBS2/m2Hnyxrts8clmJc1qK
+NzF524nc/o9/M1h7HIQ41JyvgHu5XQ4GkaBPgeVVGbeZm0mPkevVr4HxElQCjr1t
+o7TInTUrV131AxyruhRr7FjYOYHBbkNY/Q3HiCLYOgECgYEA+d6azGpdyuuyra+3
+M+/RBSOxIPj1ims1EpZGmCRTvRnztLqkpxhNcEpNWQfJygv4AYiFGrwf5Isa1RRn
+86KWSTx23HYssJrtbu492NMSOw5/TwJU4BuySDtLWXc8HxUlv5PmDAuf+eA3Ei6I
+v89Lyu45eypxJh9Xw1e8xMKjgN0CgYEAwNwQUrF1uDDPbeJ0QJSYBFBr+wSvXM+r
+govjkN71gca4u+g7lv0w3AkOcAUPphU+9llMUIvqELs9ADwH1K3KM9EA/2m6qOhD
+OWm9UQd+E7mNFTuOECtnzPtHymD6t0Eyj5tKYoVb9nxjArtFKGqRdayDLlYHZoLA
+ncVGD0ST+jECgYArSe+TnpHlR/RKtviMXO3fcBGN3tPbSTuGntZYIWMIjeh01SkV
+cdZor9a6p+jDwlMTIO2+df0ogRkC9WgVA4P56CnHhAm2BwE/iLLaT6cG6DoSHBtM
+QEAg13T0aP3XfLhvelYOnCz5A2lHExzJ7kX5QFBhEDdOHXv88cRYakgdWQKBgFhH
+o5UmtDsW5Kxo/0vceOVoprTlhRwX12tPe2CCHqFEBKXG/Rf1V9swrhIqzun4SbTc
+VWl9UixMePTVhcQdtTQY1LsVe6L7hmIOe+FOa7C9u83fT/0o0T/sOgVydPFTf8Bq
+lhcY8mqWr3+9lz+T4Heb+wRHzuVmrqLJfXNZsd7RAoGBAPU53izlCy1mgS1h/jQE
+kcj/+f1eE8WbW5++HNyPnsyT42zVrAqW5ZiNo3FLjIiK3QaMxiGcI3jwbq5RxiqX
+mTtNK5ItL7N1Roqa/WdE+qApDd/oXR6rJCzCBqPsN0GsH0W0mNqW+v/pg5dmS5UK
+nHeMo5HTv2HCwU4olrlIAuAN
 -----END PRIVATE KEY-----

+ 7 - 7
server/cert/wx/pub_key.pem

@@ -1,9 +1,9 @@
 -----BEGIN PUBLIC KEY-----
-MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmZ9zqIsyL46h+e1k3M1e
-sVU2pD4bVCfisms3p2eUzlCAr5Ye74sjZN29Z32zpBESWQr31ROmAhaMC0lH3yx4
-OfOw6pxLQyndiNRCGC3yzFJ9iu1PxtRDFFM0pfL6AiNU5QZlDuHcRgZzTg4Z+RDq
-nrGbOKa+OOHeiHc9wAA3XSpkmVVzMdmn5rlvTcA5aHi951BCgfkaYzTfefaanqAq
-zeb0Zi5SAN/c5y8byYek69zZ8yE5aRapGwYSxQkA5T10YNCcRCQpGG/zgvUFB/va
-gURtYKDSzzW3uMYSRv816vsRd+5cM/IhfYyQ0EkRrhlE0Sazr/QKjUhjKuLe61dj
-CQIDAQAB
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvD2/VPeh7HbPtzSjvk7a
+lmGyMu3+7jOIAXZkQerMsblaIVPnEs226vqNIlokgMD/FkojxII+mbQY1WJaq1al
+LGVREz9BQz4Khcj7fl0ZiIla220tczEOKsHpSfcXJ8r4VpJ1vq+FVGcZubrlKE5b
+OUJzU6tcmSKzSVY0t/A5F2NDT6nHqESdAPEP12SXK5bA4ITullsZcbUAkeidcCM4
+510TWwEEIhcPZD8zGEIxwLZL32muyeNbJv97PIZG+DFV7lkr+bAIVf6fR7KNrC3y
+PW6Rbmr7w0VKZOERmPKLihJWbVWbdSbDgT6ccavOfZ2nkmaZ//XY+G+KtHTNoAp8
+TQIDAQAB
 -----END PUBLIC KEY-----

+ 53 - 40
server/src/modules/payment/payment.service.ts

@@ -45,19 +45,37 @@ function getAlipaySdk(): any | null {
     return null;
   }
   const appId = process.env.ALIPAY_APP_ID || '';
-  // dotenv 不解析多行 PEM,手动加 PEM 头尾
   const rawPrivateKey = process.env.ALIPAY_PRIVATE_KEY || '';
-  // 自动检测密钥格式:PKCS#1 (BEGIN RSA PRIVATE KEY) 或 PKCS#8 (BEGIN PRIVATE KEY)
+  const rawPublicKey = process.env.ALIPAY_PUBLIC_KEY || '';
+
+  // 自动检测并正确包装密钥格式
   let privateKey: string;
+  let keyType: 'PKCS1' | 'PKCS8' = 'PKCS1';
+
   if (rawPrivateKey.includes('BEGIN PRIVATE KEY')) {
-    privateKey = rawPrivateKey; // 已是 PKCS#8 格式
+    // 已有 PKCS#8 头
+    privateKey = rawPrivateKey;
+    keyType = 'PKCS8';
   } else if (rawPrivateKey.includes('BEGIN RSA PRIVATE KEY')) {
-    privateKey = rawPrivateKey; // 已是 PKCS#1 格式
+    // 已有 PKCS#1 头
+    privateKey = rawPrivateKey;
+    keyType = 'PKCS1';
   } else {
-    // 没有头尾,添加 PKCS#1 格式头尾(支付宝推荐格式)
-    privateKey = `-----BEGIN RSA PRIVATE KEY-----\n${rawPrivateKey}\n-----END RSA PRIVATE KEY-----`;
+    // 没有 PEM 头,通过 DER 判断格式
+    // PKCS#8: MIIEvQ... 开头,SEQUENCE { INTEGER 0, AlgorithmIdentifier, OCTET STRING }
+    // PKCS#1: MIIEvA... 开头,SEQUENCE { INTEGER 0, INTEGER n, INTEGER e, ... }
+    const derBuf = Buffer.from(rawPrivateKey, 'base64');
+    if (derBuf[4] === 0x02 && derBuf[5] === 0x01 && derBuf[6] === 0x00) {
+      // 第5字节是 version=0,说明是 PKCS#8 PrivateKeyInfo
+      privateKey = `-----BEGIN PRIVATE KEY-----\n${rawPrivateKey}\n-----END PRIVATE KEY-----`;
+      keyType = 'PKCS8';
+    } else {
+      // 否则按 PKCS#1 处理
+      privateKey = `-----BEGIN RSA PRIVATE KEY-----\n${rawPrivateKey}\n-----END RSA PRIVATE KEY-----`;
+      keyType = 'PKCS1';
+    }
   }
-  const rawPublicKey = process.env.ALIPAY_PUBLIC_KEY || '';
+
   const alipayPublicKey = rawPublicKey.includes('BEGIN') ? rawPublicKey
     : `-----BEGIN PUBLIC KEY-----\n${rawPublicKey}\n-----END PUBLIC KEY-----`;
 
@@ -68,6 +86,7 @@ function getAlipaySdk(): any | null {
 
   console.log('[Alipay] 初始化 SDK', { 
     appId, 
+    keyType,
     privateKeyStart: privateKey.substring(0, 50),
     alipayPublicKeyStart: alipayPublicKey.substring(0, 50)
   });
@@ -76,6 +95,7 @@ function getAlipaySdk(): any | null {
     alipaySdk = new AlipaySdk({
       appId,
       privateKey,
+      keyType,
       alipayPublicKey,
       gateway: 'https://openapi.alipay.com/gateway.do',
     });
@@ -218,7 +238,6 @@ async function generateAlipayPayment(
   returnUrl?: string
 ): Promise<{ paymentUrl?: string; qrcode?: string }> {
   const sdk = getAlipaySdk();
-  const isProduction = process.env.NODE_ENV === 'production';
 
   // 默认回调地址
   const defaultReturnUrl = process.env.ALIPAY_RETURN_URL || `${process.env.BASE_URL || 'https://your-domain.com'}/#/pages/payment-result/index`;
@@ -231,25 +250,21 @@ async function generateAlipayPayment(
     };
   }
 
-  // 使用电脑网站支付 API
+  // 使用手机网站支付 API(WAP支付)
   try {
-    const bizContent = {
-      out_trade_no: orderNo,
-      total_amount: String(amount),
-      subject: subject,
-      product_code: 'FAST_INSTANT_TRADE_PAY',
-      quit_url: finalReturnUrl,
-    };
-
-    // 使用 wap.pay 返回表单,提取 action URL 用于跳转
-    const htmlForm = sdk.pageExec('alipay.trade.wap.pay', {
+    const notifyUrl = process.env.ALIPAY_NOTIFY_URL || '';
+    // pageExecute(method, httpMethod, bizParams)
+    // httpMethod: 'POST' 返回HTML表单, 'GET' 返回支付链接
+    const htmlForm = sdk.pageExecute('alipay.trade.wap.pay', 'POST', {
       bizContent: {
         out_trade_no: orderNo,
         total_amount: String(amount),
         subject: subject,
+        product_code: 'QUICK_WAP_WAY',
         quit_url: finalReturnUrl,
       },
-      useSandbox: false,
+      returnUrl: finalReturnUrl,
+      notifyUrl,  // 异步通知地址,用于接收支付结果
     });
 
     // 从 HTML 表单中提取 action URL
@@ -299,21 +314,21 @@ async function generateWechatPayment(
 
     // 检查返回状态(SDK 不会reject错误响应,而是返回包含error的对象)
     if (result.status !== 200 && result.status !== '200') {
-      const errorMsg = result.error || JSON.stringify(result);
+      const errorMsg = JSON.stringify(result);
       console.error('[WeChatPay] 支付失败:', errorMsg);
-      // 微信支付签名错误通常是商户证书问题,返回更友好的错误信息
-      throw new Error(`微信支付暂不可用,请稍后重试或使用其他支付方式`);
+      throw new Error(`微信支付接口返回错误: ${errorMsg}`);
     }
 
-    console.log('[WeChatPay] 支付二维码生成成功, code_url:', result.code_url);
+    console.log('[WeChatPay] 支付二维码生成成功, code_url:', result.data?.code_url || result.code_url);
 
     // 返回二维码链接(前端自行生成二维码图片)
     return {
-      qrcode: result.code_url,
+      qrcode: result.data?.code_url || result.code_url,
     };
   } catch (error: any) {
-    console.error('[WeChatPay] 支付二维码生成失败:', error.message || error);
-    throw new Error('微信支付暂不可用,请稍后重试或使用其他支付方式');
+    const detail = error.message || JSON.stringify(error);
+    console.error('[WeChatPay] 支付二维码生成失败:', detail);
+    throw new Error(`微信支付暂不可用(${detail}),请稍后重试或使用其他支付方式`);
   }
 }
 
@@ -331,18 +346,15 @@ export async function generateAlipayQrcode(
   }
 
   try {
-    // 使用当面付预下单 API
-    const bizContent = {
-      outTradeNo: orderNo,
-      totalAmount: amount,
-      subject: subject,
-    };
-
-    const result = await sdk.exec(
-      'alipay.trade.precreate',
-      {},
-      { bizContent, useSandbox: false }
-    );
+    // 使用当面付预下单 API(v3 curl 替代已废弃的 exec)
+    const result = await sdk.curl('POST', '/v3/alipay/trade/precreate', {
+      body: {
+        out_trade_no: orderNo,
+        total_amount: String(amount),
+        subject: subject,
+      },
+      timeout: 10000,
+    });
 
     // 解析返回的二维码链接
     const response = result as any;
@@ -366,7 +378,8 @@ export function verifyAlipaySign(params: Record<string, string>): boolean {
   }
 
   try {
-    const signVerified = sdk.checkNotifySign(params);
+    // checkNotifySignV2 不会对参数值做 decode,避免特殊字符导致验签失败
+    const signVerified = sdk.checkNotifySignV2(params);
     console.log('[Alipay] 签名验证结果:', signVerified);
     return signVerified;
   } catch (error) {