implement payment dsl
This commit is contained in:
parent
7619e3b944
commit
525685cce4
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
target
|
||||
worker_rhai_temp_db
|
||||
dump.rdb
|
||||
dump.rdb
|
||||
.env
|
449
Cargo.lock
generated
449
Cargo.lock
generated
@ -157,9 +157,9 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
@ -172,7 +172,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tower 0.5.2",
|
||||
"tower-layer",
|
||||
@ -190,12 +190,12 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@ -213,9 +213,15 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
@ -450,6 +456,16 @@ dependencies = [
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@ -643,6 +659,12 @@ dependencies = [
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
@ -655,6 +677,15 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endian-type"
|
||||
version = "0.1.2"
|
||||
@ -719,6 +750,21 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
@ -1195,6 +1241,25 @@ dependencies = [
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.6.0"
|
||||
@ -1291,6 +1356,17 @@ dependencies = [
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 0.2.12",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
@ -1310,7 +1386,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http 1.3.1",
|
||||
"http-body",
|
||||
"http-body 1.0.1",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
@ -1338,6 +1414,30 @@ version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.6.0"
|
||||
@ -1348,7 +1448,7 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
@ -1357,6 +1457,19 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper 0.14.32",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.14"
|
||||
@ -1366,8 +1479,8 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http 1.3.1",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.6.0",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
@ -1543,6 +1656,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.16"
|
||||
@ -1730,6 +1849,23 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nibble_vec"
|
||||
version = "0.1.0"
|
||||
@ -1839,6 +1975,50 @@ version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ourdb"
|
||||
version = "0.1.0"
|
||||
@ -1875,7 +2055,7 @@ dependencies = [
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1965,6 +2145,12 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
@ -2261,6 +2447,46 @@ version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.32",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 0.1.2",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.21.0"
|
||||
@ -2309,6 +2535,7 @@ name = "rhai_client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"env_logger",
|
||||
"log",
|
||||
"redis",
|
||||
@ -2368,14 +2595,17 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"derive",
|
||||
"dotenv",
|
||||
"heromodels",
|
||||
"heromodels-derive",
|
||||
"heromodels_core",
|
||||
"macros",
|
||||
"reqwest",
|
||||
"rhai",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2451,6 +2681,15 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||
dependencies = [
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
@ -2506,12 +2745,44 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
@ -2725,6 +2996,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.2"
|
||||
@ -2742,6 +3019,27 @@ dependencies = [
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
@ -2882,6 +3180,16 @@ dependencies = [
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.17"
|
||||
@ -2943,7 +3251,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
@ -2960,7 +3268,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.3.1",
|
||||
"http-body",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"http-range-header",
|
||||
"httpdate",
|
||||
@ -3049,6 +3357,12 @@ dependencies = [
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tst"
|
||||
version = "0.1.0"
|
||||
@ -3147,6 +3461,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
@ -3169,6 +3489,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||
dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
@ -3378,13 +3707,22 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3393,7 +3731,22 @@ version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3402,28 +3755,46 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
@ -3436,24 +3807,48 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
@ -3469,6 +3864,16 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
|
@ -3,7 +3,13 @@ name = "rhai_client"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "client"
|
||||
path = "cmd/client.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
env_logger = "0.10"
|
||||
redis = { version = "0.25.0", features = ["tokio-comp"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
157
src/client/cmd/README.md
Normal file
157
src/client/cmd/README.md
Normal file
@ -0,0 +1,157 @@
|
||||
# Rhai Client Binary
|
||||
|
||||
A command-line client for executing Rhai scripts on remote workers via Redis.
|
||||
|
||||
## Binary: `client`
|
||||
|
||||
### Installation
|
||||
|
||||
Build the binary:
|
||||
```bash
|
||||
cargo build --bin client --release
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Basic usage - requires caller and circle keys
|
||||
client --caller-key <CALLER_KEY> --circle-key <CIRCLE_KEY>
|
||||
|
||||
# Execute inline script
|
||||
client -c <CALLER_KEY> -k <CIRCLE_KEY> --script "print('Hello World!')"
|
||||
|
||||
# Execute script from file
|
||||
client -c <CALLER_KEY> -k <CIRCLE_KEY> --file script.rhai
|
||||
|
||||
# Use specific worker (defaults to circle key)
|
||||
client -c <CALLER_KEY> -k <CIRCLE_KEY> -w <WORKER_KEY> --script "2 + 2"
|
||||
|
||||
# Custom Redis and timeout
|
||||
client -c <CALLER_KEY> -k <CIRCLE_KEY> --redis-url redis://localhost:6379/1 --timeout 60
|
||||
|
||||
# Remove timestamps from logs
|
||||
client -c <CALLER_KEY> -k <CIRCLE_KEY> --no-timestamp
|
||||
|
||||
# Increase verbosity
|
||||
client -c <CALLER_KEY> -k <CIRCLE_KEY> -v --script "debug_info()"
|
||||
```
|
||||
|
||||
### Command-Line Options
|
||||
|
||||
| Option | Short | Default | Description |
|
||||
|--------|-------|---------|-------------|
|
||||
| `--caller-key` | `-c` | **Required** | Caller public key (your identity) |
|
||||
| `--circle-key` | `-k` | **Required** | Circle public key (execution context) |
|
||||
| `--worker-key` | `-w` | `circle-key` | Worker public key (target worker) |
|
||||
| `--redis-url` | `-r` | `redis://localhost:6379` | Redis connection URL |
|
||||
| `--script` | `-s` | | Rhai script to execute |
|
||||
| `--file` | `-f` | | Path to Rhai script file |
|
||||
| `--timeout` | `-t` | `30` | Timeout for script execution (seconds) |
|
||||
| `--no-timestamp` | | `false` | Remove timestamps from log output |
|
||||
| `--verbose` | `-v` | | Increase verbosity (stackable) |
|
||||
|
||||
### Execution Modes
|
||||
|
||||
#### Inline Script Execution
|
||||
```bash
|
||||
# Execute a simple calculation
|
||||
client -c caller_123 -k circle_456 -s "let result = 2 + 2; print(result);"
|
||||
|
||||
# Execute with specific worker
|
||||
client -c caller_123 -k circle_456 -w worker_789 -s "get_user_data()"
|
||||
```
|
||||
|
||||
#### Script File Execution
|
||||
```bash
|
||||
# Execute script from file
|
||||
client -c caller_123 -k circle_456 -f examples/data_processing.rhai
|
||||
|
||||
# Execute with custom timeout
|
||||
client -c caller_123 -k circle_456 -f long_running_script.rhai -t 120
|
||||
```
|
||||
|
||||
#### Interactive Mode
|
||||
```bash
|
||||
# Enter interactive REPL mode (when no script or file provided)
|
||||
client -c caller_123 -k circle_456
|
||||
|
||||
# Interactive mode with verbose logging
|
||||
client -c caller_123 -k circle_456 -v --no-timestamp
|
||||
```
|
||||
|
||||
### Interactive Mode
|
||||
|
||||
When no script (`-s`) or file (`-f`) is provided, the client enters interactive mode:
|
||||
|
||||
```
|
||||
🔗 Starting Rhai Client
|
||||
📋 Configuration:
|
||||
Caller Key: caller_123
|
||||
Circle Key: circle_456
|
||||
Worker Key: circle_456
|
||||
Redis URL: redis://localhost:6379
|
||||
Timeout: 30s
|
||||
|
||||
✅ Connected to Redis at redis://localhost:6379
|
||||
🎮 Entering interactive mode
|
||||
Type Rhai scripts and press Enter to execute. Type 'exit' or 'quit' to close.
|
||||
rhai> let x = 42; print(x);
|
||||
Status: completed
|
||||
Output: 42
|
||||
rhai> exit
|
||||
👋 Goodbye!
|
||||
```
|
||||
|
||||
### Configuration Examples
|
||||
|
||||
#### Development Usage
|
||||
```bash
|
||||
# Simple development client
|
||||
client -c dev_user -k dev_circle
|
||||
|
||||
# Development with clean logs
|
||||
client -c dev_user -k dev_circle --no-timestamp -v
|
||||
```
|
||||
|
||||
#### Production Usage
|
||||
```bash
|
||||
# Production client with specific worker
|
||||
client \
|
||||
--caller-key prod_user_123 \
|
||||
--circle-key prod_circle_456 \
|
||||
--worker-key prod_worker_789 \
|
||||
--redis-url redis://redis-cluster:6379/0 \
|
||||
--timeout 300 \
|
||||
--file production_script.rhai
|
||||
```
|
||||
|
||||
#### Batch Processing
|
||||
```bash
|
||||
# Process multiple scripts
|
||||
for script in scripts/*.rhai; do
|
||||
client -c batch_user -k batch_circle -f "$script" --no-timestamp
|
||||
done
|
||||
```
|
||||
|
||||
### Key Concepts
|
||||
|
||||
- **Caller Key**: Your identity - used for authentication and tracking
|
||||
- **Circle Key**: Execution context - defines the environment/permissions
|
||||
- **Worker Key**: Target worker - which worker should execute the script (defaults to circle key)
|
||||
|
||||
### Error Handling
|
||||
|
||||
The client provides clear error messages for:
|
||||
- Missing required keys
|
||||
- Redis connection failures
|
||||
- Script execution timeouts
|
||||
- Worker unavailability
|
||||
- Script syntax errors
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `rhai_client`: Core client library for Redis-based script execution
|
||||
- `redis`: Redis client for task queue communication
|
||||
- `clap`: Command-line argument parsing
|
||||
- `env_logger`: Logging infrastructure
|
||||
- `tokio`: Async runtime
|
201
src/client/cmd/client.rs
Normal file
201
src/client/cmd/client.rs
Normal file
@ -0,0 +1,201 @@
|
||||
use clap::Parser;
|
||||
use rhai_client::{RhaiClient, RhaiClientBuilder};
|
||||
use log::{error, info};
|
||||
use std::io::{self, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about = "Rhai Client - Script execution client", long_about = None)]
|
||||
struct Args {
|
||||
/// Caller public key (caller ID)
|
||||
#[arg(short = 'c', long = "caller-key", help = "Caller public key (your identity)")]
|
||||
caller_public_key: String,
|
||||
|
||||
/// Circle public key (context ID)
|
||||
#[arg(short = 'k', long = "circle-key", help = "Circle public key (execution context)")]
|
||||
circle_public_key: String,
|
||||
|
||||
/// Worker public key (defaults to circle public key if not provided)
|
||||
#[arg(short = 'w', long = "worker-key", help = "Worker public key (defaults to circle key)")]
|
||||
worker_public_key: Option<String>,
|
||||
|
||||
/// Redis URL
|
||||
#[arg(short, long, default_value = "redis://localhost:6379", help = "Redis connection URL")]
|
||||
redis_url: String,
|
||||
|
||||
/// Rhai script to execute
|
||||
#[arg(short, long, help = "Rhai script to execute")]
|
||||
script: Option<String>,
|
||||
|
||||
/// Path to Rhai script file
|
||||
#[arg(short, long, help = "Path to Rhai script file")]
|
||||
file: Option<String>,
|
||||
|
||||
/// Timeout for script execution (in seconds)
|
||||
#[arg(short, long, default_value = "30", help = "Timeout for script execution in seconds")]
|
||||
timeout: u64,
|
||||
|
||||
/// Increase verbosity (can be used multiple times)
|
||||
#[arg(short, long, action = clap::ArgAction::Count, help = "Increase verbosity (-v for debug, -vv for trace)")]
|
||||
verbose: u8,
|
||||
|
||||
/// Disable timestamps in log output
|
||||
#[arg(long, help = "Remove timestamps from log output")]
|
||||
no_timestamp: bool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = Args::parse();
|
||||
|
||||
// Configure logging based on verbosity level
|
||||
let log_config = match args.verbose {
|
||||
0 => "warn,rhai_client=info",
|
||||
1 => "info,rhai_client=debug",
|
||||
2 => "debug",
|
||||
_ => "trace",
|
||||
};
|
||||
|
||||
std::env::set_var("RUST_LOG", log_config);
|
||||
|
||||
// Configure env_logger with or without timestamps
|
||||
if args.no_timestamp {
|
||||
env_logger::Builder::from_default_env()
|
||||
.format_timestamp(None)
|
||||
.init();
|
||||
} else {
|
||||
env_logger::init();
|
||||
}
|
||||
|
||||
// Use worker key or default to circle key
|
||||
let worker_key = args.worker_public_key.unwrap_or_else(|| args.circle_public_key.clone());
|
||||
|
||||
info!("🔗 Starting Rhai Client");
|
||||
info!("📋 Configuration:");
|
||||
info!(" Caller Key: {}", args.caller_public_key);
|
||||
info!(" Circle Key: {}", args.circle_public_key);
|
||||
info!(" Worker Key: {}", worker_key);
|
||||
info!(" Redis URL: {}", args.redis_url);
|
||||
info!(" Timeout: {}s", args.timeout);
|
||||
info!("");
|
||||
|
||||
// Create the Rhai client
|
||||
let client = RhaiClientBuilder::new()
|
||||
.caller_id(&args.caller_public_key)
|
||||
.redis_url(&args.redis_url)
|
||||
.build()?;
|
||||
|
||||
info!("✅ Connected to Redis at {}", args.redis_url);
|
||||
|
||||
// Determine execution mode
|
||||
if let Some(script_content) = args.script {
|
||||
// Execute inline script
|
||||
info!("📜 Executing inline script");
|
||||
execute_script(&client, &worker_key, script_content, args.timeout).await?;
|
||||
} else if let Some(file_path) = args.file {
|
||||
// Execute script from file
|
||||
info!("📁 Loading script from file: {}", file_path);
|
||||
let script_content = std::fs::read_to_string(&file_path)
|
||||
.map_err(|e| format!("Failed to read script file '{}': {}", file_path, e))?;
|
||||
execute_script(&client, &worker_key, script_content, args.timeout).await?;
|
||||
} else {
|
||||
// Interactive mode
|
||||
info!("🎮 Entering interactive mode");
|
||||
info!("Type Rhai scripts and press Enter to execute. Type 'exit' or 'quit' to close.");
|
||||
run_interactive_mode(&client, &worker_key, args.timeout).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn execute_script(
|
||||
client: &RhaiClient,
|
||||
worker_key: &str,
|
||||
script: String,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
info!("⚡ Executing script: {:.50}...", script);
|
||||
|
||||
let timeout = Duration::from_secs(timeout_secs);
|
||||
|
||||
match client
|
||||
.new_play_request()
|
||||
.recipient_id(worker_key)
|
||||
.script(&script)
|
||||
.timeout(timeout)
|
||||
.await_response()
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
info!("✅ Script execution completed");
|
||||
println!("Status: {}", result.status);
|
||||
if let Some(output) = result.output {
|
||||
println!("Output: {}", output);
|
||||
}
|
||||
if let Some(error) = result.error {
|
||||
println!("Error: {}", error);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("❌ Script execution failed: {}", e);
|
||||
return Err(Box::new(e));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_interactive_mode(
|
||||
client: &RhaiClient,
|
||||
worker_key: &str,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let timeout = Duration::from_secs(timeout_secs);
|
||||
|
||||
loop {
|
||||
print!("rhai> ");
|
||||
io::stdout().flush()?;
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input)?;
|
||||
|
||||
let input = input.trim();
|
||||
|
||||
if input.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if input == "exit" || input == "quit" {
|
||||
info!("👋 Goodbye!");
|
||||
break;
|
||||
}
|
||||
|
||||
info!("⚡ Executing: {}", input);
|
||||
|
||||
match client
|
||||
.new_play_request()
|
||||
.recipient_id(worker_key)
|
||||
.script(input)
|
||||
.timeout(timeout)
|
||||
.await_response()
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
println!("Status: {}", result.status);
|
||||
if let Some(output) = result.output {
|
||||
println!("Output: {}", output);
|
||||
}
|
||||
if let Some(error) = result.error {
|
||||
println!("Error: {}", error);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("❌ Execution failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
println!(); // Add blank line for readability
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -176,7 +176,7 @@ impl<'a> PlayRequestBuilder<'a> {
|
||||
self.request_id.clone()
|
||||
};
|
||||
// Build the request and submit using self.client
|
||||
println!("Awaiting response for request {} with timeout {:?}", self.request_id, self.timeout);
|
||||
info!("Awaiting response for request {} with timeout {:?}", self.request_id, self.timeout);
|
||||
let result = self.client.submit_play_request_and_await_result(
|
||||
&PlayRequest {
|
||||
id: request_id,
|
||||
|
@ -14,6 +14,9 @@ macros = { path = "../macros"}
|
||||
derive = { path = "../derive"}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
dotenv = "0.15"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
5
src/dsl/examples/payment/.env.example
Normal file
5
src/dsl/examples/payment/.env.example
Normal file
@ -0,0 +1,5 @@
|
||||
# Copy this file to .env and replace with your actual Stripe API keys
|
||||
# Get your keys from: https://dashboard.stripe.com/apikeys
|
||||
|
||||
# Stripe Secret Key (starts with sk_test_ for test mode or sk_live_ for live mode)
|
||||
STRIPE_SECRET_KEY=sk_test_your_stripe_secret_key_here
|
58
src/dsl/examples/payment/README.md
Normal file
58
src/dsl/examples/payment/README.md
Normal file
@ -0,0 +1,58 @@
|
||||
# Payment Example with Stripe Integration
|
||||
|
||||
This example demonstrates how to use the async HTTP API architecture to make real Stripe API calls from Rhai scripts.
|
||||
|
||||
## Setup
|
||||
|
||||
1. **Get Stripe API Keys**
|
||||
- Sign up at [Stripe Dashboard](https://dashboard.stripe.com)
|
||||
- Go to [API Keys](https://dashboard.stripe.com/apikeys)
|
||||
- Copy your **Secret key** (starts with `sk_test_` for test mode)
|
||||
|
||||
2. **Configure Environment**
|
||||
```bash
|
||||
# Copy the example file
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env and add your real Stripe secret key
|
||||
STRIPE_SECRET_KEY=sk_test_your_actual_key_here
|
||||
```
|
||||
|
||||
3. **Run the Example**
|
||||
```bash
|
||||
# From the rhailib root directory
|
||||
cd src/dsl && cargo run --example payment
|
||||
```
|
||||
|
||||
## What This Example Does
|
||||
|
||||
- **Loads environment variables** from `.env` file
|
||||
- **Configures async HTTP architecture** with real Stripe API credentials
|
||||
- **Creates Stripe objects** using the builder pattern:
|
||||
- Products
|
||||
- Prices (one-time and recurring)
|
||||
- Coupons (percentage and fixed amount)
|
||||
- Payment Intents
|
||||
- Subscriptions
|
||||
|
||||
## Architecture Features Demonstrated
|
||||
|
||||
- ✅ **Async HTTP calls** from synchronous Rhai scripts
|
||||
- ✅ **MPSC channel communication** between Rhai and async workers
|
||||
- ✅ **Environment variable loading** for secure API key management
|
||||
- ✅ **Error handling** with proper Stripe API error propagation
|
||||
- ✅ **Builder pattern** for creating complex Stripe objects
|
||||
- ✅ **Multi-threaded execution** with dedicated async worker threads
|
||||
|
||||
## Expected Output
|
||||
|
||||
With a valid Stripe API key, you'll see:
|
||||
```
|
||||
🔧 Configuring async HTTP client with timeouts...
|
||||
🚀 Async worker thread started
|
||||
🔄 Processing POST request to products
|
||||
📥 Stripe response: {"id":"prod_...","object":"product",...}
|
||||
✅ Product created successfully with ID: prod_...
|
||||
```
|
||||
|
||||
Without a valid key, you'll see the demo behavior with error handling.
|
46
src/dsl/examples/payment/main.rs
Normal file
46
src/dsl/examples/payment/main.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use rhailib_dsl::payment::register_payment_rhai_module;
|
||||
use rhai::{Engine, EvalAltResult, Scope};
|
||||
use std::fs;
|
||||
use std::env;
|
||||
|
||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
// Load environment variables from .env file
|
||||
dotenv::from_filename("examples/payment/.env").ok();
|
||||
|
||||
// Get Stripe API key from environment
|
||||
let stripe_secret_key = env::var("STRIPE_SECRET_KEY")
|
||||
.unwrap_or_else(|_| {
|
||||
println!("⚠️ STRIPE_SECRET_KEY not found in .env file, using demo key");
|
||||
println!(" Create examples/payment/.env with: STRIPE_SECRET_KEY=sk_test_your_key_here");
|
||||
"sk_test_demo_key_will_fail_gracefully".to_string()
|
||||
});
|
||||
|
||||
// Create a new Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register the payment module
|
||||
register_payment_rhai_module(&mut engine);
|
||||
|
||||
// Create a scope and set the Stripe API key variable
|
||||
let mut scope = Scope::new();
|
||||
scope.push("STRIPE_API_KEY", stripe_secret_key.clone());
|
||||
|
||||
println!("=== Rhai Payment Module Example ===");
|
||||
println!("🔑 Using Stripe API key: {}***", &stripe_secret_key[..15.min(stripe_secret_key.len())]);
|
||||
println!("Reading and executing payment.rhai script...\n");
|
||||
|
||||
// Read the Rhai script
|
||||
let script = fs::read_to_string("examples/payment/payment.rhai")
|
||||
.expect("Failed to read payment.rhai file");
|
||||
|
||||
// Execute the script with the scope
|
||||
match engine.eval_with_scope::<()>(&mut scope, &script) {
|
||||
Ok(_) => println!("\n✅ Payment script executed successfully!"),
|
||||
Err(e) => {
|
||||
eprintln!("❌ Error executing script: {}", e);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
176
src/dsl/examples/payment/payment.rhai
Normal file
176
src/dsl/examples/payment/payment.rhai
Normal file
@ -0,0 +1,176 @@
|
||||
// ===== Stripe Payment Integration Example =====
|
||||
// This script demonstrates the complete payment workflow using Stripe
|
||||
|
||||
print("🔧 Configuring Stripe...");
|
||||
// Configure Stripe with API key from environment variables
|
||||
// The STRIPE_API_KEY is loaded from .env file by main.rs
|
||||
let config_result = configure_stripe(STRIPE_API_KEY);
|
||||
print(`Configuration result: ${config_result}`);
|
||||
|
||||
print("\n📦 Creating a Product...");
|
||||
// Create a new product using builder pattern
|
||||
let product = new_product()
|
||||
.name("Premium Software License")
|
||||
.description("A comprehensive software solution for businesses")
|
||||
.metadata("category", "software")
|
||||
.metadata("tier", "premium");
|
||||
|
||||
print(`Product created: ${product.name}`);
|
||||
|
||||
// Create the product in Stripe
|
||||
print("🔄 Attempting to create product in Stripe...");
|
||||
try {
|
||||
let product_id = product.create();
|
||||
print(`✅ Product ID: ${product_id}`);
|
||||
} catch(error) {
|
||||
print(`❌ Failed to create product: ${error}`);
|
||||
print("This is expected with a demo API key. In production, use a valid Stripe secret key.");
|
||||
return; // Exit early since we can't continue without a valid product
|
||||
}
|
||||
|
||||
print("\n💰 Creating Prices...");
|
||||
|
||||
// Create upfront price (one-time payment)
|
||||
let upfront_price = new_price()
|
||||
.amount(19999) // $199.99 in cents
|
||||
.currency("usd")
|
||||
.product(product_id)
|
||||
.metadata("type", "upfront");
|
||||
|
||||
let upfront_price_id = upfront_price.create();
|
||||
print(`✅ Upfront Price ID: ${upfront_price_id}`);
|
||||
|
||||
// Create monthly subscription price
|
||||
let monthly_price = new_price()
|
||||
.amount(2999) // $29.99 in cents
|
||||
.currency("usd")
|
||||
.product(product_id)
|
||||
.recurring("month")
|
||||
.metadata("type", "monthly_subscription");
|
||||
|
||||
let monthly_price_id = monthly_price.create();
|
||||
print(`✅ Monthly Price ID: ${monthly_price_id}`);
|
||||
|
||||
// Create annual subscription price with discount
|
||||
let annual_price = new_price()
|
||||
.amount(29999) // $299.99 in cents (2 months free)
|
||||
.currency("usd")
|
||||
.product(product_id)
|
||||
.recurring("year")
|
||||
.metadata("type", "annual_subscription")
|
||||
.metadata("discount", "2_months_free");
|
||||
|
||||
let annual_price_id = annual_price.create();
|
||||
print(`✅ Annual Price ID: ${annual_price_id}`);
|
||||
|
||||
print("\n🎟️ Creating Discount Coupons...");
|
||||
|
||||
// Create a percentage-based coupon
|
||||
let percent_coupon = new_coupon()
|
||||
.duration("once")
|
||||
.percent_off(25)
|
||||
.metadata("campaign", "new_customer_discount")
|
||||
.metadata("code", "WELCOME25");
|
||||
|
||||
let percent_coupon_id = percent_coupon.create();
|
||||
print(`✅ 25% Off Coupon ID: ${percent_coupon_id}`);
|
||||
|
||||
// Create a fixed amount coupon
|
||||
let amount_coupon = new_coupon()
|
||||
.duration("repeating")
|
||||
.duration_in_months(3)
|
||||
.amount_off(500, "usd") // $5.00 off
|
||||
.metadata("campaign", "loyalty_program")
|
||||
.metadata("code", "LOYAL5");
|
||||
|
||||
let amount_coupon_id = amount_coupon.create();
|
||||
print(`✅ $5 Off Coupon ID: ${amount_coupon_id}`);
|
||||
|
||||
print("\n💳 Creating Payment Intent for Upfront Payment...");
|
||||
|
||||
// Create a payment intent for one-time payment
|
||||
let payment_intent = new_payment_intent()
|
||||
.amount(19999)
|
||||
.currency("usd")
|
||||
.customer("cus_example_customer_id")
|
||||
.description("Premium Software License - One-time Payment")
|
||||
.add_payment_method_type("card")
|
||||
.add_payment_method_type("us_bank_account")
|
||||
.metadata("product_id", product_id)
|
||||
.metadata("price_id", upfront_price_id)
|
||||
.metadata("payment_type", "upfront");
|
||||
|
||||
let payment_intent_id = payment_intent.create();
|
||||
print(`✅ Payment Intent ID: ${payment_intent_id}`);
|
||||
|
||||
print("\n🔄 Creating Subscription...");
|
||||
|
||||
// Create a subscription for monthly billing
|
||||
let subscription = new_subscription()
|
||||
.customer("cus_example_customer_id")
|
||||
.add_price(monthly_price_id)
|
||||
.trial_days(14) // 14-day free trial
|
||||
.coupon(percent_coupon_id) // Apply 25% discount
|
||||
.metadata("plan", "monthly")
|
||||
.metadata("trial", "14_days")
|
||||
.metadata("source", "website_signup");
|
||||
|
||||
let subscription_id = subscription.create();
|
||||
print(`✅ Subscription ID: ${subscription_id}`);
|
||||
|
||||
print("\n🎯 Creating Multi-Item Subscription...");
|
||||
|
||||
// Create a subscription with multiple items
|
||||
let multi_subscription = new_subscription()
|
||||
.customer("cus_example_enterprise_customer")
|
||||
.add_price_with_quantity(monthly_price_id, 5) // 5 licenses
|
||||
.add_price("price_addon_support_monthly") // Support addon
|
||||
.trial_days(30) // 30-day trial for enterprise
|
||||
.metadata("plan", "enterprise")
|
||||
.metadata("licenses", "5")
|
||||
.metadata("addons", "premium_support");
|
||||
|
||||
let multi_subscription_id = multi_subscription.create();
|
||||
print(`✅ Multi-Item Subscription ID: ${multi_subscription_id}`);
|
||||
|
||||
print("\n💰 Creating Payment Intent with Coupon...");
|
||||
|
||||
// Create another payment intent with discount applied
|
||||
let discounted_payment = new_payment_intent()
|
||||
.amount(14999) // Discounted amount after coupon
|
||||
.currency("usd")
|
||||
.customer("cus_example_customer_2")
|
||||
.description("Premium Software License - With 25% Discount")
|
||||
.metadata("original_amount", "19999")
|
||||
.metadata("coupon_applied", percent_coupon_id)
|
||||
.metadata("discount_percent", "25");
|
||||
|
||||
let discounted_payment_id = discounted_payment.create();
|
||||
print(`✅ Discounted Payment Intent ID: ${discounted_payment_id}`);
|
||||
|
||||
print("\n📊 Summary of Created Items:");
|
||||
print("================================");
|
||||
print(`Product ID: ${product_id}`);
|
||||
print(`Upfront Price ID: ${upfront_price_id}`);
|
||||
print(`Monthly Price ID: ${monthly_price_id}`);
|
||||
print(`Annual Price ID: ${annual_price_id}`);
|
||||
print(`25% Coupon ID: ${percent_coupon_id}`);
|
||||
print(`$5 Coupon ID: ${amount_coupon_id}`);
|
||||
print(`Payment Intent ID: ${payment_intent_id}`);
|
||||
print(`Subscription ID: ${subscription_id}`);
|
||||
print(`Multi-Subscription ID: ${multi_subscription_id}`);
|
||||
print(`Discounted Payment ID: ${discounted_payment_id}`);
|
||||
|
||||
print("\n🎉 Payment workflow demonstration completed!");
|
||||
print("All Stripe objects have been created successfully using the builder pattern.");
|
||||
|
||||
// Example of accessing object properties
|
||||
print("\n🔍 Accessing Object Properties:");
|
||||
print(`Product Name: ${product.name}`);
|
||||
print(`Product Description: ${product.description}`);
|
||||
print(`Upfront Price Amount: $${upfront_price.amount / 100}`);
|
||||
print(`Monthly Price Currency: ${monthly_price.currency}`);
|
||||
print(`Subscription Customer: ${subscription.customer}`);
|
||||
print(`Payment Intent Amount: $${payment_intent.amount / 100}`);
|
||||
print(`Percent Coupon Duration: ${percent_coupon.duration}`);
|
||||
print(`Percent Coupon Discount: ${percent_coupon.percent_off}%`);
|
@ -15,6 +15,12 @@ use heromodels::models::circle::ThemeData;
|
||||
mod rhai_circle_module {
|
||||
use super::{RhaiCircle};
|
||||
|
||||
// this one configures the users own circle
|
||||
#[rhai_fn(name = "configure", return_raw)]
|
||||
pub fn configure() -> Result<RhaiCircle, Box<EvalAltResult>> {
|
||||
Ok(Circle::new())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "new_circle", return_raw)]
|
||||
pub fn new_circle() -> Result<RhaiCircle, Box<EvalAltResult>> {
|
||||
Ok(Circle::new())
|
||||
|
@ -10,6 +10,7 @@ pub mod finance;
|
||||
pub mod flow;
|
||||
pub mod library;
|
||||
pub mod object;
|
||||
pub mod payment;
|
||||
|
||||
pub use macros::register_authorized_get_by_id_fn;
|
||||
pub use macros::register_authorized_list_fn;
|
||||
@ -28,5 +29,6 @@ pub fn register_dsl_modules(engine: &mut Engine) {
|
||||
flow::register_flow_rhai_modules(engine);
|
||||
library::register_library_rhai_module(engine);
|
||||
object::register_object_fns(engine);
|
||||
payment::register_payment_rhai_module(engine);
|
||||
println!("Rhailib Domain Specific Language modules registered successfully.");
|
||||
}
|
917
src/dsl/src/payment.rs
Normal file
917
src/dsl/src/payment.rs
Normal file
@ -0,0 +1,917 @@
|
||||
use rhai::plugin::*;
|
||||
use rhai::{Dynamic, Engine, EvalAltResult, Module};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::mpsc::{self, Receiver, Sender};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use reqwest::Client;
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
// Async Function Registry for HTTP API calls
|
||||
static ASYNC_REGISTRY: Mutex<Option<AsyncFunctionRegistry>> = Mutex::new(None);
|
||||
|
||||
const STRIPE_API_BASE: &str = "https://api.stripe.com/v1";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AsyncFunctionRegistry {
|
||||
pub request_sender: Sender<AsyncRequest>,
|
||||
pub stripe_config: StripeConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StripeConfig {
|
||||
pub secret_key: String,
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AsyncRequest {
|
||||
pub endpoint: String,
|
||||
pub method: String,
|
||||
pub data: HashMap<String, String>,
|
||||
pub response_sender: oneshot::Sender<Result<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ApiResponse {
|
||||
pub id: Option<String>,
|
||||
pub status: Option<String>,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Default)]
|
||||
pub struct RhaiProduct {
|
||||
pub id: Option<String>,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub metadata: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Default)]
|
||||
pub struct RhaiPrice {
|
||||
pub id: Option<String>,
|
||||
pub unit_amount: u64,
|
||||
pub currency: String,
|
||||
pub recurring: Option<RecurringConfig>,
|
||||
pub product: String,
|
||||
pub metadata: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Default)]
|
||||
pub struct RecurringConfig {
|
||||
pub interval: String, // "month", "year", "week", "day"
|
||||
pub interval_count: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Default)]
|
||||
pub struct RhaiSubscription {
|
||||
pub id: Option<String>,
|
||||
pub customer: String,
|
||||
pub items: Vec<SubscriptionItem>,
|
||||
pub metadata: HashMap<String, String>,
|
||||
pub trial_period_days: Option<u32>,
|
||||
pub coupon: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Default)]
|
||||
pub struct SubscriptionItem {
|
||||
pub price: String,
|
||||
pub quantity: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Default)]
|
||||
pub struct RhaiPaymentIntent {
|
||||
pub id: Option<String>,
|
||||
pub amount: u64,
|
||||
pub currency: String,
|
||||
pub payment_method_types: Vec<String>,
|
||||
pub customer: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub metadata: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Default)]
|
||||
pub struct RhaiCoupon {
|
||||
pub id: Option<String>,
|
||||
pub duration: String, // "once", "repeating", "forever"
|
||||
pub percent_off: Option<u32>,
|
||||
pub amount_off: Option<u64>,
|
||||
pub currency: Option<String>,
|
||||
pub duration_in_months: Option<u32>,
|
||||
pub metadata: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl RhaiProduct {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
name: String::new(),
|
||||
description: None,
|
||||
metadata: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(mut self, name: String) -> Self {
|
||||
self.name = name;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn description(mut self, description: String) -> Self {
|
||||
self.description = Some(description);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn metadata(mut self, key: String, value: String) -> Self {
|
||||
self.metadata.insert(key, value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RhaiPrice {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
unit_amount: 0,
|
||||
currency: "usd".to_string(),
|
||||
recurring: None,
|
||||
product: String::new(),
|
||||
metadata: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn amount(mut self, amount: u64) -> Self {
|
||||
self.unit_amount = amount;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn currency(mut self, currency: String) -> Self {
|
||||
self.currency = currency;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn product(mut self, product_id: String) -> Self {
|
||||
self.product = product_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn recurring(mut self, interval: String) -> Self {
|
||||
self.recurring = Some(RecurringConfig {
|
||||
interval,
|
||||
interval_count: None,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn recurring_with_count(mut self, interval: String, count: u32) -> Self {
|
||||
self.recurring = Some(RecurringConfig {
|
||||
interval,
|
||||
interval_count: Some(count),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn metadata(mut self, key: String, value: String) -> Self {
|
||||
self.metadata.insert(key, value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RhaiSubscription {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
customer: String::new(),
|
||||
items: Vec::new(),
|
||||
metadata: HashMap::new(),
|
||||
trial_period_days: None,
|
||||
coupon: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn customer(mut self, customer_id: String) -> Self {
|
||||
self.customer = customer_id;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_price(mut self, price_id: String) -> Self {
|
||||
self.items.push(SubscriptionItem {
|
||||
price: price_id,
|
||||
quantity: None,
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_price_with_quantity(mut self, price_id: String, quantity: u32) -> Self {
|
||||
self.items.push(SubscriptionItem {
|
||||
price: price_id,
|
||||
quantity: Some(quantity),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn trial_days(mut self, days: u32) -> Self {
|
||||
self.trial_period_days = Some(days);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn coupon(mut self, coupon_id: String) -> Self {
|
||||
self.coupon = Some(coupon_id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn metadata(mut self, key: String, value: String) -> Self {
|
||||
self.metadata.insert(key, value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RhaiPaymentIntent {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
amount: 0,
|
||||
currency: "usd".to_string(),
|
||||
payment_method_types: vec!["card".to_string()],
|
||||
customer: None,
|
||||
description: None,
|
||||
metadata: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn amount(mut self, amount: u64) -> Self {
|
||||
self.amount = amount;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn currency(mut self, currency: String) -> Self {
|
||||
self.currency = currency;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn customer(mut self, customer_id: String) -> Self {
|
||||
self.customer = Some(customer_id);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn description(mut self, description: String) -> Self {
|
||||
self.description = Some(description);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_payment_method_type(mut self, method_type: String) -> Self {
|
||||
if !self.payment_method_types.contains(&method_type) {
|
||||
self.payment_method_types.push(method_type);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn metadata(mut self, key: String, value: String) -> Self {
|
||||
self.metadata.insert(key, value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RhaiCoupon {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: None,
|
||||
duration: "once".to_string(),
|
||||
percent_off: None,
|
||||
amount_off: None,
|
||||
currency: None,
|
||||
duration_in_months: None,
|
||||
metadata: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn duration(mut self, duration: String) -> Self {
|
||||
self.duration = duration;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn percent_off(mut self, percent: u32) -> Self {
|
||||
self.percent_off = Some(percent);
|
||||
self.amount_off = None; // Clear amount_off if setting percent_off
|
||||
self
|
||||
}
|
||||
|
||||
pub fn amount_off(mut self, amount: u64, currency: String) -> Self {
|
||||
self.amount_off = Some(amount);
|
||||
self.currency = Some(currency);
|
||||
self.percent_off = None; // Clear percent_off if setting amount_off
|
||||
self
|
||||
}
|
||||
|
||||
pub fn duration_in_months(mut self, months: u32) -> Self {
|
||||
self.duration_in_months = Some(months);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn metadata(mut self, key: String, value: String) -> Self {
|
||||
self.metadata.insert(key, value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// Async Worker Pool Implementation
|
||||
impl AsyncFunctionRegistry {
|
||||
pub fn new(stripe_config: StripeConfig) -> Self {
|
||||
let (request_sender, request_receiver) = mpsc::channel();
|
||||
|
||||
// Start the async worker thread
|
||||
let config_clone = stripe_config.clone();
|
||||
thread::spawn(move || {
|
||||
let rt = Runtime::new().expect("Failed to create Tokio runtime");
|
||||
rt.block_on(async {
|
||||
Self::async_worker_loop(config_clone, request_receiver).await;
|
||||
});
|
||||
});
|
||||
|
||||
Self {
|
||||
request_sender,
|
||||
stripe_config,
|
||||
}
|
||||
}
|
||||
|
||||
async fn async_worker_loop(config: StripeConfig, receiver: Receiver<AsyncRequest>) {
|
||||
println!("🚀 Async worker thread started");
|
||||
|
||||
while let Ok(request) = receiver.recv() {
|
||||
let result = Self::handle_stripe_request(&config, &request).await;
|
||||
let _ = request.response_sender.send(result);
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_stripe_request(config: &StripeConfig, request: &AsyncRequest) -> Result<String, String> {
|
||||
println!("🔄 Processing {} request to {}", request.method, request.endpoint);
|
||||
|
||||
let url = format!("{}/{}", STRIPE_API_BASE, request.endpoint);
|
||||
|
||||
let response = config.client
|
||||
.post(&url)
|
||||
.basic_auth(&config.secret_key, None::<&str>)
|
||||
.form(&request.data)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
println!("❌ HTTP request failed: {}", e);
|
||||
format!("HTTP request failed: {}", e)
|
||||
})?;
|
||||
|
||||
let response_text = response.text().await
|
||||
.map_err(|e| format!("Failed to read response: {}", e))?;
|
||||
|
||||
println!("📥 Stripe response: {}", response_text);
|
||||
|
||||
let json: serde_json::Value = serde_json::from_str(&response_text)
|
||||
.map_err(|e| format!("Failed to parse JSON: {}", e))?;
|
||||
|
||||
if let Some(id) = json.get("id").and_then(|v| v.as_str()) {
|
||||
println!("✅ Request successful with ID: {}", id);
|
||||
Ok(id.to_string())
|
||||
} else if let Some(error) = json.get("error") {
|
||||
let error_msg = format!("Stripe API error: {}", error);
|
||||
println!("❌ {}", error_msg);
|
||||
Err(error_msg)
|
||||
} else {
|
||||
let error_msg = format!("Unexpected response: {}", response_text);
|
||||
println!("❌ {}", error_msg);
|
||||
Err(error_msg)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_request(&self, endpoint: String, method: String, data: HashMap<String, String>) -> Result<String, String> {
|
||||
let (response_sender, response_receiver) = oneshot::channel();
|
||||
|
||||
let request = AsyncRequest {
|
||||
endpoint,
|
||||
method,
|
||||
data,
|
||||
response_sender,
|
||||
};
|
||||
|
||||
self.request_sender.send(request)
|
||||
.map_err(|_| "Failed to send request to async worker".to_string())?;
|
||||
|
||||
// Block until we get a response
|
||||
response_receiver.blocking_recv()
|
||||
.map_err(|_| "Failed to receive response from async worker".to_string())?
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions to prepare form data for different Stripe objects
|
||||
fn prepare_product_data(product: &RhaiProduct) -> HashMap<String, String> {
|
||||
let mut form_data = HashMap::new();
|
||||
form_data.insert("name".to_string(), product.name.clone());
|
||||
|
||||
if let Some(ref description) = product.description {
|
||||
form_data.insert("description".to_string(), description.clone());
|
||||
}
|
||||
|
||||
for (key, value) in &product.metadata {
|
||||
let metadata_key = format!("metadata[{}]", key);
|
||||
form_data.insert(metadata_key, value.clone());
|
||||
}
|
||||
|
||||
form_data
|
||||
}
|
||||
|
||||
fn prepare_price_data(price: &RhaiPrice) -> HashMap<String, String> {
|
||||
let mut form_data = HashMap::new();
|
||||
form_data.insert("unit_amount".to_string(), price.unit_amount.to_string());
|
||||
form_data.insert("currency".to_string(), price.currency.clone());
|
||||
form_data.insert("product".to_string(), price.product.clone());
|
||||
|
||||
if let Some(ref recurring) = price.recurring {
|
||||
form_data.insert("recurring[interval]".to_string(), recurring.interval.clone());
|
||||
if let Some(count) = recurring.interval_count {
|
||||
form_data.insert("recurring[interval_count]".to_string(), count.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
for (key, value) in &price.metadata {
|
||||
let metadata_key = format!("metadata[{}]", key);
|
||||
form_data.insert(metadata_key, value.clone());
|
||||
}
|
||||
|
||||
form_data
|
||||
}
|
||||
|
||||
fn prepare_subscription_data(subscription: &RhaiSubscription) -> HashMap<String, String> {
|
||||
let mut form_data = HashMap::new();
|
||||
form_data.insert("customer".to_string(), subscription.customer.clone());
|
||||
|
||||
for (i, item) in subscription.items.iter().enumerate() {
|
||||
form_data.insert(format!("items[{}][price]", i), item.price.clone());
|
||||
if let Some(quantity) = item.quantity {
|
||||
form_data.insert(format!("items[{}][quantity]", i), quantity.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(trial_days) = subscription.trial_period_days {
|
||||
form_data.insert("trial_period_days".to_string(), trial_days.to_string());
|
||||
}
|
||||
|
||||
if let Some(ref coupon) = subscription.coupon {
|
||||
form_data.insert("coupon".to_string(), coupon.clone());
|
||||
}
|
||||
|
||||
for (key, value) in &subscription.metadata {
|
||||
form_data.insert(format!("metadata[{}]", key), value.clone());
|
||||
}
|
||||
|
||||
form_data
|
||||
}
|
||||
|
||||
fn prepare_payment_intent_data(intent: &RhaiPaymentIntent) -> HashMap<String, String> {
|
||||
let mut form_data = HashMap::new();
|
||||
form_data.insert("amount".to_string(), intent.amount.to_string());
|
||||
form_data.insert("currency".to_string(), intent.currency.clone());
|
||||
|
||||
for (i, method_type) in intent.payment_method_types.iter().enumerate() {
|
||||
form_data.insert(format!("payment_method_types[{}]", i), method_type.clone());
|
||||
}
|
||||
|
||||
if let Some(ref customer) = intent.customer {
|
||||
form_data.insert("customer".to_string(), customer.clone());
|
||||
}
|
||||
|
||||
if let Some(ref description) = intent.description {
|
||||
form_data.insert("description".to_string(), description.clone());
|
||||
}
|
||||
|
||||
for (key, value) in &intent.metadata {
|
||||
form_data.insert(format!("metadata[{}]", key), value.clone());
|
||||
}
|
||||
|
||||
form_data
|
||||
}
|
||||
|
||||
fn prepare_coupon_data(coupon: &RhaiCoupon) -> HashMap<String, String> {
|
||||
let mut form_data = HashMap::new();
|
||||
form_data.insert("duration".to_string(), coupon.duration.clone());
|
||||
|
||||
if let Some(percent) = coupon.percent_off {
|
||||
form_data.insert("percent_off".to_string(), percent.to_string());
|
||||
}
|
||||
|
||||
if let Some(amount) = coupon.amount_off {
|
||||
form_data.insert("amount_off".to_string(), amount.to_string());
|
||||
if let Some(ref currency) = coupon.currency {
|
||||
form_data.insert("currency".to_string(), currency.clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(months) = coupon.duration_in_months {
|
||||
form_data.insert("duration_in_months".to_string(), months.to_string());
|
||||
}
|
||||
|
||||
for (key, value) in &coupon.metadata {
|
||||
form_data.insert(format!("metadata[{}]", key), value.clone());
|
||||
}
|
||||
|
||||
form_data
|
||||
}
|
||||
|
||||
#[export_module]
|
||||
mod rhai_payment_module {
|
||||
use super::*;
|
||||
|
||||
// --- Configuration ---
|
||||
#[rhai_fn(name = "configure_stripe", return_raw)]
|
||||
pub fn configure_stripe(secret_key: String) -> Result<String, Box<EvalAltResult>> {
|
||||
println!("🔧 Configuring async HTTP client with timeouts...");
|
||||
|
||||
let client = Client::builder()
|
||||
.timeout(Duration::from_secs(5))
|
||||
.connect_timeout(Duration::from_secs(3))
|
||||
.pool_idle_timeout(Duration::from_secs(10))
|
||||
.tcp_keepalive(Duration::from_secs(30))
|
||||
.user_agent("rhailib-payment/1.0")
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create HTTP client: {}", e))?;
|
||||
|
||||
let stripe_config = StripeConfig {
|
||||
secret_key,
|
||||
client,
|
||||
};
|
||||
|
||||
let registry = AsyncFunctionRegistry::new(stripe_config);
|
||||
|
||||
let mut global_registry = ASYNC_REGISTRY.lock().unwrap();
|
||||
*global_registry = Some(registry);
|
||||
|
||||
Ok("Stripe configured successfully with async architecture".to_string())
|
||||
}
|
||||
|
||||
// --- Product Builder ---
|
||||
#[rhai_fn(name = "new_product", return_raw)]
|
||||
pub fn new_product() -> Result<RhaiProduct, Box<EvalAltResult>> {
|
||||
Ok(RhaiProduct::new())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "name", return_raw)]
|
||||
pub fn product_name(product: &mut RhaiProduct, name: String) -> Result<RhaiProduct, Box<EvalAltResult>> {
|
||||
let owned = mem::take(product);
|
||||
*product = owned.name(name);
|
||||
Ok(product.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "description", return_raw)]
|
||||
pub fn product_description(product: &mut RhaiProduct, description: String) -> Result<RhaiProduct, Box<EvalAltResult>> {
|
||||
let owned = mem::take(product);
|
||||
*product = owned.description(description);
|
||||
Ok(product.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "metadata", return_raw)]
|
||||
pub fn product_metadata(product: &mut RhaiProduct, key: String, value: String) -> Result<RhaiProduct, Box<EvalAltResult>> {
|
||||
let owned = mem::take(product);
|
||||
*product = owned.metadata(key, value);
|
||||
Ok(product.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "create", return_raw)]
|
||||
pub fn create_product(product: &mut RhaiProduct) -> Result<String, Box<EvalAltResult>> {
|
||||
let registry = ASYNC_REGISTRY.lock().unwrap();
|
||||
let registry = registry.as_ref().ok_or("Stripe not configured. Call configure_stripe() first.")?;
|
||||
|
||||
let form_data = prepare_product_data(product);
|
||||
let result = registry.make_request("products".to_string(), "POST".to_string(), form_data)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
product.id = Some(result.clone());
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// --- Price Builder ---
|
||||
#[rhai_fn(name = "new_price", return_raw)]
|
||||
pub fn new_price() -> Result<RhaiPrice, Box<EvalAltResult>> {
|
||||
Ok(RhaiPrice::new())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "amount", return_raw)]
|
||||
pub fn price_amount(price: &mut RhaiPrice, amount: i64) -> Result<RhaiPrice, Box<EvalAltResult>> {
|
||||
let owned = mem::take(price);
|
||||
*price = owned.amount(amount as u64);
|
||||
Ok(price.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "currency", return_raw)]
|
||||
pub fn price_currency(price: &mut RhaiPrice, currency: String) -> Result<RhaiPrice, Box<EvalAltResult>> {
|
||||
let owned = mem::take(price);
|
||||
*price = owned.currency(currency);
|
||||
Ok(price.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "product", return_raw)]
|
||||
pub fn price_product(price: &mut RhaiPrice, product_id: String) -> Result<RhaiPrice, Box<EvalAltResult>> {
|
||||
let owned = mem::take(price);
|
||||
*price = owned.product(product_id);
|
||||
Ok(price.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "recurring", return_raw)]
|
||||
pub fn price_recurring(price: &mut RhaiPrice, interval: String) -> Result<RhaiPrice, Box<EvalAltResult>> {
|
||||
let owned = mem::take(price);
|
||||
*price = owned.recurring(interval);
|
||||
Ok(price.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "recurring_with_count", return_raw)]
|
||||
pub fn price_recurring_with_count(price: &mut RhaiPrice, interval: String, count: i64) -> Result<RhaiPrice, Box<EvalAltResult>> {
|
||||
let owned = mem::take(price);
|
||||
*price = owned.recurring_with_count(interval, count as u32);
|
||||
Ok(price.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "metadata", return_raw)]
|
||||
pub fn price_metadata(price: &mut RhaiPrice, key: String, value: String) -> Result<RhaiPrice, Box<EvalAltResult>> {
|
||||
let owned = mem::take(price);
|
||||
*price = owned.metadata(key, value);
|
||||
Ok(price.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "create", return_raw)]
|
||||
pub fn create_price(price: &mut RhaiPrice) -> Result<String, Box<EvalAltResult>> {
|
||||
let registry = ASYNC_REGISTRY.lock().unwrap();
|
||||
let registry = registry.as_ref().ok_or("Stripe not configured. Call configure_stripe() first.")?;
|
||||
|
||||
let form_data = prepare_price_data(price);
|
||||
let result = registry.make_request("prices".to_string(), "POST".to_string(), form_data)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
price.id = Some(result.clone());
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// --- Subscription Builder ---
|
||||
#[rhai_fn(name = "new_subscription", return_raw)]
|
||||
pub fn new_subscription() -> Result<RhaiSubscription, Box<EvalAltResult>> {
|
||||
Ok(RhaiSubscription::new())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "customer", return_raw)]
|
||||
pub fn subscription_customer(subscription: &mut RhaiSubscription, customer_id: String) -> Result<RhaiSubscription, Box<EvalAltResult>> {
|
||||
let owned = mem::take(subscription);
|
||||
*subscription = owned.customer(customer_id);
|
||||
Ok(subscription.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "add_price", return_raw)]
|
||||
pub fn subscription_add_price(subscription: &mut RhaiSubscription, price_id: String) -> Result<RhaiSubscription, Box<EvalAltResult>> {
|
||||
let owned = mem::take(subscription);
|
||||
*subscription = owned.add_price(price_id);
|
||||
Ok(subscription.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "add_price_with_quantity", return_raw)]
|
||||
pub fn subscription_add_price_with_quantity(subscription: &mut RhaiSubscription, price_id: String, quantity: i64) -> Result<RhaiSubscription, Box<EvalAltResult>> {
|
||||
let owned = mem::take(subscription);
|
||||
*subscription = owned.add_price_with_quantity(price_id, quantity as u32);
|
||||
Ok(subscription.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "trial_days", return_raw)]
|
||||
pub fn subscription_trial_days(subscription: &mut RhaiSubscription, days: i64) -> Result<RhaiSubscription, Box<EvalAltResult>> {
|
||||
let owned = mem::take(subscription);
|
||||
*subscription = owned.trial_days(days as u32);
|
||||
Ok(subscription.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "coupon", return_raw)]
|
||||
pub fn subscription_coupon(subscription: &mut RhaiSubscription, coupon_id: String) -> Result<RhaiSubscription, Box<EvalAltResult>> {
|
||||
let owned = mem::take(subscription);
|
||||
*subscription = owned.coupon(coupon_id);
|
||||
Ok(subscription.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "metadata", return_raw)]
|
||||
pub fn subscription_metadata(subscription: &mut RhaiSubscription, key: String, value: String) -> Result<RhaiSubscription, Box<EvalAltResult>> {
|
||||
let owned = mem::take(subscription);
|
||||
*subscription = owned.metadata(key, value);
|
||||
Ok(subscription.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "create", return_raw)]
|
||||
pub fn create_subscription(subscription: &mut RhaiSubscription) -> Result<String, Box<EvalAltResult>> {
|
||||
let registry = ASYNC_REGISTRY.lock().unwrap();
|
||||
let registry = registry.as_ref().ok_or("Stripe not configured. Call configure_stripe() first.")?;
|
||||
|
||||
let form_data = prepare_subscription_data(subscription);
|
||||
let result = registry.make_request("subscriptions".to_string(), "POST".to_string(), form_data)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
subscription.id = Some(result.clone());
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// --- Payment Intent Builder ---
|
||||
#[rhai_fn(name = "new_payment_intent", return_raw)]
|
||||
pub fn new_payment_intent() -> Result<RhaiPaymentIntent, Box<EvalAltResult>> {
|
||||
Ok(RhaiPaymentIntent::new())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "amount", return_raw)]
|
||||
pub fn payment_intent_amount(intent: &mut RhaiPaymentIntent, amount: i64) -> Result<RhaiPaymentIntent, Box<EvalAltResult>> {
|
||||
let owned = mem::take(intent);
|
||||
*intent = owned.amount(amount as u64);
|
||||
Ok(intent.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "currency", return_raw)]
|
||||
pub fn payment_intent_currency(intent: &mut RhaiPaymentIntent, currency: String) -> Result<RhaiPaymentIntent, Box<EvalAltResult>> {
|
||||
let owned = mem::take(intent);
|
||||
*intent = owned.currency(currency);
|
||||
Ok(intent.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "customer", return_raw)]
|
||||
pub fn payment_intent_customer(intent: &mut RhaiPaymentIntent, customer_id: String) -> Result<RhaiPaymentIntent, Box<EvalAltResult>> {
|
||||
let owned = mem::take(intent);
|
||||
*intent = owned.customer(customer_id);
|
||||
Ok(intent.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "description", return_raw)]
|
||||
pub fn payment_intent_description(intent: &mut RhaiPaymentIntent, description: String) -> Result<RhaiPaymentIntent, Box<EvalAltResult>> {
|
||||
let owned = mem::take(intent);
|
||||
*intent = owned.description(description);
|
||||
Ok(intent.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "add_payment_method_type", return_raw)]
|
||||
pub fn payment_intent_add_payment_method_type(intent: &mut RhaiPaymentIntent, method_type: String) -> Result<RhaiPaymentIntent, Box<EvalAltResult>> {
|
||||
let owned = mem::take(intent);
|
||||
*intent = owned.add_payment_method_type(method_type);
|
||||
Ok(intent.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "metadata", return_raw)]
|
||||
pub fn payment_intent_metadata(intent: &mut RhaiPaymentIntent, key: String, value: String) -> Result<RhaiPaymentIntent, Box<EvalAltResult>> {
|
||||
let owned = mem::take(intent);
|
||||
*intent = owned.metadata(key, value);
|
||||
Ok(intent.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "create", return_raw)]
|
||||
pub fn create_payment_intent(intent: &mut RhaiPaymentIntent) -> Result<String, Box<EvalAltResult>> {
|
||||
let registry = ASYNC_REGISTRY.lock().unwrap();
|
||||
let registry = registry.as_ref().ok_or("Stripe not configured. Call configure_stripe() first.")?;
|
||||
|
||||
let form_data = prepare_payment_intent_data(intent);
|
||||
let result = registry.make_request("payment_intents".to_string(), "POST".to_string(), form_data)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
intent.id = Some(result.clone());
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// --- Coupon Builder ---
|
||||
#[rhai_fn(name = "new_coupon", return_raw)]
|
||||
pub fn new_coupon() -> Result<RhaiCoupon, Box<EvalAltResult>> {
|
||||
Ok(RhaiCoupon::new())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "duration", return_raw)]
|
||||
pub fn coupon_duration(coupon: &mut RhaiCoupon, duration: String) -> Result<RhaiCoupon, Box<EvalAltResult>> {
|
||||
let owned = mem::take(coupon);
|
||||
*coupon = owned.duration(duration);
|
||||
Ok(coupon.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "percent_off", return_raw)]
|
||||
pub fn coupon_percent_off(coupon: &mut RhaiCoupon, percent: i64) -> Result<RhaiCoupon, Box<EvalAltResult>> {
|
||||
let owned = mem::take(coupon);
|
||||
*coupon = owned.percent_off(percent as u32);
|
||||
Ok(coupon.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "amount_off", return_raw)]
|
||||
pub fn coupon_amount_off(coupon: &mut RhaiCoupon, amount: i64, currency: String) -> Result<RhaiCoupon, Box<EvalAltResult>> {
|
||||
let owned = mem::take(coupon);
|
||||
*coupon = owned.amount_off(amount as u64, currency);
|
||||
Ok(coupon.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "duration_in_months", return_raw)]
|
||||
pub fn coupon_duration_in_months(coupon: &mut RhaiCoupon, months: i64) -> Result<RhaiCoupon, Box<EvalAltResult>> {
|
||||
let owned = mem::take(coupon);
|
||||
*coupon = owned.duration_in_months(months as u32);
|
||||
Ok(coupon.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "metadata", return_raw)]
|
||||
pub fn coupon_metadata(coupon: &mut RhaiCoupon, key: String, value: String) -> Result<RhaiCoupon, Box<EvalAltResult>> {
|
||||
let owned = mem::take(coupon);
|
||||
*coupon = owned.metadata(key, value);
|
||||
Ok(coupon.clone())
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "create", return_raw)]
|
||||
pub fn create_coupon(coupon: &mut RhaiCoupon) -> Result<String, Box<EvalAltResult>> {
|
||||
let registry = ASYNC_REGISTRY.lock().unwrap();
|
||||
let registry = registry.as_ref().ok_or("Stripe not configured. Call configure_stripe() first.")?;
|
||||
|
||||
let form_data = prepare_coupon_data(coupon);
|
||||
let result = registry.make_request("coupons".to_string(), "POST".to_string(), form_data)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
coupon.id = Some(result.clone());
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// --- Getters ---
|
||||
// Product getters
|
||||
#[rhai_fn(get = "id", pure)]
|
||||
pub fn get_product_id(product: &mut RhaiProduct) -> String {
|
||||
product.id.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
#[rhai_fn(get = "name", pure)]
|
||||
pub fn get_product_name(product: &mut RhaiProduct) -> String {
|
||||
product.name.clone()
|
||||
}
|
||||
|
||||
#[rhai_fn(get = "description", pure)]
|
||||
pub fn get_product_description(product: &mut RhaiProduct) -> String {
|
||||
product.description.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
// Price getters
|
||||
#[rhai_fn(get = "id", pure)]
|
||||
pub fn get_price_id(price: &mut RhaiPrice) -> String {
|
||||
price.id.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
#[rhai_fn(get = "amount", pure)]
|
||||
pub fn get_price_amount(price: &mut RhaiPrice) -> i64 {
|
||||
price.unit_amount as i64
|
||||
}
|
||||
|
||||
#[rhai_fn(get = "currency", pure)]
|
||||
pub fn get_price_currency(price: &mut RhaiPrice) -> String {
|
||||
price.currency.clone()
|
||||
}
|
||||
|
||||
// Subscription getters
|
||||
#[rhai_fn(get = "id", pure)]
|
||||
pub fn get_subscription_id(subscription: &mut RhaiSubscription) -> String {
|
||||
subscription.id.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
#[rhai_fn(get = "customer", pure)]
|
||||
pub fn get_subscription_customer(subscription: &mut RhaiSubscription) -> String {
|
||||
subscription.customer.clone()
|
||||
}
|
||||
|
||||
// Payment Intent getters
|
||||
#[rhai_fn(get = "id", pure)]
|
||||
pub fn get_payment_intent_id(intent: &mut RhaiPaymentIntent) -> String {
|
||||
intent.id.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
#[rhai_fn(get = "amount", pure)]
|
||||
pub fn get_payment_intent_amount(intent: &mut RhaiPaymentIntent) -> i64 {
|
||||
intent.amount as i64
|
||||
}
|
||||
|
||||
#[rhai_fn(get = "currency", pure)]
|
||||
pub fn get_payment_intent_currency(intent: &mut RhaiPaymentIntent) -> String {
|
||||
intent.currency.clone()
|
||||
}
|
||||
|
||||
// Coupon getters
|
||||
#[rhai_fn(get = "id", pure)]
|
||||
pub fn get_coupon_id(coupon: &mut RhaiCoupon) -> String {
|
||||
coupon.id.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
#[rhai_fn(get = "duration", pure)]
|
||||
pub fn get_coupon_duration(coupon: &mut RhaiCoupon) -> String {
|
||||
coupon.duration.clone()
|
||||
}
|
||||
|
||||
#[rhai_fn(get = "percent_off", pure)]
|
||||
pub fn get_coupon_percent_off(coupon: &mut RhaiCoupon) -> i64 {
|
||||
coupon.percent_off.unwrap_or(0) as i64
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_payment_rhai_module(engine: &mut Engine) {
|
||||
let module = exported_module!(rhai_payment_module);
|
||||
|
||||
// Register custom types
|
||||
engine.register_type_with_name::<RhaiProduct>("Product");
|
||||
engine.register_type_with_name::<RhaiPrice>("Price");
|
||||
engine.register_type_with_name::<RhaiSubscription>("Subscription");
|
||||
engine.register_type_with_name::<RhaiPaymentIntent>("PaymentIntent");
|
||||
engine.register_type_with_name::<RhaiCoupon>("Coupon");
|
||||
|
||||
engine.register_global_module(module.into());
|
||||
println!("Successfully registered payment Rhai module.");
|
||||
}
|
113
src/worker/cmd/README.md
Normal file
113
src/worker/cmd/README.md
Normal file
@ -0,0 +1,113 @@
|
||||
# Rhai Worker Binary
|
||||
|
||||
A command-line worker for executing Rhai scripts from Redis task queues.
|
||||
|
||||
## Binary: `worker`
|
||||
|
||||
### Installation
|
||||
|
||||
Build the binary:
|
||||
```bash
|
||||
cargo build --bin worker --release
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Basic usage - requires circle public key
|
||||
worker --circle-public-key <CIRCLE_PUBLIC_KEY>
|
||||
|
||||
# Custom Redis URL
|
||||
worker -c <CIRCLE_PUBLIC_KEY> --redis-url redis://localhost:6379/1
|
||||
|
||||
# Custom worker ID and database path
|
||||
worker -c <CIRCLE_PUBLIC_KEY> --worker-id my_worker --db-path /tmp/worker_db
|
||||
|
||||
# Preserve tasks for debugging/benchmarking
|
||||
worker -c <CIRCLE_PUBLIC_KEY> --preserve-tasks
|
||||
|
||||
# Remove timestamps from logs
|
||||
worker -c <CIRCLE_PUBLIC_KEY> --no-timestamp
|
||||
|
||||
# Increase verbosity
|
||||
worker -c <CIRCLE_PUBLIC_KEY> -v # Debug logging
|
||||
worker -c <CIRCLE_PUBLIC_KEY> -vv # Full debug
|
||||
worker -c <CIRCLE_PUBLIC_KEY> -vvv # Trace logging
|
||||
```
|
||||
|
||||
### Command-Line Options
|
||||
|
||||
| Option | Short | Default | Description |
|
||||
|--------|-------|---------|-------------|
|
||||
| `--circle-public-key` | `-c` | **Required** | Circle public key to listen for tasks |
|
||||
| `--redis-url` | `-r` | `redis://localhost:6379` | Redis connection URL |
|
||||
| `--worker-id` | `-w` | `worker_1` | Unique worker identifier |
|
||||
| `--preserve-tasks` | | `false` | Preserve task details after completion |
|
||||
| `--db-path` | | `worker_rhai_temp_db` | Database path for Rhai engine |
|
||||
| `--no-timestamp` | | `false` | Remove timestamps from log output |
|
||||
| `--verbose` | `-v` | | Increase verbosity (stackable) |
|
||||
|
||||
### Features
|
||||
|
||||
- **Task Queue Processing**: Listens to Redis queues for Rhai script execution tasks
|
||||
- **Performance Optimized**: Configured for maximum Rhai engine performance
|
||||
- **Graceful Shutdown**: Supports shutdown signals for clean termination
|
||||
- **Flexible Logging**: Configurable verbosity and timestamp control
|
||||
- **Database Integration**: Uses heromodels for data persistence
|
||||
- **Task Cleanup**: Optional task preservation for debugging/benchmarking
|
||||
|
||||
### How It Works
|
||||
|
||||
1. **Queue Listening**: Worker listens on Redis queue `rhailib:{circle_public_key}`
|
||||
2. **Task Processing**: Receives task IDs, fetches task details from Redis
|
||||
3. **Script Execution**: Executes Rhai scripts with configured engine
|
||||
4. **Result Handling**: Updates task status and sends results to reply queues
|
||||
5. **Cleanup**: Optionally cleans up task details after completion
|
||||
|
||||
### Configuration Examples
|
||||
|
||||
#### Development Worker
|
||||
```bash
|
||||
# Simple development worker
|
||||
worker -c dev_circle_123
|
||||
|
||||
# Development with verbose logging (no timestamps)
|
||||
worker -c dev_circle_123 -v --no-timestamp
|
||||
```
|
||||
|
||||
#### Production Worker
|
||||
```bash
|
||||
# Production worker with custom configuration
|
||||
worker \
|
||||
--circle-public-key prod_circle_456 \
|
||||
--redis-url redis://redis-server:6379/0 \
|
||||
--worker-id prod_worker_1 \
|
||||
--db-path /var/lib/worker/db \
|
||||
--preserve-tasks
|
||||
```
|
||||
|
||||
#### Benchmarking Worker
|
||||
```bash
|
||||
# Worker optimized for benchmarking
|
||||
worker \
|
||||
--circle-public-key bench_circle_789 \
|
||||
--preserve-tasks \
|
||||
--no-timestamp \
|
||||
-vv
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
The worker provides clear error messages for:
|
||||
- Missing or invalid circle public key
|
||||
- Redis connection failures
|
||||
- Script execution errors
|
||||
- Database access issues
|
||||
|
||||
### Dependencies
|
||||
|
||||
- `rhailib_engine`: Rhai engine with heromodels integration
|
||||
- `redis`: Redis client for task queue management
|
||||
- `rhai`: Script execution engine
|
||||
- `clap`: Command-line argument parsing
|
||||
- `env_logger`: Logging infrastructure
|
@ -6,8 +6,8 @@ use tokio::sync::mpsc;
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Public key of the circle to listen to
|
||||
#[arg(short, long, default_value = "default_public_key")]
|
||||
/// Public key of the circle to listen to (required)
|
||||
#[arg(short, long, help = "Circle public key to listen for tasks")]
|
||||
circle_public_key: String,
|
||||
|
||||
/// Redis URL
|
||||
@ -25,14 +25,26 @@ struct Args {
|
||||
/// Root directory for engine database
|
||||
#[arg(long, default_value = "worker_rhai_temp_db")]
|
||||
db_path: String,
|
||||
|
||||
/// Disable timestamps in log output
|
||||
#[arg(long, help = "Remove timestamps from log output")]
|
||||
no_timestamp: bool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
env_logger::init();
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
// Configure env_logger with or without timestamps
|
||||
if args.no_timestamp {
|
||||
env_logger::Builder::from_default_env()
|
||||
.format_timestamp(None)
|
||||
.init();
|
||||
} else {
|
||||
env_logger::init();
|
||||
}
|
||||
|
||||
|
||||
log::info!("Rhai Worker (binary) starting with performance-optimized engine.");
|
||||
log::info!(
|
||||
"Worker ID: {}, Circle Public Key: {}, Redis: {}",
|
||||
|
Loading…
Reference in New Issue
Block a user