create-dwy 0.25.3 → 0.27.0
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.
- package/CHANGELOG.md +20 -0
- package/package.json +1 -1
- package/src/sync-all.js +46 -2
- package/src/sync-plan.js +9 -1
- package/templates/ai-tools/CHANGELOG.md +17 -0
- package/templates/ai-tools/VERSION +1 -1
- package/templates/ai-tools/skills/Apple/dwy-apple-appkit/SKILL.md +17 -0
- package/templates/ai-tools/skills/Apple/dwy-apple-architecture/SKILL.md +12 -0
- package/templates/ai-tools/skills/Apple/dwy-apple-architecture/references/extraction.md +9 -0
- package/templates/ai-tools/{rules/iOS/dwy-apple-layering.md → skills/Apple/dwy-apple-architecture/references/layering.md} +2 -10
- package/templates/ai-tools/skills/Apple/dwy-apple-architecture/references/pitfalls.md +3 -0
- package/templates/ai-tools/skills/Apple/dwy-ios-development/SKILL.md +14 -0
- package/templates/ai-tools/skills/Apple/dwy-ios-development/references/appearance.md +6 -0
- package/templates/ai-tools/{rules/iOS/dwy-ios-layout-adaptation.md → skills/Apple/dwy-ios-development/references/layout.md} +0 -6
- package/templates/ai-tools/{rules/iOS/dwy-ios-pitfalls.md → skills/Apple/dwy-ios-development/references/pitfalls.md} +1 -9
- package/templates/ai-tools/skills/Apple/dwy-ios-development/references/presentations.md +11 -0
- package/templates/ai-tools/skills/Apple/dwy-macos-development/SKILL.md +14 -0
- package/templates/ai-tools/skills/Apple/dwy-macos-development/references/pitfalls.md +9 -0
- package/templates/ai-tools/skills/Apple/dwy-macos-development/references/presentations.md +5 -0
- package/templates/ai-tools/skills/Apple/dwy-macos-development/references/ui-adaptation.md +6 -0
- package/templates/ai-tools/skills/Apple/dwy-swift-development/SKILL.md +16 -0
- package/templates/ai-tools/{rules/iOS/dwy-swift-core.md → skills/Apple/dwy-swift-development/references/conventions.md} +2 -8
- package/templates/ai-tools/skills/Apple/dwy-swift-development/references/pitfalls.md +5 -0
- package/templates/ai-tools/skills/Apple/dwy-swift-development/references/presentations.md +8 -0
- package/templates/ai-tools/skills/Python/dwy-apple-notify/SKILL.md +24 -0
- package/templates/ai-tools/skills/Python/dwy-apple-notify/references/v0/0.1.6.md +81 -0
- package/templates/ai-tools/skills/Python/dwy-auth/SKILL.md +24 -0
- package/templates/ai-tools/skills/Python/dwy-auth/references/v0/0.2.0.md +163 -0
- package/templates/ai-tools/skills/Python/dwy-device-fingerprint/SKILL.md +19 -0
- package/templates/ai-tools/skills/Python/dwy-device-fingerprint/references/v0/0.1.1.md +90 -0
- package/templates/ai-tools/skills/Python/dwy-eapi/SKILL.md +1 -1
- package/templates/ai-tools/skills/Python/dwy-eapi/versions.yml +3 -3
- package/templates/ai-tools/skills/Python/dwy-email-notify/SKILL.md +19 -0
- package/templates/ai-tools/skills/Python/dwy-email-notify/references/v0/0.1.0.md +31 -0
- package/templates/ai-tools/skills/Python/dwy-notify/SKILL.md +24 -0
- package/templates/ai-tools/skills/Python/dwy-notify/references/v0/0.3.2.md +128 -0
- package/templates/ai-tools/skills/Python/dwy-payment/SKILL.md +19 -0
- package/templates/ai-tools/skills/Python/dwy-payment/references/v0/0.3.0.md +90 -0
- package/templates/ai-tools/skills/Python/dwy-python-api/SKILL.md +8 -0
- package/templates/ai-tools/skills/Python/dwy-python-architecture/SKILL.md +8 -0
- package/templates/ai-tools/skills/Python/dwy-python-development/SKILL.md +17 -0
- package/templates/ai-tools/{rules/Python → skills/Python/dwy-python-development/references}/dwy-python-base-lib.md +3 -3
- package/templates/ai-tools/skills/Python/dwy-webhooks/SKILL.md +24 -0
- package/templates/ai-tools/skills/Python/dwy-webhooks/references/v0/0.6.2.md +238 -0
- package/templates/ai-tools/skills/Python/dwy-wechatpay-notify/SKILL.md +19 -0
- package/templates/ai-tools/skills/Python/dwy-wechatpay-notify/references/v0/0.1.1.md +101 -0
- package/templates/ai-tools/skills/Vue/dwy-admin-kit/SKILL.md +12 -0
- package/templates/ai-tools/skills/Vue/dwy-admin-kit/references/v0/0.3.0.md +176 -0
- package/templates/ai-tools/skills/Vue/dwy-auth-web/SKILL.md +10 -0
- package/templates/ai-tools/skills/Vue/dwy-ekit/SKILL.md +2 -3
- package/templates/ai-tools/skills/Vue/dwy-eui/SKILL.md +1 -1
- package/templates/ai-tools/skills/Vue/dwy-eui/versions.yml +2 -2
- package/templates/ai-tools/skills/Vue/dwy-vue-architecture/SKILL.md +8 -0
- package/templates/ai-tools/skills/Vue/dwy-vue-development/SKILL.md +16 -0
- package/templates/ai-tools/{rules/Vue → skills/Vue/dwy-vue-development/references}/dwy-vue-base-lib.md +3 -3
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-architecture/SKILL.md +6 -6
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-commercial-sdk/SKILL.md +20 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/SKILL.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/SKILL.md +6 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/android.md +2 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/backend-deploy.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/frontend-deploy.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/generic-release.md +2 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/harmony.md +2 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/ios.md +2 -2
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-dependency-choice/SKILL.md +2 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/SKILL.md +0 -244
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/assets/files-whitelist-snippet.json +0 -22
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/assets/js-commercial.npmignore +0 -76
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/assets/python-commercial-MANIFEST.in +0 -37
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/assets/python-commercial.gitignore +0 -62
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/references/js-commercial-guide.md +0 -133
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/references/publish-blacklist.md +0 -106
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/references/python-commercial-guide.md +0 -147
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/references/sensitive-patterns.md +0 -112
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/scripts/check_comments.py +0 -260
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/scripts/check_js_publish.sh +0 -110
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/scripts/check_python_publish.sh +0 -171
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/scripts/detect_sdk.sh +0 -71
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/scripts/init_config.sh +0 -91
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/SKILL.md +0 -136
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/references/semver-spec.md +0 -70
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/scripts/bump.py +0 -137
- /package/templates/ai-tools/{rules/Python → skills/Python/dwy-python-api/references}/dwy-python-backend.md +0 -0
- /package/templates/ai-tools/{rules/Python → skills/Python/dwy-python-architecture/references}/dwy-python-layering.md +0 -0
- /package/templates/ai-tools/{rules/Python → skills/Python/dwy-python-development/references}/dwy-python-core.md +0 -0
- /package/templates/ai-tools/{rules/Python → skills/Python/dwy-python-development/references}/dwy-python-pitfalls.md +0 -0
- /package/templates/ai-tools/{rules/Python → skills/Python/dwy-python-development/references}/dwy-python-testing.md +0 -0
- /package/templates/ai-tools/{rules/Vue → skills/Vue/dwy-vue-architecture/references}/dwy-vue-layering.md +0 -0
- /package/templates/ai-tools/{rules/Vue → skills/Vue/dwy-vue-development/references}/dwy-vue-core.md +0 -0
- /package/templates/ai-tools/{rules/Vue → skills/Vue/dwy-vue-development/references}/dwy-vue-pinia.md +0 -0
- /package/templates/ai-tools/{rules/Vue → skills/Vue/dwy-vue-development/references}/dwy-vue-pitfalls.md +0 -0
- /package/templates/ai-tools/{rules/Vue → skills/Vue/dwy-vue-development/references}/dwy-vue-testing.md +0 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<!-- 来源:dwy-payment@0.3.0 的 backend/packages/payment/README.md;文中相对链接相对原仓库路径。 -->
|
|
2
|
+
|
|
3
|
+
# dwy-payment
|
|
4
|
+
|
|
5
|
+
业务无关的服务端支付与退款能力。Python 3.11/3.12,微信普通商户APIv3 Native支付、回调核验、支付和退款资金记录;支付宝订单码作为可选extra保留。审核、管理员密码、业务订单、流量和权益由宿主实现。
|
|
6
|
+
|
|
7
|
+
## 安装与接入
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python -m pip install 'dwy-payment==0.3.0'
|
|
11
|
+
# 已有支付宝业务可按需安装,不是微信接入的必需依赖。
|
|
12
|
+
python -m pip install 'dwy-payment[alipay]==0.3.0'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
安装不初始化数据库、不挂载HTTP、不读取环境变量、不连接微信。商户凭据由宿主安全读取并构造 `WechatPayConfig`;不要在前端或客户端存放私钥。回调地址必须指向宿主提供的公网HTTPS接口,不是企业微信机器人地址。
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from dwy_payment import PaymentConfig, WechatPayConfig, create_payment
|
|
19
|
+
from dwy_payment.gateways.wechat_native import WechatNativeGateway
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def checkout(session_factory, *, merchant: WechatPayConfig, business_key: str):
|
|
23
|
+
"""宿主先提交自己的业务订单;business_key用于跨重试关联,不依赖请求会话。"""
|
|
24
|
+
config = PaymentConfig(wechat=merchant)
|
|
25
|
+
result = await create_payment(
|
|
26
|
+
session_factory=session_factory, gateway=WechatNativeGateway(merchant), config=config,
|
|
27
|
+
user_id="customer_1", amount_fen=1000, description="商品订单",
|
|
28
|
+
idempotency_key=business_key,
|
|
29
|
+
)
|
|
30
|
+
return result
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`user_id`只是宿主关联标识,不外键到用户表。`create_payment`先独立提交稳定意图再触网,同键同参数复用原单,同键不同参数拒绝;未知下单按原号和原截止重取二维码,不另建资金记录。微信官方说明支持[同订单号与原参数重新获取二维码](https://pay.wechatpay.cn/doc/v3/merchant/4054257695)。跨项目首期采用独立数据库或schema,业务订单号、支付单号、退款单号须分别保存映射,幂等键可在结果中帮助业务恢复关联。
|
|
34
|
+
|
|
35
|
+
金额使用整数分,只支持已实现的CNY范围。`PaymentService.create`保留旧同事务桥接接口,成功时不提交,要求宿主完成关联提交;它不提供独立指令的崩溃持久保证。没有幂等键时每次创建新支付;新接入使用上面的独立命令与稳定键。
|
|
36
|
+
|
|
37
|
+
宿主负责HTTP读取上限、路由与鉴权,使用 `PaymentService.handle_notify(headers, body)` 处理支付回调,使用渠道 `parse_refund_notify` 和 `RefundResultService.apply` 处理退款回调。退款结果更新与同事务钩子由宿主统一commit后再返回渠道成功应答。微信SDK验签、商户/应用与金额核对不能绕过;不自行构造已验证对象当作渠道证据。
|
|
38
|
+
|
|
39
|
+
## 退款与资金安全
|
|
40
|
+
|
|
41
|
+
`dwy_payment.refunds.commands.request_refund` 接受已持久支付记录、固定退款号和整数金额,显式提供独立会话工厂与微信网关。调用前业务方完成审核、密码确认、权益预留并提交;不要持有父业务锁跨独立资金命令。命令不接收管理员密码,也不判断退款政策。
|
|
42
|
+
|
|
43
|
+
金额占用包含已成功、处理中和待核对退款,仅明确关闭后释放;同一支付的本库退款意图串行核算,不能并发超退。网络超时保留同号查询,只有渠道明确不存在才同号同参数恢复发送,不能以新退款号重试。资金占用不能阻止商户后台在本库之外发起退款;外部退款观察后保守占用并查证,最终以渠道事实为准。
|
|
44
|
+
|
|
45
|
+
历史已退金额缺少可证明来源的退款明细时,新退款保持阻挡并要求人工核对;不凭金额相等推断两笔退款是同一笔。已存在同号意图仍允许查询恢复。
|
|
46
|
+
|
|
47
|
+
`RefundRecord`及其已核验状态是逐笔资金事实。`PaymentOrder.status/refunded_fen/refund_no`保留既有宿主业务投影兼容语义,不能将这些字段当作累计渠道到账证明;纯资金接入读取退款明细或资金事件,不调用 `mark_refunded` 推断到账。
|
|
48
|
+
|
|
49
|
+
`RefundResultService` 只登记核验状态,不自行提交;`reconcile_refunds` 由宿主每分钟调度,并显式提供会话工厂和网关。宿主负责任务生命周期与异常监控。退款“已受理”不代表成功,`ABNORMAL`保留占用并继续核对,不能据此再次付款或解除业务冻结。
|
|
50
|
+
|
|
51
|
+
## 可靠结果交付
|
|
52
|
+
|
|
53
|
+
付款成功及已核验退款结果与 `PaymentEvent` 同事务登记。`pending_payment_events(db, limit=20)` 可重放未确认事件;业务方幂等消费后,调用 `acknowledge_payment_event(db, event_id)`,与本地业务状态一起提交。消费失败不确认,下次再处理。该队列是单宿主消费组;并发读取可能重复,跨数据库消费需要本地去重,不能承诺恰好一次。
|
|
54
|
+
|
|
55
|
+
事件只包含资金字段和支付/退款标识,不包含商品或用户资料;业务方通过自己的映射找到订单。事件的版本用于处理乱序结果,不能让旧异常状态覆盖新成功。事件存储不设置自动删除,保留策略由宿主审计要求决定。
|
|
56
|
+
|
|
57
|
+
既有 `PaymentPaidHook` / `RefundResultHook` 是提交前同事务扩展点,可在宿主内维护同库业务或登记自己的持久任务;不得自行commit或执行外部网络副作用。兼容钩子失败会回滚本地资金投影和事件,等待渠道重试或查单恢复,不代表真实收款失败。需要独立提交资金事实的新接入使用空钩子并在提交后消费事件,无需实现统一商品处理器。
|
|
58
|
+
|
|
59
|
+
## 存储与迁移
|
|
60
|
+
|
|
61
|
+
使用SQLAlchemy异步会话,资金模型复用 `dwyeapi.database.Base`,无需 `dwy-shared`、`dwy-auth`、`app` 或 `quant_billing`。宿主通过Alembic显式安装/升级资金表,包内迁移入口与现有数据库升级说明随版本交付;不在导入或启动时自动改表。数据库连接取宿主配置,文档及日志不得写入凭据。
|
|
62
|
+
|
|
63
|
+
宿主在自己的Alembic revision中调用版本固定的辅助;Alembic只需安装在迁移环境,不是支付运行时强依赖:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from alembic import op
|
|
67
|
+
from dwy_payment.migrations import downgrade_payment_tables, upgrade_payment_tables
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def upgrade():
|
|
71
|
+
# 新库创建三张资金表;已有完整0.2表新增幂等键、固定付款截止时间和事件表。
|
|
72
|
+
upgrade_payment_tables(op)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def downgrade():
|
|
76
|
+
# 受控退回0.2,保留原支付/退款记录,但会删除事件、幂等键和截止时间证据。
|
|
77
|
+
downgrade_payment_tables(op)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
首次创建也可显式调用 `create_payment_tables(op)`;`validate_payment_tables(connection)`只检查,不修改数据库。三张表是 `payment_orders`、`payment_refunds`、`payment_events`;辅助不会对共享Base执行 `create_all`,也不会创建用户、商品、审核或权益表。已有表缺列、类型/空值不符、缺少关键主键、唯一性或索引时直接失败,不会通过 `checkfirst` 假装迁移成功。唯一约束的自动命名和默认表达式的等价数据库表示不作脆弱比较。
|
|
81
|
+
|
|
82
|
+
现有宿主应在自己的最新revision之后新增升级revision,保留历史revision及原表数据,不重建或清空资金表,也不回填旧付款事件。0.3.0迁移快照随版本固定,后续结构变化应增加新的迁移步骤。辅助需要真实连接进行schema核对,仅支持SQLite/PostgreSQL在线迁移,显式拒绝离线 `--sql`;数据库驱动由宿主选择并安装。
|
|
83
|
+
|
|
84
|
+
旧记录的 `idempotency_key` 与 `expires_at` 均保持NULL,不猜测原商户付款截止时间。新支付意图必须固定保存截止时间,重试沿用原值;不能根据当前 `timeout_seconds` 配置重新计算旧请求的渠道 `time_expire`。
|
|
85
|
+
|
|
86
|
+
服务构造函数支持显式配置与钩子;`runtime.configure`、`configure_refunds`只是单应用进程的便利装配,默认不读取宿主数据库。退款独立会话必须显式注入。同进程多应用应使用显式服务依赖,不重复覆盖全局装配;本版不提供完整多租户资金服务。
|
|
87
|
+
|
|
88
|
+
## 验收边界
|
|
89
|
+
|
|
90
|
+
包测试和独立wheel验证使用模拟渠道及隔离数据库,不执行真实收款或退款。Native验收不代表JSAPI、App、小程序等产品已实现。发布由 `.github/workflows/publish-payment.yml` 使用GitHub Actions OIDC完成,包标签为 `dwy-payment@0.3.0`。
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dwy-python-api
|
|
3
|
+
description: 开发、修改、测试或排查 Python FastAPI 接口与服务时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Python API 开发
|
|
7
|
+
|
|
8
|
+
确认项目实际 FastAPI、Pydantic、数据库与依赖版本,复用已有服务和认证实现。按任务阅读 [后端接口与数据访问](references/dwy-python-backend.md);旧项目遵循其实际技术栈,不擅自更换 ORM 或依赖。涉及 dwyeapi 或其他自有包时使用对应 Skill,并核实项目实际版本。
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dwy-python-development
|
|
3
|
+
description: 开发、修改或排查 Python 应用、脚本或基础库时使用;覆盖团队写法、测试、异步问题和自有基础库发现。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Python 开发
|
|
7
|
+
|
|
8
|
+
先检查项目现有版本、依赖和架构,优先复用已安装库。项目未使用自有库时,仅提出可用能力,不自动添加依赖。涉及 FastAPI 使用可用的 dwy-python-api,涉及分层使用 dwy-python-architecture。
|
|
9
|
+
|
|
10
|
+
- [Python 规范](references/dwy-python-core.md):语言、依赖与配置。
|
|
11
|
+
- [测试](references/dwy-python-testing.md):编写和运行测试。
|
|
12
|
+
- [踩坑](references/dwy-python-pitfalls.md):排查环境、异步与运行时问题。
|
|
13
|
+
- [基础库](references/dwy-python-base-lib.md):定位 dwyeapi 既有能力;以项目实际依赖与版本为准。
|
|
14
|
+
|
|
15
|
+
自有库导航:基础设施查 `dwy-eapi`;认证查 `dwy-auth`;支付查 `dwy-payment`;通用通知查 `dwy-notify`;Webhook 查 `dwy-webhooks`;Apple、邮件和微信支付来源分别查 `dwy-apple-notify`、`dwy-email-notify`、`dwy-wechatpay-notify`;设备信息查 `dwy-device-fingerprint`。对应 Skill 未安装时直接核实项目实际依赖和源码,不自动安装。
|
|
16
|
+
|
|
17
|
+
参考资料中的强制选型只适用于已经采用该技术栈或用户明确选择的新项目;现有项目以其依赖、版本和用户要求为准。
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: 已使用 dwyeapi 的项目如何查找和复用基础能力
|
|
3
3
|
paths:
|
|
4
4
|
- "**/*.py"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Python 后端 dwyeapi
|
|
7
|
+
# Python 后端 dwyeapi 基础能力
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
本资料适用于已采用 dwyeapi 的项目;版本及公开接口以实际安装包和 dwy-eapi Skill 为准。项目未采用时此处仅用于了解能力,不要求引入或替换现有实现。
|
|
10
10
|
|
|
11
11
|
`dwyeapi` 是团队内部 PyPI 基础设施包,已封装后端项目所需的通用能力。
|
|
12
12
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dwy-webhooks
|
|
3
|
+
description: 使用或排查 dwy-webhooks Webhook 集成能力时使用;先核实项目安装版本及公开 API。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dwy-webhooks
|
|
7
|
+
|
|
8
|
+
Webhook 事件、接收安装和发送能力;按版本检查可选 extra、签名校验和宿主处理边界。
|
|
9
|
+
|
|
10
|
+
## 查找与使用
|
|
11
|
+
|
|
12
|
+
项目使用 0.6.2 时可阅读 [0.6.2 接入和示例](references/v0/0.6.2.md);其他版本先查该版本的资料和代码,不把此快照当作其他版本的合同。
|
|
13
|
+
|
|
14
|
+
1. 检查项目锁文件和当前 Python 环境;用 `importlib.metadata.version("dwy-webhooks")` 查询实际发行版本,依赖范围不等于实际安装版本。未安装时不得假设可用或自动安装。
|
|
15
|
+
2. 在实际环境中检查 `dwy_webhooks` 的公开接口、类型及项目已有调用;editable 安装以实际源码为准。说明、实现或安装版本不一致时以运行代码为准。
|
|
16
|
+
3. 本索引基于仓库 0.6.2,能力与签名可能随小版本变化。精确版本源码位于仓库 `backend/packages/webhooks`,文档为该目录的 `README.md`;只在确认其版本与消费项目一致时使用。缺少安装源码时查对应 `dwy-webhooks@版本` 标签,不能用默认分支冒充。
|
|
17
|
+
4. 在已有调用和该版本文档中核对配置、必需依赖、输入校验、失败处理及生命周期后接入。没有足够证据时说明缺口,不猜 API。
|
|
18
|
+
|
|
19
|
+
## 0.x 已核实差异
|
|
20
|
+
|
|
21
|
+
- 0.6.1 公开无数据库依赖的逐目标投递与退避函数;0.6.2 要求发送者名称由配置、URL 或模块预设提供。旧版本不可假定具备相同合同。
|
|
22
|
+
- Apple 来源能力从本包拆分到 dwy-apple-notify,具体边界按消费版本的代码和迁移说明核实。
|
|
23
|
+
|
|
24
|
+
不要因为发现此库就改动项目依赖;新增依赖遵循项目选型规则。关联 FastAPI 或其他自有包时同时参考可用的对应 Skill。
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<!-- 来源:dwy-webhooks@0.6.2 的 backend/packages/webhooks/README.md;文中相对链接相对原仓库路径。 -->
|
|
2
|
+
|
|
3
|
+
# dwy-webhooks
|
|
4
|
+
|
|
5
|
+
## 模块展示预设
|
|
6
|
+
|
|
7
|
+
业务展示由各模块拥有:Apple 购买为「购买通知」、ASC 为「应用审核通知」、经营日报/周报为「经营报表」、Resend 为「邮件通知」,其它来源共用「系统通知」。模块预设不下沉到 dwy-notify。独立宿主队列可以通过 `dwy_webhooks.appearance.notification_appearance(source)` 获取同一预设,并传给 `deliver_notifications(..., appearance=...)`。
|
|
8
|
+
|
|
9
|
+
五张 PNG、SVG 源图和 Lucide 许可维护在 `backend/packages/webhooks/assets/`;默认公网地址固定到资产提交 `091ad4fea18ed3840fec83dc833f18b759256ca7`,例如 `https://raw.githubusercontent.com/danweiyuancircle/dwy-ai-experience/091ad4fea18ed3840fec83dc833f18b759256ca7/backend/packages/webhooks/assets/purchase.png`。Discord 自行读取公开图片,各应用不需要部署图片服务。
|
|
10
|
+
|
|
11
|
+
应用可在各来源的 `notifications.appearance` 设置 `username` 和 `avatar_url`,覆盖单个字段或整套预设;`avatar_url: null` 使用平台默认头像。现有目标 URL 中显式配置的展示也优先于模块默认值。直接转发和持久队列共用预设;首次发送前把预设写入现有 JSON outbox,失败重试保留原预设,无需数据库迁移。
|
|
12
|
+
|
|
13
|
+
## Apple 经营日报与周报
|
|
14
|
+
|
|
15
|
+
`dwy-webhooks[analytics]==0.6.2` 提供公开的 `dwy_webhooks.analytics`。默认北京时间 `Asia/Shanghai` 每天 08:30,固定统计发送日期前两天的数据(如 30 日取 28 日、31 日取 29 日),为 Apple UTC 报表预留生成时间;周一附加截至前两天的连续七天周报。未发布或不可用的指标按状态展示,不等待或自动补发旧日报。
|
|
16
|
+
|
|
17
|
+
调用项目注入 `AppleAnalyticsConfig`、会话及 `build_analytics_tables(metadata)` 的表定义,再使用 `install_analytics_monitor(app, config, sessions, tables)` 安装。数据库迁移和 engine 生命周期仍由项目拥有;示例见 `backend/packages/webhooks/examples/new_project/analytics.py`。
|
|
18
|
+
|
|
19
|
+
指标包含官方可取得的首次/重新下载、曝光与页面访问事件次数、USD 估算销售/收益/退款、订阅状态/新增/续费/流失、地区与来源。缺报及未取得的维度不会补零。跨行 Unique Counts 不能用来拼接官方下载转化率;MRR、留存、去重付费用户及付费 cohort 未经验证不计算。
|
|
20
|
+
|
|
21
|
+
`AppleAnalyticsClient.initialize(app_id)` 显式申请 ONGOING 报表,不在常驻采集时自动升级权限。初次生成需要等待;报告原始统计日独立于通知发送时区。API Bearer 不传给报表下载地址,网络读取与 gzip/TSV 解析均有预算。
|
|
22
|
+
|
|
23
|
+
运行时使用账号/App 隔离的持久租约,日期快照和 outbox 同事务提交,复用统一通知发送与逐目标重试。近七天修正替换旧快照;重启不补发所有历史日报;网络确认丢失仍存在重复送达窗口。可选 `feedback(app_id, date)` 回调提供文字评论计数,不依赖评论模块或 App Cloud。
|
|
24
|
+
|
|
25
|
+
`ReviewStore.summary(app_id, day)` 返回已采集完整 UTC 创建日的 `review_count`、`low_star_reviews`;无完整基线、未来或已清理日期返回空映射。该摘要不是商店全部星级,不包含正文或昵称。
|
|
26
|
+
|
|
27
|
+
基于 EAPI 的通用 Webhook 模块。接收 Resend、App Store Connect、Apple Server Notifications V2,并按项目配置通过 `dwy-notify` 转发到配置的通知目标或外部注入的业务接收函数;也支持主动采集 App Store 文字评论及星级。
|
|
28
|
+
|
|
29
|
+
## 安装与依赖
|
|
30
|
+
|
|
31
|
+
核心依赖固定兼容版本 `dwyeapi==0.12.1` 与 `dwy-notify==0.2.0`;平台 SDK 按需选择 `dwy-webhooks[resend,apple]`,评论与经营采集分别选择 `reviews`、`analytics`。旧 `discord` extra 保留为空,不安装 `discord-webhook` SDK。ASC Webhook 使用标准库 HMAC;只有 Apple Server Notifications 需要 Apple SDK。源码由 dwy-shared 维护,项目安装独立 wheel,不引用共享仓库的绝对源码路径。发布配置不表示已经发布。
|
|
32
|
+
|
|
33
|
+
## 统一通知配置(0.6.0)
|
|
34
|
+
|
|
35
|
+
`NotificationConfig(targets={目标名: SecretStr或字符串}, timeout_seconds=10)` 使用显式传入的 Apprise URL。Discord 形如 `discord://Mail@WEBHOOK_ID/WEBHOOK_TOKEN`,企业微信机器人形如 `wecombot://BOT_KEY`;其它已支持的 Apprise HTTPS 渠道尽量只改配置。URL 由宿主密钥配置注入,日志和队列不保存明文 URL。直接通知也能在普通 Python 程序使用:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from dwy_notify import NotificationConfig, Notifier
|
|
39
|
+
|
|
40
|
+
async def notify(targets: dict[str, str]) -> bool:
|
|
41
|
+
"""targets 由宿主配置注入;结果按目标汇总,失败不会被当成成功。"""
|
|
42
|
+
result = await Notifier(NotificationConfig(targets=targets)).send("任务已完成", title="任务通知")
|
|
43
|
+
return result.success
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`result.deliveries` 保留每个目标的结果、消息 ID、安全错误码和重试信息;`succeeded`、`failed` 是目标名元组。`send(..., targets=("mail",))` 可显式选择配置内目标。SDK 类型不出现在公开 API。
|
|
47
|
+
|
|
48
|
+
当前支持基于 `requests` 的 HTTPS 渠道;SMTP、本地命令、明文协议及带不可禁用内部重试的插件会在构造时拒绝,具体边界见 `backend/packages/notify/README.md`。普通渠道以 SDK 返回值判断成功,失败统一为安全的 `send_failed`;可能需要宿主补装渠道可选依赖。取消等待会传播,但已开始的同步请求仍在线程中由 socket 超时结束;分段或多接收人通知没有整条硬截止,整体重试可能重复已成功片段。
|
|
49
|
+
|
|
50
|
+
Webhook 全局 `notifications` 仅作默认值,Resend、ASC、Apple 来源各自的 `notifications` 可覆盖。评论的 `AppleReviewsConfig.notifications` 和日报的 `AppleAnalyticsConfig.notifications` 独立配置;例如评论只发 Discord,日报同时发 Discord 和企业微信。各安装器只读取自己的配置,不要求安装另一个功能或额外注入 Notifier;自定义 sink / sender 仍有最高优先级。完整装配示例见 `backend/packages/webhooks/examples/new_project/notifications.py`。
|
|
51
|
+
|
|
52
|
+
旧 `discord_webhook_url` / `discord_username` 和 `DiscordWebhookSender` 保留兼容,内部同样转调 `dwy-notify`,没有第二套发送 SDK。新配置用 URL 的用户名设置渠道名称;同一层不能同时配置新旧目标,模型会拒绝。来源配置可以覆盖全局默认。
|
|
53
|
+
|
|
54
|
+
持久队列将逐目标状态、次数、下次重试时间和目标指纹写进既有 payload JSON,无需 schema 迁移;已成功目标不会随失败目标重发。旧待发载荷首次处理时绑定当时目标,之后目标删除或 URL 改变不会把旧消息静默投给新接收方。部分永久失败仍保留其它目标的成功状态;网络成功但确认丢失仍可能重复送达。
|
|
55
|
+
|
|
56
|
+
## Apple 内购详情与通知
|
|
57
|
+
|
|
58
|
+
安装 `dwy-webhooks[apple]==0.6.1` 即可在其他 FastAPI 项目使用,不需要 App Cloud、ADBox、账户或商品权益表:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from fastapi import FastAPI
|
|
62
|
+
from dwy_notify import NotificationConfig
|
|
63
|
+
from dwy_webhooks import WebhookConfig, install_webhooks
|
|
64
|
+
from dwy_webhooks.config import AppleNotificationApp, AppleNotificationsConfig
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_app(apple_id: int, certificates: tuple[bytes, ...], discord_url: str) -> FastAPI:
|
|
68
|
+
"""discord_url 是宿主提供的 Apprise URL;证书传字节。"""
|
|
69
|
+
app = FastAPI()
|
|
70
|
+
install_webhooks(app, WebhookConfig(
|
|
71
|
+
notifications=NotificationConfig(targets={"purchases": discord_url}),
|
|
72
|
+
apple_notifications=AppleNotificationsConfig(apps={
|
|
73
|
+
"my-app": AppleNotificationApp(
|
|
74
|
+
name="My App", bundle_id="com.example.myapp", environment="Production",
|
|
75
|
+
app_apple_id=apple_id, root_certificates=certificates,
|
|
76
|
+
),
|
|
77
|
+
}),
|
|
78
|
+
))
|
|
79
|
+
return app
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Apple V2 回调路径为 `/webhooks/apple/my-app`。旧 Discord 配置默认发送者为「Apple 应用通知」,`AppleNotificationsConfig.discord_username` 可覆写;新通知 URL 用用户名配置名称。应用名称由 `AppleNotificationApp.name` 注入。默认注册器的去重及可注入持久 sink 语义沿用原接口。
|
|
83
|
+
|
|
84
|
+
通知展示交易金额/币种、购买店面、时间、本人购买/家庭共享、退款申请的用户选择分类、退款结果的 Apple 分类及适用续订设置。金额按整数毫单位精确换算,退款仅展示原交易价格;缺失显示未提供,未知分类保留代码,不推断用户自由文本、开发者收入或购买设备平台。
|
|
85
|
+
|
|
86
|
+
已有业务队列可使用公开 `dwy_webhooks.apple`:`AppleNotificationVerifier`、`VerifiedAppleNotification`、`AppleTransactionDetails`、`AppleRenewalDetails` 和 `apple_notification_content(notice, app_name="My App")`;格式化后的字符串直接交既有 `DiscordWebhookSender`,其 `username` 可覆写。`apple_transaction_details`、`apple_renewal_details` 和 `apple_refund_request_reason` 只消费已经完成官方验签的 SDK 对象,本身不代替验签。
|
|
87
|
+
|
|
88
|
+
`VerifiedAppleNotification` 保留原八字段构造及 `public_data()` 白名单,新增可选 `transaction_details`、`renewal_details`、`consumption_request_reason`。详情模型不包含账户 token、原始 JWS、签名摘要或权益策略。商品白名单、账户绑定、持久化及申请退款不提前撤销权益等业务规则仍由宿主管理。
|
|
89
|
+
|
|
90
|
+
PyPI 从此版本起只上传 `py3-none-any.whl`;通用 wheel 含可读 Python 模块,不提供源码隐藏。源码包只参与内部归档检查,不上传 PyPI。
|
|
91
|
+
|
|
92
|
+
## 配置与接入
|
|
93
|
+
|
|
94
|
+
### ASC 审核通知的平台与版本
|
|
95
|
+
|
|
96
|
+
审核事件本身只包含状态变化和 `appStoreVersions` 资源 ID;载荷里的 `version: 1` 不是应用版本号。安装 `dwy-webhooks[asc]` 并显式配置 `AppStoreConnectWebhookConfig.api` 后,适配器会按事件中的具体版本 ID 查询 `platform` 与 `versionString`,校验所属 App,再将平台、版本号补入通知。查询结果的最新审核状态不会覆盖事件原始状态。
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from pydantic import SecretStr
|
|
100
|
+
|
|
101
|
+
from dwy_webhooks.config import (
|
|
102
|
+
AppStoreConnectAPIConfig,
|
|
103
|
+
AppStoreConnectApp,
|
|
104
|
+
AppStoreConnectWebhookConfig,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def asc_config(
|
|
109
|
+
*, apps: dict[str, AppStoreConnectApp], issuer_id: str, key_id: str, private_key: SecretStr,
|
|
110
|
+
) -> AppStoreConnectWebhookConfig:
|
|
111
|
+
"""参数由宿主 Settings 提供;API 账号凭据与各 App 的 HMAC 密钥分别注入。"""
|
|
112
|
+
return AppStoreConnectWebhookConfig(
|
|
113
|
+
apps=apps,
|
|
114
|
+
api=AppStoreConnectAPIConfig(issuer_id=issuer_id, key_id=key_id, private_key=private_key),
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
宿主可以显式复用已有评论或经营报表的 ASC 账号凭据;无需启用这些功能,也不由库读取其他模块或环境。所用密钥需要有权读取该 App 的版本信息,Webhook 签名密钥不能代替 ASC API 账号凭据。
|
|
119
|
+
|
|
120
|
+
查询使用固定地址 `https://api.appstoreconnect.apple.com/v1/appStoreVersions/{id}`,不跟随重定向,限制超时和响应体积。查询失败返回 502,留给 Apple 重投,不确认或发送缺少详情的完整事件;未配置 API 的旧接入仍接收通知,但平台、版本明确标记“未查询(未配置 ASC API)”。构建上传事件和官方 ping 不触发版本查询。无需数据库迁移。
|
|
121
|
+
|
|
122
|
+
库不读取环境变量、`.env`、项目设置或证书文件。`WebhookConfig` 是纯 Pydantic 模型;项目可组合进自己的 EAPI Settings。密钥由项目取得后传入,Apple 根证书传入字节。
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from dwy_notify import NotificationConfig
|
|
126
|
+
from dwy_webhooks import WebhookConfig, install_webhooks
|
|
127
|
+
from dwy_webhooks.config import AppleNotificationsConfig, ResendWebhookConfig
|
|
128
|
+
|
|
129
|
+
# project_settings 由宿主取得密钥;URL 为 Apprise 格式,不写进源码。
|
|
130
|
+
config = WebhookConfig(
|
|
131
|
+
notifications=NotificationConfig(targets=project_settings.default_notification_targets),
|
|
132
|
+
resend=ResendWebhookConfig(
|
|
133
|
+
secret=project_settings.resend_webhook_secret,
|
|
134
|
+
name="My Project", events=frozenset({"email.received", "email.failed"}),
|
|
135
|
+
notifications=NotificationConfig(targets={
|
|
136
|
+
"mail": project_settings.discord_notification_url,
|
|
137
|
+
}),
|
|
138
|
+
),
|
|
139
|
+
apple_notifications=AppleNotificationsConfig(
|
|
140
|
+
apps=project_settings.apple_notification_apps,
|
|
141
|
+
notifications=NotificationConfig(targets={
|
|
142
|
+
"purchases": project_settings.wecom_notification_url,
|
|
143
|
+
}),
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
runtime = install_webhooks(app, config)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Resend 使用 Webhook 签名密钥,不使用发送邮件的 API Key。未配置来源不注册路由;各来源可独立覆盖通知目标、事件集合和接收路径。
|
|
150
|
+
|
|
151
|
+
- `AppStoreConnectWebhookConfig(apps={app_id: AppStoreConnectApp(name=..., secret=...)})` 配置审核、构建及 ping 通知。
|
|
152
|
+
- `AppleNotificationsConfig(apps={key: AppleNotificationApp(name=..., bundle_id=..., environment="Production", app_apple_id=..., root_certificates=(certificate_bytes, ...))})` 配置 Server Notifications V2。
|
|
153
|
+
- 将前两者放入 `WebhookConfig.app_store_connect`、`.apple_notifications` 后,一次注册即可接入。
|
|
154
|
+
- 默认路径:`/webhooks/resend`、`/webhooks/app-store-connect/{app_id}`、`/webhooks/apple/{app_id}`;完整公开 URL 由项目域名及反向代理决定。
|
|
155
|
+
|
|
156
|
+
## 资源与接收语义
|
|
157
|
+
|
|
158
|
+
- 默认 relay 在全部目标发送成功后确认,实例内保留有界去重记录,部分失败仅重试未成功目标。失败返回非 2xx 供提供方重试;重启可能再次发送,不承诺持久或恰好一次投递。
|
|
159
|
+
- 项目可注入 `ReceiptStore`,或通过 `WebhookSinks` 按来源注入接收函数。函数接收已验证的 `WebhookEvent`,返回 True 表示接受、False 表示重复;异常不能被确认成功。
|
|
160
|
+
- `SQLAlchemyDeliveryQueue` 提供持久队列,sessions、table、sender、source 和可选策略由外部注入。使用 EAPI 创建的会话,不创建、迁移或关闭项目数据库。一个表固定一个来源,防止跨来源编号碰撞。
|
|
161
|
+
- 已有其它队列表结构可直接使用 `dwy_webhooks.notification_delivery.deliver_notifications(notifier, payload, *, policy, clock, checkpoint)`,复用同一逐目标算法。`payload` 是当前记录独立解码的可变字典,含 `content` 和可选的 `notifications` 状态;其它宿主字段保留。`checkpoint(**values)` 必须按当前租约原子持久化 `payload` 或续租 `lease_until`,成功返回 True;False 会停止并返回 `{"status": "processing"}`,调用方不得把它标记成功或清除新租约。正常返回的聚合状态仍须由调用方按租约提交;取消及检查点异常直接传播。`delivery_failure(attempts, failure, *, policy, clock)` 复用相同的有界退避与安全错误码,不执行持久化。
|
|
162
|
+
- `build_delivery_table` 注册到项目 metadata,由项目自己的 Alembic 管理 schema。库不在注册路由时自动建表。
|
|
163
|
+
- `install_webhooks` 保留宿主生命周期,只关闭自己拥有的验证器;`create_app(config)` 可建立独立 ASGI 应用,配置仍然必填。普通错误复用 EAPI,第三方成功 ACK 保持提供方协议。
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from dwy_webhooks import WebhookSinks, install_webhooks
|
|
167
|
+
|
|
168
|
+
# persist_verified_event 是项目自己的持久入口,或已注入资源的队列实例。
|
|
169
|
+
install_webhooks(app, config, sinks=WebhookSinks(app_store_connect=persist_verified_event))
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Apple 商品目录、账户绑定和权益发放保留在业务项目。通用模块只验证通知并提供安全事件元数据,不把测试通知或续订设置变更报告为收入。
|
|
173
|
+
|
|
174
|
+
## 安全和扩展
|
|
175
|
+
|
|
176
|
+
来源分别验签,并在读取原始字节时限制体积。Apple 外层、交易及续订 JWS 分别走官方 SDK,生产强制 App Apple ID。Discord 固定官方 HTTPS 地址,拒绝重定向和 mentions,等待有效消息编号;错误不记录 token 或原始响应正文。
|
|
177
|
+
|
|
178
|
+
新增来源增加 provider;通知渠道通过 `dwy-notify` 的 Apprise URL 选择,SDK 异常与安全修正集中在通知包。注册入口不覆盖项目已有的全局异常或缓存配置。
|
|
179
|
+
|
|
180
|
+
`examples/new_project/app.py` 是不依赖 App Cloud 的接入示例。隔离 wheel 验收要求 `app_cloud`、`apple_iap`、`cloud_kernel`、旧 `cloud_webhooks` 和产品包缺席,公共配置、注册和三类来源仍能通过测试。
|
|
181
|
+
|
|
182
|
+
## App Store 评论定时通知
|
|
183
|
+
|
|
184
|
+
`dwy_webhooks.reviews` 提供主动采集能力,独立于审核/构建 Webhook 和内购通知。评论通知安装 `dwy-webhooks[reviews]`:`reviews` 直接声明 HTTPX、PyJWT 和 cryptography,使用账号 API 密钥签发 ES256 JWT 并访问 ASC REST API;不需要 Apple Server SDK 或 Resend SDK,也不依赖其它包偶然传入 PyJWT。无需 Redis 服务、ARQ Worker 或新增调度框架。
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
uv add 'dwy-webhooks[reviews]==0.6.1'
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from sqlalchemy import MetaData
|
|
192
|
+
from dwy_notify import NotificationConfig
|
|
193
|
+
from dwy_webhooks.reviews import AppleReviewsConfig, build_review_tables, install_review_monitor
|
|
194
|
+
|
|
195
|
+
# project_settings、EAPI 创建的 sessions 及 app 都由消费项目提供。
|
|
196
|
+
config = AppleReviewsConfig(
|
|
197
|
+
apps=project_settings.review_apps,
|
|
198
|
+
issuer_id=project_settings.apple_issuer_id,
|
|
199
|
+
key_id=project_settings.apple_key_id,
|
|
200
|
+
private_key=project_settings.apple_private_key,
|
|
201
|
+
notifications=NotificationConfig(targets=project_settings.review_notification_targets),
|
|
202
|
+
poll_interval_seconds=3600,
|
|
203
|
+
retention_days=30,
|
|
204
|
+
)
|
|
205
|
+
metadata = MetaData()
|
|
206
|
+
tables = build_review_tables(metadata)
|
|
207
|
+
# 先通过消费项目的 Alembic 创建上述三张表,再启动任务;库不自动建表。
|
|
208
|
+
runtime = install_review_monitor(app, config, sessions, tables)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
- 默认每小时采集一次,可配置 `poll_interval_seconds`(60–604800 秒);FastAPI lifespan 管理 asyncio 后台任务。每分钟只检查数据库计划和执行清理,不代表每分钟请求 Apple。
|
|
212
|
+
- 每个应用持久化下次执行时间和租约;多消费者不会同时提交同一轮。重启保留计划,失败一分钟后尝试恢复;完整分页失败不会推进基线。
|
|
213
|
+
- 每轮完整读取全部评论历史,最多 100 页、每页 200 条,每页响应不超过 2 MiB;整轮同步最长 120 秒。超过这些上限会失败并保留原有基线,不能把它视为只拉取最近 N 天;`retention_days` 控制数据库保留范围。
|
|
214
|
+
- 首次只建立保留期内历史基线并发送一条摘要;随后新评论或改文/改星产生通知。通知截断到 Discord 限长,完整原文存于保留期内的数据库记录。
|
|
215
|
+
- `retention_days` 默认 30,允许 1–3650(包括 60/90)。按 Apple 评论创建时间保留最近 N 天;评论原文、指纹及该评论的投递载荷都会清理。基线摘要按生成时间清理,最小的应用调度/基线状态长期保留,其中没有评论正文或昵称。
|
|
216
|
+
- 扩大保留期时静默补入较老历史,避免当作新评论刷屏;既有保留窗口内的新评论、修改仍通知。已超出窗口的旧评论修改不追踪。
|
|
217
|
+
- 清理独立于 Apple 拉取成功与否;投递前也检查过期状态。持久队列提供有界重试,不承诺网络超时场景下恰好一次送达。
|
|
218
|
+
- 表、会话、时钟、HTTP 客户端和 sender 可显式注入;库不读取 `.env`、打开项目数据库或自动执行迁移。关闭任务后才允许宿主释放数据库。
|
|
219
|
+
- 本功能读取文字评论及其附带星级,不提供纯星级逐条事件、总评分统计或 Discord 内回复。
|
|
220
|
+
|
|
221
|
+
本轮清单版本为 `0.6.1`,不代表已发布到公开 PyPI。消费项目应在发布完成后固定版本并验收实际安装产物。
|
|
222
|
+
|
|
223
|
+
## Discord 发送者名称
|
|
224
|
+
|
|
225
|
+
旧配置的发送者默认使用「Apple 应用通知」,通过 Discord 原生 `username` 字段逐条指定,不修改平台侧 Webhook 配置。调用项目可按自己的场景覆写:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
sender = DiscordWebhookSender(webhook_url, username="团队评分通知")
|
|
229
|
+
# 评分安装器自动使用此名称;传入自定义 sender 时由该 sender 决定名称。
|
|
230
|
+
config = AppleReviewsConfig(
|
|
231
|
+
apps=apps, issuer_id=issuer_id, key_id=key_id, private_key=private_key,
|
|
232
|
+
discord_webhook_url=webhook_url, discord_username="团队评分通知",
|
|
233
|
+
)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`WebhookConfig.discord_username` 设置通用来源的名称;`ResendWebhookConfig`、`AppStoreConnectWebhookConfig` 和 `AppleNotificationsConfig` 的同名字段可逐来源覆写,省略时继承全局值。邮件等非 Apple 场景应显式设置合适名称。名称必须为 1–80 字符,不能全为空白或包含控制字符。
|
|
237
|
+
|
|
238
|
+
评分正文以加粗标题区分监控启用、新增和更新,独立展示应用名称与五颗可视星级。调用项目继续管理凭据、数据库连接和 Alembic 迁移,复用时无需复制评分源码。
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dwy-wechatpay-notify
|
|
3
|
+
description: 使用或排查 dwy-wechatpay-notify 微信支付通知能力时使用;先核实项目安装版本及公开 API。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# dwy-wechatpay-notify
|
|
7
|
+
|
|
8
|
+
微信支付与退款通知验证;可选 FastAPI 路由需要显式 verifier 和持久 sink。
|
|
9
|
+
|
|
10
|
+
## 查找与使用
|
|
11
|
+
|
|
12
|
+
项目使用 0.1.1 时可阅读 [0.1.1 接入和示例](references/v0/0.1.1.md);其他版本先查该版本的资料和代码,不把此快照当作其他版本的合同。
|
|
13
|
+
|
|
14
|
+
1. 检查项目锁文件和当前 Python 环境;用 `importlib.metadata.version("dwy-wechatpay-notify")` 查询实际发行版本,依赖范围不等于实际安装版本。未安装时不得假设可用或自动安装。
|
|
15
|
+
2. 在实际环境中检查 `dwy_wechatpay_notify` 的公开接口、类型及项目已有调用;editable 安装以实际源码为准。说明、实现或安装版本不一致时以运行代码为准。
|
|
16
|
+
3. 本索引基于仓库 0.1.1,能力与签名可能随小版本变化。精确版本源码位于仓库 `backend/packages/wechatpay-notify`,文档为该目录的 `README.md`;只在确认其版本与消费项目一致时使用。缺少安装源码时查对应 `dwy-wechatpay-notify@版本` 标签,不能用默认分支冒充。
|
|
17
|
+
4. 在已有调用和该版本文档中核对配置、必需依赖、输入校验、失败处理及生命周期后接入。没有足够证据时说明缺口,不猜 API。
|
|
18
|
+
|
|
19
|
+
不要因为发现此库就改动项目依赖;新增依赖遵循项目选型规则。关联 FastAPI 或其他自有包时同时参考可用的对应 Skill。
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<!-- 来源:dwy-wechatpay-notify@0.1.1 的 backend/packages/wechatpay-notify/README.md;文中相对链接相对原仓库路径。 -->
|
|
2
|
+
|
|
3
|
+
# dwy-wechatpay-notify
|
|
4
|
+
|
|
5
|
+
独立微信支付来源包(0.1.1):复用 `wechatpayv3[async]==2.0.4` 的公开 `callback(headers, body)` 验签解密,把付款和退款结果转成 `dwy_notify.events.NotificationEvent`。不依赖宿主支付模块,不实现 RSA/AES、业务账本、权益变更或群消息发送。
|
|
6
|
+
|
|
7
|
+
本包采用 [MIT License](LICENSE)。微信支付 SDK `wechatpayv3==2.0.4` 同样采用 MIT,具体版权声明以各发行包为准。
|
|
8
|
+
|
|
9
|
+
## 已有支付后台
|
|
10
|
+
|
|
11
|
+
已有官方回调入口的项目直接使用 verifier,不重复注册本包路由。SDK 客户端、商户私钥、APIv3 密钥、公钥/平台证书全部由宿主配置;在 SDK 客户端有效上下文内注入它的同步公开 callback。本包不读取环境变量、不复制密钥、不调用 SDK 私有 core。
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from dwy_wechatpay_notify import (
|
|
15
|
+
PaymentNotificationContext,
|
|
16
|
+
WechatPayNotificationVerifier,
|
|
17
|
+
WechatPayNotifyConfig,
|
|
18
|
+
wechatpay_notification_event,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def handle_callback(headers, raw_body, sdk_client, payment_service, durable_sink):
|
|
23
|
+
"""由宿主传入已打开的 SDK 客户端、原始 bytes 和持久接收器。"""
|
|
24
|
+
config = WechatPayNotifyConfig(mchid="1900000001", appid="wx123", scope="shop-production")
|
|
25
|
+
verified = await WechatPayNotificationVerifier(config, callback=sdk_client.callback).verify(headers, raw_body)
|
|
26
|
+
# 宿主核对本地订单/金额并完成业务提交;持久 outbox 应与业务事务一起写入。
|
|
27
|
+
confirmed = await payment_service.apply_verified_notification(verified)
|
|
28
|
+
event = wechatpay_notification_event(
|
|
29
|
+
confirmed,
|
|
30
|
+
scope=config.scope,
|
|
31
|
+
context=PaymentNotificationContext(description="基础版月付", outcome="权益已确认"),
|
|
32
|
+
)
|
|
33
|
+
await durable_sink(event)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`payment_service` 与 `durable_sink` 是宿主注入对象,示例不定义业务账本。主动查单、补单或对账时构造 `ConfirmedWechatPayResult`:付款需 `event_type="TRANSACTION.SUCCESS"`、`order_no`、`transaction_id`、整数分 `amount_fen`、`trade_type`、`trade_state="SUCCESS"`;退款另需 `refund_no`、`refund_id`、`refund_fen`、与事件一致的 `refund_status`。查单没有官方回调 ID 时保持 `provider_event_id=None`。
|
|
37
|
+
|
|
38
|
+
收到已验证官方事件**不证明本地已入账**。宿主必须核对订单、币种、原金额与状态,并由自己的事务确定权益。`REFUND.ABNORMAL`、`REFUND.CLOSED` 只表示异常/关闭,不能当作退款成功。退款回调可能没有 appid,始终核验 mchid;有 appid 时同时核验。
|
|
39
|
+
|
|
40
|
+
## 新项目:可选 FastAPI 安装器
|
|
41
|
+
|
|
42
|
+
安装包的 `[fastapi]` extra 后调用以下装配函数;生产必须传入持久化 `EventSink`,不得用只发 HTTP 群消息的函数替代持久接受。`sink(event)` 返回 True 表示新接受,False 表示之前已接受的重复,二者均确认;异常返回 HTTP 503,微信可重试。本模块不在验签前用内存 receipt 吞掉业务重试。
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from dwy_wechatpay_notify import (
|
|
46
|
+
WechatPayNotificationVerifier,
|
|
47
|
+
WechatPayNotifyConfig,
|
|
48
|
+
install_wechatpay_notifications,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def configure_callbacks(app, sdk_client, durable_sink):
|
|
53
|
+
"""宿主 lifespan 保持 SDK 客户端存活,关闭资源仍由宿主负责。"""
|
|
54
|
+
config = WechatPayNotifyConfig(mchid="1900000001", appid="wx123", scope="shop-production")
|
|
55
|
+
install_wechatpay_notifications(
|
|
56
|
+
app,
|
|
57
|
+
config,
|
|
58
|
+
verifier=WechatPayNotificationVerifier(config, callback=sdk_client.callback),
|
|
59
|
+
sink=durable_sink,
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
默认接收地址示例:`http://localhost:8000/webhooks/wechatpay`;生产应配置真实公网 HTTPS,例如 `https://payments.example.com:443/webhooks/wechatpay`。可通过 config.path 更改。安装器流式读取请求,默认最多 65536 bytes;超限 HTTP 413,验签/身份/协议失败 HTTP 400,持久接受成功或重复 HTTP 204。只交给 SDK 一次原始 bytes,不改变宿主 lifespan、不启动或关闭其他应用。
|
|
64
|
+
|
|
65
|
+
## 安全与幂等
|
|
66
|
+
|
|
67
|
+
0.1.1 起,外部项目可以通过 `PaymentNotificationContext.extra_fields` 提供自定义展示字段:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from dwy_notify import NotificationField
|
|
71
|
+
from dwy_wechatpay_notify import ConfirmedWechatPayResult, PaymentNotificationContext, wechatpay_notification_event
|
|
72
|
+
|
|
73
|
+
result = ConfirmedWechatPayResult(
|
|
74
|
+
event_type="TRANSACTION.SUCCESS", order_no="order123", transaction_id="wx123",
|
|
75
|
+
amount_fen=1, trade_type="NATIVE", trade_state="SUCCESS",
|
|
76
|
+
)
|
|
77
|
+
event = wechatpay_notification_event(
|
|
78
|
+
result, scope="shop-preview",
|
|
79
|
+
context=PaymentNotificationContext(
|
|
80
|
+
description="基础套餐", outcome="权益已发放",
|
|
81
|
+
extra_fields=(
|
|
82
|
+
NotificationField(label="客户编号", value="customer-001"),
|
|
83
|
+
NotificationField(label="项目名称", value="示例项目"),
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
最多 8 项,每项标签 1–32、值 1–256 字符,均为单行纯文本。禁止重复标签及“订单号、金额、退款金额、业务结果”等固定标签;字段不会合并进官方 `data` 或改变去重键。库不查询用户表,也不自动序列化业务对象。字段是否允许包含个人资料及是否脱敏由宿主显式决定;不得传入密码、密钥或原始回调。入队前渲染正文,重试沿用快照。`NotificationEvent.data` 不会自动成为群消息。
|
|
90
|
+
|
|
91
|
+
传入字段时,固定金融事实与业务结果优先,其次附加字段,最后商品描述;总预算仍为 1800 UTF-8 字节,附加展示过长会标记截断或省略。未传字段时保持 0.1.0 的正文兼容行为。
|
|
92
|
+
|
|
93
|
+
- 配置显式注入,商户/应用身份非空,scope 最长 128 字符;不同商户/应用必须使用不同 scope。
|
|
94
|
+
- 付款键 `wechat:payment:{order_no}:success`;退款键 `wechat:refund:{refund_no}:{SUCCESS|ABNORMAL|CLOSED}`。回调与主动查单使用同一业务键;sink 按 source、scope、event_id 持久幂等。
|
|
95
|
+
- 事件 data 仅包含类型化白名单字段;不自动转发 payer/openid、邮箱、手机号、密钥、签名或原文。商品与业务结果不能夹带个人信息;需要展示的宿主资料须显式选择 `extra_fields`,不接受任意账号对象。
|
|
96
|
+
- 通知正文严格不超过 1800 UTF8 bytes,不截断金融标识。未传自定义字段时沿用原有商品/业务结果整项省略规则。金额用整数分格式化。
|
|
97
|
+
- `WECHATPAY/SIGNTEST/` 不是交易,安全拒绝;SDK 的异常统一转成 `WechatPayVerificationError`,不携带原文或密钥。宿主不得给 SDK 配置记录回调原文的日志器。
|
|
98
|
+
|
|
99
|
+
## 开发与验收
|
|
100
|
+
|
|
101
|
+
执行本目录 `pytest` 运行全部用例,使用固定测试驱动与本地临时 RSA 密钥/AESGCM,均不访问微信/企微网络。`tests/TEST_CASES.md` 记录覆盖边界。`tests/wheel_smoke.py` 面向已安装 wheel,验证独立核心导入及事件合同。正式发行必须使用 GitHub Actions OIDC;本地构建仅做临时 wheel 验收,不替代发布工作流。
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dwy-admin-kit
|
|
3
|
+
description: 使用或排查 @dwydev/admin-kit 的接入和现有功能时使用,核实消费项目实际版本。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @dwydev/admin-kit
|
|
7
|
+
|
|
8
|
+
管理后台模块、路由装配及 AdminShell 布局。项目使用 0.3.0 时可阅读 [0.3.0 接入说明](references/v0/0.3.0.md);其他版本不能直接套用。
|
|
9
|
+
|
|
10
|
+
先查项目实际依赖、锁文件与已安装包的声明文件和现有调用;版本范围不是安装版本。核实组件 props、初始化和生命周期后再使用。当前能力索引基于 0.3.0;版本不同可能存在差异。相同版本的源码与接入文档见仓库 `frontend/admin-shell`,其他版本查对应 `@dwydev/admin-kit@版本` 标签;不能用默认分支代替。缺少证据时说明,不猜接口。项目未安装时不自行添加依赖。
|
|
11
|
+
|
|
12
|
+
0.2.0 重写 AdminShell,并移除旧版内置主题、通知和用户菜单;0.3.0 新增组件、composables、分页列表等子入口,EUI peer 下限为 2.7.2。升级时核实宿主插槽与配套版本,不能把 0.3.0 接口用于 0.1.x。
|