改造充值订单过期补偿流程
新增 SpacetimeDB 原生充值订单过期 timer 与 expired 状态。 移除 api-server 轮询 worker,改为 HTTP 角色订阅 Pending 到 Expired 事件并查单补偿。 统一微信下单 5 分钟 time_expire,并恢复前端支付通道选择与 expired 展示。 补齐生成绑定、前后端契约、测试和项目文档。
This commit is contained in:
@@ -16,6 +16,14 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-09 充值订单过期改为 SpacetimeDB scheduled 表触发
|
||||
|
||||
- 背景:旧充值过期处理使用 api-server 后台轮询 worker claim 普通 schedule 表,非 HTTP 的 external-generation-worker / controller 进程也可能启动同一过期任务;扩外部生成 worker 会意外放大微信查单 / 关单流量,并且本地过期后若微信仍可支付,容易出现“微信扣款但本地拒绝入账”的风险。
|
||||
- 决策:新建原生 scheduled 表 `profile_recharge_order_expiration_timer`,创建真实微信 pending 充值订单时写入 5 分钟 timer;scheduled reducer 到点只把仍为 `pending` 的订单改为 `expired` 并写 `expired_at`。HTTP `api-server` 订阅 `profile_recharge_order` 的 `Pending -> Expired` 更新并执行微信查单补偿;`SUCCESS` 允许 `Expired -> Paid` 入账,未支付或远端已终态只记录检查结果,本地保持 `expired`。`external-generation-worker` 和 controller 不处理充值过期。
|
||||
- 影响范围:`profile_recharge_order`、`profile_recharge_order_expiration_timer`、充值订单状态契约、`spacetime-client` bindings/facade、`api-server` 充值过期监听器、微信支付查单 / 关单、个人中心充值前端、后台表查询和运维文档。
|
||||
- 验证方式:`npm run spacetime:generate`、`npm run check:spacetime-schema`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、充值过期 listener / 微信支付 / shared contracts / 前端充值定向测试、`npm run check:encoding`、`git diff --check`。
|
||||
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【开发运维】本地开发验证与生产运维-2026-05-15.md`。
|
||||
|
||||
## 2026-07-09 会员有效期与周期泥点重置分离
|
||||
|
||||
- 背景:账户会员制度新增 Starter / Basic / Pro / Ultimate 四档后,会员有效期、周期限时泥点和普通永久泥点容易被混成同一条时间线;升级场景尤其容易误把“补差额”实现成延长会员或重算 reset time。
|
||||
|
||||
@@ -2058,10 +2058,18 @@
|
||||
|
||||
- 现象:后台“表查询”查看 `profile_recharge_order` 时,`kind` 和 `status` 显示为空数组 `[]`,例如充值订单原始行里 `points_60` 的类型和状态都不可读。
|
||||
- 原因:SpacetimeDB HTTP SQL 对无载荷枚举会返回 SATS 形态 `[variant_index, []]`;后台通用 normalizer 曾把任何 `[0, value]` 都当作 `Option::Some(value)` 展开,导致 `[0, []]` 最终只剩 `[]`。
|
||||
- 处理:通用表查询解析应先按表名和列名识别已知业务枚举,再落回 Option / Timestamp 通用展开;例如 `profile_recharge_order.kind` 映射为 `points` / `membership`,`profile_recharge_order.status` 映射为 `pending` / `paid` / `failed` / `closed` / `refunded`。
|
||||
- 处理:通用表查询解析应先按表名和列名识别已知业务枚举,再落回 Option / Timestamp 通用展开;例如 `profile_recharge_order.kind` 映射为 `points` / `membership`,`profile_recharge_order.status` 映射为 `pending` / `paid` / `failed` / `closed` / `refunded` / `expired`。
|
||||
- 验证:执行 `cargo test -p api-server admin_database -- --nocapture`,并确认后台详情弹层的 `raw` 与表格 `cells` 都显示业务字符串。
|
||||
- 关联:`server-rs/crates/api-server/src/admin.rs`、`docs/technical/ADMIN_DATABASE_TABLE_QUERY_2026-05-08.md`。
|
||||
|
||||
## 充值订单过期补偿不要放进外部生成 worker
|
||||
|
||||
- 现象:外部生成 worker/controller 扩容后,微信充值过期查单和关单流量也被同步放大;排查时还会误去外部生成 worker 日志里找支付过期任务。
|
||||
- 原因:支付过期是账户资金链路,不是外部内容生成队列;旧实现把充值过期轮询 worker 挂在通用后台任务启动函数里,非 HTTP 角色也会启动。
|
||||
- 处理:充值订单过期由 SpacetimeDB 原生 `profile_recharge_order_expiration_timer` 到点把 `pending` 改为 `expired`,只有 HTTP `api-server` 订阅 `profile_recharge_order` 的 `Pending -> Expired` 更新并查微信补偿。未支付终态本地保持 `expired`,不要再改写成 `closed`;微信成功支付通知或补偿查单仍可把 `Expired -> Paid` 入账。
|
||||
- 验证:确认 `GENARRATIVE_PROCESS_ROLE=external-generation-worker` / `external-generation-controller` 不启动充值过期监听;创建 pending 充值单后只由 scheduled reducer 产生 `expired`,HTTP api-server listener 记录 `expiration_checked_at` 或补入账。
|
||||
- 关联:`server-rs/crates/api-server/src/profile_recharge_expiration_listener.rs`、`server-rs/crates/spacetime-module/src/runtime/profile.rs`、`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`。
|
||||
|
||||
## 抓大鹅历史草稿外部 Rodin GLB 链接必须转存后再试玩或发布
|
||||
|
||||
- 现象:草稿页预览模型失败并报 `GL_INVALID_ENUM: Invalid cap.`,或结果页能看到历史生成记录但试玩、发布和正式运行态仍显示默认积木。
|
||||
|
||||
@@ -197,10 +197,10 @@ npm run check:server-rs-ddd
|
||||
7. 会员有效期和周期重置时间是两条独立时间线。`expires_at` 只决定会员是否生效;`cycle_resets_at` 只决定当前周期限时泥点何时重置。会员升级只更新档位并补齐当前周期限时泥点差额,不延长 `expires_at`,不移动 `cycle_resets_at` 和周期天数。同级会员购买只从当前 `expires_at` 延长有效期,不发放额外当前周期泥点,也不移动重置时间。
|
||||
8. 会员周期刷新发生在个人中心、充值中心、任务中心、账单读取和钱包扣费入口:到达 `cycle_resets_at` 时先清除上周期剩余限时泥点,再发放当前会员档位周期额度;会员过期时清除剩余限时泥点并把状态降为普通。周期发放和重置流水分别使用 `membership_period_grant`、`membership_period_reset`。
|
||||
9. `paymentChannel` 缺失、未知或冒用小程序支付设备时必须拒绝;真实微信渠道只允许 `wechat_mp`、`wechat_mp_virtual`、`wechat_jsapi`、`wechat_h5`、`wechat_native`,生产配置不得把真实支付静默降级为 `mock`。
|
||||
10. access JWT 只携带最小设备快照 `device.client_type`、`device.client_runtime`、`device.client_platform`。充值下单按该快照拦截小程序渠道:小程序只允许 `wechat_mp` / `wechat_mp_virtual`;微信内浏览器使用 `wechat_jsapi`;普通 Web 使用 `wechat_native`,历史普通 Web 登录态若缺少设备快照也允许继续进入 JSAPI / H5 / Native 渠道的后续支付配置校验,但不放宽小程序虚拟支付。
|
||||
10. access JWT 只携带最小设备快照 `device.client_type`、`device.client_runtime`、`device.client_platform`。充值下单按该快照拦截小程序渠道:小程序只允许 `wechat_mp` / `wechat_mp_virtual`;移动网页和微信内 H5 走 `wechat_h5`;桌面网页和桌面微信走 `wechat_native`;`wechat_jsapi` 仅保留后端能力,未接微信开放平台前不由前端自动选择。历史普通 Web 登录态若缺少设备快照也允许继续进入 JSAPI / H5 / Native 渠道的后续支付配置校验,但不放宽小程序虚拟支付。
|
||||
11. 所有微信真实渠道都以微信支付通知或服务端查单确认 `SUCCESS` 为到账事实;小程序、H5 跳转和 Native 二维码返回都不能直接发放泥点或会员。
|
||||
12. 微信 Native 下单显式传 `time_expire`,当前有效期为 5 分钟,并通过 `wechatNativePayment.expiresAt` 下发给前端二维码弹窗展示。
|
||||
13. 普通微信支付渠道的新建 pending 充值订单会写入 `profile_recharge_order_expiration_schedule`。到期处理由 `api-server` 后台 worker claim 调度行后调用微信查单;只有微信返回 `SUCCESS` 才补确认入账,返回 `NOTPAY` / `CLOSED` / `REVOKED` / `PAYERROR` 才关闭本地订单,查询失败或 `USERPAYING` 保留租约等待重试。SpacetimeDB module 不直接发起微信 HTTP 请求。
|
||||
12. 微信 JSAPI / H5 / 小程序 / Native 下单统一显式传 5 分钟 `time_expire`,格式为 RFC3339 秒级时间;Native 额外通过 `wechatNativePayment.expiresAt` 下发给前端二维码弹窗展示。
|
||||
13. 真实微信渠道的新建 pending 充值订单会写入 SpacetimeDB 原生 scheduled 表 `profile_recharge_order_expiration_timer`。到期 reducer 只做数据库内状态转换:订单仍为 `pending` 时更新为 `expired` 并写 `expired_at`,同时删除 timer。HTTP `api-server` 订阅 `profile_recharge_order` 的 `Pending -> Expired` 更新并执行微信查单补偿:`SUCCESS` 可把 `expired` 补确认成 `paid` 入账;`NOTPAY` 会调用微信关单并把本地订单保持为 `expired`;`CLOSED` / `REVOKED` / `PAYERROR` / `ORDER_NOT_EXIST` 只记录检查结果。`external-generation-worker` / controller 不处理充值过期;`wechat_mp_virtual` 到期只记录虚拟渠道不可查,后续真实支付通知仍允许 `Expired -> Paid`。
|
||||
|
||||
## 创作入口泥点扣费契约
|
||||
|
||||
@@ -704,12 +704,19 @@ npm run check:server-rs-ddd
|
||||
|
||||
- Rust 结构体:`ProfileRechargeOrder`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/runtime/profile.rs`
|
||||
- 作用:账户充值订单事实源。`status` 包含 `pending`、`paid`、`failed`、`closed`、`refunded`、`expired`;过期补偿字段 `expired_at`、`expiration_checked_at`、`expiration_provider_state`、`expiration_last_error` 用于记录本地过期和微信查单结果。
|
||||
|
||||
### `profile_recharge_order_expiration_schedule`
|
||||
|
||||
- Rust 结构体:`ProfileRechargeOrderExpirationSchedule`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/runtime/profile.rs`
|
||||
- 作用:普通微信充值订单的到期查单调度表。表内只保存待检查订单、计划检查时间和 worker 短租约;支付成功或本地关闭后删除对应行。
|
||||
- 作用:旧普通微信充值订单到期查单调度表,保留 schema 兼容但当前不再写入;当前过期路径改由原生 scheduled 表 `profile_recharge_order_expiration_timer` 触发。
|
||||
|
||||
### `profile_recharge_order_expiration_timer`
|
||||
|
||||
- Rust 结构体:`ProfileRechargeOrderExpirationTimer`
|
||||
- 源码:`server-rs/crates/spacetime-module/src/runtime/profile.rs`
|
||||
- 作用:充值订单原生 scheduled 表,`scheduled_at` 到点触发 `expire_profile_recharge_order_timer`;`order_id` 唯一,支付成功、本地关闭或 scheduled reducer 执行后删除对应行。
|
||||
|
||||
### `profile_redeem_code`
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ Windows 本地如果已在 `%LOCALAPPDATA%\Genarrative\ffmpeg\bin` 安装 FFmpeg
|
||||
|
||||
微信小程序虚拟支付使用 `WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_OFFER_ID`、`WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_APP_KEY`、`WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_SANDBOX_APP_KEY` 和 `WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_ENV` 配置。小程序充值统一走 `wechat_mp_virtual` / `wx.requestVirtualPayment`:泥点属于代币(`coin`),`buyQuantity` 按当前充值商品快照里的 `points_amount` 传;会员和后台新增道具类商品走 `short_series_goods`,`productId` 对应微信后台道具 ID。旧登录快照若缺 `session_key`,需要用户在小程序内重新登录后再支付;客户端成功回调不是最终到账,仍以后端通知或查询确认订单为准。详细口径见 `docs/【技术方案】微信虚拟支付接入-2026-05-26.md`。
|
||||
|
||||
普通微信充值订单本地有效期为 5 分钟。SpacetimeDB 原生 `profile_recharge_order_expiration_timer` 到点后只把仍为 `pending` 的订单改为 `expired`;HTTP `api-server` 通过订阅 `profile_recharge_order` 的 `Pending -> Expired` 更新执行微信查单补偿。`external-generation-worker` 和 `external-generation-controller` 不运行充值过期逻辑,也不应因为扩容外部生成 worker 放大微信查单或关单流量。查账时本地未支付终态保持 `expired`,不再改写为 `closed`;`expiration_checked_at`、`expiration_provider_state`、`expiration_last_error` 用于判断 HTTP 监听器是否已经完成补偿。
|
||||
|
||||
微信小程序订阅消息生成结果通知使用 `WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_ENABLED`、`WECHAT_MINIPROGRAM_GENERATION_RESULT_TEMPLATE_ID` 和 `WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_STATE` 配置。当前模板为 `AI创作生成结果通知`;H5 在生成动作发起前先进入生成进度态并立即继续生成动作,同时非阻塞跳转到小程序原生订阅授权页尝试请求授权,用户接受、拒绝或返回都不能阻塞生成,且原生页不改写上一页 `webViewUrl`,避免返回后丢失 H5 当前进度页状态。后端只在玩法草稿生成成功或失败终态后用微信登录保存的 openid 调用 `subscribeMessage.send`,发送失败只打 warning,不影响生成主链路。模板 `thing1` 字段发送玩法模板名,例如 `拼图`、`敲木鱼`、`抓大鹅`;`number6` 字段发送本次生成结算后的实际泥点扣除,失败退款后固定为 `0`。模板 `time4` 字段固定发送北京时间 `YYYY-MM-DD HH:mm`,不要使用内部微秒时间戳、秒级时间戳或带时区后缀的 RFC3339 字符串,否则微信会返回 `argument invalid! data.time4.value invalid`。当前已接入拼图、敲木鱼、抓大鹅、跳一跳、方洞、视觉小说的草稿生成终态;分槽素材生成或发布动作不得直接复用生成结果通知,避免一次作品生成产生多条订阅消息。
|
||||
|
||||
如果本地 `GET /api/creation-entry/config` 返回 `No such procedure`,或 `api-server` 日志出现 `no such table: puzzle_gallery_card_view` / `no such table: wooden_fish_gallery_card_view` 这类公开 view 缺失,通常是 `.env.local` 指向的 SpacetimeDB 库还没有发布当前 `spacetime-module`,或当前 CLI 身份无权发布该库。debug 构建的 `api-server` 会临时使用后端默认入口配置兜底,避免创作作品架整块消失;正式修复仍应切换到拥有目标库权限的 SpacetimeDB 身份后重新运行 `npm run dev` 完成发布,或用 gitignored 的 `spacetime.local.json` 指向可发布的本地库。
|
||||
@@ -587,6 +589,9 @@ SELECT * FROM profile_wallet_ledger WHERE user_id = '<user_id>' ORDER BY created
|
||||
-- 充值订单
|
||||
SELECT * FROM profile_recharge_order ORDER BY created_at DESC LIMIT 50;
|
||||
|
||||
-- 未完成过期补偿的充值订单
|
||||
SELECT * FROM profile_recharge_order WHERE status = 'expired' AND expiration_checked_at IS NULL ORDER BY expired_at ASC;
|
||||
|
||||
-- 充值商品配置
|
||||
SELECT * FROM profile_recharge_product_config ORDER BY sort_order ASC;
|
||||
```
|
||||
|
||||
@@ -117,7 +117,8 @@ export type ProfileRechargeOrderStatus =
|
||||
| 'paid'
|
||||
| 'failed'
|
||||
| 'closed'
|
||||
| 'refunded';
|
||||
| 'refunded'
|
||||
| 'expired';
|
||||
|
||||
export type ProfileRechargeProduct = {
|
||||
productId: string;
|
||||
@@ -174,6 +175,10 @@ export type ProfileRechargeOrder = {
|
||||
createdAt: string;
|
||||
pointsDelta: number;
|
||||
membershipExpiresAt: string | null;
|
||||
expiredAt?: string | null;
|
||||
expirationCheckedAt?: string | null;
|
||||
expirationProviderState?: string | null;
|
||||
expirationLastError?: string | null;
|
||||
};
|
||||
|
||||
export type ProfileRechargeCenterResponse = {
|
||||
|
||||
@@ -2447,6 +2447,7 @@ fn normalize_admin_database_known_enum(
|
||||
2 => "failed",
|
||||
3 => "closed",
|
||||
4 => "refunded",
|
||||
5 => "expired",
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
|
||||
@@ -75,7 +75,7 @@ mod phone_auth;
|
||||
mod platform_errors;
|
||||
mod process_metrics;
|
||||
mod profile_identity;
|
||||
mod profile_recharge_expiration_worker;
|
||||
mod profile_recharge_expiration_listener;
|
||||
mod prompt;
|
||||
mod public_work;
|
||||
mod puzzle;
|
||||
@@ -132,7 +132,7 @@ use crate::{
|
||||
config::{AppConfig, ProcessRole},
|
||||
external_generation_worker::run_external_generation_worker,
|
||||
external_generation_worker_controller::run_external_generation_worker_controller,
|
||||
profile_recharge_expiration_worker::spawn_profile_recharge_expiration_worker,
|
||||
profile_recharge_expiration_listener::spawn_profile_recharge_expiration_listener,
|
||||
state::{AppState, AppStateInitError},
|
||||
tracking_outbox::TrackingOutbox,
|
||||
wallet_refund_outbox::WalletRefundOutbox,
|
||||
@@ -200,7 +200,7 @@ async fn run_worker_only(config: AppConfig) -> Result<(), io::Error> {
|
||||
"初始化 external generation worker 状态失败:{error}"
|
||||
))
|
||||
})?;
|
||||
spawn_app_state_background_workers(&state);
|
||||
spawn_common_app_state_background_workers(&state);
|
||||
info!(
|
||||
process_role = process_role.as_str(),
|
||||
"api-server 以非 HTTP 角色启动"
|
||||
@@ -245,7 +245,7 @@ async fn run_http_role(config: AppConfig) -> Result<(), io::Error> {
|
||||
let (router, shutdown_context, worker_state) = match restore_app_state_for_startup(config).await
|
||||
{
|
||||
Ok(state) => {
|
||||
spawn_app_state_background_workers(&state);
|
||||
spawn_http_app_state_background_workers(&state, process_role);
|
||||
let tracking_outbox = state.tracking_outbox();
|
||||
let wallet_refund_outbox = state.wallet_refund_outbox();
|
||||
let worker_state = process_role
|
||||
@@ -408,9 +408,8 @@ async fn finalize_shutdown(context: ShutdownContext) {
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_app_state_background_workers(state: &AppState) {
|
||||
fn spawn_common_app_state_background_workers(state: &AppState) {
|
||||
state.puzzle_gallery_cache().spawn_cleanup_task();
|
||||
spawn_profile_recharge_expiration_worker(state.clone());
|
||||
if let Some(outbox) = state.tracking_outbox() {
|
||||
outbox.spawn_worker();
|
||||
}
|
||||
@@ -419,6 +418,17 @@ fn spawn_app_state_background_workers(state: &AppState) {
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_http_app_state_background_workers(state: &AppState, process_role: ProcessRole) {
|
||||
spawn_common_app_state_background_workers(state);
|
||||
if should_start_profile_recharge_expiration_listener(process_role) {
|
||||
spawn_profile_recharge_expiration_listener(state.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn should_start_profile_recharge_expiration_listener(process_role: ProcessRole) -> bool {
|
||||
process_role.runs_http()
|
||||
}
|
||||
|
||||
fn build_tcp_listener(
|
||||
bind_address: SocketAddr,
|
||||
listen_backlog: i32,
|
||||
@@ -549,7 +559,8 @@ fn is_valid_env_key(key: &str) -> bool {
|
||||
mod tests {
|
||||
use super::{
|
||||
AUTH_STORE_STARTUP_RETRY_INTERVAL, is_valid_env_key, protected_env_keys_from,
|
||||
should_restore_auth_store_for_startup, strip_env_value,
|
||||
should_restore_auth_store_for_startup, should_start_profile_recharge_expiration_listener,
|
||||
strip_env_value,
|
||||
};
|
||||
use crate::config::ProcessRole;
|
||||
|
||||
@@ -609,4 +620,16 @@ mod tests {
|
||||
ProcessRole::ExternalGenerationController
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_recharge_expiration_listener_is_limited_to_http_roles() {
|
||||
assert!(should_start_profile_recharge_expiration_listener(ProcessRole::Api));
|
||||
assert!(should_start_profile_recharge_expiration_listener(ProcessRole::All));
|
||||
assert!(!should_start_profile_recharge_expiration_listener(
|
||||
ProcessRole::ExternalGenerationWorker
|
||||
));
|
||||
assert!(!should_start_profile_recharge_expiration_listener(
|
||||
ProcessRole::ExternalGenerationController
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
|
||||
use module_runtime::{
|
||||
PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_MINI_PROGRAM_VIRTUAL,
|
||||
RuntimeProfileRechargeOrderRecord, RuntimeProfileRechargeOrderStatus,
|
||||
};
|
||||
use platform_wechat::pay::{WechatPayError, WechatPayNotifyOrder};
|
||||
use shared_kernel::{offset_datetime_to_unix_micros, parse_rfc3339};
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use crate::{state::AppState, wechat::pay::current_unix_micros};
|
||||
|
||||
const PROFILE_RECHARGE_EXPIRATION_LISTENER_RECONNECT_DELAY: Duration = Duration::from_secs(5);
|
||||
const PROFILE_RECHARGE_EXPIRATION_CATCH_UP_LIMIT: u32 = 100;
|
||||
const PROFILE_RECHARGE_EXPIRATION_RETRY_DELAYS: [Duration; 3] = [
|
||||
Duration::from_secs(5),
|
||||
Duration::from_secs(15),
|
||||
Duration::from_secs(30),
|
||||
];
|
||||
|
||||
pub fn spawn_profile_recharge_expiration_listener(state: AppState) {
|
||||
tokio::spawn(async move {
|
||||
run_profile_recharge_expiration_listener(state).await;
|
||||
});
|
||||
}
|
||||
|
||||
async fn run_profile_recharge_expiration_listener(state: AppState) {
|
||||
loop {
|
||||
match state
|
||||
.spacetime_client()
|
||||
.subscribe_profile_recharge_order_expiration_events()
|
||||
.await
|
||||
{
|
||||
Ok(mut subscription) => {
|
||||
info!("profile recharge expiration listener connected");
|
||||
run_profile_recharge_expiration_catch_up(&state).await;
|
||||
|
||||
loop {
|
||||
match subscription.recv().await {
|
||||
Ok(order) => {
|
||||
let state = state.clone();
|
||||
tokio::spawn(async move {
|
||||
process_expired_profile_recharge_order_with_retries(state, order)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
Err(error) => {
|
||||
warn!(
|
||||
error = %error,
|
||||
"profile recharge expiration listener disconnected"
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
warn!(
|
||||
error = %error,
|
||||
"profile recharge expiration listener failed to subscribe"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
sleep(PROFILE_RECHARGE_EXPIRATION_LISTENER_RECONNECT_DELAY).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_profile_recharge_expiration_catch_up(state: &AppState) {
|
||||
let mut processed_count = 0usize;
|
||||
let mut seen_order_ids = HashSet::new();
|
||||
loop {
|
||||
let mut orders = match state
|
||||
.spacetime_client()
|
||||
.list_unchecked_expired_profile_recharge_orders(
|
||||
PROFILE_RECHARGE_EXPIRATION_CATCH_UP_LIMIT,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(orders) => orders,
|
||||
Err(error) => {
|
||||
warn!(
|
||||
error = %error,
|
||||
"profile recharge expiration catch-up failed to list unchecked orders"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if orders.is_empty() {
|
||||
if processed_count == 0 {
|
||||
debug!("profile recharge expiration catch-up found no unchecked orders");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let listed_len = orders.len();
|
||||
orders.retain(|order| seen_order_ids.insert(order.order_id.clone()));
|
||||
if orders.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let batch_len = orders.len();
|
||||
processed_count += batch_len;
|
||||
info!(
|
||||
order_count = batch_len,
|
||||
total_count = processed_count,
|
||||
"profile recharge expiration catch-up processing unchecked orders"
|
||||
);
|
||||
let mut handles = Vec::with_capacity(batch_len);
|
||||
for order in orders {
|
||||
let state = state.clone();
|
||||
handles.push(tokio::spawn(async move {
|
||||
process_expired_profile_recharge_order_with_retries(state, order).await;
|
||||
}));
|
||||
}
|
||||
for handle in handles {
|
||||
if let Err(error) = handle.await {
|
||||
warn!(
|
||||
error = %error,
|
||||
"profile recharge expiration catch-up task failed to join"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if listed_len < PROFILE_RECHARGE_EXPIRATION_CATCH_UP_LIMIT as usize {
|
||||
return;
|
||||
}
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_expired_profile_recharge_order_with_retries(
|
||||
state: AppState,
|
||||
order: RuntimeProfileRechargeOrderRecord,
|
||||
) {
|
||||
let order_id = order.order_id.clone();
|
||||
for (attempt_index, delay) in std::iter::once(Duration::ZERO)
|
||||
.chain(PROFILE_RECHARGE_EXPIRATION_RETRY_DELAYS)
|
||||
.enumerate()
|
||||
{
|
||||
if !delay.is_zero() {
|
||||
sleep(delay).await;
|
||||
}
|
||||
|
||||
match process_expired_profile_recharge_order_once(&state, &order).await {
|
||||
Ok(()) => return,
|
||||
Err(error) => {
|
||||
warn!(
|
||||
order_id = order_id.as_str(),
|
||||
attempt = attempt_index + 1,
|
||||
error = %error,
|
||||
"profile recharge expiration compensation attempt failed"
|
||||
);
|
||||
record_profile_recharge_expiration_error(&state, &order_id, error.to_string())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_expired_profile_recharge_order_once(
|
||||
state: &AppState,
|
||||
order: &RuntimeProfileRechargeOrderRecord,
|
||||
) -> Result<(), ExpirationCompensationError> {
|
||||
if order.status != RuntimeProfileRechargeOrderStatus::Expired {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if order.payment_channel == PROFILE_RECHARGE_PAYMENT_CHANNEL_WECHAT_MINI_PROGRAM_VIRTUAL {
|
||||
mark_profile_recharge_expiration_checked(
|
||||
state,
|
||||
&order.order_id,
|
||||
Some(current_unix_micros()),
|
||||
Some("VIRTUAL_UNQUERYABLE".to_string()),
|
||||
Some("wechat_mp_virtual has no v3 transaction query".to_string()),
|
||||
)
|
||||
.await?;
|
||||
state.publish_profile_recharge_order_update(order.order_id.clone());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let wechat_order = match state
|
||||
.wechat_pay_client()
|
||||
.query_order_by_out_trade_no(&order.order_id)
|
||||
.await
|
||||
{
|
||||
Ok(order) => order,
|
||||
Err(WechatPayError::OrderNotExist(message)) => {
|
||||
mark_profile_recharge_expiration_checked(
|
||||
state,
|
||||
&order.order_id,
|
||||
Some(current_unix_micros()),
|
||||
Some("ORDER_NOT_EXIST".to_string()),
|
||||
Some(message),
|
||||
)
|
||||
.await?;
|
||||
state.publish_profile_recharge_order_update(order.order_id.clone());
|
||||
return Ok(());
|
||||
}
|
||||
Err(error) => return Err(error.into()),
|
||||
};
|
||||
|
||||
if wechat_order.out_trade_no != order.order_id {
|
||||
return Err(ExpirationCompensationError::Runtime(format!(
|
||||
"wechat query returned mismatched out_trade_no: {}",
|
||||
wechat_order.out_trade_no
|
||||
)));
|
||||
}
|
||||
|
||||
match wechat_order.trade_state.as_str() {
|
||||
"SUCCESS" => mark_expired_profile_recharge_order_paid(state, order, wechat_order).await,
|
||||
"NOTPAY" => {
|
||||
state
|
||||
.wechat_pay_client()
|
||||
.close_order_by_out_trade_no(&order.order_id)
|
||||
.await?;
|
||||
mark_profile_recharge_expiration_checked(
|
||||
state,
|
||||
&order.order_id,
|
||||
Some(current_unix_micros()),
|
||||
Some("NOTPAY".to_string()),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
state.publish_profile_recharge_order_update(order.order_id.clone());
|
||||
Ok(())
|
||||
}
|
||||
"CLOSED" | "REVOKED" | "PAYERROR" => {
|
||||
mark_profile_recharge_expiration_checked(
|
||||
state,
|
||||
&order.order_id,
|
||||
Some(current_unix_micros()),
|
||||
Some(wechat_order.trade_state),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
state.publish_profile_recharge_order_update(order.order_id.clone());
|
||||
Ok(())
|
||||
}
|
||||
trade_state => Err(ExpirationCompensationError::Runtime(format!(
|
||||
"wechat trade_state is not final for expiration compensation: {trade_state}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn mark_expired_profile_recharge_order_paid(
|
||||
state: &AppState,
|
||||
order: &RuntimeProfileRechargeOrderRecord,
|
||||
wechat_order: WechatPayNotifyOrder,
|
||||
) -> Result<(), ExpirationCompensationError> {
|
||||
let paid_at_micros = wechat_order
|
||||
.success_time
|
||||
.as_deref()
|
||||
.and_then(|value| parse_rfc3339(value).ok())
|
||||
.map(offset_datetime_to_unix_micros)
|
||||
.unwrap_or_else(current_unix_micros);
|
||||
state
|
||||
.spacetime_client()
|
||||
.mark_profile_recharge_order_paid(
|
||||
order.order_id.clone(),
|
||||
paid_at_micros,
|
||||
wechat_order.transaction_id,
|
||||
)
|
||||
.await?;
|
||||
state.publish_profile_recharge_order_update(order.order_id.clone());
|
||||
info!(
|
||||
order_id = order.order_id.as_str(),
|
||||
"expired profile recharge order compensated as paid"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn mark_profile_recharge_expiration_checked(
|
||||
state: &AppState,
|
||||
order_id: &str,
|
||||
checked_at_micros: Option<i64>,
|
||||
provider_state: Option<String>,
|
||||
last_error: Option<String>,
|
||||
) -> Result<(), ExpirationCompensationError> {
|
||||
state
|
||||
.spacetime_client()
|
||||
.mark_profile_recharge_order_expiration_checked(
|
||||
order_id.to_string(),
|
||||
checked_at_micros,
|
||||
provider_state,
|
||||
last_error,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn record_profile_recharge_expiration_error(
|
||||
state: &AppState,
|
||||
order_id: &str,
|
||||
error: String,
|
||||
) {
|
||||
if let Err(mark_error) = mark_profile_recharge_expiration_checked(
|
||||
state,
|
||||
order_id,
|
||||
None,
|
||||
None,
|
||||
Some(error),
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!(
|
||||
order_id,
|
||||
error = %mark_error,
|
||||
"failed to record profile recharge expiration compensation error"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ExpirationCompensationError {
|
||||
WechatPay(WechatPayError),
|
||||
Spacetime(spacetime_client::SpacetimeClientError),
|
||||
Runtime(String),
|
||||
}
|
||||
|
||||
impl From<WechatPayError> for ExpirationCompensationError {
|
||||
fn from(error: WechatPayError) -> Self {
|
||||
Self::WechatPay(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<spacetime_client::SpacetimeClientError> for ExpirationCompensationError {
|
||||
fn from(error: spacetime_client::SpacetimeClientError) -> Self {
|
||||
Self::Spacetime(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ExpirationCompensationError {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::WechatPay(error) => write!(formatter, "wechat pay error: {error}"),
|
||||
Self::Spacetime(error) => write!(formatter, "spacetime error: {error}"),
|
||||
Self::Runtime(message) => formatter.write_str(message),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ExpirationCompensationError {}
|
||||
@@ -1,207 +0,0 @@
|
||||
use std::{process, time::Duration};
|
||||
|
||||
use module_runtime::RuntimeProfileRechargeOrderExpirationScheduleSnapshot;
|
||||
use platform_wechat::pay::WechatPayError;
|
||||
use shared_kernel::offset_datetime_to_unix_micros;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::time::MissedTickBehavior;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
const PROFILE_RECHARGE_EXPIRATION_WORKER_INTERVAL: Duration = Duration::from_secs(15);
|
||||
const PROFILE_RECHARGE_EXPIRATION_WORKER_LEASE: Duration = Duration::from_secs(90);
|
||||
const PROFILE_RECHARGE_EXPIRATION_WORKER_CLAIM_LIMIT: u32 = 20;
|
||||
|
||||
pub fn spawn_profile_recharge_expiration_worker(state: AppState) {
|
||||
tokio::spawn(async move {
|
||||
run_profile_recharge_expiration_worker(state).await;
|
||||
});
|
||||
}
|
||||
|
||||
async fn run_profile_recharge_expiration_worker(state: AppState) {
|
||||
let worker_id = format!("api:{}:profile-recharge-expiration", process::id());
|
||||
let mut interval = tokio::time::interval(PROFILE_RECHARGE_EXPIRATION_WORKER_INTERVAL);
|
||||
interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
let now_micros = current_unix_micros();
|
||||
let lease_expires_at_micros = now_micros
|
||||
+ i64::try_from(PROFILE_RECHARGE_EXPIRATION_WORKER_LEASE.as_micros())
|
||||
.unwrap_or(90_000_000);
|
||||
let schedules = match state
|
||||
.spacetime_client()
|
||||
.claim_profile_recharge_order_expiration_schedules(
|
||||
worker_id.clone(),
|
||||
now_micros,
|
||||
lease_expires_at_micros,
|
||||
PROFILE_RECHARGE_EXPIRATION_WORKER_CLAIM_LIMIT,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(schedules) => schedules,
|
||||
Err(error) => {
|
||||
warn!("充值订单过期检查 claim 失败:{error}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if schedules.is_empty() {
|
||||
debug!("充值订单过期检查暂无到期任务");
|
||||
continue;
|
||||
}
|
||||
|
||||
for schedule in schedules {
|
||||
process_profile_recharge_expiration_schedule(&state, schedule).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn process_profile_recharge_expiration_schedule(
|
||||
state: &AppState,
|
||||
schedule: RuntimeProfileRechargeOrderExpirationScheduleSnapshot,
|
||||
) {
|
||||
let order_id = schedule.order_id.clone();
|
||||
let wechat_order = match state
|
||||
.wechat_pay_client()
|
||||
.query_order_by_out_trade_no(&order_id)
|
||||
.await
|
||||
{
|
||||
Ok(order) => order,
|
||||
Err(WechatPayError::OrderNotExist(message)) => {
|
||||
warn!(
|
||||
order_id = order_id.as_str(),
|
||||
error = message.as_str(),
|
||||
"过期前微信查单确认订单不存在,关闭本地 pending 订单"
|
||||
);
|
||||
close_order_not_found_profile_recharge_order(state, &order_id).await;
|
||||
return;
|
||||
}
|
||||
Err(error) => {
|
||||
warn!(
|
||||
order_id = order_id.as_str(),
|
||||
"过期前微信查单失败,将等待租约过期后重试:{error}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if wechat_order.out_trade_no != order_id {
|
||||
warn!(
|
||||
order_id = order_id.as_str(),
|
||||
provider_order_id = wechat_order.out_trade_no.as_str(),
|
||||
"过期前微信查单返回的商户订单号不匹配,将等待租约过期后重试"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
match wechat_order.trade_state.as_str() {
|
||||
"SUCCESS" => {
|
||||
let paid_at_micros = wechat_order
|
||||
.success_time
|
||||
.as_deref()
|
||||
.and_then(|value| shared_kernel::parse_rfc3339(value).ok())
|
||||
.map(offset_datetime_to_unix_micros)
|
||||
.unwrap_or_else(current_unix_micros);
|
||||
match state
|
||||
.spacetime_client()
|
||||
.mark_profile_recharge_order_paid(
|
||||
order_id.clone(),
|
||||
paid_at_micros,
|
||||
wechat_order.transaction_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
complete_profile_recharge_expiration_schedule(state, &order_id).await;
|
||||
state.publish_profile_recharge_order_update(order_id.clone());
|
||||
info!(
|
||||
order_id = order_id.as_str(),
|
||||
"过期检查发现微信已支付,已补确认入账"
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
warn!(
|
||||
order_id = order_id.as_str(),
|
||||
"过期检查确认已支付订单失败:{error}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"NOTPAY" | "CLOSED" | "REVOKED" | "PAYERROR" => {
|
||||
match state
|
||||
.spacetime_client()
|
||||
.close_profile_recharge_order(order_id.clone(), current_unix_micros())
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
complete_profile_recharge_expiration_schedule(state, &order_id).await;
|
||||
state.publish_profile_recharge_order_update(order_id.clone());
|
||||
info!(
|
||||
order_id = order_id.as_str(),
|
||||
trade_state = wechat_order.trade_state.as_str(),
|
||||
"充值订单到期且微信确认未支付,已关闭本地订单"
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
warn!(
|
||||
order_id = order_id.as_str(),
|
||||
"关闭过期充值订单失败:{error}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
"USERPAYING" => {
|
||||
info!(
|
||||
order_id = order_id.as_str(),
|
||||
"微信订单仍在支付中,将等待租约过期后重试"
|
||||
);
|
||||
}
|
||||
trade_state => {
|
||||
warn!(
|
||||
order_id = order_id.as_str(),
|
||||
trade_state, "微信订单状态暂不适合本地关闭,将等待租约过期后重试"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn close_order_not_found_profile_recharge_order(state: &AppState, order_id: &str) {
|
||||
match state
|
||||
.spacetime_client()
|
||||
.close_profile_recharge_order(order_id.to_string(), current_unix_micros())
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
complete_profile_recharge_expiration_schedule(state, order_id).await;
|
||||
state.publish_profile_recharge_order_update(order_id.to_string());
|
||||
info!(
|
||||
order_id,
|
||||
reason = "ORDER_NOT_EXIST",
|
||||
"过期检查发现微信订单不存在,已关闭本地充值订单"
|
||||
);
|
||||
}
|
||||
Err(error) => {
|
||||
warn!(
|
||||
order_id,
|
||||
reason = "ORDER_NOT_EXIST",
|
||||
"关闭微信不存在的过期充值订单失败:{error}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn complete_profile_recharge_expiration_schedule(state: &AppState, order_id: &str) {
|
||||
if let Err(error) = state
|
||||
.spacetime_client()
|
||||
.complete_profile_recharge_order_expiration_schedule(order_id.to_string())
|
||||
.await
|
||||
{
|
||||
warn!(order_id, "删除充值订单过期检查任务失败:{error}");
|
||||
}
|
||||
}
|
||||
|
||||
fn current_unix_micros() -> i64 {
|
||||
offset_datetime_to_unix_micros(OffsetDateTime::now_utc())
|
||||
}
|
||||
@@ -26,7 +26,7 @@ use module_runtime::{
|
||||
RuntimeProfileTaskItemRecord, RuntimeProfileTaskStatus, RuntimeProfileWalletLedgerSourceType,
|
||||
RuntimeReferralInviteCenterRecord, RuntimeTrackingScopeKind,
|
||||
};
|
||||
use platform_wechat::pay::WechatPayNotifyOrder;
|
||||
use platform_wechat::pay::{WechatPayError, WechatPayNotifyOrder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use sha2::Sha256;
|
||||
@@ -368,7 +368,10 @@ pub async fn confirm_wechat_profile_recharge_order(
|
||||
build_wechat_profile_recharge_order_confirmation(center, order),
|
||||
));
|
||||
}
|
||||
if order.status != RuntimeProfileRechargeOrderStatus::Pending {
|
||||
if !matches!(
|
||||
order.status,
|
||||
RuntimeProfileRechargeOrderStatus::Pending | RuntimeProfileRechargeOrderStatus::Expired
|
||||
) {
|
||||
return Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
build_wechat_profile_recharge_order_confirmation(center, order),
|
||||
@@ -381,13 +384,26 @@ pub async fn confirm_wechat_profile_recharge_order(
|
||||
));
|
||||
}
|
||||
|
||||
let wechat_order = state
|
||||
let order_was_expired = order.status == RuntimeProfileRechargeOrderStatus::Expired;
|
||||
let wechat_order = match state
|
||||
.wechat_pay_client()
|
||||
.query_order_by_out_trade_no(&order.order_id)
|
||||
.await
|
||||
.map_err(|error| {
|
||||
runtime_profile_error_response(&request_context, map_wechat_pay_error(error))
|
||||
})?;
|
||||
{
|
||||
Ok(wechat_order) => wechat_order,
|
||||
Err(WechatPayError::OrderNotExist(_)) if order_was_expired => {
|
||||
return Ok(json_success_body(
|
||||
Some(&request_context),
|
||||
build_wechat_profile_recharge_order_confirmation(center, order),
|
||||
));
|
||||
}
|
||||
Err(error) => {
|
||||
return Err(runtime_profile_error_response(
|
||||
&request_context,
|
||||
map_wechat_pay_error(error),
|
||||
));
|
||||
}
|
||||
};
|
||||
if wechat_order.out_trade_no != order.order_id {
|
||||
return Err(runtime_profile_error_response(
|
||||
&request_context,
|
||||
@@ -1252,6 +1268,7 @@ fn build_profile_recharge_order_status(status: RuntimeProfileRechargeOrderStatus
|
||||
RuntimeProfileRechargeOrderStatus::Failed => "failed",
|
||||
RuntimeProfileRechargeOrderStatus::Closed => "closed",
|
||||
RuntimeProfileRechargeOrderStatus::Refunded => "refunded",
|
||||
RuntimeProfileRechargeOrderStatus::Expired => "expired",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
@@ -1691,6 +1708,10 @@ fn build_profile_recharge_order_response(
|
||||
created_at: record.created_at,
|
||||
points_delta: record.points_delta,
|
||||
membership_expires_at: record.membership_expires_at,
|
||||
expired_at: record.expired_at,
|
||||
expiration_checked_at: record.expiration_checked_at,
|
||||
expiration_provider_state: record.expiration_provider_state,
|
||||
expiration_last_error: record.expiration_last_error,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2895,6 +2916,12 @@ mod tests {
|
||||
points_delta: 0,
|
||||
membership_expires_at: None,
|
||||
membership_expires_at_micros: None,
|
||||
expired_at: None,
|
||||
expired_at_micros: None,
|
||||
expiration_checked_at: None,
|
||||
expiration_checked_at_micros: None,
|
||||
expiration_provider_state: None,
|
||||
expiration_last_error: None,
|
||||
};
|
||||
|
||||
let center = RuntimeProfileRechargeCenterRecord {
|
||||
@@ -3004,6 +3031,12 @@ mod tests {
|
||||
points_delta: 0,
|
||||
membership_expires_at: None,
|
||||
membership_expires_at_micros: None,
|
||||
expired_at: None,
|
||||
expired_at_micros: None,
|
||||
expiration_checked_at: None,
|
||||
expiration_checked_at_micros: None,
|
||||
expiration_provider_state: None,
|
||||
expiration_last_error: None,
|
||||
};
|
||||
|
||||
let center = RuntimeProfileRechargeCenterRecord {
|
||||
@@ -3111,6 +3144,12 @@ mod tests {
|
||||
points_delta: 0,
|
||||
membership_expires_at: None,
|
||||
membership_expires_at_micros: None,
|
||||
expired_at: None,
|
||||
expired_at_micros: None,
|
||||
expiration_checked_at: None,
|
||||
expiration_checked_at_micros: None,
|
||||
expiration_provider_state: None,
|
||||
expiration_last_error: None,
|
||||
};
|
||||
|
||||
let center = RuntimeProfileRechargeCenterRecord {
|
||||
@@ -3231,6 +3270,12 @@ mod tests {
|
||||
points_delta: 0,
|
||||
membership_expires_at: None,
|
||||
membership_expires_at_micros: None,
|
||||
expired_at: None,
|
||||
expired_at_micros: None,
|
||||
expiration_checked_at: None,
|
||||
expiration_checked_at_micros: None,
|
||||
expiration_provider_state: None,
|
||||
expiration_last_error: None,
|
||||
};
|
||||
|
||||
let center = RuntimeProfileRechargeCenterRecord {
|
||||
|
||||
@@ -1159,6 +1159,12 @@ pub fn build_runtime_profile_recharge_order_record(
|
||||
points_delta: snapshot.points_delta,
|
||||
membership_expires_at: snapshot.membership_expires_at_micros.map(format_utc_micros),
|
||||
membership_expires_at_micros: snapshot.membership_expires_at_micros,
|
||||
expired_at: snapshot.expired_at_micros.map(format_utc_micros),
|
||||
expired_at_micros: snapshot.expired_at_micros,
|
||||
expiration_checked_at: snapshot.expiration_checked_at_micros.map(format_utc_micros),
|
||||
expiration_checked_at_micros: snapshot.expiration_checked_at_micros,
|
||||
expiration_provider_state: snapshot.expiration_provider_state,
|
||||
expiration_last_error: snapshot.expiration_last_error,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -449,6 +449,28 @@ pub fn build_runtime_profile_recharge_order_expiration_complete_input(
|
||||
Ok(RuntimeProfileRechargeOrderExpirationCompleteInput { order_id })
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_recharge_order_expiration_check_list_input(
|
||||
limit: u32,
|
||||
) -> Result<RuntimeProfileRechargeOrderExpirationCheckListInput, RuntimeProfileFieldError> {
|
||||
Ok(RuntimeProfileRechargeOrderExpirationCheckListInput { limit })
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_recharge_order_expiration_check_input(
|
||||
order_id: String,
|
||||
checked_at_micros: Option<i64>,
|
||||
provider_state: Option<String>,
|
||||
last_error: Option<String>,
|
||||
) -> Result<RuntimeProfileRechargeOrderExpirationCheckInput, RuntimeProfileFieldError> {
|
||||
let order_id =
|
||||
normalize_required_string(order_id).ok_or(RuntimeProfileFieldError::MissingOrderId)?;
|
||||
Ok(RuntimeProfileRechargeOrderExpirationCheckInput {
|
||||
order_id,
|
||||
checked_at_micros,
|
||||
provider_state: provider_state.and_then(normalize_required_string),
|
||||
last_error: last_error.and_then(normalize_required_string),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_runtime_profile_feedback_submission_input(
|
||||
user_id: String,
|
||||
description: String,
|
||||
|
||||
@@ -1184,6 +1184,7 @@ pub enum RuntimeProfileRechargeOrderStatus {
|
||||
Failed,
|
||||
Closed,
|
||||
Refunded,
|
||||
Expired,
|
||||
}
|
||||
|
||||
impl RuntimeProfileRechargeOrderStatus {
|
||||
@@ -1194,6 +1195,7 @@ impl RuntimeProfileRechargeOrderStatus {
|
||||
Self::Failed => "failed",
|
||||
Self::Closed => "closed",
|
||||
Self::Refunded => "refunded",
|
||||
Self::Expired => "expired",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1288,6 +1290,10 @@ pub struct RuntimeProfileRechargeOrderSnapshot {
|
||||
pub created_at_micros: i64,
|
||||
pub points_delta: i64,
|
||||
pub membership_expires_at_micros: Option<i64>,
|
||||
pub expired_at_micros: Option<i64>,
|
||||
pub expiration_checked_at_micros: Option<i64>,
|
||||
pub expiration_provider_state: Option<String>,
|
||||
pub expiration_last_error: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
@@ -1415,6 +1421,21 @@ pub struct RuntimeProfileRechargeOrderExpirationCompleteInput {
|
||||
pub order_id: String,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeOrderExpirationCheckListInput {
|
||||
pub limit: u32,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeOrderExpirationCheckInput {
|
||||
pub order_id: String,
|
||||
pub checked_at_micros: Option<i64>,
|
||||
pub provider_state: Option<String>,
|
||||
pub last_error: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeOrderExpirationScheduleSnapshot {
|
||||
@@ -1442,6 +1463,22 @@ pub struct RuntimeProfileRechargeOrderExpirationCompleteProcedureResult {
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeOrderExpirationCheckListProcedureResult {
|
||||
pub ok: bool,
|
||||
pub entries: Vec<RuntimeProfileRechargeOrderSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileRechargeOrderExpirationCheckProcedureResult {
|
||||
pub ok: bool,
|
||||
pub record: Option<RuntimeProfileRechargeOrderSnapshot>,
|
||||
pub error_message: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "spacetime-types", derive(SpacetimeType))]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RuntimeProfileWalletLedgerEntrySnapshot {
|
||||
@@ -1931,6 +1968,12 @@ pub struct RuntimeProfileRechargeOrderRecord {
|
||||
pub points_delta: i64,
|
||||
pub membership_expires_at: Option<String>,
|
||||
pub membership_expires_at_micros: Option<i64>,
|
||||
pub expired_at: Option<String>,
|
||||
pub expired_at_micros: Option<i64>,
|
||||
pub expiration_checked_at: Option<String>,
|
||||
pub expiration_checked_at_micros: Option<i64>,
|
||||
pub expiration_provider_state: Option<String>,
|
||||
pub expiration_last_error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
||||
@@ -47,7 +47,7 @@ const WECHAT_PAY_CLIENT_IP_MAX_CHARS: usize = 45;
|
||||
const WECHAT_PAY_JSAPI_PATH: &str = "/v3/pay/transactions/jsapi";
|
||||
const WECHAT_PAY_H5_PATH: &str = "/v3/pay/transactions/h5";
|
||||
const WECHAT_PAY_NATIVE_PATH: &str = "/v3/pay/transactions/native";
|
||||
const WECHAT_NATIVE_PAY_EXPIRE_SECONDS: i64 = 5 * 60;
|
||||
const WECHAT_PAY_ORDER_EXPIRE_SECONDS: i64 = 5 * 60;
|
||||
const WECHAT_MINIPROGRAM_MESSAGE_ENCODING_AES_KEY_BYTES: usize = 43;
|
||||
const WECHAT_MINIPROGRAM_MESSAGE_AES_KEY_BYTES: usize = 32;
|
||||
const WECHAT_MINIPROGRAM_MESSAGE_RANDOM_BYTES: usize = 16;
|
||||
@@ -149,6 +149,7 @@ struct WechatJsapiOrderRequest<'a> {
|
||||
mchid: &'a str,
|
||||
description: &'a str,
|
||||
out_trade_no: &'a str,
|
||||
time_expire: &'a str,
|
||||
notify_url: &'a str,
|
||||
amount: WechatJsapiAmount,
|
||||
payer: WechatJsapiPayer<'a>,
|
||||
@@ -171,6 +172,7 @@ struct WechatH5OrderRequest<'a> {
|
||||
mchid: &'a str,
|
||||
description: &'a str,
|
||||
out_trade_no: &'a str,
|
||||
time_expire: &'a str,
|
||||
notify_url: &'a str,
|
||||
amount: WechatJsapiAmount,
|
||||
scene_info: WechatH5SceneInfo<'a>,
|
||||
@@ -205,6 +207,11 @@ struct WechatNativeSceneInfo<'a> {
|
||||
payer_client_ip: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct WechatCloseOrderRequest<'a> {
|
||||
mchid: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct WechatJsapiOrderResponse {
|
||||
prepay_id: Option<String>,
|
||||
@@ -452,6 +459,17 @@ impl WechatPayClient {
|
||||
Self::Real(client) => client.query_order_by_out_trade_no(order_id).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn close_order_by_out_trade_no(&self, order_id: &str) -> Result<(), WechatPayError> {
|
||||
match self {
|
||||
Self::Disabled => Err(WechatPayError::Disabled),
|
||||
Self::Mock => {
|
||||
normalize_out_trade_no(order_id)?;
|
||||
Ok(())
|
||||
}
|
||||
Self::Real(client) => client.close_order_by_out_trade_no(order_id).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RealWechatPayClient {
|
||||
@@ -462,11 +480,13 @@ impl RealWechatPayClient {
|
||||
validate_jsapi_order_request(self, &request)?;
|
||||
let amount_total = i64::try_from(request.amount_cents)
|
||||
.map_err(|_| WechatPayError::InvalidRequest("微信支付金额超出 i64 范围".to_string()))?;
|
||||
let (_, expires_at_text) = build_wechat_pay_expire_time("wechat pay JSAPI time_expire")?;
|
||||
let body = serde_json::to_string(&WechatJsapiOrderRequest {
|
||||
appid: &self.app_id,
|
||||
mchid: &self.mch_id,
|
||||
description: &request.description,
|
||||
out_trade_no: &request.order_id,
|
||||
time_expire: &expires_at_text,
|
||||
notify_url: &self.notify_url,
|
||||
amount: WechatJsapiAmount {
|
||||
total: amount_total,
|
||||
@@ -527,11 +547,13 @@ impl RealWechatPayClient {
|
||||
validate_web_order_request(self, &request)?;
|
||||
let amount_total = i64::try_from(request.amount_cents)
|
||||
.map_err(|_| WechatPayError::InvalidRequest("微信支付金额超出 i64 范围".to_string()))?;
|
||||
let (_, expires_at_text) = build_wechat_pay_expire_time("wechat pay H5 time_expire")?;
|
||||
let body = serde_json::to_string(&WechatH5OrderRequest {
|
||||
appid: &self.app_id,
|
||||
mchid: &self.mch_id,
|
||||
description: &request.description,
|
||||
out_trade_no: &request.order_id,
|
||||
time_expire: &expires_at_text,
|
||||
notify_url: &self.notify_url,
|
||||
amount: WechatJsapiAmount {
|
||||
total: amount_total,
|
||||
@@ -580,10 +602,7 @@ impl RealWechatPayClient {
|
||||
validate_web_order_request(self, &request)?;
|
||||
let amount_total = i64::try_from(request.amount_cents)
|
||||
.map_err(|_| WechatPayError::InvalidRequest("微信支付金额超出 i64 范围".to_string()))?;
|
||||
let expires_at =
|
||||
OffsetDateTime::now_utc() + TimeDuration::seconds(WECHAT_NATIVE_PAY_EXPIRE_SECONDS);
|
||||
let expires_at_text =
|
||||
format_wechat_pay_rfc3339_seconds(expires_at, "微信支付 Native 过期时间")?;
|
||||
let (_, expires_at_text) = build_wechat_pay_expire_time("wechat pay Native time_expire")?;
|
||||
let body = serde_json::to_string(&WechatNativeOrderRequest {
|
||||
appid: &self.app_id,
|
||||
mchid: &self.mch_id,
|
||||
@@ -805,6 +824,26 @@ impl RealWechatPayClient {
|
||||
})
|
||||
}
|
||||
|
||||
async fn close_order_by_out_trade_no(&self, order_id: &str) -> Result<(), WechatPayError> {
|
||||
let order_id = normalize_out_trade_no(order_id)?;
|
||||
let encoded_order_id = urlencoding::encode(&order_id);
|
||||
let path = format!("/v3/pay/transactions/out-trade-no/{encoded_order_id}/close");
|
||||
let request_url = format!(
|
||||
"{}/{encoded_order_id}/close",
|
||||
self.query_order_endpoint_base.trim_end_matches('/')
|
||||
);
|
||||
let body = serde_json::to_string(&WechatCloseOrderRequest {
|
||||
mchid: &self.mch_id,
|
||||
})
|
||||
.map_err(|error| {
|
||||
WechatPayError::Deserialize(format!("wechat pay close request serialize failed: {error}"))
|
||||
})?;
|
||||
|
||||
self.post_wechat_json(&request_url, &path, body, "wechat pay close request failed")
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
fn verify_notify_signature(
|
||||
&self,
|
||||
headers: &HeaderMap,
|
||||
@@ -905,6 +944,15 @@ fn build_mock_h5_payment(order_id: &str) -> WechatH5PaymentResponse {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_wechat_pay_expire_time(
|
||||
context: &str,
|
||||
) -> Result<(OffsetDateTime, String), WechatPayError> {
|
||||
let expires_at =
|
||||
OffsetDateTime::now_utc() + TimeDuration::seconds(WECHAT_PAY_ORDER_EXPIRE_SECONDS);
|
||||
let expires_at_text = format_wechat_pay_rfc3339_seconds(expires_at, context)?;
|
||||
Ok((expires_at, expires_at_text))
|
||||
}
|
||||
|
||||
fn format_wechat_pay_rfc3339_seconds(
|
||||
value: OffsetDateTime,
|
||||
context: &str,
|
||||
@@ -919,7 +967,7 @@ fn format_wechat_pay_rfc3339_seconds(
|
||||
|
||||
fn build_mock_native_payment(order_id: &str) -> WechatNativePaymentResponse {
|
||||
let expires_at =
|
||||
OffsetDateTime::now_utc() + TimeDuration::seconds(WECHAT_NATIVE_PAY_EXPIRE_SECONDS);
|
||||
OffsetDateTime::now_utc() + TimeDuration::seconds(WECHAT_PAY_ORDER_EXPIRE_SECONDS);
|
||||
WechatNativePaymentResponse {
|
||||
code_url: format!(
|
||||
"weixin://pay.weixin.qq.com/bizpayurl/up?pr=mock-{}",
|
||||
@@ -1635,6 +1683,7 @@ mod tests {
|
||||
mchid: "1900000001",
|
||||
description: "陶泥儿 - 60泥点",
|
||||
out_trade_no: "rcgtest001",
|
||||
time_expire: "2026-05-15T10:05:00Z",
|
||||
notify_url: "https://api.example.com/api/profile/recharge/wechat/notify",
|
||||
amount: WechatJsapiAmount {
|
||||
total: 600,
|
||||
@@ -1647,6 +1696,7 @@ mod tests {
|
||||
.expect("JSAPI order request should serialize");
|
||||
|
||||
assert_eq!(body["out_trade_no"], "rcgtest001");
|
||||
assert_eq!(body["time_expire"], "2026-05-15T10:05:00Z");
|
||||
assert_eq!(
|
||||
body["notify_url"],
|
||||
"https://api.example.com/api/profile/recharge/wechat/notify"
|
||||
@@ -1662,6 +1712,7 @@ mod tests {
|
||||
mchid: "1900000001",
|
||||
description: "陶泥儿 - 60泥点",
|
||||
out_trade_no: "rcgtest001",
|
||||
time_expire: "2026-05-15T10:05:00Z",
|
||||
notify_url: "https://api.example.com/api/profile/recharge/wechat/notify",
|
||||
amount: WechatJsapiAmount {
|
||||
total: 600,
|
||||
@@ -1676,6 +1727,7 @@ mod tests {
|
||||
|
||||
assert_eq!(body["scene_info"]["payer_client_ip"], "203.0.113.10");
|
||||
assert_eq!(body["scene_info"]["h5_info"]["type"], "Wap");
|
||||
assert_eq!(body["time_expire"], "2026-05-15T10:05:00Z");
|
||||
assert_eq!(body["amount"]["currency"], "CNY");
|
||||
assert!(body.get("sceneInfo").is_none());
|
||||
assert!(body["scene_info"].get("payerClientIp").is_none());
|
||||
|
||||
@@ -254,6 +254,10 @@ pub struct ProfileRechargeOrderResponse {
|
||||
pub created_at: String,
|
||||
pub points_delta: i64,
|
||||
pub membership_expires_at: Option<String>,
|
||||
pub expired_at: Option<String>,
|
||||
pub expiration_checked_at: Option<String>,
|
||||
pub expiration_provider_state: Option<String>,
|
||||
pub expiration_last_error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
@@ -1517,6 +1521,10 @@ mod tests {
|
||||
created_at: "2026-05-15T10:00:00Z".to_string(),
|
||||
points_delta: 0,
|
||||
membership_expires_at: None,
|
||||
expired_at: None,
|
||||
expiration_checked_at: None,
|
||||
expiration_provider_state: None,
|
||||
expiration_last_error: None,
|
||||
};
|
||||
let center = ProfileRechargeCenterResponse {
|
||||
wallet_balance: 0,
|
||||
@@ -1581,6 +1589,10 @@ mod tests {
|
||||
created_at: "2026-05-15T10:00:00Z".to_string(),
|
||||
points_delta: 0,
|
||||
membership_expires_at: Some("2026-06-15T10:00:00Z".to_string()),
|
||||
expired_at: None,
|
||||
expiration_checked_at: None,
|
||||
expiration_provider_state: None,
|
||||
expiration_last_error: None,
|
||||
};
|
||||
let center = ProfileRechargeCenterResponse {
|
||||
wallet_balance: 0,
|
||||
|
||||
@@ -144,6 +144,8 @@ pub mod editor_project;
|
||||
pub mod external_api_key;
|
||||
pub mod external_generation;
|
||||
pub use external_generation::ExternalGenerationQueueWakeSubscription;
|
||||
pub mod profile_recharge_expiration;
|
||||
pub use profile_recharge_expiration::ProfileRechargeExpirationSubscription;
|
||||
|
||||
pub mod inventory;
|
||||
pub mod jump_hop;
|
||||
|
||||
@@ -291,9 +291,12 @@ pub(crate) use self::runtime_profile::{
|
||||
map_runtime_profile_invite_code_admin_procedure_result,
|
||||
map_runtime_profile_play_stats_procedure_result,
|
||||
map_runtime_profile_recharge_center_procedure_result,
|
||||
map_runtime_profile_recharge_order_expiration_check_list_procedure_result,
|
||||
map_runtime_profile_recharge_order_expiration_check_procedure_result,
|
||||
map_runtime_profile_recharge_order_expiration_claim_procedure_result,
|
||||
map_runtime_profile_recharge_order_expiration_complete_procedure_result,
|
||||
map_runtime_profile_recharge_order_procedure_result,
|
||||
map_runtime_profile_recharge_order_table_row,
|
||||
map_runtime_profile_recharge_product_admin_list_procedure_result,
|
||||
map_runtime_profile_recharge_product_admin_procedure_result,
|
||||
map_runtime_profile_redeem_code_admin_list_procedure_result,
|
||||
|
||||
@@ -244,6 +244,27 @@ impl From<module_runtime::RuntimeProfileRechargeOrderExpirationCompleteInput>
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileRechargeOrderExpirationCheckListInput>
|
||||
for RuntimeProfileRechargeOrderExpirationCheckListInput
|
||||
{
|
||||
fn from(input: module_runtime::RuntimeProfileRechargeOrderExpirationCheckListInput) -> Self {
|
||||
Self { limit: input.limit }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileRechargeOrderExpirationCheckInput>
|
||||
for RuntimeProfileRechargeOrderExpirationCheckInput
|
||||
{
|
||||
fn from(input: module_runtime::RuntimeProfileRechargeOrderExpirationCheckInput) -> Self {
|
||||
Self {
|
||||
order_id: input.order_id,
|
||||
checked_at_micros: input.checked_at_micros,
|
||||
provider_state: input.provider_state,
|
||||
last_error: input.last_error,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<module_runtime::RuntimeProfileRechargeProductAdminListInput>
|
||||
for RuntimeProfileRechargeProductAdminListInput
|
||||
{
|
||||
@@ -518,6 +539,36 @@ pub(crate) fn map_runtime_profile_recharge_order_expiration_complete_procedure_r
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_recharge_order_expiration_check_list_procedure_result(
|
||||
result: RuntimeProfileRechargeOrderExpirationCheckListProcedureResult,
|
||||
) -> Result<Vec<RuntimeProfileRechargeOrderRecord>, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
|
||||
Ok(result
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(map_runtime_profile_recharge_order_snapshot)
|
||||
.map(module_runtime::build_runtime_profile_recharge_order_record)
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_recharge_order_expiration_check_procedure_result(
|
||||
result: RuntimeProfileRechargeOrderExpirationCheckProcedureResult,
|
||||
) -> Result<RuntimeProfileRechargeOrderRecord, SpacetimeClientError> {
|
||||
if !result.ok {
|
||||
return Err(SpacetimeClientError::procedure_failed(result.error_message));
|
||||
}
|
||||
let order = result
|
||||
.record
|
||||
.ok_or_else(|| SpacetimeClientError::missing_snapshot("profile recharge order snapshot"))?;
|
||||
|
||||
Ok(module_runtime::build_runtime_profile_recharge_order_record(
|
||||
map_runtime_profile_recharge_order_snapshot(order),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_feedback_submission_procedure_result(
|
||||
result: RuntimeProfileFeedbackSubmissionProcedureResult,
|
||||
) -> Result<RuntimeProfileFeedbackSubmissionRecord, SpacetimeClientError> {
|
||||
@@ -1046,9 +1097,47 @@ pub(crate) fn map_runtime_profile_recharge_order_snapshot(
|
||||
created_at_micros: snapshot.created_at_micros,
|
||||
points_delta: snapshot.points_delta,
|
||||
membership_expires_at_micros: snapshot.membership_expires_at_micros,
|
||||
expired_at_micros: snapshot.expired_at_micros,
|
||||
expiration_checked_at_micros: snapshot.expiration_checked_at_micros,
|
||||
expiration_provider_state: snapshot.expiration_provider_state,
|
||||
expiration_last_error: snapshot.expiration_last_error,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_recharge_order_table_row(
|
||||
row: ProfileRechargeOrder,
|
||||
) -> RuntimeProfileRechargeOrderRecord {
|
||||
module_runtime::build_runtime_profile_recharge_order_record(
|
||||
module_runtime::RuntimeProfileRechargeOrderSnapshot {
|
||||
order_id: row.order_id,
|
||||
user_id: row.user_id,
|
||||
product_id: row.product_id,
|
||||
product_title: row.product_title,
|
||||
kind: map_runtime_profile_recharge_product_kind_back(row.kind),
|
||||
amount_cents: row.amount_cents,
|
||||
status: map_runtime_profile_recharge_order_status_back(row.status),
|
||||
payment_channel: row.payment_channel,
|
||||
paid_at_micros: row
|
||||
.paid_at
|
||||
.map(|value| value.to_micros_since_unix_epoch()),
|
||||
provider_transaction_id: row.provider_transaction_id,
|
||||
created_at_micros: row.created_at.to_micros_since_unix_epoch(),
|
||||
points_delta: row.points_delta,
|
||||
membership_expires_at_micros: row
|
||||
.membership_expires_at
|
||||
.map(|value| value.to_micros_since_unix_epoch()),
|
||||
expired_at_micros: row
|
||||
.expired_at
|
||||
.map(|value| value.to_micros_since_unix_epoch()),
|
||||
expiration_checked_at_micros: row
|
||||
.expiration_checked_at
|
||||
.map(|value| value.to_micros_since_unix_epoch()),
|
||||
expiration_provider_state: row.expiration_provider_state,
|
||||
expiration_last_error: row.expiration_last_error,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn map_runtime_profile_recharge_order_expiration_schedule_snapshot(
|
||||
snapshot: RuntimeProfileRechargeOrderExpirationScheduleSnapshot,
|
||||
) -> module_runtime::RuntimeProfileRechargeOrderExpirationScheduleSnapshot {
|
||||
@@ -1479,6 +1568,9 @@ pub(crate) fn map_runtime_profile_recharge_order_status_back(
|
||||
crate::module_bindings::RuntimeProfileRechargeOrderStatus::Refunded => {
|
||||
module_runtime::RuntimeProfileRechargeOrderStatus::Refunded
|
||||
}
|
||||
crate::module_bindings::RuntimeProfileRechargeOrderStatus::Expired => {
|
||||
module_runtime::RuntimeProfileRechargeOrderStatus::Expired
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -448,6 +448,7 @@ pub mod enqueue_external_generation_job_and_return_procedure;
|
||||
pub mod ensure_analytics_date_dimension_for_date_reducer;
|
||||
pub mod equip_inventory_item_input_type;
|
||||
pub mod execute_custom_world_agent_action_procedure;
|
||||
pub mod expire_profile_recharge_order_timer_reducer;
|
||||
pub mod export_auth_store_projection_from_tables_procedure;
|
||||
pub mod export_database_migration_to_file_procedure;
|
||||
pub mod external_api_key_authenticate_input_type;
|
||||
@@ -636,10 +637,12 @@ pub mod list_puzzle_clear_works_procedure;
|
||||
pub mod list_puzzle_gallery_procedure;
|
||||
pub mod list_puzzle_works_procedure;
|
||||
pub mod list_square_hole_works_procedure;
|
||||
pub mod list_unchecked_expired_profile_recharge_orders_procedure;
|
||||
pub mod list_visual_novel_runtime_history_procedure;
|
||||
pub mod list_visual_novel_works_procedure;
|
||||
pub mod list_wooden_fish_works_procedure;
|
||||
pub mod mark_editor_showcase_asset_refunded_and_return_procedure;
|
||||
pub mod mark_profile_recharge_order_expiration_checked_procedure;
|
||||
pub mod mark_profile_recharge_order_paid_and_return_procedure;
|
||||
pub mod mark_puzzle_clear_level_time_up_procedure;
|
||||
pub mod mark_puzzle_draft_generation_failed_procedure;
|
||||
@@ -719,6 +722,8 @@ pub mod profile_played_world_table;
|
||||
pub mod profile_played_world_type;
|
||||
pub mod profile_recharge_order_expiration_schedule_table;
|
||||
pub mod profile_recharge_order_expiration_schedule_type;
|
||||
pub mod profile_recharge_order_expiration_timer_table;
|
||||
pub mod profile_recharge_order_expiration_timer_type;
|
||||
pub mod profile_recharge_order_table;
|
||||
pub mod profile_recharge_order_type;
|
||||
pub mod profile_recharge_product_config_table;
|
||||
@@ -1004,6 +1009,10 @@ pub mod runtime_profile_recharge_center_procedure_result_type;
|
||||
pub mod runtime_profile_recharge_center_snapshot_type;
|
||||
pub mod runtime_profile_recharge_order_close_input_type;
|
||||
pub mod runtime_profile_recharge_order_create_input_type;
|
||||
pub mod runtime_profile_recharge_order_expiration_check_input_type;
|
||||
pub mod runtime_profile_recharge_order_expiration_check_list_input_type;
|
||||
pub mod runtime_profile_recharge_order_expiration_check_list_procedure_result_type;
|
||||
pub mod runtime_profile_recharge_order_expiration_check_procedure_result_type;
|
||||
pub mod runtime_profile_recharge_order_expiration_claim_input_type;
|
||||
pub mod runtime_profile_recharge_order_expiration_claim_procedure_result_type;
|
||||
pub mod runtime_profile_recharge_order_expiration_complete_input_type;
|
||||
@@ -1745,6 +1754,7 @@ pub use enqueue_external_generation_job_and_return_procedure::enqueue_external_g
|
||||
pub use ensure_analytics_date_dimension_for_date_reducer::ensure_analytics_date_dimension_for_date;
|
||||
pub use equip_inventory_item_input_type::EquipInventoryItemInput;
|
||||
pub use execute_custom_world_agent_action_procedure::execute_custom_world_agent_action;
|
||||
pub use expire_profile_recharge_order_timer_reducer::expire_profile_recharge_order_timer;
|
||||
pub use export_auth_store_projection_from_tables_procedure::export_auth_store_projection_from_tables;
|
||||
pub use export_database_migration_to_file_procedure::export_database_migration_to_file;
|
||||
pub use external_api_key_authenticate_input_type::ExternalApiKeyAuthenticateInput;
|
||||
@@ -1933,10 +1943,12 @@ pub use list_puzzle_clear_works_procedure::list_puzzle_clear_works;
|
||||
pub use list_puzzle_gallery_procedure::list_puzzle_gallery;
|
||||
pub use list_puzzle_works_procedure::list_puzzle_works;
|
||||
pub use list_square_hole_works_procedure::list_square_hole_works;
|
||||
pub use list_unchecked_expired_profile_recharge_orders_procedure::list_unchecked_expired_profile_recharge_orders;
|
||||
pub use list_visual_novel_runtime_history_procedure::list_visual_novel_runtime_history;
|
||||
pub use list_visual_novel_works_procedure::list_visual_novel_works;
|
||||
pub use list_wooden_fish_works_procedure::list_wooden_fish_works;
|
||||
pub use mark_editor_showcase_asset_refunded_and_return_procedure::mark_editor_showcase_asset_refunded_and_return;
|
||||
pub use mark_profile_recharge_order_expiration_checked_procedure::mark_profile_recharge_order_expiration_checked;
|
||||
pub use mark_profile_recharge_order_paid_and_return_procedure::mark_profile_recharge_order_paid_and_return;
|
||||
pub use mark_puzzle_clear_level_time_up_procedure::mark_puzzle_clear_level_time_up;
|
||||
pub use mark_puzzle_draft_generation_failed_procedure::mark_puzzle_draft_generation_failed;
|
||||
@@ -2016,6 +2028,8 @@ pub use profile_played_world_table::*;
|
||||
pub use profile_played_world_type::ProfilePlayedWorld;
|
||||
pub use profile_recharge_order_expiration_schedule_table::*;
|
||||
pub use profile_recharge_order_expiration_schedule_type::ProfileRechargeOrderExpirationSchedule;
|
||||
pub use profile_recharge_order_expiration_timer_table::*;
|
||||
pub use profile_recharge_order_expiration_timer_type::ProfileRechargeOrderExpirationTimer;
|
||||
pub use profile_recharge_order_table::*;
|
||||
pub use profile_recharge_order_type::ProfileRechargeOrder;
|
||||
pub use profile_recharge_product_config_table::*;
|
||||
@@ -2301,6 +2315,10 @@ pub use runtime_profile_recharge_center_procedure_result_type::RuntimeProfileRec
|
||||
pub use runtime_profile_recharge_center_snapshot_type::RuntimeProfileRechargeCenterSnapshot;
|
||||
pub use runtime_profile_recharge_order_close_input_type::RuntimeProfileRechargeOrderCloseInput;
|
||||
pub use runtime_profile_recharge_order_create_input_type::RuntimeProfileRechargeOrderCreateInput;
|
||||
pub use runtime_profile_recharge_order_expiration_check_input_type::RuntimeProfileRechargeOrderExpirationCheckInput;
|
||||
pub use runtime_profile_recharge_order_expiration_check_list_input_type::RuntimeProfileRechargeOrderExpirationCheckListInput;
|
||||
pub use runtime_profile_recharge_order_expiration_check_list_procedure_result_type::RuntimeProfileRechargeOrderExpirationCheckListProcedureResult;
|
||||
pub use runtime_profile_recharge_order_expiration_check_procedure_result_type::RuntimeProfileRechargeOrderExpirationCheckProcedureResult;
|
||||
pub use runtime_profile_recharge_order_expiration_claim_input_type::RuntimeProfileRechargeOrderExpirationClaimInput;
|
||||
pub use runtime_profile_recharge_order_expiration_claim_procedure_result_type::RuntimeProfileRechargeOrderExpirationClaimProcedureResult;
|
||||
pub use runtime_profile_recharge_order_expiration_complete_input_type::RuntimeProfileRechargeOrderExpirationCompleteInput;
|
||||
@@ -2644,6 +2662,9 @@ pub enum Reducer {
|
||||
EnsureAnalyticsDateDimensionForDate {
|
||||
input: AnalyticsDateDimensionEnsureInput,
|
||||
},
|
||||
ExpireProfileRechargeOrderTimer {
|
||||
timer: ProfileRechargeOrderExpirationTimer,
|
||||
},
|
||||
GrantPlayerProgressionExperience {
|
||||
input: PlayerProgressionGrantInput,
|
||||
},
|
||||
@@ -2711,6 +2732,9 @@ impl __sdk::Reducer for Reducer {
|
||||
Reducer::EnsureAnalyticsDateDimensionForDate { .. } => {
|
||||
"ensure_analytics_date_dimension_for_date"
|
||||
}
|
||||
Reducer::ExpireProfileRechargeOrderTimer { .. } => {
|
||||
"expire_profile_recharge_order_timer"
|
||||
}
|
||||
Reducer::GrantPlayerProgressionExperience { .. } => {
|
||||
"grant_player_progression_experience"
|
||||
}
|
||||
@@ -2792,6 +2816,11 @@ impl __sdk::Reducer for Reducer {
|
||||
input,
|
||||
} => __sats::bsatn::to_vec(&ensure_analytics_date_dimension_for_date_reducer::EnsureAnalyticsDateDimensionForDateArgs {
|
||||
input: input.clone(),
|
||||
}),
|
||||
Reducer::ExpireProfileRechargeOrderTimer{
|
||||
timer,
|
||||
} => __sats::bsatn::to_vec(&expire_profile_recharge_order_timer_reducer::ExpireProfileRechargeOrderTimerArgs {
|
||||
timer: timer.clone(),
|
||||
}),
|
||||
Reducer::GrantPlayerProgressionExperience{
|
||||
input,
|
||||
@@ -2947,6 +2976,8 @@ pub struct DbUpdate {
|
||||
profile_recharge_order: __sdk::TableUpdate<ProfileRechargeOrder>,
|
||||
profile_recharge_order_expiration_schedule:
|
||||
__sdk::TableUpdate<ProfileRechargeOrderExpirationSchedule>,
|
||||
profile_recharge_order_expiration_timer:
|
||||
__sdk::TableUpdate<ProfileRechargeOrderExpirationTimer>,
|
||||
profile_recharge_product_config: __sdk::TableUpdate<ProfileRechargeProductConfig>,
|
||||
profile_redeem_code: __sdk::TableUpdate<ProfileRedeemCode>,
|
||||
profile_redeem_code_usage: __sdk::TableUpdate<ProfileRedeemCodeUsage>,
|
||||
@@ -3250,6 +3281,13 @@ impl TryFrom<__ws::v2::TransactionUpdate> for DbUpdate {
|
||||
)?,
|
||||
)
|
||||
}
|
||||
"profile_recharge_order_expiration_timer" => {
|
||||
db_update.profile_recharge_order_expiration_timer.append(
|
||||
profile_recharge_order_expiration_timer_table::parse_table_update(
|
||||
table_update,
|
||||
)?,
|
||||
)
|
||||
}
|
||||
"profile_recharge_product_config" => {
|
||||
db_update.profile_recharge_product_config.append(
|
||||
profile_recharge_product_config_table::parse_table_update(table_update)?,
|
||||
@@ -3821,6 +3859,12 @@ impl __sdk::DbUpdate for DbUpdate {
|
||||
&self.profile_recharge_order_expiration_schedule,
|
||||
)
|
||||
.with_updates_by_pk(|row| &row.order_id);
|
||||
diff.profile_recharge_order_expiration_timer = cache
|
||||
.apply_diff_to_table::<ProfileRechargeOrderExpirationTimer>(
|
||||
"profile_recharge_order_expiration_timer",
|
||||
&self.profile_recharge_order_expiration_timer,
|
||||
)
|
||||
.with_updates_by_pk(|row| &row.scheduled_id);
|
||||
diff.profile_recharge_product_config = cache
|
||||
.apply_diff_to_table::<ProfileRechargeProductConfig>(
|
||||
"profile_recharge_product_config",
|
||||
@@ -4360,6 +4404,9 @@ impl __sdk::DbUpdate for DbUpdate {
|
||||
"profile_recharge_order_expiration_schedule" => db_update
|
||||
.profile_recharge_order_expiration_schedule
|
||||
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"profile_recharge_order_expiration_timer" => db_update
|
||||
.profile_recharge_order_expiration_timer
|
||||
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
"profile_recharge_product_config" => db_update
|
||||
.profile_recharge_product_config
|
||||
.append(__sdk::parse_row_list_as_inserts(table_rows.rows)?),
|
||||
@@ -4772,6 +4819,9 @@ impl __sdk::DbUpdate for DbUpdate {
|
||||
"profile_recharge_order_expiration_schedule" => db_update
|
||||
.profile_recharge_order_expiration_schedule
|
||||
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"profile_recharge_order_expiration_timer" => db_update
|
||||
.profile_recharge_order_expiration_timer
|
||||
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
"profile_recharge_product_config" => db_update
|
||||
.profile_recharge_product_config
|
||||
.append(__sdk::parse_row_list_as_deletes(table_rows.rows)?),
|
||||
@@ -5041,6 +5091,8 @@ pub struct AppliedDiff<'r> {
|
||||
profile_recharge_order: __sdk::TableAppliedDiff<'r, ProfileRechargeOrder>,
|
||||
profile_recharge_order_expiration_schedule:
|
||||
__sdk::TableAppliedDiff<'r, ProfileRechargeOrderExpirationSchedule>,
|
||||
profile_recharge_order_expiration_timer:
|
||||
__sdk::TableAppliedDiff<'r, ProfileRechargeOrderExpirationTimer>,
|
||||
profile_recharge_product_config: __sdk::TableAppliedDiff<'r, ProfileRechargeProductConfig>,
|
||||
profile_redeem_code: __sdk::TableAppliedDiff<'r, ProfileRedeemCode>,
|
||||
profile_redeem_code_usage: __sdk::TableAppliedDiff<'r, ProfileRedeemCodeUsage>,
|
||||
@@ -5472,6 +5524,11 @@ impl<'r> __sdk::AppliedDiff<'r> for AppliedDiff<'r> {
|
||||
&self.profile_recharge_order_expiration_schedule,
|
||||
event,
|
||||
);
|
||||
callbacks.invoke_table_row_callbacks::<ProfileRechargeOrderExpirationTimer>(
|
||||
"profile_recharge_order_expiration_timer",
|
||||
&self.profile_recharge_order_expiration_timer,
|
||||
event,
|
||||
);
|
||||
callbacks.invoke_table_row_callbacks::<ProfileRechargeProductConfig>(
|
||||
"profile_recharge_product_config",
|
||||
&self.profile_recharge_product_config,
|
||||
@@ -6014,19 +6071,19 @@ impl __sdk::SubscriptionHandle for SubscriptionHandle {
|
||||
/// either a [`DbConnection`] or an [`EventContext`] and operate on either.
|
||||
pub trait RemoteDbContext:
|
||||
__sdk::DbContext<
|
||||
DbView = RemoteTables,
|
||||
Reducers = RemoteReducers,
|
||||
SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>,
|
||||
>
|
||||
DbView = RemoteTables,
|
||||
Reducers = RemoteReducers,
|
||||
SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>,
|
||||
>
|
||||
{
|
||||
}
|
||||
impl<
|
||||
Ctx: __sdk::DbContext<
|
||||
Ctx: __sdk::DbContext<
|
||||
DbView = RemoteTables,
|
||||
Reducers = RemoteReducers,
|
||||
SubscriptionBuilder = __sdk::SubscriptionBuilder<RemoteModule>,
|
||||
>,
|
||||
> RemoteDbContext for Ctx
|
||||
> RemoteDbContext for Ctx
|
||||
{
|
||||
}
|
||||
|
||||
@@ -6493,6 +6550,7 @@ impl __sdk::SpacetimeModule for RemoteModule {
|
||||
profile_played_world_table::register_table(client_cache);
|
||||
profile_recharge_order_table::register_table(client_cache);
|
||||
profile_recharge_order_expiration_schedule_table::register_table(client_cache);
|
||||
profile_recharge_order_expiration_timer_table::register_table(client_cache);
|
||||
profile_recharge_product_config_table::register_table(client_cache);
|
||||
profile_redeem_code_table::register_table(client_cache);
|
||||
profile_redeem_code_usage_table::register_table(client_cache);
|
||||
@@ -6628,6 +6686,7 @@ impl __sdk::SpacetimeModule for RemoteModule {
|
||||
"profile_played_world",
|
||||
"profile_recharge_order",
|
||||
"profile_recharge_order_expiration_schedule",
|
||||
"profile_recharge_order_expiration_timer",
|
||||
"profile_recharge_product_config",
|
||||
"profile_redeem_code",
|
||||
"profile_redeem_code_usage",
|
||||
|
||||
@@ -47,9 +47,11 @@ pub trait accept_quest {
|
||||
&self,
|
||||
input: QuestRecordInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
@@ -58,9 +60,11 @@ impl accept_quest for super::RemoteReducers {
|
||||
&self,
|
||||
input: QuestRecordInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(AcceptQuestArgs { input }, callback)
|
||||
|
||||
+8
-8
@@ -34,10 +34,10 @@ pub trait acknowledge_external_generation_jobs_and_return {
|
||||
input: ExternalGenerationJobAcknowledgeInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<ExternalGenerationJobProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
&super::ProcedureEventContext,
|
||||
Result<ExternalGenerationJobProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,10 +47,10 @@ impl acknowledge_external_generation_jobs_and_return for super::RemoteProcedures
|
||||
input: ExternalGenerationJobAcknowledgeInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<ExternalGenerationJobProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
&super::ProcedureEventContext,
|
||||
Result<ExternalGenerationJobProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, ExternalGenerationJobProcedureResult>(
|
||||
|
||||
+10
-6
@@ -47,9 +47,11 @@ pub trait acknowledge_quest_completion {
|
||||
&self,
|
||||
input: QuestCompletionAckInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()>;
|
||||
}
|
||||
|
||||
@@ -58,9 +60,11 @@ impl acknowledge_quest_completion for super::RemoteReducers {
|
||||
&self,
|
||||
input: QuestCompletionAckInput,
|
||||
|
||||
callback: impl FnOnce(&super::ReducerEventContext, Result<Result<(), String>, __sdk::InternalError>)
|
||||
+ Send
|
||||
+ 'static,
|
||||
callback: impl FnOnce(
|
||||
&super::ReducerEventContext,
|
||||
Result<Result<(), String>, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) -> __sdk::Result<()> {
|
||||
self.imp
|
||||
.invoke_reducer_with_callback(AcknowledgeQuestCompletionArgs { input }, callback)
|
||||
|
||||
+8
-8
@@ -31,10 +31,10 @@ pub trait admin_disable_profile_redeem_code {
|
||||
input: RuntimeProfileRedeemCodeAdminDisableInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileRedeemCodeAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileRedeemCodeAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,10 +44,10 @@ impl admin_disable_profile_redeem_code for super::RemoteProcedures {
|
||||
input: RuntimeProfileRedeemCodeAdminDisableInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileRedeemCodeAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileRedeemCodeAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileRedeemCodeAdminProcedureResult>(
|
||||
|
||||
+8
-8
@@ -31,10 +31,10 @@ pub trait admin_disable_profile_task_config {
|
||||
input: RuntimeProfileTaskConfigAdminDisableInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskConfigAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskConfigAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,10 +44,10 @@ impl admin_disable_profile_task_config for super::RemoteProcedures {
|
||||
input: RuntimeProfileTaskConfigAdminDisableInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskConfigAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileTaskConfigAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileTaskConfigAdminProcedureResult>(
|
||||
|
||||
+8
-8
@@ -31,10 +31,10 @@ pub trait admin_get_profile_wallet_config {
|
||||
input: RuntimeProfileWalletConfigAdminGetInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileWalletConfigAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileWalletConfigAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,10 +44,10 @@ impl admin_get_profile_wallet_config for super::RemoteProcedures {
|
||||
input: RuntimeProfileWalletConfigAdminGetInput,
|
||||
|
||||
__callback: impl FnOnce(
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileWalletConfigAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
&super::ProcedureEventContext,
|
||||
Result<RuntimeProfileWalletConfigAdminProcedureResult, __sdk::InternalError>,
|
||||
) + Send
|
||||
+ 'static,
|
||||
) {
|
||||
self.imp
|
||||
.invoke_procedure_with_callback::<_, RuntimeProfileWalletConfigAdminProcedureResult>(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user