Compare commits
11 Commits
7ac1c519e3
...
SEND010
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ecd17d2dc | ||
| d649748f6f | |||
|
|
61177a4e30 | ||
| c7b9b70d57 | |||
|
|
5030453807 | ||
| 5565081b69 | |||
| b216aa68b7 | |||
|
|
a2c05745ad | ||
|
|
82b2f88122 | ||
|
|
28d74d058b | ||
|
|
6ee146b95a |
@@ -31,7 +31,7 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
github.com/nats-io/nats.go v1.48.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/prometheus/client_golang v1.23.2
|
github.com/prometheus/client_golang v1.23.2
|
||||||
@@ -49,6 +49,6 @@ require (
|
|||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -212,8 +212,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
github.com/nats-io/nats.go v1.48.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
@@ -49,7 +49,7 @@ require (
|
|||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||||
google.golang.org/grpc v1.77.0 // indirect
|
google.golang.org/grpc v1.77.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -212,8 +212,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
github.com/nats-io/nats.go v1.48.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
@@ -50,5 +50,5 @@ require (
|
|||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -212,8 +212,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251211224604-2e727cd2e6fe // indirect
|
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251213223233-751f36331c62 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||||
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
|
github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect
|
||||||
@@ -60,7 +60,7 @@ require (
|
|||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
github.com/nats-io/nats.go v1.48.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
@@ -86,5 +86,5 @@ require (
|
|||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
|||||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251211224604-2e727cd2e6fe h1:Z93WiwkZABbBBb0hGVFSF9nofjiYRvdF7PUxB75oeyE=
|
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251213223233-751f36331c62 h1:Rge3uIIO891+nLqKTfMulCw+tWHtTl16Oudi0yUcAoE=
|
||||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251211224604-2e727cd2e6fe/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
|
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251213223233-751f36331c62/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
|
||||||
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0=
|
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0=
|
||||||
github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
|
github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
@@ -207,8 +207,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -362,8 +362,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
github.com/nats-io/nats.go v1.48.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
@@ -50,5 +50,5 @@ require (
|
|||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -214,8 +214,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
github.com/nats-io/nats.go v1.48.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
@@ -51,5 +51,5 @@ require (
|
|||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -214,8 +214,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
github.com/nats-io/nats.go v1.48.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||||
@@ -52,7 +52,7 @@ require (
|
|||||||
golang.org/x/net v0.48.0 // indirect
|
golang.org/x/net v0.48.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||||
google.golang.org/grpc v1.77.0 // indirect
|
google.golang.org/grpc v1.77.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -227,8 +227,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import (
|
|||||||
// Client exposes typed helpers around the payment orchestrator gRPC API.
|
// Client exposes typed helpers around the payment orchestrator gRPC API.
|
||||||
type Client interface {
|
type Client interface {
|
||||||
QuotePayment(ctx context.Context, req *orchestratorv1.QuotePaymentRequest) (*orchestratorv1.QuotePaymentResponse, error)
|
QuotePayment(ctx context.Context, req *orchestratorv1.QuotePaymentRequest) (*orchestratorv1.QuotePaymentResponse, error)
|
||||||
|
QuotePayments(ctx context.Context, req *orchestratorv1.QuotePaymentsRequest) (*orchestratorv1.QuotePaymentsResponse, error)
|
||||||
|
InitiatePayments(ctx context.Context, req *orchestratorv1.InitiatePaymentsRequest) (*orchestratorv1.InitiatePaymentsResponse, error)
|
||||||
InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error)
|
InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error)
|
||||||
CancelPayment(ctx context.Context, req *orchestratorv1.CancelPaymentRequest) (*orchestratorv1.CancelPaymentResponse, error)
|
CancelPayment(ctx context.Context, req *orchestratorv1.CancelPaymentRequest) (*orchestratorv1.CancelPaymentResponse, error)
|
||||||
GetPayment(ctx context.Context, req *orchestratorv1.GetPaymentRequest) (*orchestratorv1.GetPaymentResponse, error)
|
GetPayment(ctx context.Context, req *orchestratorv1.GetPaymentRequest) (*orchestratorv1.GetPaymentResponse, error)
|
||||||
@@ -29,6 +31,8 @@ type Client interface {
|
|||||||
|
|
||||||
type grpcOrchestratorClient interface {
|
type grpcOrchestratorClient interface {
|
||||||
QuotePayment(ctx context.Context, in *orchestratorv1.QuotePaymentRequest, opts ...grpc.CallOption) (*orchestratorv1.QuotePaymentResponse, error)
|
QuotePayment(ctx context.Context, in *orchestratorv1.QuotePaymentRequest, opts ...grpc.CallOption) (*orchestratorv1.QuotePaymentResponse, error)
|
||||||
|
QuotePayments(ctx context.Context, in *orchestratorv1.QuotePaymentsRequest, opts ...grpc.CallOption) (*orchestratorv1.QuotePaymentsResponse, error)
|
||||||
|
InitiatePayments(ctx context.Context, in *orchestratorv1.InitiatePaymentsRequest, opts ...grpc.CallOption) (*orchestratorv1.InitiatePaymentsResponse, error)
|
||||||
InitiatePayment(ctx context.Context, in *orchestratorv1.InitiatePaymentRequest, opts ...grpc.CallOption) (*orchestratorv1.InitiatePaymentResponse, error)
|
InitiatePayment(ctx context.Context, in *orchestratorv1.InitiatePaymentRequest, opts ...grpc.CallOption) (*orchestratorv1.InitiatePaymentResponse, error)
|
||||||
CancelPayment(ctx context.Context, in *orchestratorv1.CancelPaymentRequest, opts ...grpc.CallOption) (*orchestratorv1.CancelPaymentResponse, error)
|
CancelPayment(ctx context.Context, in *orchestratorv1.CancelPaymentRequest, opts ...grpc.CallOption) (*orchestratorv1.CancelPaymentResponse, error)
|
||||||
GetPayment(ctx context.Context, in *orchestratorv1.GetPaymentRequest, opts ...grpc.CallOption) (*orchestratorv1.GetPaymentResponse, error)
|
GetPayment(ctx context.Context, in *orchestratorv1.GetPaymentRequest, opts ...grpc.CallOption) (*orchestratorv1.GetPaymentResponse, error)
|
||||||
@@ -97,6 +101,18 @@ func (c *orchestratorClient) QuotePayment(ctx context.Context, req *orchestrator
|
|||||||
return c.client.QuotePayment(ctx, req)
|
return c.client.QuotePayment(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *orchestratorClient) QuotePayments(ctx context.Context, req *orchestratorv1.QuotePaymentsRequest) (*orchestratorv1.QuotePaymentsResponse, error) {
|
||||||
|
ctx, cancel := c.callContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
return c.client.QuotePayments(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *orchestratorClient) InitiatePayments(ctx context.Context, req *orchestratorv1.InitiatePaymentsRequest) (*orchestratorv1.InitiatePaymentsResponse, error) {
|
||||||
|
ctx, cancel := c.callContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
return c.client.InitiatePayments(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *orchestratorClient) InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error) {
|
func (c *orchestratorClient) InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error) {
|
||||||
ctx, cancel := c.callContext(ctx)
|
ctx, cancel := c.callContext(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
// Fake implements Client for tests.
|
// Fake implements Client for tests.
|
||||||
type Fake struct {
|
type Fake struct {
|
||||||
QuotePaymentFn func(ctx context.Context, req *orchestratorv1.QuotePaymentRequest) (*orchestratorv1.QuotePaymentResponse, error)
|
QuotePaymentFn func(ctx context.Context, req *orchestratorv1.QuotePaymentRequest) (*orchestratorv1.QuotePaymentResponse, error)
|
||||||
|
QuotePaymentsFn func(ctx context.Context, req *orchestratorv1.QuotePaymentsRequest) (*orchestratorv1.QuotePaymentsResponse, error)
|
||||||
|
InitiatePaymentsFn func(ctx context.Context, req *orchestratorv1.InitiatePaymentsRequest) (*orchestratorv1.InitiatePaymentsResponse, error)
|
||||||
InitiatePaymentFn func(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error)
|
InitiatePaymentFn func(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error)
|
||||||
CancelPaymentFn func(ctx context.Context, req *orchestratorv1.CancelPaymentRequest) (*orchestratorv1.CancelPaymentResponse, error)
|
CancelPaymentFn func(ctx context.Context, req *orchestratorv1.CancelPaymentRequest) (*orchestratorv1.CancelPaymentResponse, error)
|
||||||
GetPaymentFn func(ctx context.Context, req *orchestratorv1.GetPaymentRequest) (*orchestratorv1.GetPaymentResponse, error)
|
GetPaymentFn func(ctx context.Context, req *orchestratorv1.GetPaymentRequest) (*orchestratorv1.GetPaymentResponse, error)
|
||||||
@@ -26,6 +28,20 @@ func (f *Fake) QuotePayment(ctx context.Context, req *orchestratorv1.QuotePaymen
|
|||||||
return &orchestratorv1.QuotePaymentResponse{}, nil
|
return &orchestratorv1.QuotePaymentResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Fake) QuotePayments(ctx context.Context, req *orchestratorv1.QuotePaymentsRequest) (*orchestratorv1.QuotePaymentsResponse, error) {
|
||||||
|
if f.QuotePaymentsFn != nil {
|
||||||
|
return f.QuotePaymentsFn(ctx, req)
|
||||||
|
}
|
||||||
|
return &orchestratorv1.QuotePaymentsResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Fake) InitiatePayments(ctx context.Context, req *orchestratorv1.InitiatePaymentsRequest) (*orchestratorv1.InitiatePaymentsResponse, error) {
|
||||||
|
if f.InitiatePaymentsFn != nil {
|
||||||
|
return f.InitiatePaymentsFn(ctx, req)
|
||||||
|
}
|
||||||
|
return &orchestratorv1.InitiatePaymentsResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Fake) InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error) {
|
func (f *Fake) InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error) {
|
||||||
if f.InitiatePaymentFn != nil {
|
if f.InitiatePaymentFn != nil {
|
||||||
return f.InitiatePaymentFn(ctx, req)
|
return f.InitiatePaymentFn(ctx, req)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ require (
|
|||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
github.com/nats-io/nats.go v1.48.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
@@ -62,5 +62,5 @@ require (
|
|||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -215,8 +215,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -61,6 +61,13 @@ func (f *paymentCommandFactory) QuotePayment() *quotePaymentCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *paymentCommandFactory) QuotePayments() *quotePaymentsCommand {
|
||||||
|
return "ePaymentsCommand{
|
||||||
|
engine: f.engine,
|
||||||
|
logger: f.logger.Named("quote_payments"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (f *paymentCommandFactory) InitiatePayment() *initiatePaymentCommand {
|
func (f *paymentCommandFactory) InitiatePayment() *initiatePaymentCommand {
|
||||||
return &initiatePaymentCommand{
|
return &initiatePaymentCommand{
|
||||||
engine: f.engine,
|
engine: f.engine,
|
||||||
@@ -68,6 +75,13 @@ func (f *paymentCommandFactory) InitiatePayment() *initiatePaymentCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *paymentCommandFactory) InitiatePayments() *initiatePaymentsCommand {
|
||||||
|
return &initiatePaymentsCommand{
|
||||||
|
engine: f.engine,
|
||||||
|
logger: f.logger.Named("initiate_payments"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (f *paymentCommandFactory) CancelPayment() *cancelPaymentCommand {
|
func (f *paymentCommandFactory) CancelPayment() *cancelPaymentCommand {
|
||||||
return &cancelPaymentCommand{
|
return &cancelPaymentCommand{
|
||||||
engine: f.engine,
|
engine: f.engine,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/tech/sendico/payments/orchestrator/storage"
|
"github.com/tech/sendico/payments/orchestrator/storage"
|
||||||
"github.com/tech/sendico/payments/orchestrator/storage/model"
|
"github.com/tech/sendico/payments/orchestrator/storage/model"
|
||||||
@@ -66,6 +67,177 @@ func (h *quotePaymentCommand) Execute(ctx context.Context, req *orchestratorv1.Q
|
|||||||
return gsresponse.Success(&orchestratorv1.QuotePaymentResponse{Quote: quote})
|
return gsresponse.Success(&orchestratorv1.QuotePaymentResponse{Quote: quote})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type quotePaymentsCommand struct {
|
||||||
|
engine paymentEngine
|
||||||
|
logger mlogger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *quotePaymentsCommand) Execute(ctx context.Context, req *orchestratorv1.QuotePaymentsRequest) gsresponse.Responder[orchestratorv1.QuotePaymentsResponse] {
|
||||||
|
if err := h.engine.EnsureRepository(ctx); err != nil {
|
||||||
|
return gsresponse.Unavailable[orchestratorv1.QuotePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
if req == nil {
|
||||||
|
return gsresponse.InvalidArgument[orchestratorv1.QuotePaymentsResponse](h.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("nil request"))
|
||||||
|
}
|
||||||
|
orgRef, orgID, err := validateMetaAndOrgRef(req.GetMeta())
|
||||||
|
if err != nil {
|
||||||
|
return gsresponse.InvalidArgument[orchestratorv1.QuotePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
intents := req.GetIntents()
|
||||||
|
if len(intents) == 0 {
|
||||||
|
return gsresponse.InvalidArgument[orchestratorv1.QuotePaymentsResponse](h.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("intents are required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
baseKey := strings.TrimSpace(req.GetIdempotencyKey())
|
||||||
|
quotes := make([]*orchestratorv1.PaymentQuote, 0, len(intents))
|
||||||
|
expires := make([]time.Time, 0, len(intents))
|
||||||
|
for i, intent := range intents {
|
||||||
|
if err := requireNonNilIntent(intent); err != nil {
|
||||||
|
return gsresponse.InvalidArgument[orchestratorv1.QuotePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
quoteReq := &orchestratorv1.QuotePaymentRequest{
|
||||||
|
Meta: req.GetMeta(),
|
||||||
|
IdempotencyKey: perIntentIdempotencyKey(baseKey, i, len(intents)),
|
||||||
|
Intent: intent,
|
||||||
|
PreviewOnly: req.GetPreviewOnly(),
|
||||||
|
}
|
||||||
|
quote, expiresAt, err := h.engine.BuildPaymentQuote(ctx, orgRef, quoteReq)
|
||||||
|
if err != nil {
|
||||||
|
return gsresponse.Auto[orchestratorv1.QuotePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
quotes = append(quotes, quote)
|
||||||
|
expires = append(expires, expiresAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregate, err := aggregatePaymentQuotes(quotes)
|
||||||
|
if err != nil {
|
||||||
|
return gsresponse.Auto[orchestratorv1.QuotePaymentsResponse](h.logger, mservice.PaymentOrchestrator, merrors.InternalWrap(err, "quote aggregation failed"))
|
||||||
|
}
|
||||||
|
expiresAt, ok := minQuoteExpiry(expires)
|
||||||
|
if !ok {
|
||||||
|
return gsresponse.Auto[orchestratorv1.QuotePaymentsResponse](h.logger, mservice.PaymentOrchestrator, merrors.Internal("quote expiry missing"))
|
||||||
|
}
|
||||||
|
|
||||||
|
quoteRef := ""
|
||||||
|
if !req.GetPreviewOnly() {
|
||||||
|
quotesStore, err := ensureQuotesStore(h.engine.Repository())
|
||||||
|
if err != nil {
|
||||||
|
return gsresponse.Unavailable[orchestratorv1.QuotePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
quoteRef = primitive.NewObjectID().Hex()
|
||||||
|
record := &model.PaymentQuoteRecord{
|
||||||
|
QuoteRef: quoteRef,
|
||||||
|
Intents: intentsFromProto(intents),
|
||||||
|
Quotes: quoteSnapshotsFromProto(quotes),
|
||||||
|
ExpiresAt: expiresAt,
|
||||||
|
}
|
||||||
|
record.SetID(primitive.NewObjectID())
|
||||||
|
record.SetOrganizationRef(orgID)
|
||||||
|
if err := quotesStore.Create(ctx, record); err != nil {
|
||||||
|
return gsresponse.Auto[orchestratorv1.QuotePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
h.logger.Info("stored payment quotes", zap.String("quote_ref", quoteRef), zap.String("org_ref", orgID.Hex()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return gsresponse.Success(&orchestratorv1.QuotePaymentsResponse{
|
||||||
|
QuoteRef: quoteRef,
|
||||||
|
Aggregate: aggregate,
|
||||||
|
Quotes: quotes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type initiatePaymentsCommand struct {
|
||||||
|
engine paymentEngine
|
||||||
|
logger mlogger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *initiatePaymentsCommand) Execute(ctx context.Context, req *orchestratorv1.InitiatePaymentsRequest) gsresponse.Responder[orchestratorv1.InitiatePaymentsResponse] {
|
||||||
|
if err := h.engine.EnsureRepository(ctx); err != nil {
|
||||||
|
return gsresponse.Unavailable[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
if req == nil {
|
||||||
|
return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("nil request"))
|
||||||
|
}
|
||||||
|
_, orgID, err := validateMetaAndOrgRef(req.GetMeta())
|
||||||
|
if err != nil {
|
||||||
|
return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
idempotencyKey, err := requireIdempotencyKey(req.GetIdempotencyKey())
|
||||||
|
if err != nil {
|
||||||
|
return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
quoteRef := strings.TrimSpace(req.GetQuoteRef())
|
||||||
|
if quoteRef == "" {
|
||||||
|
return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("quote_ref is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
quotesStore, err := ensureQuotesStore(h.engine.Repository())
|
||||||
|
if err != nil {
|
||||||
|
return gsresponse.Unavailable[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
record, err := quotesStore.GetByRef(ctx, orgID, quoteRef)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, storage.ErrQuoteNotFound) {
|
||||||
|
return gsresponse.FailedPrecondition[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, "quote_not_found", merrors.InvalidArgument("quote_ref not found or expired"))
|
||||||
|
}
|
||||||
|
return gsresponse.Auto[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
intents := record.Intents
|
||||||
|
quotes := record.Quotes
|
||||||
|
if len(intents) == 0 && record.Intent.Kind != "" && record.Intent.Kind != model.PaymentKindUnspecified {
|
||||||
|
intents = []model.PaymentIntent{record.Intent}
|
||||||
|
}
|
||||||
|
if len(quotes) == 0 && record.Quote != nil {
|
||||||
|
quotes = []*model.PaymentQuoteSnapshot{record.Quote}
|
||||||
|
}
|
||||||
|
if len(intents) == 0 || len(quotes) == 0 || len(intents) != len(quotes) {
|
||||||
|
return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("stored quote payload is incomplete"))
|
||||||
|
}
|
||||||
|
|
||||||
|
store, err := ensurePaymentsStore(h.engine.Repository())
|
||||||
|
if err != nil {
|
||||||
|
return gsresponse.Unavailable[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
payments := make([]*orchestratorv1.Payment, 0, len(intents))
|
||||||
|
for i := range intents {
|
||||||
|
intentProto := protoIntentFromModel(intents[i])
|
||||||
|
if err := requireNonNilIntent(intentProto); err != nil {
|
||||||
|
return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
quoteProto := modelQuoteToProto(quotes[i])
|
||||||
|
if quoteProto == nil {
|
||||||
|
return gsresponse.InvalidArgument[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, merrors.InvalidArgument("stored quote is empty"))
|
||||||
|
}
|
||||||
|
quoteProto.QuoteRef = quoteRef
|
||||||
|
|
||||||
|
perKey := perIntentIdempotencyKey(idempotencyKey, i, len(intents))
|
||||||
|
if existing, err := getPaymentByIdempotencyKey(ctx, store, orgID, perKey); err == nil && existing != nil {
|
||||||
|
payments = append(payments, toProtoPayment(existing))
|
||||||
|
continue
|
||||||
|
} else if err != nil && !errors.Is(err, storage.ErrPaymentNotFound) {
|
||||||
|
return gsresponse.Auto[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entity := newPayment(orgID, intentProto, perKey, req.GetMetadata(), quoteProto)
|
||||||
|
if err = store.Create(ctx, entity); err != nil {
|
||||||
|
if errors.Is(err, storage.ErrDuplicatePayment) {
|
||||||
|
return gsresponse.Auto[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, merrors.DataConflict("payment already exists"))
|
||||||
|
}
|
||||||
|
return gsresponse.Auto[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.engine.ExecutePayment(ctx, store, entity, quoteProto); err != nil {
|
||||||
|
return gsresponse.Auto[orchestratorv1.InitiatePaymentsResponse](h.logger, mservice.PaymentOrchestrator, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
payments = append(payments, toProtoPayment(entity))
|
||||||
|
}
|
||||||
|
|
||||||
|
return gsresponse.Success(&orchestratorv1.InitiatePaymentsResponse{Payments: payments})
|
||||||
|
}
|
||||||
|
|
||||||
type initiatePaymentCommand struct {
|
type initiatePaymentCommand struct {
|
||||||
engine paymentEngine
|
engine paymentEngine
|
||||||
logger mlogger.Logger
|
logger mlogger.Logger
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package orchestrator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
"github.com/tech/sendico/payments/orchestrator/storage/model"
|
||||||
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
|
orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func perIntentIdempotencyKey(base string, index int, total int) string {
|
||||||
|
base = strings.TrimSpace(base)
|
||||||
|
if base == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if total <= 1 {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d", base, index+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func minQuoteExpiry(expires []time.Time) (time.Time, bool) {
|
||||||
|
var min time.Time
|
||||||
|
for _, exp := range expires {
|
||||||
|
if exp.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if min.IsZero() || exp.Before(min) {
|
||||||
|
min = exp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if min.IsZero() {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
return min, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func aggregatePaymentQuotes(quotes []*orchestratorv1.PaymentQuote) (*orchestratorv1.PaymentQuoteAggregate, error) {
|
||||||
|
if len(quotes) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
debitTotals := map[string]decimal.Decimal{}
|
||||||
|
settlementTotals := map[string]decimal.Decimal{}
|
||||||
|
feeTotals := map[string]decimal.Decimal{}
|
||||||
|
networkTotals := map[string]decimal.Decimal{}
|
||||||
|
|
||||||
|
for _, quote := range quotes {
|
||||||
|
if quote == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := accumulateMoney(debitTotals, quote.GetDebitAmount()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := accumulateMoney(settlementTotals, quote.GetExpectedSettlementAmount()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := accumulateMoney(feeTotals, quote.GetExpectedFeeTotal()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if nf := quote.GetNetworkFee(); nf != nil {
|
||||||
|
if err := accumulateMoney(networkTotals, nf.GetNetworkFee()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &orchestratorv1.PaymentQuoteAggregate{
|
||||||
|
DebitAmounts: totalsToMoney(debitTotals),
|
||||||
|
ExpectedSettlementAmounts: totalsToMoney(settlementTotals),
|
||||||
|
ExpectedFeeTotals: totalsToMoney(feeTotals),
|
||||||
|
NetworkFeeTotals: totalsToMoney(networkTotals),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func accumulateMoney(totals map[string]decimal.Decimal, money *moneyv1.Money) error {
|
||||||
|
if money == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
currency := strings.TrimSpace(money.GetCurrency())
|
||||||
|
if currency == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
amount, err := decimal.NewFromString(money.GetAmount())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if current, ok := totals[currency]; ok {
|
||||||
|
totals[currency] = current.Add(amount)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
totals[currency] = amount
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func totalsToMoney(totals map[string]decimal.Decimal) []*moneyv1.Money {
|
||||||
|
if len(totals) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
currencies := make([]string, 0, len(totals))
|
||||||
|
for currency := range totals {
|
||||||
|
currencies = append(currencies, currency)
|
||||||
|
}
|
||||||
|
sort.Strings(currencies)
|
||||||
|
|
||||||
|
result := make([]*moneyv1.Money, 0, len(currencies))
|
||||||
|
for _, currency := range currencies {
|
||||||
|
amount := totals[currency]
|
||||||
|
result = append(result, &moneyv1.Money{
|
||||||
|
Amount: amount.String(),
|
||||||
|
Currency: currency,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func intentsFromProto(intents []*orchestratorv1.PaymentIntent) []model.PaymentIntent {
|
||||||
|
if len(intents) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := make([]model.PaymentIntent, 0, len(intents))
|
||||||
|
for _, intent := range intents {
|
||||||
|
result = append(result, intentFromProto(intent))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func quoteSnapshotsFromProto(quotes []*orchestratorv1.PaymentQuote) []*model.PaymentQuoteSnapshot {
|
||||||
|
if len(quotes) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := make([]*model.PaymentQuoteSnapshot, 0, len(quotes))
|
||||||
|
for _, quote := range quotes {
|
||||||
|
if quote == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if snapshot := quoteSnapshotToModel(quote); snapshot != nil {
|
||||||
|
result = append(result, snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package orchestrator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/shopspring/decimal"
|
||||||
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
|
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||||
|
orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAggregatePaymentQuotes(t *testing.T) {
|
||||||
|
quotes := []*orchestratorv1.PaymentQuote{
|
||||||
|
{
|
||||||
|
DebitAmount: &moneyv1.Money{Amount: "10", Currency: "USD"},
|
||||||
|
ExpectedSettlementAmount: &moneyv1.Money{Amount: "8", Currency: "EUR"},
|
||||||
|
ExpectedFeeTotal: &moneyv1.Money{Amount: "1", Currency: "USD"},
|
||||||
|
NetworkFee: &chainv1.EstimateTransferFeeResponse{
|
||||||
|
NetworkFee: &moneyv1.Money{Amount: "0.5", Currency: "USD"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DebitAmount: &moneyv1.Money{Amount: "5", Currency: "USD"},
|
||||||
|
ExpectedSettlementAmount: &moneyv1.Money{Amount: "1000", Currency: "NGN"},
|
||||||
|
ExpectedFeeTotal: &moneyv1.Money{Amount: "2", Currency: "USD"},
|
||||||
|
NetworkFee: &chainv1.EstimateTransferFeeResponse{
|
||||||
|
NetworkFee: &moneyv1.Money{Amount: "100", Currency: "NGN"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
agg, err := aggregatePaymentQuotes(quotes)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("aggregatePaymentQuotes returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertMoneyTotals(t, agg.GetDebitAmounts(), map[string]string{"USD": "15"})
|
||||||
|
assertMoneyTotals(t, agg.GetExpectedSettlementAmounts(), map[string]string{"EUR": "8", "NGN": "1000"})
|
||||||
|
assertMoneyTotals(t, agg.GetExpectedFeeTotals(), map[string]string{"USD": "3"})
|
||||||
|
assertMoneyTotals(t, agg.GetNetworkFeeTotals(), map[string]string{"USD": "0.5", "NGN": "100"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAggregatePaymentQuotesInvalidAmount(t *testing.T) {
|
||||||
|
quotes := []*orchestratorv1.PaymentQuote{
|
||||||
|
{
|
||||||
|
DebitAmount: &moneyv1.Money{Amount: "bad", Currency: "USD"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if _, err := aggregatePaymentQuotes(quotes); err == nil {
|
||||||
|
t.Fatal("expected error for invalid amount")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinQuoteExpiry(t *testing.T) {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
later := now.Add(10 * time.Minute)
|
||||||
|
earliest := now.Add(5 * time.Minute)
|
||||||
|
|
||||||
|
min, ok := minQuoteExpiry([]time.Time{later, time.Time{}, earliest})
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected min expiry to be set")
|
||||||
|
}
|
||||||
|
if !min.Equal(earliest) {
|
||||||
|
t.Fatalf("expected min expiry %v, got %v", earliest, min)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := minQuoteExpiry([]time.Time{time.Time{}}); ok {
|
||||||
|
t.Fatal("expected min expiry to be unset")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertMoneyTotals(t *testing.T, list []*moneyv1.Money, expected map[string]string) {
|
||||||
|
t.Helper()
|
||||||
|
got := make(map[string]decimal.Decimal, len(list))
|
||||||
|
for _, item := range list {
|
||||||
|
if item == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val, err := decimal.NewFromString(item.GetAmount())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid money amount %q: %v", item.GetAmount(), err)
|
||||||
|
}
|
||||||
|
got[item.GetCurrency()] = val
|
||||||
|
}
|
||||||
|
if len(got) != len(expected) {
|
||||||
|
t.Fatalf("expected %d totals, got %d", len(expected), len(got))
|
||||||
|
}
|
||||||
|
for currency, amount := range expected {
|
||||||
|
val, err := decimal.NewFromString(amount)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("invalid expected amount %q: %v", amount, err)
|
||||||
|
}
|
||||||
|
gotVal, ok := got[currency]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("missing currency %s", currency)
|
||||||
|
}
|
||||||
|
if !gotVal.Equal(val) {
|
||||||
|
t.Fatalf("expected %s %s, got %s", amount, currency, gotVal.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -117,12 +117,24 @@ func (s *Service) QuotePayment(ctx context.Context, req *orchestratorv1.QuotePay
|
|||||||
return executeUnary(ctx, s, "QuotePayment", s.h.commands.QuotePayment().Execute, req)
|
return executeUnary(ctx, s, "QuotePayment", s.h.commands.QuotePayment().Execute, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QuotePayments aggregates downstream quotes for multiple intents.
|
||||||
|
func (s *Service) QuotePayments(ctx context.Context, req *orchestratorv1.QuotePaymentsRequest) (*orchestratorv1.QuotePaymentsResponse, error) {
|
||||||
|
s.ensureHandlers()
|
||||||
|
return executeUnary(ctx, s, "QuotePayments", s.h.commands.QuotePayments().Execute, req)
|
||||||
|
}
|
||||||
|
|
||||||
// InitiatePayment captures a payment intent and reserves funds orchestration.
|
// InitiatePayment captures a payment intent and reserves funds orchestration.
|
||||||
func (s *Service) InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error) {
|
func (s *Service) InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error) {
|
||||||
s.ensureHandlers()
|
s.ensureHandlers()
|
||||||
return executeUnary(ctx, s, "InitiatePayment", s.h.commands.InitiatePayment().Execute, req)
|
return executeUnary(ctx, s, "InitiatePayment", s.h.commands.InitiatePayment().Execute, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitiatePayments executes multiple payments using a stored quote reference.
|
||||||
|
func (s *Service) InitiatePayments(ctx context.Context, req *orchestratorv1.InitiatePaymentsRequest) (*orchestratorv1.InitiatePaymentsResponse, error) {
|
||||||
|
s.ensureHandlers()
|
||||||
|
return executeUnary(ctx, s, "InitiatePayments", s.h.commands.InitiatePayments().Execute, req)
|
||||||
|
}
|
||||||
|
|
||||||
// CancelPayment attempts to cancel an in-flight payment.
|
// CancelPayment attempts to cancel an in-flight payment.
|
||||||
func (s *Service) CancelPayment(ctx context.Context, req *orchestratorv1.CancelPaymentRequest) (*orchestratorv1.CancelPaymentResponse, error) {
|
func (s *Service) CancelPayment(ctx context.Context, req *orchestratorv1.CancelPaymentRequest) (*orchestratorv1.CancelPaymentResponse, error) {
|
||||||
s.ensureHandlers()
|
s.ensureHandlers()
|
||||||
|
|||||||
@@ -12,10 +12,12 @@ type PaymentQuoteRecord struct {
|
|||||||
storable.Base `bson:",inline" json:",inline"`
|
storable.Base `bson:",inline" json:",inline"`
|
||||||
model.OrganizationBoundBase `bson:",inline" json:",inline"`
|
model.OrganizationBoundBase `bson:",inline" json:",inline"`
|
||||||
|
|
||||||
QuoteRef string `bson:"quoteRef" json:"quoteRef"`
|
QuoteRef string `bson:"quoteRef" json:"quoteRef"`
|
||||||
Intent PaymentIntent `bson:"intent" json:"intent"`
|
Intent PaymentIntent `bson:"intent,omitempty" json:"intent,omitempty"`
|
||||||
Quote *PaymentQuoteSnapshot `bson:"quote" json:"quote"`
|
Intents []PaymentIntent `bson:"intents,omitempty" json:"intents,omitempty"`
|
||||||
ExpiresAt time.Time `bson:"expiresAt" json:"expiresAt"`
|
Quote *PaymentQuoteSnapshot `bson:"quote,omitempty" json:"quote,omitempty"`
|
||||||
|
Quotes []*PaymentQuoteSnapshot `bson:"quotes,omitempty" json:"quotes,omitempty"`
|
||||||
|
ExpiresAt time.Time `bson:"expiresAt" json:"expiresAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collection implements storable.Storable.
|
// Collection implements storable.Storable.
|
||||||
|
|||||||
@@ -73,6 +73,16 @@ func (q *Quotes) Create(ctx context.Context, quote *model.PaymentQuoteRecord) er
|
|||||||
quote.Intent.Attributes[k] = strings.TrimSpace(v)
|
quote.Intent.Attributes[k] = strings.TrimSpace(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(quote.Intents) > 0 {
|
||||||
|
for i := range quote.Intents {
|
||||||
|
if quote.Intents[i].Attributes == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for k, v := range quote.Intents[i].Attributes {
|
||||||
|
quote.Intents[i].Attributes[k] = strings.TrimSpace(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
quote.Update()
|
quote.Update()
|
||||||
|
|
||||||
filter := repository.OrgFilter(quote.OrganizationRef).And(
|
filter := repository.OrgFilter(quote.OrganizationRef).And(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ require (
|
|||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/mattn/go-colorable v0.1.14
|
github.com/mattn/go-colorable v0.1.14
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/nats-io/nats.go v1.47.0
|
github.com/nats-io/nats.go v1.48.0
|
||||||
github.com/prometheus/client_golang v1.23.2
|
github.com/prometheus/client_golang v1.23.2
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/testcontainers/testcontainers-go v0.33.0
|
github.com/testcontainers/testcontainers-go v0.33.0
|
||||||
@@ -93,6 +93,6 @@ require (
|
|||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -106,8 +106,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -269,8 +269,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
|||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
|
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -125,6 +125,13 @@ message PaymentQuote {
|
|||||||
string quote_ref = 8;
|
string quote_ref = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message PaymentQuoteAggregate {
|
||||||
|
repeated common.money.v1.Money debit_amounts = 1;
|
||||||
|
repeated common.money.v1.Money expected_settlement_amounts = 2;
|
||||||
|
repeated common.money.v1.Money expected_fee_totals = 3;
|
||||||
|
repeated common.money.v1.Money network_fee_totals = 4;
|
||||||
|
}
|
||||||
|
|
||||||
message ExecutionRefs {
|
message ExecutionRefs {
|
||||||
string debit_entry_ref = 1;
|
string debit_entry_ref = 1;
|
||||||
string credit_entry_ref = 2;
|
string credit_entry_ref = 2;
|
||||||
@@ -172,6 +179,30 @@ message QuotePaymentResponse {
|
|||||||
PaymentQuote quote = 1;
|
PaymentQuote quote = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message QuotePaymentsRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string idempotency_key = 2;
|
||||||
|
repeated PaymentIntent intents = 3;
|
||||||
|
bool preview_only = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message QuotePaymentsResponse {
|
||||||
|
string quote_ref = 1;
|
||||||
|
PaymentQuoteAggregate aggregate = 2;
|
||||||
|
repeated PaymentQuote quotes = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InitiatePaymentsRequest {
|
||||||
|
RequestMeta meta = 1;
|
||||||
|
string idempotency_key = 2;
|
||||||
|
string quote_ref = 3;
|
||||||
|
map<string, string> metadata = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InitiatePaymentsResponse {
|
||||||
|
repeated Payment payments = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message InitiatePaymentRequest {
|
message InitiatePaymentRequest {
|
||||||
RequestMeta meta = 1;
|
RequestMeta meta = 1;
|
||||||
string idempotency_key = 2;
|
string idempotency_key = 2;
|
||||||
@@ -259,6 +290,8 @@ message InitiateConversionResponse {
|
|||||||
|
|
||||||
service PaymentOrchestrator {
|
service PaymentOrchestrator {
|
||||||
rpc QuotePayment(QuotePaymentRequest) returns (QuotePaymentResponse);
|
rpc QuotePayment(QuotePaymentRequest) returns (QuotePaymentResponse);
|
||||||
|
rpc QuotePayments(QuotePaymentsRequest) returns (QuotePaymentsResponse);
|
||||||
|
rpc InitiatePayments(InitiatePaymentsRequest) returns (InitiatePaymentsResponse);
|
||||||
rpc InitiatePayment(InitiatePaymentRequest) returns (InitiatePaymentResponse);
|
rpc InitiatePayment(InitiatePaymentRequest) returns (InitiatePaymentResponse);
|
||||||
rpc CancelPayment(CancelPaymentRequest) returns (CancelPaymentResponse);
|
rpc CancelPayment(CancelPaymentRequest) returns (CancelPaymentResponse);
|
||||||
rpc GetPayment(GetPaymentRequest) returns (GetPaymentResponse);
|
rpc GetPayment(GetPaymentRequest) returns (GetPaymentResponse);
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ replace github.com/tech/sendico/gateway/chain => ../gateway/chain
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.0
|
github.com/aws/aws-sdk-go-v2 v1.41.0
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.5
|
github.com/aws/aws-sdk-go-v2/config v1.32.6
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.5
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.6
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.93.2
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0
|
||||||
github.com/go-chi/chi/v5 v5.2.3
|
github.com/go-chi/chi/v5 v5.2.3
|
||||||
github.com/go-chi/cors v1.2.2
|
github.com/go-chi/cors v1.2.2
|
||||||
github.com/go-chi/jwtauth/v5 v5.3.3
|
github.com/go-chi/jwtauth/v5 v5.3.3
|
||||||
@@ -58,7 +58,7 @@ require (
|
|||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect
|
||||||
github.com/aws/smithy-go v1.24.0 // indirect
|
github.com/aws/smithy-go v1.24.0 // indirect
|
||||||
@@ -103,7 +103,7 @@ require (
|
|||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nats-io/nats.go v1.47.0 // indirect
|
github.com/nats-io/nats.go v1.48.0 // indirect
|
||||||
github.com/nats-io/nkeys v0.4.12 // indirect
|
github.com/nats-io/nkeys v0.4.12 // indirect
|
||||||
github.com/nats-io/nuid v1.0.1 // indirect
|
github.com/nats-io/nuid v1.0.1 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
@@ -139,6 +139,6 @@ require (
|
|||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.32.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||||
google.golang.org/grpc v1.77.0 // indirect
|
google.golang.org/grpc v1.77.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgP
|
|||||||
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
|
github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4 h1:489krEF9xIGkOaaX3CE/Be2uWjiXrkCH6gUX+bZA/BU=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.4/go.mod h1:IOAPF6oT9KCsceNTvvYMNHy0+kMF8akOjeDvPENWxp4=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEyRD9Og0QrE8=
|
github.com/aws/aws-sdk-go-v2/config v1.32.6 h1:hFLBGUKjmLAekvi1evLi5hVvFQtSo3GYwi+Bx4lpJf8=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE=
|
github.com/aws/aws-sdk-go-v2/config v1.32.6/go.mod h1:lcUL/gcd8WyjCrMnxez5OXkO3/rwcNmvfno62tnXNcI=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4=
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.6 h1:F9vWao2TwjV2MyiyVS+duza0NIRtAslgLUM0vTA1ZaE=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk=
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.6/go.mod h1:SgHzKjEVsdQr6Opor0ihgWtkWdfRAIwxYzSJ8O85VHY=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
|
||||||
@@ -32,12 +32,12 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy
|
|||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16 h1:NSbvS17MlI2lurYgXnCOLvCFX38sBW4eiVER7+kkgsU=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.16/go.mod h1:SwT8Tmqd4sA6G1qaGdzWCJN99bUmPGHfRwwq3G5Qb+A=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.93.2 h1:U3ygWUhCpiSPYSHOrRhb3gOl9T5Y3kB8k5Vjs//57bE=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0 h1:SWTxh/EcUCDVqi/0s26V6pVUq0BBG7kx0tDTmF/hCgA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.93.2/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.94.0/go.mod h1:79S2BdqCJpScXZA2y+cpZuocWsjGjJINyXnOsf5DTz8=
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
|
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
|
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 h1:eYnlt6QxnFINKzwxP5/Ucs1vkG7VT3Iezmvfgc2waUw=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8 h1:aM/Q24rIlS3bRAhTyFurowU8A0SMyGDtEOY/l/s/1Uw=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.30.8/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70=
|
||||||
@@ -175,8 +175,8 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nats-io/nats.go v1.47.0 h1:YQdADw6J/UfGUd2Oy6tn4Hq6YHxCaJrVKayxxFqYrgM=
|
github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U=
|
||||||
github.com/nats-io/nats.go v1.47.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g=
|
||||||
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
||||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
@@ -361,8 +361,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
|||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
|
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
|||||||
@@ -36,6 +36,27 @@ func (r *QuotePayment) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QuotePayments struct {
|
||||||
|
PaymentBase `json:",inline"`
|
||||||
|
Intents []PaymentIntent `json:"intents"`
|
||||||
|
PreviewOnly bool `json:"previewOnly"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *QuotePayments) Validate() error {
|
||||||
|
if err := r.PaymentBase.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(r.Intents) == 0 {
|
||||||
|
return merrors.InvalidArgument("intents are required", "intents")
|
||||||
|
}
|
||||||
|
for i := range r.Intents {
|
||||||
|
if err := r.Intents[i].Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type InitiatePayment struct {
|
type InitiatePayment struct {
|
||||||
PaymentBase `json:",inline"`
|
PaymentBase `json:",inline"`
|
||||||
Intent *PaymentIntent `json:"intent,omitempty"`
|
Intent *PaymentIntent `json:"intent,omitempty"`
|
||||||
@@ -68,3 +89,18 @@ func (r InitiatePayment) Validate() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InitiatePayments struct {
|
||||||
|
PaymentBase `json:",inline"`
|
||||||
|
QuoteRef string `json:"quoteRef,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r InitiatePayments) Validate() error {
|
||||||
|
if err := r.PaymentBase.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.QuoteRef == "" {
|
||||||
|
return merrors.InvalidArgument("quoteRef is required", "quoteRef")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,3 +14,19 @@ func toMoney(m *moneyv1.Money) *model.Money {
|
|||||||
Currency: m.GetCurrency(),
|
Currency: m.GetCurrency(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toMoneyList(list []*moneyv1.Money) []*model.Money {
|
||||||
|
if len(list) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := make([]*model.Money, 0, len(list))
|
||||||
|
for _, item := range list {
|
||||||
|
if m := toMoney(item); m != nil {
|
||||||
|
result = append(result, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(result) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,6 +49,19 @@ type PaymentQuote struct {
|
|||||||
FxQuote *FxQuote `json:"fxQuote,omitempty"`
|
FxQuote *FxQuote `json:"fxQuote,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PaymentQuoteAggregate struct {
|
||||||
|
DebitAmounts []*model.Money `json:"debitAmounts,omitempty"`
|
||||||
|
ExpectedSettlementAmounts []*model.Money `json:"expectedSettlementAmounts,omitempty"`
|
||||||
|
ExpectedFeeTotals []*model.Money `json:"expectedFeeTotals,omitempty"`
|
||||||
|
NetworkFeeTotals []*model.Money `json:"networkFeeTotals,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaymentQuotes struct {
|
||||||
|
QuoteRef string `json:"quoteRef,omitempty"`
|
||||||
|
Aggregate *PaymentQuoteAggregate `json:"aggregate,omitempty"`
|
||||||
|
Quotes []PaymentQuote `json:"quotes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type Payment struct {
|
type Payment struct {
|
||||||
PaymentRef string `json:"paymentRef,omitempty"`
|
PaymentRef string `json:"paymentRef,omitempty"`
|
||||||
IdempotencyKey string `json:"idempotencyKey,omitempty"`
|
IdempotencyKey string `json:"idempotencyKey,omitempty"`
|
||||||
@@ -63,6 +76,16 @@ type paymentQuoteResponse struct {
|
|||||||
Quote *PaymentQuote `json:"quote"`
|
Quote *PaymentQuote `json:"quote"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type paymentQuotesResponse struct {
|
||||||
|
authResponse `json:",inline"`
|
||||||
|
Quote *PaymentQuotes `json:"quote"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type paymentsResponse struct {
|
||||||
|
authResponse `json:",inline"`
|
||||||
|
Payments []Payment `json:"payments"`
|
||||||
|
}
|
||||||
|
|
||||||
type paymentResponse struct {
|
type paymentResponse struct {
|
||||||
authResponse `json:",inline"`
|
authResponse `json:",inline"`
|
||||||
Payment *Payment `json:"payment"`
|
Payment *Payment `json:"payment"`
|
||||||
@@ -76,6 +99,22 @@ func PaymentQuoteResponse(logger mlogger.Logger, quote *orchestratorv1.PaymentQu
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PaymentQuotes wraps batch quotes with refreshed access token.
|
||||||
|
func PaymentQuotesResponse(logger mlogger.Logger, resp *orchestratorv1.QuotePaymentsResponse, token *TokenData) http.HandlerFunc {
|
||||||
|
return response.Ok(logger, paymentQuotesResponse{
|
||||||
|
Quote: toPaymentQuotes(resp),
|
||||||
|
authResponse: authResponse{AccessToken: *token},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payments wraps a list of payments with refreshed access token.
|
||||||
|
func PaymentsResponse(logger mlogger.Logger, payments []*orchestratorv1.Payment, token *TokenData) http.HandlerFunc {
|
||||||
|
return response.Ok(logger, paymentsResponse{
|
||||||
|
Payments: toPayments(payments),
|
||||||
|
authResponse: authResponse{AccessToken: *token},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Payment wraps a payment with refreshed access token.
|
// Payment wraps a payment with refreshed access token.
|
||||||
func PaymentResponse(logger mlogger.Logger, payment *orchestratorv1.Payment, token *TokenData) http.HandlerFunc {
|
func PaymentResponse(logger mlogger.Logger, payment *orchestratorv1.Payment, token *TokenData) http.HandlerFunc {
|
||||||
return response.Ok(logger, paymentResponse{
|
return response.Ok(logger, paymentResponse{
|
||||||
@@ -158,6 +197,54 @@ func toPaymentQuote(q *orchestratorv1.PaymentQuote) *PaymentQuote {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toPaymentQuoteAggregate(q *orchestratorv1.PaymentQuoteAggregate) *PaymentQuoteAggregate {
|
||||||
|
if q == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &PaymentQuoteAggregate{
|
||||||
|
DebitAmounts: toMoneyList(q.GetDebitAmounts()),
|
||||||
|
ExpectedSettlementAmounts: toMoneyList(q.GetExpectedSettlementAmounts()),
|
||||||
|
ExpectedFeeTotals: toMoneyList(q.GetExpectedFeeTotals()),
|
||||||
|
NetworkFeeTotals: toMoneyList(q.GetNetworkFeeTotals()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPaymentQuotes(resp *orchestratorv1.QuotePaymentsResponse) *PaymentQuotes {
|
||||||
|
if resp == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
quotes := make([]PaymentQuote, 0, len(resp.GetQuotes()))
|
||||||
|
for _, quote := range resp.GetQuotes() {
|
||||||
|
if dto := toPaymentQuote(quote); dto != nil {
|
||||||
|
quotes = append(quotes, *dto)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(quotes) == 0 {
|
||||||
|
quotes = nil
|
||||||
|
}
|
||||||
|
return &PaymentQuotes{
|
||||||
|
QuoteRef: resp.GetQuoteRef(),
|
||||||
|
Aggregate: toPaymentQuoteAggregate(resp.GetAggregate()),
|
||||||
|
Quotes: quotes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPayments(items []*orchestratorv1.Payment) []Payment {
|
||||||
|
if len(items) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := make([]Payment, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
if p := toPayment(item); p != nil {
|
||||||
|
result = append(result, *p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(result) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func toPayment(p *orchestratorv1.Payment) *Payment {
|
func toPayment(p *orchestratorv1.Payment) *Payment {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
74
api/server/internal/server/paymentapiimp/paybatch.go
Normal file
74
api/server/internal/server/paymentapiimp/paybatch.go
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package paymentapiimp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/pkg/api/http/response"
|
||||||
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
|
||||||
|
"github.com/tech/sendico/server/interface/api/srequest"
|
||||||
|
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||||
|
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *PaymentAPI) initiatePaymentsByQuote(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||||
|
orgRef, err := a.oph.GetRef(r)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to parse organization reference for batch payment initiation", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(r)))
|
||||||
|
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
allowed, err := a.enf.Enforce(ctx, a.permissionRef, account.ID, orgRef, primitive.NilObjectID, model.ActionCreate)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to check payments access permissions", zap.Error(err), mutil.PLog(a.oph, r))
|
||||||
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
a.logger.Debug("Access denied when initiating batch payments", mutil.PLog(a.oph, r))
|
||||||
|
return response.AccessDenied(a.logger, a.Name(), "payments write permission denied")
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := decodeInitiatePaymentsPayload(r)
|
||||||
|
if err != nil {
|
||||||
|
return response.BadPayload(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &orchestratorv1.InitiatePaymentsRequest{
|
||||||
|
Meta: &orchestratorv1.RequestMeta{
|
||||||
|
OrganizationRef: orgRef.Hex(),
|
||||||
|
},
|
||||||
|
IdempotencyKey: strings.TrimSpace(payload.IdempotencyKey),
|
||||||
|
QuoteRef: strings.TrimSpace(payload.QuoteRef),
|
||||||
|
Metadata: payload.Metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := a.client.InitiatePayments(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to initiate batch payments", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
||||||
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sresponse.PaymentsResponse(a.logger, resp.GetPayments(), token)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeInitiatePaymentsPayload(r *http.Request) (*srequest.InitiatePayments, error) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
payload := &srequest.InitiatePayments{}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
|
||||||
|
return nil, merrors.InvalidArgument("invalid payload: " + err.Error())
|
||||||
|
}
|
||||||
|
payload.IdempotencyKey = strings.TrimSpace(payload.IdempotencyKey)
|
||||||
|
payload.QuoteRef = strings.TrimSpace(payload.QuoteRef)
|
||||||
|
|
||||||
|
if err := payload.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
@@ -67,6 +67,62 @@ func (a *PaymentAPI) quotePayment(r *http.Request, account *model.Account, token
|
|||||||
return sresponse.PaymentQuoteResponse(a.logger, resp.GetQuote(), token)
|
return sresponse.PaymentQuoteResponse(a.logger, resp.GetQuote(), token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *PaymentAPI) quotePayments(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||||
|
orgRef, err := a.oph.GetRef(r)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to parse organization reference for quotes", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(r)))
|
||||||
|
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
allowed, err := a.enf.Enforce(ctx, a.permissionRef, account.ID, orgRef, primitive.NilObjectID, model.ActionCreate)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to check payments access permissions", zap.Error(err), mutil.PLog(a.oph, r))
|
||||||
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
a.logger.Debug("Access denied when quoting payments", mutil.PLog(a.oph, r))
|
||||||
|
return response.AccessDenied(a.logger, a.Name(), "payments write permission denied")
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := decodeQuotePaymentsPayload(r)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Debug("Failed to decode payload", zap.Error(err), mutil.PLog(a.oph, r))
|
||||||
|
return response.BadPayload(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
if err := payload.Validate(); err != nil {
|
||||||
|
a.logger.Debug("Failed to validate payload", zap.Error(err), mutil.PLog(a.oph, r))
|
||||||
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
intents := make([]*orchestratorv1.PaymentIntent, 0, len(payload.Intents))
|
||||||
|
for i := range payload.Intents {
|
||||||
|
intent, err := mapPaymentIntent(&payload.Intents[i])
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Debug("Failed to map payment intent", zap.Error(err), mutil.PLog(a.oph, r))
|
||||||
|
return response.BadPayload(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
intents = append(intents, intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &orchestratorv1.QuotePaymentsRequest{
|
||||||
|
Meta: &orchestratorv1.RequestMeta{
|
||||||
|
OrganizationRef: orgRef.Hex(),
|
||||||
|
},
|
||||||
|
IdempotencyKey: payload.IdempotencyKey,
|
||||||
|
Intents: intents,
|
||||||
|
PreviewOnly: payload.PreviewOnly,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := a.client.QuotePayments(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("Failed to quote payments", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
||||||
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sresponse.PaymentQuotesResponse(a.logger, resp, token)
|
||||||
|
}
|
||||||
|
|
||||||
func decodeQuotePayload(r *http.Request) (*srequest.QuotePayment, error) {
|
func decodeQuotePayload(r *http.Request) (*srequest.QuotePayment, error) {
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
|
|
||||||
@@ -80,3 +136,17 @@ func decodeQuotePayload(r *http.Request) (*srequest.QuotePayment, error) {
|
|||||||
}
|
}
|
||||||
return payload, nil
|
return payload, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeQuotePaymentsPayload(r *http.Request) (*srequest.QuotePayments, error) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
payload := &srequest.QuotePayments{}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
|
||||||
|
return nil, merrors.InvalidArgument("invalid payload: "+err.Error(), "payload")
|
||||||
|
}
|
||||||
|
payload.IdempotencyKey = strings.TrimSpace(payload.IdempotencyKey)
|
||||||
|
if err := payload.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return payload, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import (
|
|||||||
|
|
||||||
type paymentClient interface {
|
type paymentClient interface {
|
||||||
QuotePayment(ctx context.Context, req *orchestratorv1.QuotePaymentRequest) (*orchestratorv1.QuotePaymentResponse, error)
|
QuotePayment(ctx context.Context, req *orchestratorv1.QuotePaymentRequest) (*orchestratorv1.QuotePaymentResponse, error)
|
||||||
|
QuotePayments(ctx context.Context, req *orchestratorv1.QuotePaymentsRequest) (*orchestratorv1.QuotePaymentsResponse, error)
|
||||||
|
InitiatePayments(ctx context.Context, req *orchestratorv1.InitiatePaymentsRequest) (*orchestratorv1.InitiatePaymentsResponse, error)
|
||||||
InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error)
|
InitiatePayment(ctx context.Context, req *orchestratorv1.InitiatePaymentRequest) (*orchestratorv1.InitiatePaymentResponse, error)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
@@ -66,8 +68,10 @@ func CreateAPI(apiCtx eapi.API) (*PaymentAPI, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/quote"), api.Post, p.quotePayment)
|
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/quote"), api.Post, p.quotePayment)
|
||||||
|
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/multiquote"), api.Post, p.quotePayments)
|
||||||
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/immediate"), api.Post, p.initiateImmediate)
|
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/immediate"), api.Post, p.initiateImmediate)
|
||||||
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/by-quote"), api.Post, p.initiateByQuote)
|
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/by-quote"), api.Post, p.initiateByQuote)
|
||||||
|
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/by-multiquote"), api.Post, p.initiatePaymentsByQuote)
|
||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|||||||
20
frontend/pshared/lib/api/requests/username.dart
Normal file
20
frontend/pshared/lib/api/requests/username.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'username.g.dart';
|
||||||
|
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class ResetUserNameRequest {
|
||||||
|
final String userName;
|
||||||
|
|
||||||
|
const ResetUserNameRequest({
|
||||||
|
required this.userName,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ResetUserNameRequest.fromJson(Map<String, dynamic> json) => _$ResetUserNameRequestFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$ResetUserNameRequestToJson(this);
|
||||||
|
|
||||||
|
static ResetUserNameRequest build({
|
||||||
|
required String userName,
|
||||||
|
}) => ResetUserNameRequest(userName: userName);
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ class CommonConstants {
|
|||||||
static String apiEndpoint = '/api/v1';
|
static String apiEndpoint = '/api/v1';
|
||||||
static String amplitudeSecret = 'c3d75b3e2520d708440acbb16b923e79';
|
static String amplitudeSecret = 'c3d75b3e2520d708440acbb16b923e79';
|
||||||
static String amplitudeServerZone = 'EU';
|
static String amplitudeServerZone = 'EU';
|
||||||
|
static String posthogApiKey = 'phc_lVhbruaZpxiQxppHBJpL36ARnPlkqbCewv6cauoceTN';
|
||||||
|
static String posthogHost = 'https://eu.i.posthog.com';
|
||||||
static Locale defaultLocale = const Locale('en');
|
static Locale defaultLocale = const Locale('en');
|
||||||
static String defaultCurrency = 'EUR';
|
static String defaultCurrency = 'EUR';
|
||||||
static int defaultDimensionLength = 500;
|
static int defaultDimensionLength = 500;
|
||||||
@@ -36,6 +38,8 @@ class CommonConstants {
|
|||||||
apiEndpoint = configJson['apiEndpoint'] ?? apiEndpoint;
|
apiEndpoint = configJson['apiEndpoint'] ?? apiEndpoint;
|
||||||
amplitudeSecret = configJson['amplitudeSecret'] ?? amplitudeSecret;
|
amplitudeSecret = configJson['amplitudeSecret'] ?? amplitudeSecret;
|
||||||
amplitudeServerZone = configJson['amplitudeServerZone'] ?? amplitudeServerZone;
|
amplitudeServerZone = configJson['amplitudeServerZone'] ?? amplitudeServerZone;
|
||||||
|
posthogApiKey = configJson['posthogApiKey'] ?? posthogApiKey;
|
||||||
|
posthogHost = configJson['posthogHost'] ?? posthogHost;
|
||||||
defaultLocale = Locale(configJson['defaultLocale'] ?? defaultLocale.languageCode);
|
defaultLocale = Locale(configJson['defaultLocale'] ?? defaultLocale.languageCode);
|
||||||
defaultCurrency = configJson['defaultCurrency'] ?? defaultCurrency;
|
defaultCurrency = configJson['defaultCurrency'] ?? defaultCurrency;
|
||||||
wsProto = configJson['wsProto'] ?? wsProto;
|
wsProto = configJson['wsProto'] ?? wsProto;
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ class Constants extends CommonConstants {
|
|||||||
static String get currentOrgKey => CommonConstants.currentOrgKey;
|
static String get currentOrgKey => CommonConstants.currentOrgKey;
|
||||||
static String get apiUrl => CommonConstants.apiUrl;
|
static String get apiUrl => CommonConstants.apiUrl;
|
||||||
static String get serviceUrl => CommonConstants.serviceUrl;
|
static String get serviceUrl => CommonConstants.serviceUrl;
|
||||||
|
static String get posthogApiKey => CommonConstants.posthogApiKey;
|
||||||
|
static String get posthogHost => CommonConstants.posthogHost;
|
||||||
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
|
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
|
||||||
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
|
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
|
||||||
static String get nilObjectRef => CommonConstants.nilObjectRef;
|
static String get nilObjectRef => CommonConstants.nilObjectRef;
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ extension AppConfigExtension on AppConfig {
|
|||||||
external String? get apiEndpoint;
|
external String? get apiEndpoint;
|
||||||
external String? get amplitudeSecret;
|
external String? get amplitudeSecret;
|
||||||
external String? get amplitudeServerZone;
|
external String? get amplitudeServerZone;
|
||||||
|
external String? get posthogApiKey;
|
||||||
|
external String? get posthogHost;
|
||||||
external String? get defaultLocale;
|
external String? get defaultLocale;
|
||||||
external String? get wsProto;
|
external String? get wsProto;
|
||||||
external String? get wsEndpoint;
|
external String? get wsEndpoint;
|
||||||
@@ -40,6 +42,8 @@ class Constants extends CommonConstants {
|
|||||||
static String get currentOrgKey => CommonConstants.currentOrgKey;
|
static String get currentOrgKey => CommonConstants.currentOrgKey;
|
||||||
static String get apiUrl => CommonConstants.apiUrl;
|
static String get apiUrl => CommonConstants.apiUrl;
|
||||||
static String get serviceUrl => CommonConstants.serviceUrl;
|
static String get serviceUrl => CommonConstants.serviceUrl;
|
||||||
|
static String get posthogApiKey => CommonConstants.posthogApiKey;
|
||||||
|
static String get posthogHost => CommonConstants.posthogHost;
|
||||||
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
|
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
|
||||||
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
|
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
|
||||||
static String get nilObjectRef => CommonConstants.nilObjectRef;
|
static String get nilObjectRef => CommonConstants.nilObjectRef;
|
||||||
@@ -57,6 +61,8 @@ class Constants extends CommonConstants {
|
|||||||
'apiEndpoint': config.apiEndpoint,
|
'apiEndpoint': config.apiEndpoint,
|
||||||
'amplitudeSecret': config.amplitudeSecret,
|
'amplitudeSecret': config.amplitudeSecret,
|
||||||
'amplitudeServerZone': config.amplitudeServerZone,
|
'amplitudeServerZone': config.amplitudeServerZone,
|
||||||
|
'posthogApiKey': config.posthogApiKey,
|
||||||
|
'posthogHost': config.posthogHost,
|
||||||
'defaultLocale': config.defaultLocale,
|
'defaultLocale': config.defaultLocale,
|
||||||
'wsProto': config.wsProto,
|
'wsProto': config.wsProto,
|
||||||
'wsEndpoint': config.wsEndpoint,
|
'wsEndpoint': config.wsEndpoint,
|
||||||
|
|||||||
7
frontend/pshared/lib/models/auth/state.dart
Normal file
7
frontend/pshared/lib/models/auth/state.dart
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
enum AuthState {
|
||||||
|
idle,
|
||||||
|
checking,
|
||||||
|
ready,
|
||||||
|
empty,
|
||||||
|
error,
|
||||||
|
}
|
||||||
@@ -79,12 +79,13 @@ enum ResourceType {
|
|||||||
@JsonValue('payments')
|
@JsonValue('payments')
|
||||||
payments,
|
payments,
|
||||||
|
|
||||||
@JsonValue('payment_methods')
|
/// Represents payment orchestration service
|
||||||
paymentMethods,
|
|
||||||
|
|
||||||
@JsonValue('payment_orchestrator')
|
@JsonValue('payment_orchestrator')
|
||||||
paymentOrchestrator,
|
paymentOrchestrator,
|
||||||
|
|
||||||
|
@JsonValue('payment_methods')
|
||||||
|
paymentMethods,
|
||||||
|
|
||||||
/// Represents permissions service
|
/// Represents permissions service
|
||||||
@JsonValue('permissions')
|
@JsonValue('permissions')
|
||||||
permissions,
|
permissions,
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pshared/models/auth/state.dart';
|
||||||
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
@@ -20,8 +23,14 @@ import 'package:pshared/utils/exception.dart';
|
|||||||
|
|
||||||
|
|
||||||
class AccountProvider extends ChangeNotifier {
|
class AccountProvider extends ChangeNotifier {
|
||||||
|
AccountProvider();
|
||||||
|
|
||||||
static String get currentUserRef => Constants.nilObjectRef;
|
static String get currentUserRef => Constants.nilObjectRef;
|
||||||
|
|
||||||
|
/// Auth lifecycle state to avoid multiple ad-hoc flags.
|
||||||
|
AuthState _authState = AuthState.idle;
|
||||||
|
AuthState get authState => _authState;
|
||||||
|
|
||||||
// The resource now wraps our Account? state along with its loading/error state.
|
// The resource now wraps our Account? state along with its loading/error state.
|
||||||
Resource<Account?> _resource = Resource(data: null);
|
Resource<Account?> _resource = Resource(data: null);
|
||||||
Resource<Account?> get resource => _resource;
|
Resource<Account?> get resource => _resource;
|
||||||
@@ -52,9 +61,18 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private helper to update the resource and notify listeners.
|
@protected
|
||||||
|
Future<void> onAccountChanged(Account? previous, Account? current) => Future<void>.value();
|
||||||
|
|
||||||
void _setResource(Resource<Account?> newResource) {
|
void _setResource(Resource<Account?> newResource) {
|
||||||
|
final previousAccount = _resource.data;
|
||||||
_resource = newResource;
|
_resource = newResource;
|
||||||
|
final currentAccount = newResource.data;
|
||||||
|
|
||||||
|
if (previousAccount != currentAccount) {
|
||||||
|
unawaited(onAccountChanged(previousAccount, currentAccount));
|
||||||
|
}
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +93,7 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
locale: locale,
|
locale: locale,
|
||||||
));
|
));
|
||||||
if (outcome.account != null) {
|
if (outcome.account != null) {
|
||||||
|
_authState = AuthState.ready;
|
||||||
_setResource(Resource(data: outcome.account, isLoading: false));
|
_setResource(Resource(data: outcome.account, isLoading: false));
|
||||||
_pickupLocale(outcome.account!.locale);
|
_pickupLocale(outcome.account!.locale);
|
||||||
} else {
|
} else {
|
||||||
@@ -84,10 +103,12 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
await VerificationService.requestLoginCode(pending);
|
await VerificationService.requestLoginCode(pending);
|
||||||
_pendingLogin = pending;
|
_pendingLogin = pending;
|
||||||
|
_authState = AuthState.idle;
|
||||||
_setResource(_resource.copyWith(isLoading: false));
|
_setResource(_resource.copyWith(isLoading: false));
|
||||||
}
|
}
|
||||||
return outcome;
|
return outcome;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
_authState = AuthState.error;
|
||||||
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
@@ -95,6 +116,7 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
void completePendingLogin(Account account) {
|
void completePendingLogin(Account account) {
|
||||||
_pendingLogin = null;
|
_pendingLogin = null;
|
||||||
|
_authState = AuthState.ready;
|
||||||
_setResource(Resource(data: account, isLoading: false, error: null));
|
_setResource(Resource(data: account, isLoading: false, error: null));
|
||||||
_pickupLocale(account.locale);
|
_pickupLocale(account.locale);
|
||||||
}
|
}
|
||||||
@@ -102,13 +124,17 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
Future<bool> isAuthorizationStored() async => AuthorizationService.isAuthorizationStored();
|
Future<bool> isAuthorizationStored() async => AuthorizationService.isAuthorizationStored();
|
||||||
|
|
||||||
Future<Account?> restore() async {
|
Future<Account?> restore() async {
|
||||||
|
_authState = AuthState.checking;
|
||||||
|
notifyListeners();
|
||||||
_setResource(_resource.copyWith(isLoading: true, error: null));
|
_setResource(_resource.copyWith(isLoading: true, error: null));
|
||||||
try {
|
try {
|
||||||
final acc = await AccountService.restore();
|
final acc = await AccountService.restore();
|
||||||
|
_authState = AuthState.ready;
|
||||||
_setResource(Resource(data: acc, isLoading: false));
|
_setResource(Resource(data: acc, isLoading: false));
|
||||||
_pickupLocale(acc.locale);
|
_pickupLocale(acc.locale);
|
||||||
return acc;
|
return acc;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
_authState = AuthState.error;
|
||||||
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
@@ -140,11 +166,14 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
|
_authState = AuthState.empty;
|
||||||
_setResource(_resource.copyWith(isLoading: true, error: null));
|
_setResource(_resource.copyWith(isLoading: true, error: null));
|
||||||
|
_pendingLogin = null;
|
||||||
try {
|
try {
|
||||||
await AccountService.logout();
|
await AccountService.logout();
|
||||||
_setResource(Resource(data: null, isLoading: false));
|
_setResource(Resource(data: null, isLoading: false));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
_authState = AuthState.error;
|
||||||
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
@@ -199,6 +228,19 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Account> resetUsername(String userName) async {
|
||||||
|
if (account == null) throw ErrorUnauthorized();
|
||||||
|
_setResource(_resource.copyWith(isLoading: true, error: null));
|
||||||
|
try {
|
||||||
|
final updated = await AccountService.resetUsername(account!, userName);
|
||||||
|
_setResource(Resource(data: updated, isLoading: false));
|
||||||
|
return updated;
|
||||||
|
} catch (e) {
|
||||||
|
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> forgotPassword(String email) async {
|
Future<void> forgotPassword(String email) async {
|
||||||
_setResource(_resource.copyWith(isLoading: true, error: null));
|
_setResource(_resource.copyWith(isLoading: true, error: null));
|
||||||
try {
|
try {
|
||||||
@@ -220,4 +262,15 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> restoreIfPossible() async {
|
||||||
|
if (_authState == AuthState.checking || _authState == AuthState.ready) return;
|
||||||
|
final hasAuth = await AuthorizationService.isAuthorizationStored();
|
||||||
|
if (!hasAuth) {
|
||||||
|
_authState = AuthState.empty;
|
||||||
|
notifyListeners();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await restore();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,4 +80,12 @@ class OrganizationsProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> reset() async {
|
||||||
|
_resource = Resource(data: []);
|
||||||
|
_currentOrg = null;
|
||||||
|
notifyListeners();
|
||||||
|
// Best-effort cleanup of stored selection to avoid using stale org on next login.
|
||||||
|
await SecureStorageService.delete(Constants.currentOrgKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@@ -21,9 +23,17 @@ class PermissionsProvider extends ChangeNotifier {
|
|||||||
Resource<UserAccess> _userAccess = Resource(data: null, isLoading: false, error: null);
|
Resource<UserAccess> _userAccess = Resource(data: null, isLoading: false, error: null);
|
||||||
late OrganizationsProvider _organizations;
|
late OrganizationsProvider _organizations;
|
||||||
bool _isLoaded = false;
|
bool _isLoaded = false;
|
||||||
|
String? _loadedOrgRef;
|
||||||
|
//For permissions to auto-load when an organization is set, so the dashboard no longer hangs waiting for permissions to become ready.
|
||||||
|
|
||||||
void update(OrganizationsProvider venue) {
|
void update(OrganizationsProvider venue) {
|
||||||
_organizations = venue;
|
_organizations = venue;
|
||||||
|
// Trigger a reload when organization changes or when permissions were never loaded.
|
||||||
|
if (_organizations.isOrganizationSet &&
|
||||||
|
_loadedOrgRef != _organizations.current.id &&
|
||||||
|
!_userAccess.isLoading) {
|
||||||
|
unawaited(load());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic wrapper to perform service calls and reload state
|
// Generic wrapper to perform service calls and reload state
|
||||||
@@ -42,7 +52,11 @@ class PermissionsProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Load the [UserAccess] for the current venue.
|
/// Load the [UserAccess] for the current venue.
|
||||||
Future<UserAccess?> load() async {
|
Future<UserAccess?> load() async {
|
||||||
|
if (!_organizations.isOrganizationSet) {
|
||||||
|
// Organization is not ready yet; skip loading until it becomes available.
|
||||||
|
return _userAccess.data;
|
||||||
|
}
|
||||||
_userAccess = _userAccess.copyWith(isLoading: true, error: null);
|
_userAccess = _userAccess.copyWith(isLoading: true, error: null);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
@@ -56,6 +70,7 @@ class PermissionsProvider extends ChangeNotifier {
|
|||||||
_userAccess = _userAccess.copyWith(data: allAccess, isLoading: false);
|
_userAccess = _userAccess.copyWith(data: allAccess, isLoading: false);
|
||||||
}
|
}
|
||||||
_isLoaded = true;
|
_isLoaded = true;
|
||||||
|
_loadedOrgRef = orgRef;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_userAccess = _userAccess.copyWith(
|
_userAccess = _userAccess.copyWith(
|
||||||
error: e is Exception ? e : Exception(e.toString()),
|
error: e is Exception ? e : Exception(e.toString()),
|
||||||
@@ -164,9 +179,14 @@ class PermissionsProvider extends ChangeNotifier {
|
|||||||
void reset() {
|
void reset() {
|
||||||
_userAccess = Resource(data: null, isLoading: false, error: null);
|
_userAccess = Resource(data: null, isLoading: false, error: null);
|
||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
|
_loadedOrgRef = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> resetAsync() async {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
bool canRead(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.read, objectRef: objectRef);
|
bool canRead(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.read, objectRef: objectRef);
|
||||||
bool canUpdate(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.update, objectRef: objectRef);
|
bool canUpdate(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.update, objectRef: objectRef);
|
||||||
bool canDelete(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.delete, objectRef: objectRef);
|
bool canDelete(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.delete, objectRef: objectRef);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
import 'package:pshared/api/requests/signup.dart';
|
import 'package:pshared/api/requests/signup.dart';
|
||||||
@@ -10,6 +9,7 @@ import 'package:pshared/api/requests/password/forgot.dart';
|
|||||||
import 'package:pshared/api/requests/password/reset.dart';
|
import 'package:pshared/api/requests/password/reset.dart';
|
||||||
import 'package:pshared/data/mapper/account/account.dart';
|
import 'package:pshared/data/mapper/account/account.dart';
|
||||||
import 'package:pshared/models/account/account.dart';
|
import 'package:pshared/models/account/account.dart';
|
||||||
|
import 'package:pshared/models/describable.dart';
|
||||||
import 'package:pshared/models/auth/login_outcome.dart';
|
import 'package:pshared/models/auth/login_outcome.dart';
|
||||||
import 'package:pshared/service/authorization/service.dart';
|
import 'package:pshared/service/authorization/service.dart';
|
||||||
import 'package:pshared/service/files.dart';
|
import 'package:pshared/service/files.dart';
|
||||||
@@ -61,6 +61,14 @@ class AccountService {
|
|||||||
await getPOSTResponse(_objectType, 'password/reset/$accountRef/$token', ResetPasswordRequest.build(password: newPassword).toJson());
|
await getPOSTResponse(_objectType, 'password/reset/$accountRef/$token', ResetPasswordRequest.build(password: newPassword).toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<Account> resetUsername(Account account, String userName) async {
|
||||||
|
_logger.fine('Updating username for account: ${account.id}');
|
||||||
|
final updatedAccount = account.copyWith(
|
||||||
|
describable: account.describable.copyWith(name: userName),
|
||||||
|
);
|
||||||
|
return update(updatedAccount);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<Account> changePassword(String oldPassword, String newPassword) async {
|
static Future<Account> changePassword(String oldPassword, String newPassword) async {
|
||||||
_logger.fine('Changing password');
|
_logger.fine('Changing password');
|
||||||
return _getAccount(AuthorizationService.getPATCHResponse(
|
return _getAccount(AuthorizationService.getPATCHResponse(
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ RouteBase payoutShellRoute() => ShellRoute(
|
|||||||
pageBuilder: (context, _) {
|
pageBuilder: (context, _) {
|
||||||
final recipient = context.read<RecipientsProvider>().currentObject;
|
final recipient = context.read<RecipientsProvider>().currentObject;
|
||||||
return NoTransitionPage(
|
return NoTransitionPage(
|
||||||
child: AdressBookRecipientForm(
|
child: AddressBookRecipientForm(
|
||||||
recipient: recipient,
|
recipient: recipient,
|
||||||
onSaved: (_) => context.goToPayout(PayoutDestination.recipients),
|
onSaved: (_) => context.goToPayout(PayoutDestination.recipients),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,7 +9,13 @@
|
|||||||
"usernameErrorInvalid": "Provide a valid email address",
|
"usernameErrorInvalid": "Provide a valid email address",
|
||||||
"usernameUnknownTLD": "Domain .{domain} is not known, please, check it",
|
"usernameUnknownTLD": "Domain .{domain} is not known, please, check it",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
|
"oldPassword": "Current password",
|
||||||
|
"newPassword": "New password",
|
||||||
"confirmPassword": "Confirm password",
|
"confirmPassword": "Confirm password",
|
||||||
|
"changePassword": "Change password",
|
||||||
|
"savePassword": "Save changed password",
|
||||||
|
"changePasswordSuccess": "Password updated",
|
||||||
|
"changePasswordError": "Could not update password",
|
||||||
"passwordValidationRuleDigit": "has digit",
|
"passwordValidationRuleDigit": "has digit",
|
||||||
"passwordValidationRuleUpperCase": "has uppercase letter",
|
"passwordValidationRuleUpperCase": "has uppercase letter",
|
||||||
"passwordValidationRuleLowerCase": "has lowercase letter",
|
"passwordValidationRuleLowerCase": "has lowercase letter",
|
||||||
|
|||||||
@@ -9,7 +9,13 @@
|
|||||||
"usernameErrorInvalid": "Укажите действительный адрес электронной почты",
|
"usernameErrorInvalid": "Укажите действительный адрес электронной почты",
|
||||||
"usernameUnknownTLD": "Домен .{domain} неизвестен, пожалуйста, проверьте его",
|
"usernameUnknownTLD": "Домен .{domain} неизвестен, пожалуйста, проверьте его",
|
||||||
"password": "Пароль",
|
"password": "Пароль",
|
||||||
|
"oldPassword": "Текущий пароль",
|
||||||
|
"newPassword": "Новый пароль",
|
||||||
"confirmPassword": "Подтвердите пароль",
|
"confirmPassword": "Подтвердите пароль",
|
||||||
|
"changePassword": "Изменить пароль",
|
||||||
|
"savePassword": "Сохранить пароль",
|
||||||
|
"changePasswordSuccess": "Пароль обновлен",
|
||||||
|
"changePasswordError": "Не удалось обновить пароль",
|
||||||
"passwordValidationRuleDigit": "содержит цифру",
|
"passwordValidationRuleDigit": "содержит цифру",
|
||||||
"passwordValidationRuleUpperCase": "содержит заглавную букву",
|
"passwordValidationRuleUpperCase": "содержит заглавную букву",
|
||||||
"passwordValidationRuleLowerCase": "содержит строчную букву",
|
"passwordValidationRuleLowerCase": "содержит строчную букву",
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import 'package:pshared/provider/locale.dart';
|
|||||||
import 'package:pshared/provider/permissions.dart';
|
import 'package:pshared/provider/permissions.dart';
|
||||||
import 'package:pshared/provider/account.dart';
|
import 'package:pshared/provider/account.dart';
|
||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
|
import 'package:pshared/provider/payment/amount.dart';
|
||||||
|
import 'package:pshared/provider/payment/quotation.dart';
|
||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||||
|
|
||||||
@@ -26,8 +28,10 @@ import 'package:pweb/providers/wallets.dart';
|
|||||||
import 'package:pweb/providers/wallet_transactions.dart';
|
import 'package:pweb/providers/wallet_transactions.dart';
|
||||||
import 'package:pweb/services/operations.dart';
|
import 'package:pweb/services/operations.dart';
|
||||||
import 'package:pweb/services/payments/history.dart';
|
import 'package:pweb/services/payments/history.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
import 'package:pweb/services/wallet_transactions.dart';
|
import 'package:pweb/services/wallet_transactions.dart';
|
||||||
import 'package:pweb/services/wallets.dart';
|
import 'package:pweb/services/wallets.dart';
|
||||||
|
import 'package:pweb/providers/account.dart';
|
||||||
|
|
||||||
|
|
||||||
void _setupLogging() {
|
void _setupLogging() {
|
||||||
@@ -39,11 +43,9 @@ void _setupLogging() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
await Constants.initialize();
|
|
||||||
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await Constants.initialize();
|
||||||
// await AmplitudeService.initialize();
|
await PosthogService.initialize();
|
||||||
|
|
||||||
|
|
||||||
_setupLogging();
|
_setupLogging();
|
||||||
@@ -56,7 +58,7 @@ void main() async {
|
|||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (_) => LocaleProvider(null)),
|
ChangeNotifierProvider(create: (_) => LocaleProvider(null)),
|
||||||
ChangeNotifierProxyProvider<LocaleProvider, AccountProvider>(
|
ChangeNotifierProxyProvider<LocaleProvider, AccountProvider>(
|
||||||
create: (_) => AccountProvider(),
|
create: (_) => PwebAccountProvider(),
|
||||||
update: (context, localeProvider, provider) => provider!..updateProvider(localeProvider),
|
update: (context, localeProvider, provider) => provider!..updateProvider(localeProvider),
|
||||||
),
|
),
|
||||||
ChangeNotifierProxyProvider<AccountProvider, TwoFactorProvider>(
|
ChangeNotifierProxyProvider<AccountProvider, TwoFactorProvider>(
|
||||||
@@ -69,6 +71,7 @@ void main() async {
|
|||||||
update: (context, orgnization, provider) => provider!..update(orgnization),
|
update: (context, orgnization, provider) => provider!..update(orgnization),
|
||||||
),
|
),
|
||||||
ChangeNotifierProvider(create: (_) => CarouselIndexProvider()),
|
ChangeNotifierProvider(create: (_) => CarouselIndexProvider()),
|
||||||
|
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => UploadHistoryProvider(service: MockUploadHistoryService())..load(),
|
create: (_) => UploadHistoryProvider(service: MockUploadHistoryService())..load(),
|
||||||
),
|
),
|
||||||
@@ -90,9 +93,17 @@ void main() async {
|
|||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => MockPaymentProvider(),
|
create: (_) => MockPaymentProvider(),
|
||||||
),
|
),
|
||||||
|
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => OperationProvider(OperationService())..loadOperations(),
|
create: (_) => OperationProvider(OperationService())..loadOperations(),
|
||||||
),
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (_) => PaymentAmountProvider(),
|
||||||
|
),
|
||||||
|
ChangeNotifierProxyProvider2<OrganizationsProvider, PaymentAmountProvider, QuotationProvider>(
|
||||||
|
create: (_) => QuotationProvider(),
|
||||||
|
update: (context, orgnization, payment, provider) => provider!..update(orgnization, payment),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: const PayApp(),
|
child: const PayApp(),
|
||||||
),
|
),
|
||||||
|
|||||||
1
frontend/pweb/lib/models/edit_state.dart
Normal file
1
frontend/pweb/lib/models/edit_state.dart
Normal file
@@ -0,0 +1 @@
|
|||||||
|
enum EditState { view, edit, saving }
|
||||||
@@ -7,7 +7,7 @@ import 'package:pweb/pages/payment_methods/form.dart';
|
|||||||
import 'package:pweb/pages/payment_methods/icon.dart';
|
import 'package:pweb/pages/payment_methods/icon.dart';
|
||||||
|
|
||||||
|
|
||||||
class AdressBookPaymentMethodTile extends StatefulWidget {
|
class AddressBookPaymentMethodTile extends StatefulWidget {
|
||||||
final PaymentType type;
|
final PaymentType type;
|
||||||
final String title;
|
final String title;
|
||||||
final MethodMap methods;
|
final MethodMap methods;
|
||||||
@@ -18,7 +18,7 @@ class AdressBookPaymentMethodTile extends StatefulWidget {
|
|||||||
final double sizeM;
|
final double sizeM;
|
||||||
final TextStyle? titleTextStyle;
|
final TextStyle? titleTextStyle;
|
||||||
|
|
||||||
const AdressBookPaymentMethodTile({
|
const AddressBookPaymentMethodTile({
|
||||||
super.key,
|
super.key,
|
||||||
required this.type,
|
required this.type,
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -31,10 +31,10 @@ class AdressBookPaymentMethodTile extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AdressBookPaymentMethodTile> createState() => _AdressBookPaymentMethodTileState();
|
State<AddressBookPaymentMethodTile> createState() => _AddressBookPaymentMethodTileState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AdressBookPaymentMethodTileState extends State<AdressBookPaymentMethodTile> {
|
class _AddressBookPaymentMethodTileState extends State<AddressBookPaymentMethodTile> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@@ -14,6 +16,7 @@ import 'package:pshared/provider/recipient/pmethods.dart';
|
|||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/address_book/form/view.dart';
|
import 'package:pweb/pages/address_book/form/view.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
import 'package:pweb/utils/error/snackbar.dart';
|
import 'package:pweb/utils/error/snackbar.dart';
|
||||||
import 'package:pweb/utils/payment/label.dart';
|
import 'package:pweb/utils/payment/label.dart';
|
||||||
import 'package:pweb/utils/snackbar.dart';
|
import 'package:pweb/utils/snackbar.dart';
|
||||||
@@ -21,17 +24,17 @@ import 'package:pweb/utils/snackbar.dart';
|
|||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class AdressBookRecipientForm extends StatefulWidget {
|
class AddressBookRecipientForm extends StatefulWidget {
|
||||||
final Recipient? recipient;
|
final Recipient? recipient;
|
||||||
final ValueChanged<Recipient?>? onSaved;
|
final ValueChanged<Recipient?>? onSaved;
|
||||||
|
|
||||||
const AdressBookRecipientForm({super.key, this.recipient, this.onSaved});
|
const AddressBookRecipientForm({super.key, this.recipient, this.onSaved});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AdressBookRecipientForm> createState() => _AdressBookRecipientFormState();
|
State<AddressBookRecipientForm> createState() => _AddressBookRecipientFormState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AdressBookRecipientFormState extends State<AdressBookRecipientForm> {
|
class _AddressBookRecipientFormState extends State<AddressBookRecipientForm> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
late TextEditingController _nameCtrl;
|
late TextEditingController _nameCtrl;
|
||||||
late TextEditingController _emailCtrl;
|
late TextEditingController _emailCtrl;
|
||||||
@@ -113,11 +116,11 @@ class _AdressBookRecipientFormState extends State<AdressBookRecipientForm> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AmplitudeService.recipientAddCompleted(
|
unawaited(PosthogService.recipientAddCompleted(
|
||||||
// _type,
|
_type,
|
||||||
// _status,
|
_status,
|
||||||
// _methods.keys.toSet(),
|
_methods.keys.toSet(),
|
||||||
// );
|
));
|
||||||
final recipient = await executeActionWithNotification(
|
final recipient = await executeActionWithNotification(
|
||||||
context: context,
|
context: context,
|
||||||
action: _doSave,
|
action: _doSave,
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class FormView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
SizedBox(height: spacingFields),
|
SizedBox(height: spacingFields),
|
||||||
...PaymentType.values.map(
|
...PaymentType.values.map(
|
||||||
(p) => AdressBookPaymentMethodTile(
|
(p) => AddressBookPaymentMethodTile(
|
||||||
type: p,
|
type: p,
|
||||||
title: getPaymentTypeLabel(context, p),
|
title: getPaymentTypeLabel(context, p),
|
||||||
methods: methods,
|
methods: methods,
|
||||||
|
|||||||
37
frontend/pweb/lib/pages/address_book/page/empty.dart
Normal file
37
frontend/pweb/lib/pages/address_book/page/empty.dart
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class RecipientAddressBookEmptyState extends StatelessWidget {
|
||||||
|
final String message;
|
||||||
|
final String actionLabel;
|
||||||
|
final VoidCallback onAction;
|
||||||
|
|
||||||
|
const RecipientAddressBookEmptyState({
|
||||||
|
required this.message,
|
||||||
|
required this.actionLabel,
|
||||||
|
required this.onAction,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final textTheme = Theme.of(context).textTheme;
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.contact_mail_outlined, size: 42),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(message, style: textTheme.titleMedium),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: onAction,
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
label: Text(actionLabel),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
import 'package:pshared/models/recipient/filter.dart';
|
import 'package:pshared/models/recipient/filter.dart';
|
||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
import 'package:pweb/pages/address_book/page/empty.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/address_book/page/filter_button.dart';
|
import 'package:pweb/pages/address_book/page/filter_button.dart';
|
||||||
import 'package:pweb/pages/address_book/page/header.dart';
|
import 'package:pweb/pages/address_book/page/header.dart';
|
||||||
@@ -72,6 +73,7 @@ class _RecipientAddressBookPageState extends State<RecipientAddressBookPage> {
|
|||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
final provider = context.watch<RecipientsProvider>();
|
final provider = context.watch<RecipientsProvider>();
|
||||||
_syncSearchField(provider);
|
_syncSearchField(provider);
|
||||||
|
final filteredRecipients = provider.filteredRecipients;
|
||||||
|
|
||||||
if (provider.isLoading) {
|
if (provider.isLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
@@ -124,15 +126,23 @@ class _RecipientAddressBookPageState extends State<RecipientAddressBookPage> {
|
|||||||
height: RecipientAddressBookPage._expandedHeight,
|
height: RecipientAddressBookPage._expandedHeight,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(RecipientAddressBookPage._paddingAll),
|
padding: const EdgeInsets.all(RecipientAddressBookPage._paddingAll),
|
||||||
child: RecipientAddressBookList(
|
child: provider.recipients.isEmpty
|
||||||
filteredRecipients: provider.filteredRecipients,
|
? RecipientAddressBookEmptyState(
|
||||||
onEdit: (recipient) => widget.onEditRecipient?.call(recipient),
|
message: loc.noRecipientsYet,
|
||||||
onDelete: (recipient) => widget.onDeleteRecipient?.call(recipient),
|
actionLabel: loc.addRecipient,
|
||||||
onSelected: widget.onRecipientSelected,
|
onAction: widget.onAddRecipient,
|
||||||
),
|
)
|
||||||
|
: filteredRecipients.isEmpty
|
||||||
|
? Center(child: Text(loc.noRecipientsFound))
|
||||||
|
: RecipientAddressBookList(
|
||||||
|
filteredRecipients: filteredRecipients,
|
||||||
|
onEdit: (recipient) => widget.onEditRecipient?.call(recipient),
|
||||||
|
onDelete: (recipient) => widget.onDeleteRecipient?.call(recipient),
|
||||||
|
onSelected: widget.onRecipientSelected,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import 'package:pweb/pages/address_book/page/recipient/actions.dart';
|
|||||||
import 'package:pweb/pages/address_book/page/recipient/info_column.dart';
|
import 'package:pweb/pages/address_book/page/recipient/info_column.dart';
|
||||||
import 'package:pweb/pages/address_book/page/recipient/payment_row.dart';
|
import 'package:pweb/pages/address_book/page/recipient/payment_row.dart';
|
||||||
import 'package:pweb/pages/address_book/page/recipient/status.dart';
|
import 'package:pweb/pages/address_book/page/recipient/status.dart';
|
||||||
import 'package:pweb/pages/dashboard/payouts/single/adress_book/avatar.dart';
|
import 'package:pweb/pages/dashboard/payouts/single/address_book/avatar.dart';
|
||||||
|
|
||||||
|
|
||||||
class RecipientAddressBookItem extends StatefulWidget {
|
class RecipientAddressBookItem extends StatefulWidget {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import 'package:pshared/models/recipient/recipient.dart';
|
|||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/dashboard/payouts/single/adress_book/avatar.dart';
|
import 'package:pweb/pages/dashboard/payouts/single/address_book/avatar.dart';
|
||||||
import 'package:pweb/pages/dashboard/payouts/single/adress_book/long_list/info_row.dart';
|
import 'package:pweb/pages/dashboard/payouts/single/address_book/long_list/info_row.dart';
|
||||||
import 'package:pweb/utils/payment/label.dart';
|
import 'package:pweb/utils/payment/label.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/dashboard/payouts/single/adress_book/long_list/item.dart';
|
import 'package:pweb/pages/dashboard/payouts/single/address_book/long_list/item.dart';
|
||||||
|
|
||||||
|
|
||||||
class LongListAdressBookPayout extends StatelessWidget {
|
class LongListAddressBookPayout extends StatelessWidget {
|
||||||
final List<Recipient> filteredRecipients;
|
final List<Recipient> filteredRecipients;
|
||||||
final ValueChanged<Recipient>? onSelected;
|
final ValueChanged<Recipient>? onSelected;
|
||||||
|
|
||||||
const LongListAdressBookPayout({
|
const LongListAddressBookPayout({
|
||||||
super.key,
|
super.key,
|
||||||
required this.filteredRecipients,
|
required this.filteredRecipients,
|
||||||
this.onSelected,
|
this.onSelected,
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class AddressBookPlaceholder extends StatelessWidget {
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
const AddressBookPlaceholder({required this.text});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Center(
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
import 'package:pweb/pages/dashboard/payouts/single/adress_book/avatar.dart';
|
import 'package:pweb/pages/dashboard/payouts/single/address_book/avatar.dart';
|
||||||
|
|
||||||
|
|
||||||
class ShortListAdressBookPayout extends StatelessWidget {
|
class ShortListAddressBookPayout extends StatelessWidget {
|
||||||
final List<Recipient> recipients;
|
final List<Recipient> recipients;
|
||||||
final ValueChanged<Recipient> onSelected;
|
final ValueChanged<Recipient> onSelected;
|
||||||
|
|
||||||
const ShortListAdressBookPayout({
|
const ShortListAddressBookPayout({
|
||||||
super.key,
|
super.key,
|
||||||
required this.recipients,
|
required this.recipients,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
@@ -6,25 +6,26 @@ import 'package:pshared/models/recipient/recipient.dart';
|
|||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/address_book/page/search.dart';
|
import 'package:pweb/pages/address_book/page/search.dart';
|
||||||
import 'package:pweb/pages/dashboard/payouts/single/adress_book/long_list/widget.dart';
|
import 'package:pweb/pages/dashboard/payouts/single/address_book/long_list/widget.dart';
|
||||||
import 'package:pweb/pages/dashboard/payouts/single/adress_book/short_list.dart';
|
import 'package:pweb/pages/dashboard/payouts/single/address_book/placeholder.dart';
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/single/address_book/short_list.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class AdressBookPayout extends StatefulWidget {
|
class AddressBookPayout extends StatefulWidget {
|
||||||
final ValueChanged<Recipient> onSelected;
|
final ValueChanged<Recipient> onSelected;
|
||||||
|
|
||||||
const AdressBookPayout({
|
const AddressBookPayout({
|
||||||
super.key,
|
super.key,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AdressBookPayout> createState() => _AdressBookPayoutState();
|
State<AddressBookPayout> createState() => _AddressBookPayoutState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AdressBookPayoutState extends State<AdressBookPayout> {
|
class _AddressBookPayoutState extends State<AddressBookPayout> {
|
||||||
static const double _expandedHeight = 400;
|
static const double _expandedHeight = 400;
|
||||||
static const double _collapsedHeight = 200;
|
static const double _collapsedHeight = 200;
|
||||||
static const double _cardMargin = 1;
|
static const double _cardMargin = 1;
|
||||||
@@ -58,6 +59,8 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
final provider = context.watch<RecipientsProvider>();
|
final provider = context.watch<RecipientsProvider>();
|
||||||
|
final recipients = provider.recipients;
|
||||||
|
final filteredRecipients = provider.filteredRecipients;
|
||||||
|
|
||||||
if (provider.isLoading) {
|
if (provider.isLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
@@ -87,15 +90,19 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: _spacingBetween),
|
const SizedBox(height: _spacingBetween),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _isExpanded
|
child: recipients.isEmpty
|
||||||
? LongListAdressBookPayout(
|
? AddressBookPlaceholder(text: loc.noRecipientsYet)
|
||||||
filteredRecipients: provider.filteredRecipients,
|
: _isExpanded && filteredRecipients.isEmpty
|
||||||
onSelected: widget.onSelected,
|
? AddressBookPlaceholder(text: loc.noRecipientsFound)
|
||||||
)
|
: _isExpanded
|
||||||
: ShortListAdressBookPayout(
|
? LongListAddressBookPayout(
|
||||||
recipients: provider.recipients,
|
filteredRecipients: filteredRecipients,
|
||||||
onSelected: widget.onSelected,
|
onSelected: widget.onSelected,
|
||||||
),
|
)
|
||||||
|
: ShortListAddressBookPayout(
|
||||||
|
recipients: recipients,
|
||||||
|
onSelected: widget.onSelected,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -103,4 +110,4 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/dashboard/payouts/single/adress_book/avatar.dart';
|
import 'package:pweb/pages/dashboard/payouts/single/address_book/avatar.dart';
|
||||||
|
|
||||||
|
|
||||||
class RecipientHeader extends StatelessWidget{
|
class RecipientHeader extends StatelessWidget{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:pshared/models/payment/type.dart';
|
import 'package:pshared/models/payment/type.dart';
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/dashboard/payouts/single/adress_book/widget.dart';
|
import 'package:pweb/pages/dashboard/payouts/single/address_book/widget.dart';
|
||||||
import 'package:pweb/pages/dashboard/payouts/single/new_recipient/payout.dart';
|
import 'package:pweb/pages/dashboard/payouts/single/new_recipient/payout.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ class SinglePayoutForm extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
AdressBookPayout(onSelected: onRecipientSelected),
|
AddressBookPayout(onSelected: onRecipientSelected),
|
||||||
const SizedBox(height: _spacingBetweenAddressAndForm),
|
const SizedBox(height: _spacingBetweenAddressAndForm),
|
||||||
SinglePayout(onGoToPayment: onGoToPayment),
|
SinglePayout(onGoToPayment: onGoToPayment),
|
||||||
const SizedBox(height: _bottomSpacing),
|
const SizedBox(height: _bottomSpacing),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/auth/state.dart';
|
||||||
import 'package:pshared/provider/account.dart';
|
import 'package:pshared/provider/account.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
import 'package:pweb/app/router/pages.dart';
|
||||||
@@ -10,26 +11,64 @@ import 'package:pweb/widgets/error/snackbar.dart';
|
|||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class AccountLoader extends StatelessWidget {
|
class AccountLoader extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const AccountLoader({super.key, required this.child});
|
const AccountLoader({super.key, required this.child});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Consumer<AccountProvider>(builder: (context, provider, _) {
|
State<AccountLoader> createState() => _AccountLoaderState();
|
||||||
if (provider.isLoading) return const Center(child: CircularProgressIndicator());
|
}
|
||||||
if (provider.error != null) {
|
|
||||||
postNotifyUserOfErrorX(
|
class _AccountLoaderState extends State<AccountLoader> {
|
||||||
context: context,
|
AuthState? _handledState;
|
||||||
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
|
||||||
exception: provider.error!,
|
@override
|
||||||
);
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
Provider.of<AccountProvider>(context, listen: false).restoreIfPossible();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleSideEffects(AccountProvider provider) {
|
||||||
|
if (_handledState == provider.authState) return;
|
||||||
|
_handledState = provider.authState;
|
||||||
|
|
||||||
|
void goToLogin() {
|
||||||
|
if (!mounted) return;
|
||||||
navigateAndReplace(context, Pages.login);
|
navigateAndReplace(context, Pages.login);
|
||||||
}
|
}
|
||||||
if (provider.account == null) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => navigateAndReplace(context, Pages.login));
|
switch (provider.authState) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
case AuthState.error:
|
||||||
|
final error = provider.error ?? Exception('Authorization failed');
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
postNotifyUserOfErrorX(
|
||||||
|
context: context,
|
||||||
|
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
||||||
|
exception: error,
|
||||||
|
);
|
||||||
|
goToLogin();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case AuthState.empty:
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => goToLogin());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return child;
|
}
|
||||||
});
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<AccountProvider>(builder: (context, provider, _) {
|
||||||
|
_handleSideEffects(provider);
|
||||||
|
if (provider.authState == AuthState.ready && provider.account != null) {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
|
||||||
import 'package:pweb/widgets/error/snackbar.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -19,12 +16,20 @@ class OrganizationLoader extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) => Consumer<OrganizationsProvider>(builder: (context, provider, _) {
|
Widget build(BuildContext context) => Consumer<OrganizationsProvider>(builder: (context, provider, _) {
|
||||||
if (provider.isLoading) return const Center(child: CircularProgressIndicator());
|
if (provider.isLoading) return const Center(child: CircularProgressIndicator());
|
||||||
if (provider.error != null) {
|
if (provider.error != null) {
|
||||||
postNotifyUserOfErrorX(
|
final loc = AppLocalizations.of(context)!;
|
||||||
context: context,
|
return Center(
|
||||||
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
child: Column(
|
||||||
exception: provider.error!,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(loc.errorLogin),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: provider.load,
|
||||||
|
child: Text(loc.retry),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
navigateAndReplace(context, Pages.login);
|
|
||||||
}
|
}
|
||||||
if ((provider.error == null) && (!provider.isOrganizationSet)) {
|
if ((provider.error == null) && (!provider.isOrganizationSet)) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
|||||||
@@ -5,36 +5,49 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:pshared/provider/account.dart';
|
import 'package:pshared/provider/account.dart';
|
||||||
import 'package:pshared/provider/permissions.dart';
|
import 'package:pshared/provider/permissions.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
|
||||||
import 'package:pweb/widgets/error/snackbar.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PermissionsLoader extends StatelessWidget {
|
class PermissionsLoader extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const PermissionsLoader({super.key, required this.child});
|
const PermissionsLoader({super.key, required this.child});
|
||||||
|
|
||||||
@override
|
void _triggerLoadIfNeeded(PermissionsProvider provider) {
|
||||||
Widget build(BuildContext context) => Consumer2<PermissionsProvider, AccountProvider>(builder: (context, provider, accountProvider, _) {
|
if (!provider.isLoading && !provider.isReady) {
|
||||||
if (provider.isLoading) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
if (provider.error != null) {
|
|
||||||
postNotifyUserOfErrorX(
|
|
||||||
context: context,
|
|
||||||
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
|
||||||
exception: provider.error!,
|
|
||||||
);
|
|
||||||
navigateAndReplace(context, Pages.login);
|
|
||||||
}
|
|
||||||
if (provider.error == null && !provider.isReady && accountProvider.account != null) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
provider.load();
|
if (!provider.isLoading && !provider.isReady) {
|
||||||
|
provider.load();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
}
|
||||||
return child;
|
}
|
||||||
});
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer2<PermissionsProvider, AccountProvider>(
|
||||||
|
builder: (context, provider, _accountProvider, _) {
|
||||||
|
if (provider.error != null) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(loc.errorLogin),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: provider.load,
|
||||||
|
child: Text(loc.retry),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_triggerLoadIfNeeded(provider);
|
||||||
|
if (provider.isLoading || !provider.isReady) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -14,6 +16,7 @@ import 'package:pweb/widgets/password/password.dart';
|
|||||||
import 'package:pweb/widgets/username.dart';
|
import 'package:pweb/widgets/username.dart';
|
||||||
import 'package:pweb/widgets/vspacer.dart';
|
import 'package:pweb/widgets/vspacer.dart';
|
||||||
import 'package:pweb/widgets/error/snackbar.dart';
|
import 'package:pweb/widgets/error/snackbar.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
@@ -43,6 +46,7 @@ class _LoginFormState extends State<LoginForm> {
|
|||||||
password: _passwordController.text,
|
password: _passwordController.text,
|
||||||
locale: context.read<LocaleProvider>().locale.languageCode,
|
locale: context.read<LocaleProvider>().locale.languageCode,
|
||||||
);
|
);
|
||||||
|
unawaited(PosthogService.login(pending: outcome.isPending));
|
||||||
if (outcome.isPending) {
|
if (outcome.isPending) {
|
||||||
// TODO: fix context usage
|
// TODO: fix context usage
|
||||||
navigateAndReplace(context, Pages.sfactor);
|
navigateAndReplace(context, Pages.sfactor);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:pweb/providers/payment_flow.dart';
|
|||||||
import 'package:pweb/pages/payment_methods/payment_page/body.dart';
|
import 'package:pweb/pages/payment_methods/payment_page/body.dart';
|
||||||
import 'package:pweb/providers/wallets.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentPage extends StatefulWidget {
|
class PaymentPage extends StatefulWidget {
|
||||||
@@ -109,7 +110,7 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
|
|
||||||
void _handleSendPayment() {
|
void _handleSendPayment() {
|
||||||
// TODO: Handle Payment logic
|
// TODO: Handle Payment logic
|
||||||
// AmplitudeService.paymentInitiated();
|
PosthogService.paymentInitiated(method: _flowProvider.selectedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -195,4 +196,4 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
(method.description?.contains(wallet.walletUserID) ?? false),
|
(method.description?.contains(wallet.walletUserID) ?? false),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/provider/locale.dart';
|
import 'package:pshared/provider/locale.dart';
|
||||||
|
|
||||||
// import 'package:pweb/services/amplitude.dart';
|
import 'package:pweb/services/posthog.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
@@ -58,7 +60,7 @@ class LocalePicker extends StatelessWidget {
|
|||||||
onChanged: (locale) {
|
onChanged: (locale) {
|
||||||
if (locale != null) {
|
if (locale != null) {
|
||||||
localeProvider.setLocale(locale);
|
localeProvider.setLocale(locale);
|
||||||
// AmplitudeService.localeChanged(locale);
|
unawaited(PosthogService.localeChanged(locale));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/models/edit_state.dart';
|
||||||
|
import 'package:pshared/provider/account.dart';
|
||||||
|
|
||||||
class AccountName extends StatefulWidget {
|
class AccountName extends StatefulWidget {
|
||||||
final String name;
|
final String name;
|
||||||
@@ -26,8 +30,9 @@ class _AccountNameState extends State<AccountName> {
|
|||||||
static const double _borderWidth = 2;
|
static const double _borderWidth = 2;
|
||||||
|
|
||||||
late final TextEditingController _controller;
|
late final TextEditingController _controller;
|
||||||
bool _isEditing = false;
|
EditState _editState = EditState.view;
|
||||||
late String _originalName;
|
late String _originalName;
|
||||||
|
String _errorText = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -42,86 +47,131 @@ class _AccountNameState extends State<AccountName> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _startEditing() => setState(() => _isEditing = true);
|
void _startEditing() => setState(() => _editState = EditState.edit);
|
||||||
|
|
||||||
void _cancelEditing() {
|
void _cancelEditing() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_controller.text = _originalName;
|
_controller.text = _originalName;
|
||||||
_isEditing = false;
|
_editState = EditState.view;
|
||||||
|
_errorText = '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _saveEditing() {
|
Future<void> _saveEditing(AccountProvider provider) async {
|
||||||
|
final newName = _controller.text.trim();
|
||||||
|
if (newName.isEmpty || newName == _originalName) {
|
||||||
|
_cancelEditing();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_originalName = _controller.text;
|
_editState = EditState.saving;
|
||||||
_isEditing = false;
|
_errorText = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await provider.resetUsername(newName);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_originalName = newName;
|
||||||
|
_editState = EditState.view;
|
||||||
|
});
|
||||||
|
} catch (_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_errorText = widget.errorText;
|
||||||
|
_editState = EditState.edit;
|
||||||
|
});
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(widget.errorText)),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (_editState == EditState.saving) {
|
||||||
|
setState(() => _editState = EditState.edit);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return Column(
|
return Consumer<AccountProvider>(
|
||||||
mainAxisSize: MainAxisSize.min,
|
builder: (context, provider, _) {
|
||||||
children: [
|
final isEditing = _editState != EditState.view;
|
||||||
Row(
|
final currentName = provider.account?.name ?? _originalName;
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
final isBusy = provider.isLoading || _editState == EditState.saving;
|
||||||
|
|
||||||
|
if (!isEditing && currentName != _originalName) {
|
||||||
|
_originalName = currentName;
|
||||||
|
_controller.text = currentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (_isEditing)
|
Row(
|
||||||
SizedBox(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
width: _inputWidth,
|
children: [
|
||||||
child: TextFormField(
|
if (isEditing)
|
||||||
controller: _controller,
|
SizedBox(
|
||||||
style: theme.textTheme.headlineMedium?.copyWith(
|
width: _inputWidth,
|
||||||
fontWeight: FontWeight.bold,
|
child: TextFormField(
|
||||||
),
|
controller: _controller,
|
||||||
autofocus: true,
|
style: theme.textTheme.headlineMedium?.copyWith(
|
||||||
decoration: InputDecoration(
|
fontWeight: FontWeight.bold,
|
||||||
hintText: widget.hintText,
|
),
|
||||||
isDense: true,
|
autofocus: true,
|
||||||
border: UnderlineInputBorder(
|
enabled: !isBusy,
|
||||||
borderSide: BorderSide(
|
decoration: InputDecoration(
|
||||||
color: theme.colorScheme.primary,
|
hintText: widget.hintText,
|
||||||
width: _borderWidth,
|
isDense: true,
|
||||||
|
border: UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
width: _borderWidth,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Text(
|
||||||
|
_originalName,
|
||||||
|
style: theme.textTheme.headlineMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: _spacing),
|
||||||
)
|
if (isEditing) ...[
|
||||||
else
|
IconButton(
|
||||||
|
icon: Icon(Icons.check, color: theme.colorScheme.primary),
|
||||||
|
onPressed: isBusy ? null : () => _saveEditing(provider),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.close, color: theme.colorScheme.error),
|
||||||
|
onPressed: isBusy ? null : _cancelEditing,
|
||||||
|
),
|
||||||
|
] else
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit, color: theme.colorScheme.primary),
|
||||||
|
onPressed: isBusy ? null : _startEditing,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: _errorSpacing),
|
||||||
|
if (_errorText.isNotEmpty)
|
||||||
Text(
|
Text(
|
||||||
_originalName,
|
_errorText,
|
||||||
style: theme.textTheme.headlineMedium?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
color: theme.colorScheme.error,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: _spacing),
|
|
||||||
if (_isEditing) ...[
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.check, color: theme.colorScheme.primary),
|
|
||||||
onPressed: _saveEditing,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.close, color: theme.colorScheme.error),
|
|
||||||
onPressed: _cancelEditing,
|
|
||||||
),
|
|
||||||
] else
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.edit, color: theme.colorScheme.primary),
|
|
||||||
onPressed: _startEditing,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
);
|
||||||
const SizedBox(height: _errorSpacing),
|
},
|
||||||
if (widget.errorText.isEmpty)
|
|
||||||
Text(
|
|
||||||
widget.errorText,
|
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
|
||||||
color: theme.colorScheme.error,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/account.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/providers/password_form.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordForm extends StatelessWidget {
|
||||||
|
const PasswordForm({
|
||||||
|
super.key,
|
||||||
|
required this.formProvider,
|
||||||
|
required this.accountProvider,
|
||||||
|
required this.isBusy,
|
||||||
|
required this.oldPasswordLabel,
|
||||||
|
required this.newPasswordLabel,
|
||||||
|
required this.confirmPasswordLabel,
|
||||||
|
required this.savePassword,
|
||||||
|
required this.successText,
|
||||||
|
required this.errorText,
|
||||||
|
required this.loc,
|
||||||
|
});
|
||||||
|
|
||||||
|
static const double _fieldWidth = 320;
|
||||||
|
static const double _gapMedium = 12;
|
||||||
|
static const double _gapSmall = 8;
|
||||||
|
|
||||||
|
final PasswordFormProvider formProvider;
|
||||||
|
final AccountProvider accountProvider;
|
||||||
|
final bool isBusy;
|
||||||
|
final String oldPasswordLabel;
|
||||||
|
final String newPasswordLabel;
|
||||||
|
final String confirmPasswordLabel;
|
||||||
|
final String savePassword;
|
||||||
|
final String successText;
|
||||||
|
final String errorText;
|
||||||
|
final AppLocalizations loc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final isFormBusy = isBusy || formProvider.isSaving;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: _gapMedium),
|
||||||
|
Form(
|
||||||
|
key: formProvider.formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: _fieldWidth,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: formProvider.oldPasswordController,
|
||||||
|
obscureText: true,
|
||||||
|
enabled: !isFormBusy,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: oldPasswordLabel,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) =>
|
||||||
|
(value == null || value.isEmpty) ? loc.errorPasswordMissing : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: _gapSmall),
|
||||||
|
SizedBox(
|
||||||
|
width: _fieldWidth,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: formProvider.newPasswordController,
|
||||||
|
obscureText: true,
|
||||||
|
enabled: !isFormBusy,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: newPasswordLabel,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) =>
|
||||||
|
(value == null || value.isEmpty) ? loc.errorPasswordMissing : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: _gapSmall),
|
||||||
|
SizedBox(
|
||||||
|
width: _fieldWidth,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: formProvider.confirmPasswordController,
|
||||||
|
obscureText: true,
|
||||||
|
enabled: !isFormBusy,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: confirmPasswordLabel,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) return loc.errorPasswordMissing;
|
||||||
|
if (value != formProvider.newPasswordController.text) {
|
||||||
|
return loc.passwordsDoNotMatch;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: _gapMedium),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: isFormBusy
|
||||||
|
? null
|
||||||
|
: () => formProvider.submit(
|
||||||
|
context: context,
|
||||||
|
accountProvider: accountProvider,
|
||||||
|
successText: successText,
|
||||||
|
errorText: errorText,
|
||||||
|
),
|
||||||
|
icon: const Icon(Icons.save_outlined),
|
||||||
|
label: Text(savePassword),
|
||||||
|
),
|
||||||
|
if (formProvider.errorText.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: _gapSmall),
|
||||||
|
Text(
|
||||||
|
formProvider.errorText,
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
|
color: theme.colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/account.dart';
|
||||||
|
import 'package:pshared/utils/snackbar.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/models/edit_state.dart';
|
||||||
|
import 'package:pweb/utils/error/snackbar.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class AccountPassword extends StatefulWidget {
|
||||||
|
final String title;
|
||||||
|
final String successText;
|
||||||
|
final String errorText;
|
||||||
|
final String oldPasswordLabel;
|
||||||
|
final String newPasswordLabel;
|
||||||
|
final String confirmPasswordLabel;
|
||||||
|
final String savePassword;
|
||||||
|
|
||||||
|
const AccountPassword({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.successText,
|
||||||
|
required this.errorText,
|
||||||
|
required this.oldPasswordLabel,
|
||||||
|
required this.newPasswordLabel,
|
||||||
|
required this.confirmPasswordLabel,
|
||||||
|
required this.savePassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AccountPassword> createState() => _AccountPasswordState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountPasswordState extends State<AccountPassword> {
|
||||||
|
static const double _fieldWidth = 320;
|
||||||
|
static const double _gapMedium = 12;
|
||||||
|
static const double _gapSmall = 8;
|
||||||
|
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
final _oldPasswordController = TextEditingController();
|
||||||
|
final _newPasswordController = TextEditingController();
|
||||||
|
final _confirmPasswordController = TextEditingController();
|
||||||
|
|
||||||
|
EditState _state = EditState.view;
|
||||||
|
String _errorText = '';
|
||||||
|
|
||||||
|
bool get _isSaving => _state == EditState.saving;
|
||||||
|
bool get _isExpanded => _state != EditState.view;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_oldPasswordController.dispose();
|
||||||
|
_newPasswordController.dispose();
|
||||||
|
_confirmPasswordController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _changePassword(AccountProvider provider) async {
|
||||||
|
if (!_formKey.currentState!.validate()) return;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_state = EditState.saving;
|
||||||
|
_errorText = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await provider.changePassword(_oldPasswordController.text, _newPasswordController.text);
|
||||||
|
if (!mounted) return;
|
||||||
|
_oldPasswordController.clear();
|
||||||
|
_newPasswordController.clear();
|
||||||
|
_confirmPasswordController.clear();
|
||||||
|
notifyUser(context, widget.successText);
|
||||||
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() => _errorText = widget.errorText);
|
||||||
|
await postNotifyUserOfErrorX(
|
||||||
|
context: context,
|
||||||
|
errorSituation: widget.errorText,
|
||||||
|
exception: e,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _state = EditState.edit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Consumer<AccountProvider>(
|
||||||
|
builder: (context, provider, _) {
|
||||||
|
final isBusy = provider.isLoading || _isSaving;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
TextButton.icon(
|
||||||
|
onPressed: isBusy
|
||||||
|
? null
|
||||||
|
: () => setState(() {
|
||||||
|
_state = _isExpanded ? EditState.view : EditState.edit;
|
||||||
|
_errorText = '';
|
||||||
|
}),
|
||||||
|
icon: Icon(Icons.lock_outline, color: theme.colorScheme.primary),
|
||||||
|
label: Text(widget.title, style: theme.textTheme.bodyMedium),
|
||||||
|
),
|
||||||
|
if (_isExpanded) ...[
|
||||||
|
const SizedBox(height: _gapMedium),
|
||||||
|
Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: _fieldWidth,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _oldPasswordController,
|
||||||
|
obscureText: true,
|
||||||
|
enabled: !isBusy,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: widget.oldPasswordLabel,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) => (value == null || value.isEmpty) ? loc.errorPasswordMissing : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: _gapSmall),
|
||||||
|
SizedBox(
|
||||||
|
width: _fieldWidth,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _newPasswordController,
|
||||||
|
obscureText: true,
|
||||||
|
enabled: !isBusy,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: widget.newPasswordLabel,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) => (value == null || value.isEmpty) ? loc.errorPasswordMissing : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: _gapSmall),
|
||||||
|
SizedBox(
|
||||||
|
width: _fieldWidth,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _confirmPasswordController,
|
||||||
|
obscureText: true,
|
||||||
|
enabled: !isBusy,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: widget.confirmPasswordLabel,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) return loc.errorPasswordMissing;
|
||||||
|
if (value != _newPasswordController.text) return loc.passwordsDoNotMatch;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: _gapMedium),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: isBusy ? null : () => _changePassword(provider),
|
||||||
|
icon: const Icon(Icons.save_outlined),
|
||||||
|
label: Text(widget.savePassword),
|
||||||
|
),
|
||||||
|
if (_errorText.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: _gapSmall),
|
||||||
|
Text(
|
||||||
|
_errorText,
|
||||||
|
style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.error),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordToggleButton extends StatelessWidget {
|
||||||
|
const PasswordToggleButton({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.isExpanded,
|
||||||
|
required this.isBusy,
|
||||||
|
required this.onToggle,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final bool isExpanded;
|
||||||
|
final bool isBusy;
|
||||||
|
final VoidCallback onToggle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final iconColor = theme.colorScheme.primary;
|
||||||
|
|
||||||
|
return TextButton.icon(
|
||||||
|
onPressed: isBusy
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
onToggle();
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
isExpanded ? Icons.lock_open : Icons.lock_outline,
|
||||||
|
color: iconColor,
|
||||||
|
),
|
||||||
|
label: Text(title, style: theme.textTheme.bodyMedium),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/account.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/settings/profile/account/avatar.dart';
|
import 'package:pweb/pages/settings/profile/account/avatar.dart';
|
||||||
import 'package:pweb/pages/settings/profile/account/locale.dart';
|
import 'package:pweb/pages/settings/profile/account/locale.dart';
|
||||||
import 'package:pweb/pages/settings/profile/account/name.dart';
|
import 'package:pweb/pages/settings/profile/account/name.dart';
|
||||||
|
import 'package:pweb/pages/settings/profile/account/password/password.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
@@ -18,34 +23,48 @@ class ProfileSettingsPage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final accountName = context.select<AccountProvider, String?>(
|
||||||
|
(provider) => provider.account?.describable.name,
|
||||||
|
);
|
||||||
|
|
||||||
return Material(
|
return Align(
|
||||||
elevation: 4,
|
alignment: Alignment.topCenter,
|
||||||
borderRadius: BorderRadius.circular(_cardRadius),
|
child: Material(
|
||||||
clipBehavior: Clip.antiAlias,
|
elevation: 4,
|
||||||
color: theme.colorScheme.onSecondary,
|
borderRadius: BorderRadius.circular(_cardRadius),
|
||||||
child: Padding(
|
color: theme.colorScheme.onSecondary,
|
||||||
padding: _cardPadding,
|
child: Padding(
|
||||||
child: Column(
|
padding: _cardPadding,
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Column(
|
||||||
spacing: _itemSpacing,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
spacing: _itemSpacing,
|
||||||
AvatarTile(
|
children: [
|
||||||
avatarUrl: 'https://avatars.githubusercontent.com/u/65651201',
|
AvatarTile(
|
||||||
title: loc.avatar,
|
avatarUrl: 'https://avatars.githubusercontent.com/u/65651201',
|
||||||
description: loc.avatarHint,
|
title: loc.avatar,
|
||||||
errorText: loc.avatarUpdateError,
|
description: loc.avatarHint,
|
||||||
),
|
errorText: loc.avatarUpdateError,
|
||||||
AccountName(
|
),
|
||||||
name: loc.userNamePlaceholder,
|
AccountName(
|
||||||
title: loc.accountName,
|
name: accountName ?? loc.userNamePlaceholder,
|
||||||
hintText: loc.accountNameHint,
|
title: loc.accountName,
|
||||||
errorText: loc.accountNameUpdateError,
|
hintText: loc.accountNameHint,
|
||||||
),
|
errorText: loc.accountNameUpdateError,
|
||||||
LocalePicker(
|
),
|
||||||
title: loc.language,
|
AccountPassword(
|
||||||
),
|
title: loc.changePassword,
|
||||||
],
|
successText: loc.changePasswordSuccess,
|
||||||
|
errorText: loc.changePasswordError,
|
||||||
|
oldPasswordLabel: loc.oldPassword,
|
||||||
|
newPasswordLabel: loc.newPassword,
|
||||||
|
confirmPasswordLabel: loc.confirmPassword,
|
||||||
|
savePassword: loc.savePassword,
|
||||||
|
),
|
||||||
|
LocalePicker(
|
||||||
|
title: loc.language,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
|
import 'package:flutter_settings_ui/flutter_settings_ui.dart';
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
|
||||||
|
import 'package:pweb/models/edit_state.dart';
|
||||||
import 'package:pweb/utils/error/snackbar.dart';
|
import 'package:pweb/utils/error/snackbar.dart';
|
||||||
|
|
||||||
enum _EditState { view, edit, saving }
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
/// Базовый класс, управляющий состояниями (view/edit/saving),
|
|
||||||
/// показом snackbar ошибок и успешного сохранения.
|
|
||||||
abstract class BaseEditTile<T> extends AbstractSettingsTile {
|
abstract class BaseEditTile<T> extends AbstractSettingsTile {
|
||||||
const BaseEditTile({
|
const BaseEditTile({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -24,11 +24,8 @@ abstract class BaseEditTile<T> extends AbstractSettingsTile {
|
|||||||
final Future<void> Function(T) valueSetter;
|
final Future<void> Function(T) valueSetter;
|
||||||
final String errorSituation;
|
final String errorSituation;
|
||||||
|
|
||||||
/// Рисует в режиме просмотра (read-only).
|
|
||||||
Widget buildView(BuildContext context, T? value);
|
Widget buildView(BuildContext context, T? value);
|
||||||
|
|
||||||
/// Рисует UI редактора.
|
|
||||||
/// Если [useDialogEditor]==true, его обернут в диалог.
|
|
||||||
Widget buildEditor(
|
Widget buildEditor(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
T? initial,
|
T? initial,
|
||||||
@@ -37,7 +34,6 @@ abstract class BaseEditTile<T> extends AbstractSettingsTile {
|
|||||||
bool isSaving,
|
bool isSaving,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// true → показывать редактор в диалоге, false → inline под заголовком.
|
|
||||||
bool get useDialogEditor => false;
|
bool get useDialogEditor => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -52,16 +48,16 @@ class _BaseEditTileBody<T> extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _BaseEditTileBodyState<T> extends State<_BaseEditTileBody<T>> {
|
class _BaseEditTileBodyState<T> extends State<_BaseEditTileBody<T>> {
|
||||||
_EditState _state = _EditState.view;
|
EditState _state = EditState.view;
|
||||||
bool get _isSaving => _state == _EditState.saving;
|
bool get _isSaving => _state == EditState.saving;
|
||||||
|
|
||||||
Future<void> _performSave(T newValue) async {
|
Future<void> _performSave(T newValue) async {
|
||||||
final current = widget.delegate.valueGetter();
|
final current = widget.delegate.valueGetter();
|
||||||
if (newValue == current) {
|
if (newValue == current) {
|
||||||
setState(() => _state = _EditState.view);
|
setState(() => _state = EditState.view);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() => _state = _EditState.saving);
|
setState(() => _state = EditState.saving);
|
||||||
final sms = ScaffoldMessenger.of(context);
|
final sms = ScaffoldMessenger.of(context);
|
||||||
final locs = AppLocalizations.of(context)!;
|
final locs = AppLocalizations.of(context)!;
|
||||||
try {
|
try {
|
||||||
@@ -78,7 +74,7 @@ class _BaseEditTileBodyState<T> extends State<_BaseEditTileBody<T>> {
|
|||||||
exception: e,
|
exception: e,
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
if (mounted) setState(() => _state = _EditState.view);
|
if (mounted) setState(() => _state = EditState.view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +108,6 @@ class _BaseEditTileBodyState<T> extends State<_BaseEditTileBody<T>> {
|
|||||||
final delegate = widget.delegate;
|
final delegate = widget.delegate;
|
||||||
final current = delegate.valueGetter();
|
final current = delegate.valueGetter();
|
||||||
|
|
||||||
// Диалоговый режим
|
|
||||||
if (delegate.useDialogEditor) {
|
if (delegate.useDialogEditor) {
|
||||||
return SettingsTile.navigation(
|
return SettingsTile.navigation(
|
||||||
leading: Icon(delegate.icon),
|
leading: Icon(delegate.icon),
|
||||||
@@ -122,21 +117,20 @@ class _BaseEditTileBodyState<T> extends State<_BaseEditTileBody<T>> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inline-режим (под заголовком будет редактор прямо в tile)
|
|
||||||
return SettingsTile.navigation(
|
return SettingsTile.navigation(
|
||||||
leading: Icon(delegate.icon),
|
leading: Icon(delegate.icon),
|
||||||
title: Text(delegate.title),
|
title: Text(delegate.title),
|
||||||
value: _state == _EditState.view
|
value: _state == EditState.view
|
||||||
? delegate.buildView(context, current)
|
? delegate.buildView(context, current)
|
||||||
: delegate.buildEditor(
|
: delegate.buildEditor(
|
||||||
context,
|
context,
|
||||||
current,
|
current,
|
||||||
_performSave,
|
_performSave,
|
||||||
() => setState(() => _state = _EditState.view),
|
() => setState(() => _state = EditState.view),
|
||||||
_isSaving,
|
_isSaving,
|
||||||
),
|
),
|
||||||
onPressed: (_) {
|
onPressed: (_) {
|
||||||
if (_state == _EditState.view) setState(() => _state = _EditState.edit);
|
if (_state == EditState.view) setState(() => _state = EditState.edit);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
17
frontend/pweb/lib/providers/account.dart
Normal file
17
frontend/pweb/lib/providers/account.dart
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:pshared/models/account/account.dart';
|
||||||
|
import 'package:pshared/provider/account.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PwebAccountProvider extends AccountProvider {
|
||||||
|
@override
|
||||||
|
Future<void> onAccountChanged(Account? previous, Account? current) {
|
||||||
|
if (current != null) {
|
||||||
|
return PosthogService.identify(current);
|
||||||
|
}
|
||||||
|
return PosthogService.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
87
frontend/pweb/lib/providers/password_form.dart
Normal file
87
frontend/pweb/lib/providers/password_form.dart
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/account.dart';
|
||||||
|
import 'package:pshared/utils/snackbar.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/models/edit_state.dart';
|
||||||
|
import 'package:pweb/utils/error/snackbar.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordFormProvider extends ChangeNotifier {
|
||||||
|
final formKey = GlobalKey<FormState>();
|
||||||
|
final oldPasswordController = TextEditingController();
|
||||||
|
final newPasswordController = TextEditingController();
|
||||||
|
final confirmPasswordController = TextEditingController();
|
||||||
|
|
||||||
|
EditState _state = EditState.view;
|
||||||
|
String _errorText = '';
|
||||||
|
bool _disposed = false;
|
||||||
|
|
||||||
|
bool get isExpanded => _state != EditState.view;
|
||||||
|
bool get isSaving => _state == EditState.saving;
|
||||||
|
String get errorText => _errorText;
|
||||||
|
EditState get state => _state;
|
||||||
|
|
||||||
|
void toggleExpanded() {
|
||||||
|
if (_state == EditState.saving) return;
|
||||||
|
_setState(_state == EditState.view ? EditState.edit : EditState.view);
|
||||||
|
_setError('');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> submit({
|
||||||
|
required BuildContext context,
|
||||||
|
required AccountProvider accountProvider,
|
||||||
|
required String successText,
|
||||||
|
required String errorText,
|
||||||
|
}) async {
|
||||||
|
final currentForm = formKey.currentState;
|
||||||
|
if (currentForm == null || !currentForm.validate()) return;
|
||||||
|
|
||||||
|
_setState(EditState.saving);
|
||||||
|
_setError('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await accountProvider.changePassword(
|
||||||
|
oldPasswordController.text,
|
||||||
|
newPasswordController.text,
|
||||||
|
);
|
||||||
|
|
||||||
|
oldPasswordController.clear();
|
||||||
|
newPasswordController.clear();
|
||||||
|
confirmPasswordController.clear();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
notifyUser(context, successText);
|
||||||
|
} catch (e) {
|
||||||
|
_setError(errorText);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
await postNotifyUserOfErrorX(
|
||||||
|
context: context,
|
||||||
|
errorSituation: errorText,
|
||||||
|
exception: e,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
_setState(EditState.edit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setState(EditState value) {
|
||||||
|
if (_state == value || _disposed) return;
|
||||||
|
_state = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setError(String value) {
|
||||||
|
if (_disposed) return;
|
||||||
|
_errorText = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_disposed = true;
|
||||||
|
oldPasswordController.dispose();
|
||||||
|
newPasswordController.dispose();
|
||||||
|
confirmPasswordController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ class TwoFactorProvider extends ChangeNotifier {
|
|||||||
bool _hasError = false;
|
bool _hasError = false;
|
||||||
bool _verificationSuccess = false;
|
bool _verificationSuccess = false;
|
||||||
String? _errorMessage;
|
String? _errorMessage;
|
||||||
|
String? _currentPendingToken;
|
||||||
|
|
||||||
bool get isSubmitting => _isSubmitting;
|
bool get isSubmitting => _isSubmitting;
|
||||||
bool get hasError => _hasError;
|
bool get hasError => _hasError;
|
||||||
@@ -26,6 +27,12 @@ class TwoFactorProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
void update(AccountProvider accountProvider) {
|
void update(AccountProvider accountProvider) {
|
||||||
_accountProvider = accountProvider;
|
_accountProvider = accountProvider;
|
||||||
|
final pending = accountProvider.pendingLogin;
|
||||||
|
final token = pending?.pendingToken.token;
|
||||||
|
if (token != _currentPendingToken || accountProvider.account == null) {
|
||||||
|
_resetState();
|
||||||
|
_currentPendingToken = token;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> submitCode(String code) async {
|
Future<void> submitCode(String code) async {
|
||||||
@@ -46,6 +53,7 @@ class TwoFactorProvider extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
_accountProvider.completePendingLogin(account);
|
_accountProvider.completePendingLogin(account);
|
||||||
_verificationSuccess = true;
|
_verificationSuccess = true;
|
||||||
|
_currentPendingToken = null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_hasError = true;
|
_hasError = true;
|
||||||
_errorMessage = e.toString();
|
_errorMessage = e.toString();
|
||||||
@@ -71,4 +79,17 @@ class TwoFactorProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
void reset() {
|
||||||
|
_resetState();
|
||||||
|
_currentPendingToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resetState() {
|
||||||
|
_isSubmitting = false;
|
||||||
|
_hasError = false;
|
||||||
|
_errorMessage = null;
|
||||||
|
_verificationSuccess = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
136
frontend/pweb/lib/services/posthog.dart
Normal file
136
frontend/pweb/lib/services/posthog.dart
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
import 'package:posthog_flutter/posthog_flutter.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/config/constants.dart';
|
||||||
|
import 'package:pshared/models/account/account.dart';
|
||||||
|
import 'package:pshared/models/payment/type.dart';
|
||||||
|
import 'package:pshared/models/recipient/status.dart';
|
||||||
|
import 'package:pshared/models/recipient/type.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PosthogService {
|
||||||
|
static final _logger = Logger('service.posthog');
|
||||||
|
static String? _identifiedUserId;
|
||||||
|
static bool _initialized = false;
|
||||||
|
|
||||||
|
static bool get isEnabled => _initialized;
|
||||||
|
|
||||||
|
static Future<void> initialize() async {
|
||||||
|
final apiKey = Constants.posthogApiKey;
|
||||||
|
if (apiKey.isEmpty) {
|
||||||
|
_logger.warning('PostHog API key is not configured, analytics disabled.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final config = PostHogConfig(apiKey)
|
||||||
|
..host = Constants.posthogHost
|
||||||
|
..captureApplicationLifecycleEvents = true;
|
||||||
|
await Posthog().setup(config);
|
||||||
|
await Posthog().register('client_id', Constants.clientId);
|
||||||
|
_initialized = true;
|
||||||
|
_logger.info('PostHog initialized with host ${Constants.posthogHost}');
|
||||||
|
} catch (e, st) {
|
||||||
|
_initialized = false;
|
||||||
|
_logger.warning('Failed to initialize PostHog: $e', e, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> identify(Account account) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
if (_identifiedUserId == account.id) return;
|
||||||
|
|
||||||
|
await Posthog().identify(
|
||||||
|
userId: account.id,
|
||||||
|
userProperties: {
|
||||||
|
'email': account.login,
|
||||||
|
'name': account.name,
|
||||||
|
'locale': account.locale,
|
||||||
|
'created_at': account.createdAt.toIso8601String(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_identifiedUserId = account.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> reset() async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
_identifiedUserId = null;
|
||||||
|
await Posthog().reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> login({required bool pending}) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
await _capture(
|
||||||
|
'login',
|
||||||
|
properties: {
|
||||||
|
'result': pending ? 'pending' : 'success',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> pageOpened(PayoutDestination page, {String? path, String? uiSource}) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
return _capture(
|
||||||
|
'pageOpened',
|
||||||
|
properties: {
|
||||||
|
'page': page.name,
|
||||||
|
if (path != null) 'path': path,
|
||||||
|
if (uiSource != null) 'uiSource': uiSource,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> localeChanged(Locale locale) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
return _capture(
|
||||||
|
'localeChanged',
|
||||||
|
properties: {'locale': locale.toLanguageTag()},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> recipientAddCompleted(
|
||||||
|
RecipientType type,
|
||||||
|
RecipientStatus status,
|
||||||
|
Set<PaymentType> methods,
|
||||||
|
) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
return _capture(
|
||||||
|
'recipientAddCompleted',
|
||||||
|
properties: {
|
||||||
|
'methods': methods.map((m) => m.name).toList(),
|
||||||
|
'type': type.name,
|
||||||
|
'status': status.name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> paymentInitiated({PaymentType? method}) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
return _capture(
|
||||||
|
'paymentInitiated',
|
||||||
|
properties: {
|
||||||
|
if (method != null) 'method': method.name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> _capture(
|
||||||
|
String eventName, {
|
||||||
|
Map<String, Object?>? properties,
|
||||||
|
}) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
final filtered = <String, Object>{};
|
||||||
|
if (properties != null) {
|
||||||
|
for (final entry in properties.entries) {
|
||||||
|
final value = entry.value;
|
||||||
|
if (value != null) filtered[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Posthog().capture(eventName: eventName, properties: filtered.isEmpty ? null : filtered);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,31 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/provider/account.dart';
|
import 'package:pshared/provider/account.dart';
|
||||||
|
import 'package:pshared/provider/organizations.dart';
|
||||||
import 'package:pshared/provider/permissions.dart';
|
import 'package:pshared/provider/permissions.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
import 'package:pweb/app/router/pages.dart';
|
||||||
|
import 'package:pweb/providers/two_factor.dart';
|
||||||
|
|
||||||
|
|
||||||
void logoutUtil(BuildContext context) {
|
Future<void> logoutUtil(BuildContext context) async {
|
||||||
context.read<AccountProvider>().logout();
|
final accountProvider = context.read<AccountProvider>();
|
||||||
context.read<PermissionsProvider>().reset();
|
final permissionsProvider = context.read<PermissionsProvider>();
|
||||||
navigateAndReplace(context, Pages.login);
|
final organizationsProvider = context.read<OrganizationsProvider>();
|
||||||
|
final twoFactorProvider = context.read<TwoFactorProvider>();
|
||||||
|
await accountProvider.logout();
|
||||||
|
permissionsProvider.reset();
|
||||||
|
await organizationsProvider.reset();
|
||||||
|
twoFactorProvider.reset();
|
||||||
|
|
||||||
|
final router = GoRouter.of(context);
|
||||||
|
final loginPath = routerPage(Pages.login);
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
router.go(loginPath);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class PayoutAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
final Widget title;
|
final Widget title;
|
||||||
final VoidCallback onAddFundsPressed;
|
final VoidCallback onAddFundsPressed;
|
||||||
final List<Widget>? actions;
|
final List<Widget>? actions;
|
||||||
final VoidCallback? onLogout;
|
final Future<void> Function()? onLogout;
|
||||||
final String? avatarUrl;
|
final String? avatarUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class ProfileAvatar extends StatelessWidget {
|
|||||||
const ProfileAvatar({super.key, this.avatarUrl, this.onLogout});
|
const ProfileAvatar({super.key, this.avatarUrl, this.onLogout});
|
||||||
|
|
||||||
final String? avatarUrl;
|
final String? avatarUrl;
|
||||||
final VoidCallback? onLogout;
|
final Future<void> Function()? onLogout;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => PopupMenuButton<int>(
|
Widget build(BuildContext context) => PopupMenuButton<int>(
|
||||||
@@ -37,4 +37,4 @@ class ProfileAvatar extends StatelessWidget {
|
|||||||
child: avatarUrl == null ? const Icon(Icons.person, size: 24) : null,
|
child: avatarUrl == null ? const Icon(Icons.person, size: 24) : null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:pweb/utils/logout.dart';
|
||||||
|
|
||||||
import 'package:pshared/provider/account.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
@@ -23,10 +19,8 @@ class LogoutTile extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _logout(BuildContext context) {
|
Future<void> _logout(BuildContext context) async {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
final accountProvider = Provider.of<AccountProvider>(context, listen: false);
|
await logoutUtil(context);
|
||||||
accountProvider.logout();
|
|
||||||
navigateAndReplace(context, Pages.login);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
// import 'package:pweb/services/amplitude.dart';
|
import 'package:pweb/services/posthog.dart';
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ class SideMenuColumn extends StatelessWidget {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onSelected(item);
|
onSelected(item);
|
||||||
// AmplitudeService.pageOpened(item, uiSource: 'sidebar');
|
unawaited(PosthogService.pageOpened(item, uiSource: 'sidebar'));
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
hoverColor: theme.colorScheme.primaryContainer,
|
hoverColor: theme.colorScheme.primaryContainer,
|
||||||
@@ -76,4 +78,4 @@ class SideMenuColumn extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/account.dart';
|
||||||
|
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
import 'package:pweb/widgets/sidebar/side_menu.dart';
|
import 'package:pweb/widgets/sidebar/side_menu.dart';
|
||||||
import 'package:pweb/widgets/sidebar/user.dart';
|
import 'package:pweb/widgets/sidebar/user.dart';
|
||||||
@@ -18,7 +22,7 @@ class PayoutSidebar extends StatelessWidget {
|
|||||||
|
|
||||||
final PayoutDestination selected;
|
final PayoutDestination selected;
|
||||||
final ValueChanged<PayoutDestination> onSelected;
|
final ValueChanged<PayoutDestination> onSelected;
|
||||||
final VoidCallback? onLogout;
|
final Future<void> Function()? onLogout;
|
||||||
|
|
||||||
final String? userName;
|
final String? userName;
|
||||||
final String? avatarUrl;
|
final String? avatarUrl;
|
||||||
@@ -27,6 +31,15 @@ class PayoutSidebar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final accountName = context.select<AccountProvider, String?>(
|
||||||
|
(provider) => provider.account?.describable.name,
|
||||||
|
);
|
||||||
|
final accountAvatar = context.select<AccountProvider, String?>(
|
||||||
|
(provider) => provider.account?.avatarUrl,
|
||||||
|
);
|
||||||
|
final resolvedUserName = userName ?? accountName;
|
||||||
|
final resolvedAvatarUrl = avatarUrl ?? accountAvatar;
|
||||||
|
|
||||||
final menuItems = items ??
|
final menuItems = items ??
|
||||||
<PayoutDestination>[
|
<PayoutDestination>[
|
||||||
PayoutDestination.dashboard,
|
PayoutDestination.dashboard,
|
||||||
@@ -42,16 +55,16 @@ class PayoutSidebar extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
UserProfileCard(
|
UserProfileCard(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
avatarUrl: avatarUrl,
|
avatarUrl: resolvedAvatarUrl,
|
||||||
userName: userName,
|
userName: resolvedUserName,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
onSelected: onSelected
|
onSelected: onSelected
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
SideMenuColumn(
|
SideMenuColumn(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
avatarUrl: avatarUrl,
|
avatarUrl: resolvedAvatarUrl,
|
||||||
userName: userName,
|
userName: resolvedUserName,
|
||||||
items: menuItems,
|
items: menuItems,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import amplitude_flutter
|
|||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
import flutter_timezone
|
import flutter_timezone
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
|
import posthog_flutter
|
||||||
import share_plus
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
@@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
|
FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
PosthogFlutterPlugin.register(with: registry.registrar(forPlugin: "PosthogFlutterPlugin"))
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
pshared:
|
pshared:
|
||||||
path: ../pshared
|
path: ../pshared
|
||||||
|
posthog_flutter: ^5.9.0
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
|||||||
Reference in New Issue
Block a user