This commit is contained in:
core 2023-06-18 21:21:49 -04:00
parent 8a044653f7
commit 877a374f97
Signed by: core
GPG Key ID: FDBF740DADDCEECF
15 changed files with 1650 additions and 57 deletions

655
backend/Cargo.lock generated
View File

@ -72,7 +72,7 @@ dependencies = [
"actix-service", "actix-service",
"actix-utils", "actix-utils",
"ahash 0.8.3", "ahash 0.8.3",
"base64", "base64 0.21.2",
"bitflags", "bitflags",
"brotli", "brotli",
"bytes", "bytes",
@ -91,7 +91,7 @@ dependencies = [
"mime", "mime",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rand", "rand 0.8.5",
"sha1", "sha1",
"smallvec", "smallvec",
"tokio", "tokio",
@ -281,7 +281,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.10",
"once_cell", "once_cell",
"version_check", "version_check",
] ]
@ -293,7 +293,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"getrandom", "getrandom 0.2.10",
"once_cell", "once_cell",
"version_check", "version_check",
] ]
@ -322,6 +322,27 @@ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
] ]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]] [[package]]
name = "arc-bytes" name = "arc-bytes"
version = "0.3.5" version = "0.3.5"
@ -356,6 +377,43 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "async-channel"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]]
name = "async-stripe"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b313de0d654c4c4c46faa737b2257ce9ed79e69926aa4c734b9816be20102b2c"
dependencies = [
"chrono",
"futures-util",
"hex",
"hmac 0.12.1",
"http-types",
"hyper",
"hyper-tls",
"serde",
"serde_json",
"serde_path_to_error",
"serde_qs 0.10.1",
"sha2 0.10.7",
"smart-default",
"smol_str",
"thiserror",
"time-core",
"tokio",
"uuid",
]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.68" version = "0.1.68"
@ -422,6 +480,12 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.2" version = "0.21.2"
@ -562,7 +626,7 @@ dependencies = [
"p256", "p256",
"parking_lot", "parking_lot",
"pot", "pot",
"rand", "rand 0.8.5",
"region", "region",
"serde", "serde",
"sysinfo", "sysinfo",
@ -683,6 +747,19 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "chrono"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
"serde",
"winapi",
]
[[package]] [[package]]
name = "cipher" name = "cipher"
version = "0.3.0" version = "0.3.0"
@ -756,6 +833,15 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "concurrent-queue"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "const-oid" name = "const-oid"
version = "0.6.2" version = "0.6.2"
@ -785,6 +871,16 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.4" version = "0.8.4"
@ -874,7 +970,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03"
dependencies = [ dependencies = [
"generic-array", "generic-array",
"rand_core", "rand_core 0.6.4",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@ -1010,7 +1106,7 @@ checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372"
dependencies = [ dependencies = [
"der", "der",
"elliptic-curve", "elliptic-curve",
"hmac", "hmac 0.11.0",
"signature", "signature",
] ]
@ -1031,7 +1127,7 @@ dependencies = [
"generic-array", "generic-array",
"group", "group",
"pkcs8", "pkcs8",
"rand_core", "rand_core 0.6.4",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@ -1045,19 +1141,49 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]] [[package]]
name = "event-listener" name = "event-listener"
version = "2.5.3" version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]] [[package]]
name = "ff" name = "ff"
version = "0.10.1" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
dependencies = [ dependencies = [
"rand_core", "rand_core 0.6.4",
"subtle", "subtle",
] ]
@ -1090,6 +1216,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 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]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.0" version = "1.2.0"
@ -1147,6 +1288,21 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]]
name = "futures-lite"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.28" version = "0.3.28"
@ -1199,6 +1355,17 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.10" version = "0.2.10"
@ -1208,7 +1375,7 @@ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -1241,7 +1408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
dependencies = [ dependencies = [
"ff", "ff",
"rand_core", "rand_core 0.6.4",
"subtle", "subtle",
] ]
@ -1303,6 +1470,18 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "hkdf" name = "hkdf"
version = "0.11.0" version = "0.11.0"
@ -1310,7 +1489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b"
dependencies = [ dependencies = [
"digest 0.9.0", "digest 0.9.0",
"hmac", "hmac 0.11.0",
] ]
[[package]] [[package]]
@ -1323,6 +1502,15 @@ dependencies = [
"digest 0.9.0", "digest 0.9.0",
] ]
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest 0.10.7",
]
[[package]] [[package]]
name = "hpke" name = "hpke"
version = "0.8.0" version = "0.8.0"
@ -1338,7 +1526,7 @@ dependencies = [
"hkdf", "hkdf",
"p256", "p256",
"paste", "paste",
"rand_core", "rand_core 0.6.4",
"serde", "serde",
"sha2 0.9.9", "sha2 0.9.9",
"subtle", "subtle",
@ -1356,6 +1544,38 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http-body"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "http-types"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad"
dependencies = [
"anyhow",
"async-channel",
"base64 0.13.1",
"futures-lite",
"http",
"infer",
"pin-project-lite",
"rand 0.7.3",
"serde",
"serde_json",
"serde_qs 0.8.5",
"serde_urlencoded",
"url",
]
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.8.0" version = "1.8.0"
@ -1368,6 +1588,66 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
version = "0.14.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" version = "1.0.1"
@ -1394,6 +1674,32 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "infer"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi 0.3.1",
"libc",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.5" version = "0.10.5"
@ -1445,12 +1751,19 @@ version = "0.2.146"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]] [[package]]
name = "lmspos" name = "lmspos"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix-cors", "actix-cors",
"actix-web", "actix-web",
"async-stripe",
"bonsaidb", "bonsaidb",
"log", "log",
"serde", "serde",
@ -1565,7 +1878,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -1575,7 +1888,25 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.10",
]
[[package]]
name = "native-tls"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
] ]
[[package]] [[package]]
@ -1655,6 +1986,50 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019"
dependencies = [
"bitflags",
"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.18",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "ordered-varint" name = "ordered-varint"
version = "1.0.1" version = "1.0.1"
@ -1684,6 +2059,12 @@ dependencies = [
"sha2 0.9.9", "sha2 0.9.9",
] ]
[[package]]
name = "parking"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.1" version = "0.12.1"
@ -1714,7 +2095,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
dependencies = [ dependencies = [
"base64ct", "base64ct",
"rand_core", "rand_core 0.6.4",
"subtle", "subtle",
] ]
@ -1874,6 +2255,19 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@ -1881,8 +2275,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha 0.3.1",
"rand_core", "rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
] ]
[[package]] [[package]]
@ -1892,7 +2296,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
] ]
[[package]] [[package]]
@ -1901,7 +2314,16 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.10",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
] ]
[[package]] [[package]]
@ -1979,18 +2401,64 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "rustix"
version = "0.37.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.13" version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "schannel"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
dependencies = [
"windows-sys 0.42.0",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "security-framework"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.17" version = "1.0.17"
@ -2028,6 +2496,37 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_path_to_error"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0"
dependencies = [
"serde",
]
[[package]]
name = "serde_qs"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6"
dependencies = [
"percent-encoding",
"serde",
"thiserror",
]
[[package]]
name = "serde_qs"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cac3f1e2ca2fe333923a1ae72caca910b98ed0630bb35ef6f8c8517d6e81afa"
dependencies = [
"percent-encoding",
"serde",
"thiserror",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -2091,7 +2590,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4"
dependencies = [ dependencies = [
"digest 0.9.0", "digest 0.9.0",
"rand_core", "rand_core 0.6.4",
] ]
[[package]] [[package]]
@ -2121,6 +2620,26 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "smart-default"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "smol_str"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad6c857cbab2627dcf01ec85a623ca4e7dcb5691cbaa3d7fb7653671f0d09c9"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.4.9" version = "0.4.9"
@ -2203,6 +2722,20 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "tempfile"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
dependencies = [
"autocfg",
"cfg-if",
"fastrand",
"redox_syscall",
"rustix",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.2.0" version = "1.2.0"
@ -2240,9 +2773,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.22" version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea9e1b3cf1243ae005d9e74085d4d542f3125458f3a81af210d901dcd7411efd" checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
dependencies = [ dependencies = [
"itoa", "itoa",
"libc", "libc",
@ -2254,15 +2787,15 @@ dependencies = [
[[package]] [[package]]
name = "time-core" name = "time-core"
version = "0.1.1" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.9" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
dependencies = [ dependencies = [
"time-core", "time-core",
] ]
@ -2312,6 +2845,16 @@ dependencies = [
"syn 2.0.18", "syn 2.0.18",
] ]
[[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]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.8" version = "0.7.8"
@ -2343,6 +2886,12 @@ dependencies = [
"winnow", "winnow",
] ]
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.37" version = "0.1.37"
@ -2404,6 +2953,12 @@ dependencies = [
"transmog", "transmog",
] ]
[[package]]
name = "try-lock"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]] [[package]]
name = "trybuild" name = "trybuild"
version = "1.0.80" version = "1.0.80"
@ -2475,14 +3030,51 @@ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",
"percent-encoding", "percent-encoding",
"serde",
] ]
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom 0.2.10",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[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.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
@ -2586,6 +3178,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.42.0" version = "0.42.0"

View File

@ -12,3 +12,4 @@ actix-web = "4"
bonsaidb = { version = "0.4", features = ["local-full"] } bonsaidb = { version = "0.4", features = ["local-full"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
actix-cors = "0.6.4" actix-cors = "0.6.4"
async-stripe = { version = "0.22", features = ["runtime-tokio-hyper"] }

View File

@ -0,0 +1,17 @@
use std::collections::HashMap;
use bonsaidb::core::schema::Collection;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Collection, Clone)]
#[collection(name = "customerviews")]
pub struct CustomerView {
pub sync_id: String,
pub products: HashMap<CustomerViewProductSlice, u64>,
pub price_total: f64
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct CustomerViewProductSlice {
pub name: String,
pub price_usd: u64
}

View File

@ -7,7 +7,8 @@ use serde::{Deserialize, Serialize};
pub struct Order { pub struct Order {
pub order_type: OrderType, pub order_type: OrderType,
pub total_usd: f64, pub total_usd: f64,
pub products: HashMap<u64, u64> pub products: HashMap<u64, u64>,
pub stripe_status: StripeStatus
} }
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
@ -15,3 +16,11 @@ pub enum OrderType {
Cash, Cash,
Stripe Stripe
} }
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum StripeStatus {
SessionCreated,
Paid,
Failed,
NotStripeTransaction
}

View File

@ -1,28 +1,38 @@
use std::error::Error; use std::error::Error;
use actix_cors::Cors; use actix_cors::Cors;
use actix_web::{App, get, HttpResponse, HttpServer}; use actix_web::{App, get, HttpRequest, HttpResponse, HttpServer};
use actix_web::http::header::HeaderValue;
use actix_web::web::Data; use actix_web::web::Data;
use bonsaidb::core::schema::Schema; use bonsaidb::core::schema::Schema;
use bonsaidb::local::AsyncDatabase; use bonsaidb::local::AsyncDatabase;
use bonsaidb::local::config::{Builder, StorageConfiguration}; use bonsaidb::local::config::{Builder, StorageConfiguration};
use stripe::Client;
use crate::db_products::Product; use crate::db_products::Product;
use crate::db_orders::Order; use crate::db_orders::Order;
use crate::route_order::{cash_order, get_orders}; use crate::error::APIError;
use crate::route_customer::{custview_get, custview_push};
use crate::db_custview::CustomerView;
use crate::route_order::{cash_order, get_order, get_orders, stripe_order};
use crate::route_products::{create_product, delete_product, get_product, get_products, update_product}; use crate::route_products::{create_product, delete_product, get_product, get_products, update_product};
use crate::route_stripe::{stripe_cancel, stripe_finish};
pub mod db_products; pub mod db_products;
pub mod route_products; pub mod route_products;
pub mod error; pub mod error;
pub mod route_order; pub mod route_order;
pub mod db_orders; pub mod db_orders;
pub mod route_stripe;
pub mod db_custview;
pub mod route_customer;
#[derive(Debug, Schema)] #[derive(Debug, Schema)]
#[schema(name = "lmsposschema", collections = [Product, Order])] #[schema(name = "lmsposschema", collections = [Product, Order, CustomerView])]
pub struct DbSchema; pub struct DbSchema;
pub struct AppState { pub struct AppState {
pub db: AsyncDatabase pub db: AsyncDatabase,
pub client: Client
} }
#[actix_web::main] #[actix_web::main]
@ -31,8 +41,11 @@ async fn main() -> Result<(), Box<dyn Error>> {
let db = AsyncDatabase::open::<DbSchema>(StorageConfiguration::new("lms.bonsaidb")).await?; let db = AsyncDatabase::open::<DbSchema>(StorageConfiguration::new("lms.bonsaidb")).await?;
let client = stripe::Client::new("sk_test_51HcdjVJZz7Qxr83OUKOUmzK07dLlpzmbtlUbbxptdGxdXoECKK0BfDo0F89A7A3EFLON8xQDSPxhowVlq0UF2P7000hR3OYha7");
let state = Data::new(AppState { let state = Data::new(AppState {
db db,
client
}); });
HttpServer::new(move || { HttpServer::new(move || {
@ -47,8 +60,15 @@ async fn main() -> Result<(), Box<dyn Error>> {
.service(status) .service(status)
.service(get_orders) .service(get_orders)
.service(cash_order) .service(cash_order)
.service(stripe_order)
.service(get_order)
.service(stripe_cancel)
.service(stripe_finish)
.service(auth_status)
.service(custview_push)
.service(custview_get)
}) })
.bind(("127.0.0.1", 8080))? .bind(("0.0.0.0", 8080))?
.run() .run()
.await?; .await?;
Ok(()) Ok(())
@ -58,3 +78,11 @@ async fn main() -> Result<(), Box<dyn Error>> {
pub async fn status() -> HttpResponse { pub async fn status() -> HttpResponse {
HttpResponse::Ok().body("{\"status\":\"ok\"}") HttpResponse::Ok().body("{\"status\":\"ok\"}")
} }
#[get("/auth_status")]
pub async fn auth_status(req_info: HttpRequest) -> HttpResponse {
if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) {
return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() });
}
HttpResponse::Ok().body("{\"status\":\"ok\"}")
}

View File

@ -0,0 +1,94 @@
use std::collections::HashMap;
use actix_web::{get, HttpRequest, HttpResponse, post};
use actix_web::http::header::HeaderValue;
use actix_web::web::{Data, Json, Path};
use bonsaidb::core::Error;
use bonsaidb::core::schema::SerializedCollection;
use log::debug;
use serde::Deserialize;
use crate::AppState;
use crate::db_custview::{CustomerView, CustomerViewProductSlice};
use crate::error::APIError;
#[derive(Deserialize)]
pub struct CustviewPushReq {
pub sync_id: String,
pub products: HashMap<u64, (CustomerViewProductSlice, u64)>,
pub price_total: f64
}
#[post("/custview")]
pub async fn custview_push(req: Json<CustviewPushReq>, db: Data<AppState>, req_info: HttpRequest) -> HttpResponse {
if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) {
return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() });
}
let mut existing_db = match CustomerView::list_async(0.., &db.db).await {
Ok(r) => r,
Err(e) => {
return HttpResponse::InternalServerError().json(APIError { code: "DB_ERROR".to_string(), message: e.to_string() })
}
};
let mut converted_hashmap = HashMap::new();
for (k, (v, v2)) in &req.products {
converted_hashmap.insert(v.clone(), *v2);
}
for maybe_db in &mut existing_db {
if maybe_db.contents.sync_id == req.sync_id {
let mut definitely_db = maybe_db;
definitely_db.contents.products = converted_hashmap.clone();
definitely_db.contents.price_total = req.price_total;
match definitely_db.update_async(&db.db).await {
Ok(_) => (),
Err(e) => {
return HttpResponse::InternalServerError().json(APIError { code: "DB_ERROR".to_string(), message: e.to_string() })
}
}
return HttpResponse::Ok().finish();
}
}
let doc = CustomerView {
sync_id: req.sync_id.clone(),
products: converted_hashmap.clone(),
price_total: req.price_total,
};
match doc.push_into_async(&db.db).await {
Ok(_) => (),
Err(e) => {
return HttpResponse::InternalServerError().json(APIError { code: "DB_ERROR".to_string(), message: e.to_string() })
}
}
HttpResponse::Ok().finish()
}
#[get("/custview/{id}")]
pub async fn custview_get(id: Path<String>, db: Data<AppState>) -> HttpResponse {
let existing_db = match CustomerView::list_async(0.., &db.db).await {
Ok(r) => r,
Err(e) => {
return HttpResponse::InternalServerError().json(APIError { code: "DB_ERROR".to_string(), message: e.to_string() })
}
};
for maybe_db in &existing_db {
if maybe_db.contents.sync_id == id.clone() {
let definitely_db = maybe_db;
let mut translated: HashMap<u64, (CustomerViewProductSlice, u64)> = HashMap::new();
for (k, v) in &definitely_db.contents.products {
translated.insert(translated.len() as u64, (k.clone(), *v));
}
return HttpResponse::Ok().json(translated.clone());
}
}
HttpResponse::NotFound().json(APIError { code: "Sync ID not known".to_string(), message: "Missing sync ID in DB".to_string() })
}

View File

@ -1,15 +1,18 @@
use std::collections::HashMap; use std::collections::HashMap;
use actix_web::{get, HttpResponse, post}; use actix_web::{get, HttpRequest, HttpResponse, post};
use actix_web::web::{Data, Json}; use actix_web::http::header::HeaderValue;
use actix_web::web::{Data, Json, Path};
use bonsaidb::core::schema::SerializedCollection; use bonsaidb::core::schema::SerializedCollection;
use log::debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use stripe::{CheckoutSession, CheckoutSessionMode, CreateCheckoutSession, CreateCheckoutSessionLineItems, CreateCheckoutSessionLineItemsPriceData, CreateCheckoutSessionLineItemsPriceDataProductData, Currency, Metadata};
use crate::AppState; use crate::AppState;
use crate::db_orders::{Order, OrderType}; use crate::db_orders::{Order, OrderType, StripeStatus};
use crate::db_products::Product; use crate::db_products::Product;
use crate::error::APIError; use crate::error::APIError;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CashOrderRequest { pub struct OrderRequest {
pub products: HashMap<u64, u64>, pub products: HashMap<u64, u64>,
pub adjust_stock: bool pub adjust_stock: bool
} }
@ -20,7 +23,10 @@ pub struct CashOrderResponse {
} }
#[post("/cash_order")] #[post("/cash_order")]
pub async fn cash_order(req: Json<CashOrderRequest>, db: Data<AppState>) -> HttpResponse { pub async fn cash_order(req: Json<OrderRequest>, db: Data<AppState>, req_info: HttpRequest) -> HttpResponse {
if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) {
return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() });
}
// load product data // load product data
let mut products = match Product::get_multiple_async(req.products.keys(), &db.db).await { let mut products = match Product::get_multiple_async(req.products.keys(), &db.db).await {
Ok(r) => r, Ok(r) => r,
@ -42,6 +48,7 @@ pub async fn cash_order(req: Json<CashOrderRequest>, db: Data<AppState>) -> Http
order_type: OrderType::Cash, order_type: OrderType::Cash,
total_usd: price_total, total_usd: price_total,
products: req.products.clone(), products: req.products.clone(),
stripe_status: StripeStatus::NotStripeTransaction
}; };
if req.adjust_stock { if req.adjust_stock {
@ -95,11 +102,15 @@ pub struct OrderResponse {
pub id: u64, pub id: u64,
pub order_type: OrderType, pub order_type: OrderType,
pub total_usd: f64, pub total_usd: f64,
pub products: HashMap<u64, u64> pub products: HashMap<u64, u64>,
pub stripe_status: StripeStatus
} }
#[get("/orders")] #[get("/orders")]
pub async fn get_orders(db: Data<AppState>) -> HttpResponse { pub async fn get_orders(db: Data<AppState>, req_info: HttpRequest) -> HttpResponse {
if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) {
return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() });
}
let orders = match Order::list_async(0.., &db.db).await { let orders = match Order::list_async(0.., &db.db).await {
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {
@ -115,7 +126,218 @@ pub async fn get_orders(db: Data<AppState>) -> HttpResponse {
order_type: u.contents.order_type.clone(), order_type: u.contents.order_type.clone(),
total_usd: u.contents.total_usd, total_usd: u.contents.total_usd,
products: u.contents.products.clone(), products: u.contents.products.clone(),
stripe_status: u.contents.stripe_status.clone()
}).collect(); }).collect();
HttpResponse::Ok().json(OrdersResponse { orders: resp }) HttpResponse::Ok().json(OrdersResponse { orders: resp })
} }
#[get("/order/{id}")]
pub async fn get_order(id: Path<u64>, db: Data<AppState>, req_info: HttpRequest) -> HttpResponse {
if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) {
return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() });
}
let orders = match Order::get_async(id.into_inner(), &db.db).await {
Ok(p) => p,
Err(e) => {
return HttpResponse::InternalServerError().json(APIError {
code: "DB_ERROR".to_string(),
message: e.to_string(),
})
}
};
if let Some(u) = orders {
HttpResponse::Ok().json(OrderResponse {
id: u.header.id,
order_type: u.contents.order_type.clone(),
total_usd: u.contents.total_usd,
products: u.contents.products.clone(),
stripe_status: u.contents.stripe_status.clone()
})
} else {
HttpResponse::NotFound().finish()
}
}
#[derive(Serialize)]
pub struct StripeOrderResponse {
pub order_id: u64,
pub checkout_url: String
}
#[post("/stripe_order")]
pub async fn stripe_order(req: Json<OrderRequest>, db: Data<AppState>, req_info: HttpRequest) -> HttpResponse {
if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) {
return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() });
}
// load product data
let mut products = match Product::get_multiple_async(req.products.keys(), &db.db).await {
Ok(r) => r,
Err(e) => {
return HttpResponse::InternalServerError().json(APIError {
code: "DB_ERROR".to_string(),
message: e.to_string(),
})
}
};
let mut products_map: HashMap<u64, Product> = HashMap::new();
for product in &products {
products_map.insert(product.header.id, Product {
name: product.contents.name.clone(),
price_usd: product.contents.price_usd,
stock: product.contents.stock,
});
}
let mut price_total: f64 = 0.0;
for product in &products {
price_total += req.products[&product.header.id] as f64 * product.contents.price_usd;
}
if req.adjust_stock {
for product in &mut products {
if product.contents.stock < req.products[&product.header.id] {
return HttpResponse::BadRequest().json(APIError {
code: "NOT_ENOUGH_STOCK".to_string(),
message: "Cannot sell more items than are in stock backend".to_string(),
});
}
}
}
// Calculate the stripe fee
let f_fixed = 0.30;
let f_percent = 0.029;
let p_charge = (price_total + f_fixed) / (1f64 - f_percent);
let stripe_fee = p_charge - price_total;
debug!("Stripe transactional fee: {} for a {} total", stripe_fee, price_total);
// Create line items
let mut products_filtered = vec![];
for (product_id, product_qty) in &req.products {
if *product_qty != 0 {
products_filtered.push((*product_id, *product_qty));
}
}
let line_items = products_filtered.iter().map(|(id, qty)| {
CreateCheckoutSessionLineItems {
adjustable_quantity: None,
dynamic_tax_rates: None,
price: None,
price_data: Some(CreateCheckoutSessionLineItemsPriceData {
currency: Currency::USD,
product: None,
product_data: Some(CreateCheckoutSessionLineItemsPriceDataProductData {
description: None,
images: None,
metadata: Metadata::from([
("X-LMSPOS-ID".to_string(), id.to_string())
]),
name: products_map[id].name.clone(),
tax_code: None,
}),
recurring: None,
tax_behavior: None,
unit_amount: Some((products_map[id].price_usd * 100f64) as i64),
unit_amount_decimal: None,
}),
quantity: Some(*qty),
tax_rates: None,
}
}).collect();
let doc = Order {
order_type: OrderType::Stripe,
total_usd: price_total,
products: req.products.clone(),
stripe_status: StripeStatus::SessionCreated
};
let order = match doc.push_into_async(&db.db).await {
Ok(r) => r,
Err(e) => {
return HttpResponse::InternalServerError().json(APIError {
code: "DB_ERROR".to_string(),
message: e.to_string(),
})
}
};
let checkout_session = CheckoutSession::create(&db.client, CreateCheckoutSession {
after_expiration: None,
allow_promotion_codes: None,
automatic_tax: None,
billing_address_collection: None,
cancel_url: Some(&format!("http://192.168.254.201:8080/stripe_cancel?session_id={{CHECKOUT_SESSION_ID}}&order={}", order.header.id)),
client_reference_id: None,
consent_collection: None,
currency: None,
custom_fields: None,
custom_text: None,
customer: None,
customer_creation: None,
customer_email: None,
customer_update: None,
discounts: None,
expand: &[],
expires_at: None,
invoice_creation: None,
line_items: Some(line_items),
locale: None,
metadata: Some(Metadata::from([
("X-LMSPOS-Affects-Stock".to_string(), req.adjust_stock.to_string())
])),
mode: Some(CheckoutSessionMode::Payment),
payment_intent_data: None,
payment_method_collection: None,
payment_method_options: None,
payment_method_types: None,
phone_number_collection: None,
setup_intent_data: None,
shipping_address_collection: None,
shipping_options: None,
shipping_rates: None,
submit_type: None,
subscription_data: None,
success_url: &format!("http://192.168.254.201:8080/stripe_finish?session_id={{CHECKOUT_SESSION_ID}}&order={}", order.header.id),
tax_id_collection: None,
});
let session = match checkout_session.await {
Ok(r) => r,
Err(e) => {
return HttpResponse::InternalServerError().json(APIError {
code: "STRIPE_ERR".to_string(),
message: e.to_string(),
})
}
};
debug!("Payment session created! ID: {}", session.id);
if req.adjust_stock {
for product in &mut products {
product.contents.stock -= req.products[&product.header.id];
match product.update_async(&db.db).await {
Ok(_) => (),
Err(e) => {
return HttpResponse::InternalServerError().json(APIError {
code: "DB_ERROR".to_string(),
message: e.to_string(),
})
}
}
}
}
HttpResponse::Ok().json(StripeOrderResponse {
order_id: order.header.id,
checkout_url: session.url.unwrap(),
})
}

View File

@ -1,4 +1,5 @@
use actix_web::{get, post, HttpResponse, put, delete}; use actix_web::{get, post, HttpResponse, put, delete, HttpRequest};
use actix_web::http::header::HeaderValue;
use actix_web::web::{Data, Json, Path}; use actix_web::web::{Data, Json, Path};
use bonsaidb::core::schema::SerializedCollection; use bonsaidb::core::schema::SerializedCollection;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -20,7 +21,10 @@ pub struct ProductResponse {
} }
#[get("/products")] #[get("/products")]
pub async fn get_products(db: Data<AppState>) -> HttpResponse { pub async fn get_products(db: Data<AppState>, req_info: HttpRequest) -> HttpResponse {
if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) {
return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() });
}
let products = match Product::list_async(0.., &db.db).await { let products = match Product::list_async(0.., &db.db).await {
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {
@ -37,7 +41,10 @@ pub async fn get_products(db: Data<AppState>) -> HttpResponse {
} }
#[get("/products/{id}")] #[get("/products/{id}")]
pub async fn get_product(id: Path<u64>, db: Data<AppState>) -> HttpResponse { pub async fn get_product(id: Path<u64>, db: Data<AppState>, req_info: HttpRequest) -> HttpResponse {
if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) {
return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() });
}
let product = match Product::get_async(id.into_inner(), &db.db).await { let product = match Product::get_async(id.into_inner(), &db.db).await {
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {
@ -71,7 +78,10 @@ pub struct CreateProductRequest {
} }
#[post("/products")] #[post("/products")]
pub async fn create_product(req: Json<CreateProductRequest>, db: Data<AppState>) -> HttpResponse { pub async fn create_product(req: Json<CreateProductRequest>, db: Data<AppState>, req_info: HttpRequest) -> HttpResponse {
if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) {
return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() });
}
let document = Product { let document = Product {
name: req.name.clone(), name: req.name.clone(),
price_usd: req.price_usd, price_usd: req.price_usd,
@ -103,7 +113,10 @@ pub struct UpdateProductRequest {
} }
#[put("/products/{id}")] #[put("/products/{id}")]
pub async fn update_product(id: Path<u64>, req: Json<CreateProductRequest>, db: Data<AppState>) -> HttpResponse { pub async fn update_product(id: Path<u64>, req: Json<CreateProductRequest>, db: Data<AppState>, req_info: HttpRequest) -> HttpResponse {
if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) {
return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() });
}
let document = match Product::get_async(id.into_inner(), &db.db).await { let document = match Product::get_async(id.into_inner(), &db.db).await {
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {
@ -144,7 +157,10 @@ pub async fn update_product(id: Path<u64>, req: Json<CreateProductRequest>, db:
} }
#[delete("/products/{id}")] #[delete("/products/{id}")]
pub async fn delete_product(id: Path<u64>, db: Data<AppState>) -> HttpResponse { pub async fn delete_product(id: Path<u64>, db: Data<AppState>, req_info: HttpRequest) -> HttpResponse {
if req_info.headers().get("Authorization") != Some(&HeaderValue::from_str("Bearer 65ed1563-b9a3-4923-8f5a-a0ea955adf97").unwrap()) {
return HttpResponse::Unauthorized().json(APIError { code: "UNAUTHORIZED".to_string(), message: "Unauthorized".to_string() });
}
let document = match Product::get_async(id.into_inner(), &db.db).await { let document = match Product::get_async(id.into_inner(), &db.db).await {
Ok(p) => p, Ok(p) => p,
Err(e) => { Err(e) => {

135
backend/src/route_stripe.rs Normal file
View File

@ -0,0 +1,135 @@
use std::str::FromStr;
use actix_web::{get, HttpResponse};
use actix_web::web::{Data, Path, Query};
use bonsaidb::core::schema::SerializedCollection;
use log::error;
use serde::Deserialize;
use stripe::{CheckoutSession, CheckoutSessionId, CheckoutSessionPaymentStatus};
use crate::AppState;
use crate::db_orders::{Order, StripeStatus};
use crate::db_products::Product;
#[derive(Deserialize)]
pub struct StripeCancelQuery {
session_id: String,
order: u64
}
#[get("/stripe_cancel")]
pub async fn stripe_cancel(req: Query<StripeCancelQuery>, db: Data<AppState>) -> HttpResponse {
// lookup order ID
let order = match Order::get_async(req.order, &db.db).await {
Ok(r) => r,
Err(e) => {
error!("{}", e);
return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.")
}
};
let order = match order {
Some(o) => o,
None => {
return HttpResponse::NotFound().body("We were unable to find your order in our systems. Please try again later.")
}
};
// make sure the order isnt over yet
if order.contents.stripe_status != StripeStatus::SessionCreated {
return HttpResponse::BadRequest().body("This transaction has already been finished or cancelled. Please try again later.")
}
// cancel the order in our database
let mut order = order;
order.contents.stripe_status = StripeStatus::Failed;
match order.update_async(&db.db).await {
Ok(_) => (),
Err(e) => {
error!("{}", e);
return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.")
}
}
// lookup and cancel the order in stripe - do we need to un-adjust stock?
let session = match CheckoutSession::expire(&db.client, &CheckoutSessionId::from_str(&req.session_id).unwrap()).await {
Ok(s) => s,
Err(e) => {
error!("{}", e);
return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.")
}
};
let need_unadjust_stock = session.metadata.get("X-LMSPOS-Affects-Stock") == Some(&String::from("true"));
if need_unadjust_stock {
let mut products = match Product::get_multiple_async(order.contents.products.keys(), &db.db).await {
Ok(r) => r,
Err(e) => {
error!("{}", e);
return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.");
}
};
for product in &mut products {
product.contents.stock += order.contents.products[&product.header.id];
match product.update_async(&db.db).await {
Ok(_) => (),
Err(e) => {
error!("{}", e);
return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.");
}
}
}
}
HttpResponse::Ok().body("Your order has been cancelled. We hope to see you again soon!")
}
#[get("/stripe_finish")]
pub async fn stripe_finish(req: Query<StripeCancelQuery>, db: Data<AppState>) -> HttpResponse {
// lookup order ID
let order = match Order::get_async(req.order, &db.db).await {
Ok(r) => r,
Err(e) => {
error!("{}", e);
return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.")
}
};
let order = match order {
Some(o) => o,
None => {
return HttpResponse::NotFound().body("We were unable to find your order in our systems. Please try again later.")
}
};
// make sure the order isnt over yet
if order.contents.stripe_status != StripeStatus::SessionCreated {
return HttpResponse::BadRequest().body("This transaction has already been finished or cancelled. Please try again later.")
}
// lookup the order in stripe
let session = match CheckoutSession::retrieve(&db.client, &CheckoutSessionId::from_str(&req.session_id).unwrap(), &[]).await {
Ok(s) => s,
Err(e) => {
error!("{}", e);
return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.")
}
};
if session.payment_status != CheckoutSessionPaymentStatus::Paid {
return HttpResponse::BadRequest().body("This transaction has not yet been paid. Please try again later.")
}
// finish the order in our database
let mut order = order;
order.contents.stripe_status = StripeStatus::Paid;
match order.update_async(&db.db).await {
Ok(_) => (),
Err(e) => {
error!("{}", e);
return HttpResponse::InternalServerError().body("There was an error completing your request. Please try again later.")
}
}
HttpResponse::Ok().body("Thank you for your order! Payment has been initiated and your transaction has been completed. We hope to see you again soon!")
}

View File

@ -0,0 +1,3 @@
import { persist } from "$lib/PersistentStore";
export const authToken = persist("auth", "YOUR_TOKEN_HERE");

View File

@ -11,6 +11,10 @@
import HelperText from "@smui/textfield/helper-text"; import HelperText from "@smui/textfield/helper-text";
import Checkbox from "@smui/checkbox"; import Checkbox from "@smui/checkbox";
import FormField from "@smui/form-field"; import FormField from "@smui/form-field";
import CircularProgress from "@smui/circular-progress";
import QRJS from "$lib/components/QRJS.svelte";
import {authToken} from "$lib/stores/AuthStore";
import {browser} from "$app/environment";
let products = []; let products = [];
let productsCounter = {}; let productsCounter = {};
@ -28,7 +32,7 @@
async function loadProducts() { async function loadProducts() {
loaded = false; loaded = false;
try { try {
let resp = await fetch(`${$serverUrl}/products`); let resp = await fetch(`${$serverUrl}/products`, {headers: [["Authorization", "Bearer " + $authToken]]});
if (resp.ok) { if (resp.ok) {
let json = await resp.json(); let json = await resp.json();
console.log(json); console.log(json);
@ -147,7 +151,8 @@
let resp = await fetch(`${$serverUrl}/cash_order`, { let resp = await fetch(`${$serverUrl}/cash_order`, {
method: 'POST', method: 'POST',
headers: [ headers: [
['Content-Type', 'application/json'] ['Content-Type', 'application/json'],
["Authorization", "Bearer " + $authToken]
], ],
body: JSON.stringify({ body: JSON.stringify({
products: productsCounter, products: productsCounter,
@ -174,6 +179,222 @@
return; return;
} }
} }
let stripeOpen = false;
let stripeTransactionFinished = false;
let stripeTransactionStarted = false;
let stripeProgressSpinner = false;
let stripeProgressSpinnerText = "";
let stripeProgressSpinnerClosed = false;
let stripeCanClose = false;
function updateCanClose() {
stripeCanClose = !stripeTransactionFinished;
}
$: stripeTransactionFinished, updateCanClose();
let hasStripeUrl = false;
let order_url = '';
async function startStripeTransaction() {
stripeTransactionStarted = true;
stripeProgressSpinner = true;
stripeProgressSpinnerText = "Contacting Stripe API to initiate transaction...";
let order_id = '';
try {
let resp = await fetch(`${$serverUrl}/stripe_order`, {
method: 'POST',
headers: [
['Content-Type', 'application/json'],
["Authorization", "Bearer " + $authToken]
],
body: JSON.stringify({
products: productsCounter,
adjust_stock: adjustStockOnOrderFinish
})
});
if (resp.ok) {
let json = await resp.json();
loadedCashIn = true;
snackbarText = "Order started! Order ID: " + json.order_id;
console.log(json);
order_url = json.checkout_url;
order_id = json.order_id;
hasStripeUrl = true;
snackbar.open();
} else {
console.error(await resp.text());
snackbarText = "There was an error starting the order. Check the browser console for more information.";
snackbar.open();
stripeProgressSpinnerText = "Transaction failed. Check browser console for more information.";
stripeProgressSpinnerClosed = true;
stripeTransactionFinished = true;
return;
}
} catch (e) {
console.error(e);
snackbarText = "There was an error starting the order. Check the browser console for more information.";
snackbar.open();
stripeProgressSpinnerText = "Transaction failed. Check browser console for more information.";
stripeProgressSpinnerClosed = true;
stripeTransactionFinished = true;
return;
}
stripeProgressSpinnerText = "Waiting for customer...";
async function updateOrderStatus() {
console.log('polling for updated stripe order info');
try {
let resp = await fetch(`${$serverUrl}/order/${order_id}`, {
method: 'GET',
headers: [["Authorization", "Bearer " + $authToken]]
});
if (resp.ok) {
let json = await resp.json();
if (json.stripe_status === 'SessionCreated') {
console.log('result: SessionCreated waiting for customer');
setTimeout(updateOrderStatus, 1000);
return;
} else if (json.stripe_status === 'Paid') {
hasStripeUrl = false;
console.log('result: Paid order complete');
stripeProgressSpinnerText = 'Order complete! Payment was successful.';
stripeProgressSpinnerClosed = true;
stripeTransactionFinished = true;
snackbarText = "Order complete! Order ID: " + json.id;
snackbar.open();
return;
} else {
hasStripeUrl = false;
console.log('result: Other order cancelled');
stripeProgressSpinnerText = 'Order cancelled by customer or payment provider';
stripeProgressSpinnerClosed = true;
stripeTransactionFinished = true;
snackbarText = "Order cancelled! Order ID: " + json.id;
snackbar.open();
return;
}
} else {
hasStripeUrl = false;
console.error(await resp.text());
snackbarText = "There was an error checking the order status. Check the browser console for more information.";
snackbar.open();
stripeProgressSpinnerText = "Transaction failed. Check browser console for more information.";
stripeProgressSpinnerClosed = true;
stripeTransactionFinished = true;
return;
}
} catch (e) {
hasStripeUrl = false;
console.error(e);
snackbarText = "There was an error checking the order status. Check the browser console for more information.";
snackbar.open();
stripeProgressSpinnerText = "Transaction failed. Check browser console for more information.";
stripeProgressSpinnerClosed = true;
stripeTransactionFinished = true;
return;
}
}
await updateOrderStatus();
}
function openStripe() {
stripeTransactionFinished = false;
stripeTransactionStarted = false;
stripeProgressSpinner = false;
stripeProgressSpinnerText = "";
stripeProgressSpinnerClosed = false;
stripeOpen = true;
}
function uuid() {
var uuid = "", i, random;
for (i = 0; i < 32; i++) {
random = Math.random() * 16 | 0;
if (i == 8 || i == 12 || i == 16 || i == 20) {
uuid += "-"
}
uuid += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16);
}
return uuid;
}
let cs_view_sync_code = uuid();
let showCsQrCode = false;
let csQrCodeUrl = "";
onMount(() => {
csQrCodeUrl = `${window.location}customer?cs=${cs_view_sync_code}`;
});
async function resendCs() {
if (!browser) return;
let product_slice = {};
for (let i = 0; i < products.length; i++) {
let product = products[i];
let counter = productsCounter[product.id];
product_slice[product.id] = [
{
name: `${product.name} (#${product.id})`,
price_usd: Math.trunc(product.price_usd * 100)
},
counter
]
}
try {
let resp = await fetch(`${$serverUrl}/custview`, {
method: 'POST',
headers: [
['Content-Type', 'application/json'],
["Authorization", "Bearer " + $authToken]
],
body: JSON.stringify({
sync_id: cs_view_sync_code,
products: product_slice,
price_total: total
})
});
if (!resp.ok) {
console.error(await resp.text());
snackbarText = "There was an error updating the customer view. Check the browser console for more information.";
snackbar.open();
return;
}
} catch (e) {
console.error(e);
snackbarText = "There was an error updating the customer view. Check the browser console for more information.";
snackbar.open();
return;
}
}
$: productsCounter, resendCs();
</script> </script>
<Header selected="pos" /> <Header selected="pos" />
@ -234,7 +455,7 @@
<Button disabled={!hasProducts} variant="outlined" on:click={() => {cashIn = total.toFixed(2); cashDialogOpen = true;}}> <Button disabled={!hasProducts} variant="outlined" on:click={() => {cashIn = total.toFixed(2); cashDialogOpen = true;}}>
<ButtonLabel>Cash In</ButtonLabel> <ButtonLabel>Cash In</ButtonLabel>
</Button> </Button>
<Button disabled={!hasProducts} variant="outlined"> <Button disabled={!hasProducts} variant="outlined" on:click={() => {openStripe()}}>
<ButtonLabel>Stripe</ButtonLabel> <ButtonLabel>Stripe</ButtonLabel>
</Button> </Button>
<Button variant="unelevated" on:click={() => {for (let i = 0; i < products.length; i++) {productsCounter[products[i].id] = 0}}}> <Button variant="unelevated" on:click={() => {for (let i = 0; i < products.length; i++) {productsCounter[products[i].id] = 0}}}>
@ -248,6 +469,19 @@
<LinearProgress indeterminate bind:closed={loaded} aria-label="Data is being loaded..." slot="progress" /> <LinearProgress indeterminate bind:closed={loaded} aria-label="Data is being loaded..." slot="progress" />
</DataTable> </DataTable>
<Textfield label="Customer View Sync Code" bind:value={cs_view_sync_code}></Textfield>
<Button on:click={() => {showCsQrCode = true}}>
<ButtonLabel>Open Customer View</ButtonLabel>
</Button>
<Dialog bind:open={showCsQrCode} aria-labelledby="cs-title" aria-describedby="cs-content">
<DialogTitle id="cs-title">Customer View QR Code</DialogTitle>
<DialogContent id="cs-content">
Use this QR code on another device to open the Customer View.
<QRJS codeValue="" squareSize="200" />
</DialogContent>
</Dialog>
<Dialog bind:open={cashDialogOpen} aria-labelledby="cash-title" aria-describedby="cash-content"> <Dialog bind:open={cashDialogOpen} aria-labelledby="cash-title" aria-describedby="cash-content">
<DialogTitle id="cash-title">Cash In</DialogTitle> <DialogTitle id="cash-title">Cash In</DialogTitle>
<DialogContent id="cash-content"> <DialogContent id="cash-content">
@ -294,3 +528,34 @@
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
<Dialog bind:open={stripeOpen} aria-labelledby="stripe-title" aria-describedby="stripe-content" scrimClickAction="" escapeKeyAction="">
<DialogTitle id="stripe-title">Stripe Transaction</DialogTitle>
<DialogContent id="stripe-content">
<p>Total Price: {formatter.format(total)}</p>
<p>Stripe transactions cannot be stopped once started. Confirm with customer now before starting the transaction.</p>
<Button bind:disabled={stripeTransactionStarted} on:click={startStripeTransaction}>
<ButtonLabel>Start Transaction</ButtonLabel>
</Button>
{#if hasStripeUrl}
<QRJS squareSize="200" codeValue="{order_url}" />
<Button on:click={() => {window.open(order_url, "_blank")}}>
<ButtonLabel>Card In Hand?</ButtonLabel>
</Button>
{/if}
{#if stripeProgressSpinner}
<div style="display: flex; margin-top: 5px;">
<CircularProgress bind:closed={stripeProgressSpinnerClosed} style="height: 32px; width: 32px; margin-right: 5px;" indeterminate />
<span style="margin-top: 3px;">{stripeProgressSpinnerText}</span>
</div>
{/if}
</DialogContent>
<DialogActions>
<Button bind:disabled={stripeCanClose}>
<ButtonLabel>Finish</ButtonLabel>
</Button>
<Button bind:disabled={stripeTransactionStarted}>
<ButtonLabel>Cancel</ButtonLabel>
</Button>
</DialogActions>
</Dialog>

View File

@ -0,0 +1,96 @@
<script lang="ts">
import { onMount } from "svelte";
import {serverUrl} from "$lib/stores/ServerStore";
import Snackbar, {Label as SnackbarLabel} from "@smui/snackbar";
import DataTable, { Head, Body, Row, Cell } from '@smui/data-table';
import LinearProgress from "@smui/linear-progress";
let snackbar: Snackbar;
let snackbarText = "";
let csCode = '';
let csData = {};
let data = [];
const formatter = new Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD'});
let loaded = false;
let total = 0.0;
async function updateData() {
try {
let resp = await fetch(`${$serverUrl}/custview/${csCode}`);
if (resp.ok) {
csData = await resp.json();
console.log(csData);
data = Object.entries(csData);
setTimeout(updateData, 500);
loaded = true;
} else {
console.error(await resp.text());
snackbarText = "There was an error updating the customer view. Check the browser console for more information.";
snackbar.open();
loaded = true;
return;
}
} catch (e) {
console.error(e);
snackbarText = "There was an error updating the customer view. Check the browser console for more information.";
snackbar.open();
loaded = true;
return;
}
}
onMount(() => {
const params = new URLSearchParams(window.location.search);
if (!params.has("cs")) {
snackbarText = "CS code missing. Please scan your QR code again.";
snackbar.open();
return;
}
csCode = params.get("cs");
updateData();
})
</script>
<Snackbar bind:this={snackbar}>
<SnackbarLabel>{snackbarText}</SnackbarLabel>
</Snackbar>
<DataTable table$aria-label="Product list" style="width: 100%;">
<Head>
<Row>
<Cell>Name</Cell>
<Cell>Price Ea</Cell>
<Cell>Qty</Cell>
<Cell>Price</Cell>
</Row>
</Head>
<Body>
{#each data as [num, data]}
{#if data[1] !== 0}
<Row>
<Cell>{data[0].name}</Cell>
<Cell>{formatter.format(data[0].price_usd / 100)}</Cell>
<Cell>{data[1]}</Cell>
<Cell>{formatter.format(data[0].price_usd * data[1] / 100)}</Cell>
</Row>
{/if}
{/each}
<Row>
<Cell numeric></Cell>
<Cell></Cell>
<Cell><b>Total</b></Cell>
<Cell>
{formatter.format(total)}
</Cell>
</Row>
</Body>
<LinearProgress indeterminate bind:closed={loaded} aria-label="Data is being loaded..." slot="progress" />
</DataTable>

View File

@ -8,6 +8,7 @@
import {serverUrl} from "$lib/stores/ServerStore"; import {serverUrl} from "$lib/stores/ServerStore";
import Snackbar from "@smui/snackbar"; import Snackbar from "@smui/snackbar";
import {authToken} from "$lib/stores/AuthStore";
let snackbarTesting: Snackbar; let snackbarTesting: Snackbar;
let snackbarTestSuccess: Snackbar; let snackbarTestSuccess: Snackbar;
@ -36,6 +37,10 @@
snackbarTestFailure.open(); snackbarTestFailure.open();
return; return;
} }
} else {
authTokenTesting.close();
authTokenTestFailure.open();
return;
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
@ -44,6 +49,46 @@
return; return;
} }
} }
let authTokenTesting: Snackbar;
let authTokenTestSuccess: Snackbar;
let authTokenTestFailure: Snackbar;
let changeAuthTokenOpen = false;
let changeAuthTokenFocused;
let changeAuthTokenDirty;
let newAuthToken = $authToken;
let changeAuthTokenInvalid;
async function testAuthTokenConnection() {
authTokenTesting.open();
try {
let resp = await fetch(`${$serverUrl}/auth_status`, {headers: [["Authorization", "Bearer " + $authToken]]});
if (resp.ok) {
let json = await resp.json();
if (json.status === "ok") {
// ok!
authTokenTesting.close();
authTokenTestSuccess.open();
return;
} else {
console.error("Response .status was not ok");
authTokenTesting.close();
authTokenTestFailure.open();
return;
}
} else {
authTokenTesting.close();
authTokenTestFailure.open();
return;
}
} catch (e) {
console.error(e);
authTokenTesting.close();
authTokenTestFailure.open();
return;
}
}
</script> </script>
<Header selected="manage/env" /> <Header selected="manage/env" />
@ -62,6 +107,20 @@
</PaperContent> </PaperContent>
</Paper> </Paper>
<Paper>
<PaperTitle>Authentication Token</PaperTitle>
<PaperSubtitle>Configure the token that LMSPOS uses to authenticate with the backend.</PaperSubtitle>
<PaperContent>
<p>Current: <pre>{$authToken}</pre>
<Button variant="raised" on:click={() => {changeAuthTokenOpen = true}}>
<Label>Change</Label>
</Button>
<Button variant="outlined" on:click={testAuthTokenConnection}>
<Label>Test</Label>
</Button>
</PaperContent>
</Paper>
<Snackbar bind:this={snackbarTesting}> <Snackbar bind:this={snackbarTesting}>
<Label>Testing server connection, this may take a moment...</Label> <Label>Testing server connection, this may take a moment...</Label>
</Snackbar> </Snackbar>
@ -72,6 +131,16 @@
<Label>Connection failed. Check browser console for details.</Label> <Label>Connection failed. Check browser console for details.</Label>
</Snackbar> </Snackbar>
<Snackbar bind:this={authTokenTesting}>
<Label>Testing server connection with auth token, this may take a moment...</Label>
</Snackbar>
<Snackbar bind:this={authTokenTestSuccess}>
<Label>Connection successful!</Label>
</Snackbar>
<Snackbar bind:this={authTokenTestFailure}>
<Label>Connection failed. Check browser console for details.</Label>
</Snackbar>
<Dialog bind:open={changeServerUrlOpen} aria-labelledby="change-title" aria-describedby="change-content"> <Dialog bind:open={changeServerUrlOpen} aria-labelledby="change-title" aria-describedby="change-content">
<DialogTitle id="change-title">Change server URL</DialogTitle> <DialogTitle id="change-title">Change server URL</DialogTitle>
<DialogContent id="change-content"> <DialogContent id="change-content">
@ -91,3 +160,19 @@
</Button> </Button>
</Actions> </Actions>
</Dialog> </Dialog>
<Dialog bind:open={changeAuthTokenOpen} aria-labelledby="token-title" aria-describedby="token-content">
<DialogTitle id="token-title">Change auth token</DialogTitle>
<DialogContent id="token-content">
This is the token that LMSPOS will use to connect to it's backend. It is <i>highly recommended</i> that you use the "Test" button to ensure this is working properly before proceeding. <br>
<Textfield type="text" bind:dirty={changeAuthTokenDirty} bind:invalid={changeAuthTokenInvalid} updateInvalid bind:value={newAuthToken} label="Auth Token" on:focus={() => {changeAuthTokenFocused = true}} on:blur={() => {changeAuthTokenFocused = false}}></Textfield>
</DialogContent>
<Actions>
<Button on:click={() => {authToken.set(newAuthToken); window.location.reload();}}>
<Label>Save</Label>
</Button>
<Button on:click={() => {changeAuthTokenOpen = false; newAuthToken = $authToken;}}>
<Label>Cancel</Label>
</Button>
</Actions>
</Dialog>

View File

@ -6,6 +6,7 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import {serverUrl} from "$lib/stores/ServerStore"; import {serverUrl} from "$lib/stores/ServerStore";
import Paper, { Title as PaperTitle, Content as PaperContent } from "@smui/paper"; import Paper, { Title as PaperTitle, Content as PaperContent } from "@smui/paper";
import {authToken} from "$lib/stores/AuthStore";
let loaded = false; let loaded = false;
let orders = []; let orders = [];
@ -18,7 +19,7 @@
async function loadOrders() { async function loadOrders() {
loaded = false; loaded = false;
try { try {
let resp = await fetch(`${$serverUrl}/orders`); let resp = await fetch(`${$serverUrl}/orders`, {headers: [["Authorization", "Bearer " + $authToken]]});
if (resp.ok) { if (resp.ok) {
let json = await resp.json(); let json = await resp.json();
console.log(json); console.log(json);
@ -45,7 +46,7 @@
async function loadProducts() { async function loadProducts() {
loaded = false; loaded = false;
try { try {
let resp = await fetch(`${$serverUrl}/products`); let resp = await fetch(`${$serverUrl}/products`, {headers: [["Authorization", "Bearer " + $authToken]]});
if (resp.ok) { if (resp.ok) {
let json = await resp.json(); let json = await resp.json();
console.log(json); console.log(json);
@ -108,6 +109,10 @@
let stripe_orders = 0; let stripe_orders = 0;
let cash_orders = 0; let cash_orders = 0;
let stripe_sessioncreated = 0;
let stripe_failed = 0;
let stripe_paid = 0;
function updateStats() { function updateStats() {
for (let i = 0; i < orders.length; i++) { for (let i = 0; i < orders.length; i++) {
if (orders[i].order_type === "Cash") { if (orders[i].order_type === "Cash") {
@ -115,6 +120,13 @@
} }
if (orders[i].order_type === "Stripe") { if (orders[i].order_type === "Stripe") {
stripe_orders += 1; stripe_orders += 1;
if (orders[i].stripe_status === 'SessionCreated') {
stripe_sessioncreated += 1;
} else if (orders[i].stripe_status === 'Failed') {
stripe_failed += 1;
} else if (orders[i].stripe_status === 'Paid') {
stripe_paid += 1;
}
} }
} }
} }
@ -131,6 +143,7 @@
<Cell numeric>ID</Cell> <Cell numeric>ID</Cell>
<Cell style="width: 70%;">Products</Cell> <Cell style="width: 70%;">Products</Cell>
<Cell>Order Type</Cell> <Cell>Order Type</Cell>
<Cell>Stripe Status</Cell>
<Cell>Revenue</Cell> <Cell>Revenue</Cell>
</Row> </Row>
</Head> </Head>
@ -142,6 +155,7 @@
{buildProductsString(order)} {buildProductsString(order)}
</Cell> </Cell>
<Cell>{order.order_type}</Cell> <Cell>{order.order_type}</Cell>
<Cell>{order.stripe_status}</Cell>
<Cell>{formatter.format(order.total_usd)}</Cell> <Cell>{formatter.format(order.total_usd)}</Cell>
</Row> </Row>
{/each} {/each}
@ -149,6 +163,7 @@
<Row> <Row>
<Cell numeric></Cell> <Cell numeric></Cell>
<Cell></Cell> <Cell></Cell>
<Cell></Cell>
<Cell><b>Total Revenue</b></Cell> <Cell><b>Total Revenue</b></Cell>
<Cell>{formatter.format(total.toFixed(2))}</Cell> <Cell>{formatter.format(total.toFixed(2))}</Cell>
</Row> </Row>
@ -165,6 +180,9 @@
<p>order_type = STRIPE: {stripe_orders}</p> <p>order_type = STRIPE: {stripe_orders}</p>
<p>Percentage of orders using CASH: {(cash_orders / orders.length) * 100}%</p> <p>Percentage of orders using CASH: {(cash_orders / orders.length) * 100}%</p>
<p>Percentage of orders using STRIPE: {(stripe_orders / orders.length) * 100}%</p> <p>Percentage of orders using STRIPE: {(stripe_orders / orders.length) * 100}%</p>
<p>Percentage of STRIPE, stripe_status = SessionCreated: {stripe_sessioncreated} ({stripe_sessioncreated / stripe_orders * 100}%)</p>
<p>Percentage of STRIPE, stripe_status = Failed: {stripe_failed} ({stripe_failed / stripe_orders * 100}%)</p>
<p>Percentage of STRIPE, stripe_status = Paid: {stripe_paid} ({stripe_paid / stripe_orders * 100}%)</p>
</PaperContent> </PaperContent>
</Paper> </Paper>

View File

@ -14,6 +14,7 @@
import Tooltip, { Wrapper as TooltipWrapper } from "@smui/tooltip"; import Tooltip, { Wrapper as TooltipWrapper } from "@smui/tooltip";
import "./style.scss"; import "./style.scss";
import {authToken} from "$lib/stores/AuthStore";
let loaded = false; let loaded = false;
let products = []; let products = [];
@ -26,7 +27,7 @@
async function loadProducts() { async function loadProducts() {
loaded = false; loaded = false;
try { try {
let resp = await fetch(`${$serverUrl}/products`); let resp = await fetch(`${$serverUrl}/products`, {headers: [["Authorization", "Bearer " + $authToken]]});
if (resp.ok) { if (resp.ok) {
let json = await resp.json(); let json = await resp.json();
console.log(json); console.log(json);
@ -86,7 +87,8 @@
let resp = await fetch(`${$serverUrl}/products/${editingProductID}`, { let resp = await fetch(`${$serverUrl}/products/${editingProductID}`, {
method: 'PUT', method: 'PUT',
headers: [ headers: [
['Content-Type', 'application/json'] ['Content-Type', 'application/json'],
["Authorization", "Bearer " + $authToken]
], ],
body: JSON.stringify({ body: JSON.stringify({
name: editName, name: editName,
@ -126,7 +128,7 @@
deletingProduct = false; deletingProduct = false;
loaded = false; loaded = false;
try { try {
let resp = await fetch(`${$serverUrl}/products/${id}`, {method: 'DELETE'}); let resp = await fetch(`${$serverUrl}/products/${id}`, {method: 'DELETE', headers: [["Authorization", "Bearer " + $authToken]]});
if (resp.ok) { if (resp.ok) {
loaded = true; loaded = true;
await loadProducts(); await loadProducts();
@ -177,7 +179,8 @@
let resp = await fetch(`${$serverUrl}/products`, { let resp = await fetch(`${$serverUrl}/products`, {
method: 'POST', method: 'POST',
headers: [ headers: [
['Content-Type', 'application/json'] ['Content-Type', 'application/json'],
["Authorization", "Bearer " + $authToken]
], ],
body: JSON.stringify({ body: JSON.stringify({
name: createName, name: createName,