Skip to content

Commit 64eb513

Browse files
feat(billing, team): add billing and team management RPC functionality (tinyhumansai#159)
* feat(billing, team): add billing and team management RPC functionality - Introduced billing module with methods for fetching current plans, purchasing plans, creating portal sessions, and topping up credits. - Added team management module with methods for listing team members, creating invites, listing invites, removing members, and changing member roles. - Updated core registry to include new billing and team controllers and schemas, enhancing the overall functionality of the application. - Implemented comprehensive tests for billing and team RPC methods to ensure reliability and correctness. These additions improve the application's capabilities in managing billing and team functionalities, providing a more robust user experience. * refactor(billing, team): improve code readability and structure * fix(billing, team): enhance error handling and response structure - Improved error handling in to provide clearer error messages when reading response bodies. - Updated validation in to ensure is a finite number greater than zero. - Refined output schemas for billing and team functions to include more descriptive fields, enhancing API clarity. - Introduced a new function to standardize URL path construction, improving code maintainability. - Added tests to verify the correctness of new output structures and API path building. These changes enhance the robustness and usability of the billing and team management functionalities. * feat(billing, team): add gateway normalization and route redaction functionality - Introduced function to standardize payment gateway inputs, ensuring only valid options (stripe, coinbase) are accepted, with defaults and error handling. - Enhanced function to utilize the new gateway normalization logic, improving input validation. - Added function to obscure sensitive identifiers in API route paths, enhancing security in logging. - Implemented unit tests for both and to ensure correctness and reliability of the new features. These changes improve the robustness of billing operations and enhance security in route handling.
1 parent ec6f954 commit 64eb513

10 files changed

Lines changed: 2170 additions & 4 deletions

File tree

‎src/core/all.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ fn build_registered_controllers() -> Vec<RegisteredController> {
6363
controllers.extend(crate::openhuman::workspace::all_workspace_registered_controllers());
6464
controllers.extend(crate::openhuman::tools::all_tools_registered_controllers());
6565
controllers.extend(crate::openhuman::memory::all_memory_registered_controllers());
66+
controllers.extend(crate::openhuman::billing::all_billing_registered_controllers());
67+
controllers.extend(crate::openhuman::team::all_team_registered_controllers());
6668
controllers
6769
}
6870

@@ -91,6 +93,8 @@ fn build_declared_controller_schemas() -> Vec<ControllerSchema> {
9193
schemas.extend(crate::openhuman::workspace::all_workspace_controller_schemas());
9294
schemas.extend(crate::openhuman::tools::all_tools_controller_schemas());
9395
schemas.extend(crate::openhuman::memory::all_memory_controller_schemas());
96+
schemas.extend(crate::openhuman::billing::all_billing_controller_schemas());
97+
schemas.extend(crate::openhuman::team::all_team_controller_schemas());
9498
schemas
9599
}
96100

@@ -125,6 +129,8 @@ pub fn namespace_description(namespace: &str) -> Option<&'static str> {
125129
"skills" => Some("Skill registry, runtime lifecycle, setup, tools, and sync."),
126130
"socket" => Some("Skills runtime socket bridge controls."),
127131
"memory" => Some("Document storage, vector search, key-value store, and knowledge graph."),
132+
"billing" => Some("Subscription plan, payment links, and credit top-up via the backend."),
133+
"team" => Some("Team member management, invites, and role changes via the backend."),
128134
_ => None,
129135
}
130136
}

‎src/core/jsonrpc.rs‎

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -732,5 +732,215 @@ mod tests {
732732
.any(|m| m.method == "openhuman.health_snapshot"),
733733
"schema dump should include migrated openhuman methods"
734734
);
735+
736+
assert!(
737+
methods
738+
.iter()
739+
.any(|m| m.method == "openhuman.billing_get_current_plan"),
740+
"schema dump should include billing methods"
741+
);
742+
743+
assert!(
744+
methods
745+
.iter()
746+
.any(|m| m.method == "openhuman.team_list_members"),
747+
"schema dump should include team methods"
748+
);
749+
}
750+
751+
#[tokio::test]
752+
async fn billing_get_current_plan_rejects_unknown_param() {
753+
let err = invoke_method(
754+
default_state(),
755+
"openhuman.billing_get_current_plan",
756+
json!({ "extra": true }),
757+
)
758+
.await
759+
.expect_err("unknown param should fail");
760+
assert!(err.contains("unknown param 'extra'"));
761+
}
762+
763+
#[tokio::test]
764+
async fn billing_purchase_plan_missing_plan_fails_validation() {
765+
let err = invoke_method(
766+
default_state(),
767+
"openhuman.billing_purchase_plan",
768+
json!({}),
769+
)
770+
.await
771+
.expect_err("missing plan should fail");
772+
assert!(err.contains("missing required param 'plan'"));
773+
}
774+
775+
#[tokio::test]
776+
async fn billing_top_up_missing_amount_fails_validation() {
777+
let err = invoke_method(default_state(), "openhuman.billing_top_up", json!({}))
778+
.await
779+
.expect_err("missing amountUsd should fail");
780+
assert!(err.contains("missing required param 'amountUsd'"));
781+
}
782+
783+
#[tokio::test]
784+
async fn billing_top_up_rejects_unknown_param() {
785+
let err = invoke_method(
786+
default_state(),
787+
"openhuman.billing_top_up",
788+
json!({ "amountUsd": 10.0, "unknownField": true }),
789+
)
790+
.await
791+
.expect_err("unknown param should fail");
792+
assert!(err.contains("unknown param 'unknownField'"));
793+
}
794+
795+
#[tokio::test]
796+
async fn billing_create_portal_session_rejects_unknown_param() {
797+
let err = invoke_method(
798+
default_state(),
799+
"openhuman.billing_create_portal_session",
800+
json!({ "x": 1 }),
801+
)
802+
.await
803+
.expect_err("unknown param should fail");
804+
assert!(err.contains("unknown param 'x'"));
805+
}
806+
807+
#[tokio::test]
808+
async fn team_list_members_missing_team_id_fails_validation() {
809+
let err = invoke_method(default_state(), "openhuman.team_list_members", json!({}))
810+
.await
811+
.expect_err("missing teamId should fail");
812+
assert!(err.contains("missing required param 'teamId'"));
813+
}
814+
815+
#[tokio::test]
816+
async fn team_list_members_rejects_unknown_param() {
817+
let err = invoke_method(
818+
default_state(),
819+
"openhuman.team_list_members",
820+
json!({ "teamId": "t1", "extra": true }),
821+
)
822+
.await
823+
.expect_err("unknown param should fail");
824+
assert!(err.contains("unknown param 'extra'"));
825+
}
826+
827+
#[tokio::test]
828+
async fn team_create_invite_missing_team_id_fails_validation() {
829+
let err = invoke_method(default_state(), "openhuman.team_create_invite", json!({}))
830+
.await
831+
.expect_err("missing teamId should fail");
832+
assert!(err.contains("missing required param 'teamId'"));
833+
}
834+
835+
#[tokio::test]
836+
async fn team_remove_member_missing_required_params_fails_validation() {
837+
let err = invoke_method(
838+
default_state(),
839+
"openhuman.team_remove_member",
840+
json!({ "teamId": "t1" }),
841+
)
842+
.await
843+
.expect_err("missing userId should fail");
844+
assert!(err.contains("missing required param 'userId'"));
845+
}
846+
847+
#[tokio::test]
848+
async fn team_change_member_role_missing_role_fails_validation() {
849+
let err = invoke_method(
850+
default_state(),
851+
"openhuman.team_change_member_role",
852+
json!({ "teamId": "t1", "userId": "u1" }),
853+
)
854+
.await
855+
.expect_err("missing role should fail");
856+
assert!(err.contains("missing required param 'role'"));
857+
}
858+
859+
#[tokio::test]
860+
async fn billing_create_coinbase_charge_missing_plan_fails_validation() {
861+
let err = invoke_method(
862+
default_state(),
863+
"openhuman.billing_create_coinbase_charge",
864+
json!({}),
865+
)
866+
.await
867+
.expect_err("missing plan should fail");
868+
assert!(err.contains("missing required param 'plan'"));
869+
}
870+
871+
#[tokio::test]
872+
async fn billing_create_coinbase_charge_rejects_unknown_param() {
873+
let err = invoke_method(
874+
default_state(),
875+
"openhuman.billing_create_coinbase_charge",
876+
json!({ "plan": "pro", "extra": true }),
877+
)
878+
.await
879+
.expect_err("unknown param should fail");
880+
assert!(err.contains("unknown param 'extra'"));
881+
}
882+
883+
#[tokio::test]
884+
async fn team_list_invites_missing_team_id_fails_validation() {
885+
let err = invoke_method(default_state(), "openhuman.team_list_invites", json!({}))
886+
.await
887+
.expect_err("missing teamId should fail");
888+
assert!(err.contains("missing required param 'teamId'"));
889+
}
890+
891+
#[tokio::test]
892+
async fn team_list_invites_rejects_unknown_param() {
893+
let err = invoke_method(
894+
default_state(),
895+
"openhuman.team_list_invites",
896+
json!({ "teamId": "t1", "extra": true }),
897+
)
898+
.await
899+
.expect_err("unknown param should fail");
900+
assert!(err.contains("unknown param 'extra'"));
901+
}
902+
903+
#[tokio::test]
904+
async fn team_revoke_invite_missing_team_id_fails_validation() {
905+
let err = invoke_method(default_state(), "openhuman.team_revoke_invite", json!({}))
906+
.await
907+
.expect_err("missing teamId should fail");
908+
assert!(err.contains("missing required param 'teamId'"));
909+
}
910+
911+
#[tokio::test]
912+
async fn team_revoke_invite_missing_invite_id_fails_validation() {
913+
let err = invoke_method(
914+
default_state(),
915+
"openhuman.team_revoke_invite",
916+
json!({ "teamId": "t1" }),
917+
)
918+
.await
919+
.expect_err("missing inviteId should fail");
920+
assert!(err.contains("missing required param 'inviteId'"));
921+
}
922+
923+
#[tokio::test]
924+
async fn schema_dump_includes_new_billing_and_team_methods() {
925+
let dump = build_http_schema_dump();
926+
let methods: Vec<&str> = dump.methods.iter().map(|m| m.method.as_str()).collect();
927+
for expected in &[
928+
"openhuman.billing_get_current_plan",
929+
"openhuman.billing_purchase_plan",
930+
"openhuman.billing_create_portal_session",
931+
"openhuman.billing_top_up",
932+
"openhuman.billing_create_coinbase_charge",
933+
"openhuman.team_list_members",
934+
"openhuman.team_create_invite",
935+
"openhuman.team_list_invites",
936+
"openhuman.team_revoke_invite",
937+
"openhuman.team_remove_member",
938+
"openhuman.team_change_member_role",
939+
] {
940+
assert!(
941+
methods.contains(expected),
942+
"schema dump missing expected method: {expected}"
943+
);
944+
}
735945
}
736946
}

‎src/openhuman/billing/mod.rs‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//! Billing and payment RPC adapters that thin-wrap the hosted API.
2+
//!
3+
//! Exposes plan lookup, purchase flows, and credit top-ups through the
4+
//! standard controller registry (`openhuman.billing_*`).
5+
6+
mod ops;
7+
mod schemas;
8+
9+
pub use ops::*;
10+
pub use schemas::{
11+
all_billing_controller_schemas, all_billing_registered_controllers, billing_schemas,
12+
};

0 commit comments

Comments
 (0)