Compare commits
23 Commits
cb3f59a9d5
...
SEND010
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ecd17d2dc | ||
| d649748f6f | |||
|
|
61177a4e30 | ||
| c7b9b70d57 | |||
|
|
5030453807 | ||
| 5565081b69 | |||
| b216aa68b7 | |||
| 7ac1c519e3 | |||
| 076b0c6434 | |||
|
|
9a90e6a03b | ||
|
|
5218632c00 | ||
|
|
a2c05745ad | ||
|
|
82b2f88122 | ||
|
|
28d74d058b | ||
|
|
6ee146b95a | ||
| 67b52af150 | |||
|
|
058a3fefaf | ||
| c8a97d940c | |||
|
|
00045c1e65 | ||
| d64d7dab58 | |||
|
|
4746a00eee | ||
| 3f8399d647 | |||
|
|
028b29fe08 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,4 +9,5 @@ untranslated.txt
|
|||||||
generate_protos.sh
|
generate_protos.sh
|
||||||
update_dep.sh
|
update_dep.sh
|
||||||
.vscode/
|
.vscode/
|
||||||
.gocache/
|
.gocache/
|
||||||
|
.cache/
|
||||||
@@ -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=
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ func (c *quoteCalculator) Compute(ctx context.Context, plan *model.FeePlan, inte
|
|||||||
|
|
||||||
entrySide := mapEntrySide(rule.EntrySide)
|
entrySide := mapEntrySide(rule.EntrySide)
|
||||||
if entrySide == accountingv1.EntrySide_ENTRY_SIDE_UNSPECIFIED {
|
if entrySide == accountingv1.EntrySide_ENTRY_SIDE_UNSPECIFIED {
|
||||||
entrySide = accountingv1.EntrySide_ENTRY_SIDE_CREDIT
|
// Default fees to debit (i.e. charge the customer) when entry side is not specified.
|
||||||
|
entrySide = accountingv1.EntrySide_ENTRY_SIDE_DEBIT
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := map[string]string{
|
meta := map[string]string{
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -20,13 +20,14 @@ func intentFromProto(src *orchestratorv1.PaymentIntent) model.PaymentIntent {
|
|||||||
return model.PaymentIntent{}
|
return model.PaymentIntent{}
|
||||||
}
|
}
|
||||||
intent := model.PaymentIntent{
|
intent := model.PaymentIntent{
|
||||||
Kind: modelKindFromProto(src.GetKind()),
|
Kind: modelKindFromProto(src.GetKind()),
|
||||||
Source: endpointFromProto(src.GetSource()),
|
Source: endpointFromProto(src.GetSource()),
|
||||||
Destination: endpointFromProto(src.GetDestination()),
|
Destination: endpointFromProto(src.GetDestination()),
|
||||||
Amount: cloneMoney(src.GetAmount()),
|
Amount: cloneMoney(src.GetAmount()),
|
||||||
RequiresFX: src.GetRequiresFx(),
|
RequiresFX: src.GetRequiresFx(),
|
||||||
FeePolicy: src.GetFeePolicy(),
|
FeePolicy: src.GetFeePolicy(),
|
||||||
Attributes: cloneMetadata(src.GetAttributes()),
|
SettlementMode: src.GetSettlementMode(),
|
||||||
|
Attributes: cloneMetadata(src.GetAttributes()),
|
||||||
}
|
}
|
||||||
if src.GetFx() != nil {
|
if src.GetFx() != nil {
|
||||||
intent.FX = fxIntentFromProto(src.GetFx())
|
intent.FX = fxIntentFromProto(src.GetFx())
|
||||||
@@ -153,13 +154,14 @@ func toProtoPayment(src *model.Payment) *orchestratorv1.Payment {
|
|||||||
|
|
||||||
func protoIntentFromModel(src model.PaymentIntent) *orchestratorv1.PaymentIntent {
|
func protoIntentFromModel(src model.PaymentIntent) *orchestratorv1.PaymentIntent {
|
||||||
intent := &orchestratorv1.PaymentIntent{
|
intent := &orchestratorv1.PaymentIntent{
|
||||||
Kind: protoKindFromModel(src.Kind),
|
Kind: protoKindFromModel(src.Kind),
|
||||||
Source: protoEndpointFromModel(src.Source),
|
Source: protoEndpointFromModel(src.Source),
|
||||||
Destination: protoEndpointFromModel(src.Destination),
|
Destination: protoEndpointFromModel(src.Destination),
|
||||||
Amount: cloneMoney(src.Amount),
|
Amount: cloneMoney(src.Amount),
|
||||||
RequiresFx: src.RequiresFX,
|
RequiresFx: src.RequiresFX,
|
||||||
FeePolicy: src.FeePolicy,
|
FeePolicy: src.FeePolicy,
|
||||||
Attributes: cloneMetadata(src.Attributes),
|
SettlementMode: src.SettlementMode,
|
||||||
|
Attributes: cloneMetadata(src.Attributes),
|
||||||
}
|
}
|
||||||
if src.FX != nil {
|
if src.FX != nil {
|
||||||
intent.Fx = protoFXIntentFromModel(src.FX)
|
intent.Fx = protoFXIntentFromModel(src.FX)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ func resolveTradeAmounts(intentAmount *moneyv1.Money, fxQuote *oraclev1.Quote, s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func computeAggregates(pay, settlement, fee *moneyv1.Money, network *chainv1.EstimateTransferFeeResponse, fxQuote *oraclev1.Quote) (*moneyv1.Money, *moneyv1.Money) {
|
func computeAggregates(pay, settlement, fee *moneyv1.Money, network *chainv1.EstimateTransferFeeResponse, fxQuote *oraclev1.Quote, mode orchestratorv1.SettlementMode) (*moneyv1.Money, *moneyv1.Money) {
|
||||||
if pay == nil {
|
if pay == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ func computeAggregates(pay, settlement, fee *moneyv1.Money, network *chainv1.Est
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustDebit := func(m *moneyv1.Money) {
|
applyChargeToDebit := func(m *moneyv1.Money) {
|
||||||
converted, err := ensureCurrency(m, pay.GetCurrency(), fxQuote)
|
converted, err := ensureCurrency(m, pay.GetCurrency(), fxQuote)
|
||||||
if err != nil || converted == nil {
|
if err != nil || converted == nil {
|
||||||
return
|
return
|
||||||
@@ -176,7 +176,7 @@ func computeAggregates(pay, settlement, fee *moneyv1.Money, network *chainv1.Est
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustSettlement := func(m *moneyv1.Money) {
|
applyChargeToSettlement := func(m *moneyv1.Money) {
|
||||||
converted, err := ensureCurrency(m, settlementCurrency, fxQuote)
|
converted, err := ensureCurrency(m, settlementCurrency, fxQuote)
|
||||||
if err != nil || converted == nil {
|
if err != nil || converted == nil {
|
||||||
return
|
return
|
||||||
@@ -186,12 +186,22 @@ func computeAggregates(pay, settlement, fee *moneyv1.Money, network *chainv1.Est
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustDebit(fee)
|
switch mode {
|
||||||
adjustSettlement(fee)
|
case orchestratorv1.SettlementMode_SETTLEMENT_MODE_FIX_RECEIVED:
|
||||||
|
// Sender pays the fee: keep settlement fixed, increase debit.
|
||||||
|
applyChargeToDebit(fee)
|
||||||
|
default:
|
||||||
|
// Recipient pays the fee (default): reduce settlement, keep debit fixed.
|
||||||
|
applyChargeToSettlement(fee)
|
||||||
|
}
|
||||||
|
|
||||||
if network != nil && network.GetNetworkFee() != nil {
|
if network != nil && network.GetNetworkFee() != nil {
|
||||||
adjustDebit(network.GetNetworkFee())
|
switch mode {
|
||||||
adjustSettlement(network.GetNetworkFee())
|
case orchestratorv1.SettlementMode_SETTLEMENT_MODE_FIX_RECEIVED:
|
||||||
|
applyChargeToDebit(network.GetNetworkFee())
|
||||||
|
default:
|
||||||
|
applyChargeToSettlement(network.GetNetworkFee())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return makeMoney(pay.GetCurrency(), debitDecimal), makeMoney(settlementCurrency, settlementDecimal)
|
return makeMoney(pay.GetCurrency(), debitDecimal), makeMoney(settlementCurrency, settlementDecimal)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||||
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
|
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
|
||||||
|
orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResolveTradeAmountsBuyBase(t *testing.T) {
|
func TestResolveTradeAmountsBuyBase(t *testing.T) {
|
||||||
@@ -47,11 +48,32 @@ func TestComputeAggregatesConvertsCurrencies(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
debit, settlement := computeAggregates(pay, settle, fee, network, fxQuote)
|
debit, settlement := computeAggregates(pay, settle, fee, network, fxQuote, orchestratorv1.SettlementMode_SETTLEMENT_MODE_FIX_RECEIVED)
|
||||||
if debit.GetCurrency() != "USD" || debit.GetAmount() != "115" {
|
if debit.GetCurrency() != "USD" || debit.GetAmount() != "115" {
|
||||||
t.Fatalf("expected debit 115 USD, got %s %s", debit.GetCurrency(), debit.GetAmount())
|
t.Fatalf("expected debit 115 USD, got %s %s", debit.GetCurrency(), debit.GetAmount())
|
||||||
}
|
}
|
||||||
if settlement.GetCurrency() != "EUR" || settlement.GetAmount() != "42.5" {
|
if settlement.GetCurrency() != "EUR" || settlement.GetAmount() != "50" {
|
||||||
t.Fatalf("expected settlement 42.5 EUR, got %s %s", settlement.GetCurrency(), settlement.GetAmount())
|
t.Fatalf("expected settlement 50 EUR, got %s %s", settlement.GetCurrency(), settlement.GetAmount())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComputeAggregatesRecipientPaysFee(t *testing.T) {
|
||||||
|
pay := &moneyv1.Money{Currency: "USDT", Amount: "100"}
|
||||||
|
settle := &moneyv1.Money{Currency: "RUB", Amount: "7932"} // 100 * 79.32
|
||||||
|
fee := &moneyv1.Money{Currency: "USDT", Amount: "7"} // 7% of 100
|
||||||
|
fxQuote := &oraclev1.Quote{
|
||||||
|
Pair: &fxv1.CurrencyPair{Base: "USDT", Quote: "RUB"},
|
||||||
|
Side: fxv1.Side_SELL_BASE_BUY_QUOTE,
|
||||||
|
Price: &moneyv1.Decimal{
|
||||||
|
Value: "79.32",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
debit, settlement := computeAggregates(pay, settle, fee, nil, fxQuote, orchestratorv1.SettlementMode_SETTLEMENT_MODE_FIX_SOURCE)
|
||||||
|
if debit.GetCurrency() != "USDT" || debit.GetAmount() != "100" {
|
||||||
|
t.Fatalf("expected debit 100 USDT, got %s %s", debit.GetCurrency(), debit.GetAmount())
|
||||||
|
}
|
||||||
|
if settlement.GetCurrency() != "RUB" || settlement.GetAmount() != "7376.76" {
|
||||||
|
t.Fatalf("expected settlement 7376.76 RUB, got %s %s", settlement.GetCurrency(), settlement.GetAmount())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ func (s *Service) buildPaymentQuote(ctx context.Context, orgRef string, req *orc
|
|||||||
s.logger.Debug("network fee estimated", zap.String("org_ref", orgRef))
|
s.logger.Debug("network fee estimated", zap.String("org_ref", orgRef))
|
||||||
}
|
}
|
||||||
|
|
||||||
debitAmount, settlementAmount := computeAggregates(payAmount, settlementAmountBeforeFees, feeTotal, networkFee, fxQuote)
|
debitAmount, settlementAmount := computeAggregates(payAmount, settlementAmountBeforeFees, feeTotal, networkFee, fxQuote, intent.GetSettlementMode())
|
||||||
|
|
||||||
quote := &orchestratorv1.PaymentQuote{
|
quote := &orchestratorv1.PaymentQuote{
|
||||||
DebitAmount: debitAmount,
|
DebitAmount: debitAmount,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ func TestRequireIdempotencyKey(t *testing.T) {
|
|||||||
func TestNewPayment(t *testing.T) {
|
func TestNewPayment(t *testing.T) {
|
||||||
org := primitive.NewObjectID()
|
org := primitive.NewObjectID()
|
||||||
intent := &orchestratorv1.PaymentIntent{
|
intent := &orchestratorv1.PaymentIntent{
|
||||||
Amount: &moneyv1.Money{Currency: "USD", Amount: "10"},
|
Amount: &moneyv1.Money{Currency: "USD", Amount: "10"},
|
||||||
|
SettlementMode: orchestratorv1.SettlementMode_SETTLEMENT_MODE_FIX_RECEIVED,
|
||||||
}
|
}
|
||||||
quote := &orchestratorv1.PaymentQuote{QuoteRef: "q1"}
|
quote := &orchestratorv1.PaymentQuote{QuoteRef: "q1"}
|
||||||
p := newPayment(org, intent, "idem", map[string]string{"k": "v"}, quote)
|
p := newPayment(org, intent, "idem", map[string]string{"k": "v"}, quote)
|
||||||
@@ -58,6 +59,9 @@ func TestNewPayment(t *testing.T) {
|
|||||||
if p.Intent.Amount == nil || p.Intent.Amount.GetAmount() != "10" {
|
if p.Intent.Amount == nil || p.Intent.Amount.GetAmount() != "10" {
|
||||||
t.Fatalf("intent not copied")
|
t.Fatalf("intent not copied")
|
||||||
}
|
}
|
||||||
|
if p.Intent.SettlementMode != orchestratorv1.SettlementMode_SETTLEMENT_MODE_FIX_RECEIVED {
|
||||||
|
t.Fatalf("settlement mode not preserved")
|
||||||
|
}
|
||||||
if p.LastQuote == nil || p.LastQuote.QuoteRef != "q1" {
|
if p.LastQuote == nil || p.LastQuote.QuoteRef != "q1" {
|
||||||
t.Fatalf("quote not copied")
|
t.Fatalf("quote not copied")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||||
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
|
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
|
||||||
|
orchestratorv1 "github.com/tech/sendico/pkg/proto/payments/orchestrator/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PaymentKind captures the orchestrator intent type.
|
// PaymentKind captures the orchestrator intent type.
|
||||||
@@ -124,14 +125,15 @@ type FXIntent struct {
|
|||||||
|
|
||||||
// PaymentIntent models the requested payment operation.
|
// PaymentIntent models the requested payment operation.
|
||||||
type PaymentIntent struct {
|
type PaymentIntent struct {
|
||||||
Kind PaymentKind `bson:"kind" json:"kind"`
|
Kind PaymentKind `bson:"kind" json:"kind"`
|
||||||
Source PaymentEndpoint `bson:"source" json:"source"`
|
Source PaymentEndpoint `bson:"source" json:"source"`
|
||||||
Destination PaymentEndpoint `bson:"destination" json:"destination"`
|
Destination PaymentEndpoint `bson:"destination" json:"destination"`
|
||||||
Amount *moneyv1.Money `bson:"amount" json:"amount"`
|
Amount *moneyv1.Money `bson:"amount" json:"amount"`
|
||||||
RequiresFX bool `bson:"requiresFx,omitempty" json:"requiresFx,omitempty"`
|
RequiresFX bool `bson:"requiresFx,omitempty" json:"requiresFx,omitempty"`
|
||||||
FX *FXIntent `bson:"fx,omitempty" json:"fx,omitempty"`
|
FX *FXIntent `bson:"fx,omitempty" json:"fx,omitempty"`
|
||||||
FeePolicy *feesv1.PolicyOverrides `bson:"feePolicy,omitempty" json:"feePolicy,omitempty"`
|
FeePolicy *feesv1.PolicyOverrides `bson:"feePolicy,omitempty" json:"feePolicy,omitempty"`
|
||||||
Attributes map[string]string `bson:"attributes,omitempty" json:"attributes,omitempty"`
|
SettlementMode orchestratorv1.SettlementMode `bson:"settlementMode,omitempty" json:"settlementMode,omitempty"`
|
||||||
|
Attributes map[string]string `bson:"attributes,omitempty" json:"attributes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PaymentQuoteSnapshot stores the latest quote info.
|
// PaymentQuoteSnapshot stores the latest quote info.
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:pshared/data/dto/wallet/balance.dart';
|
|||||||
import 'package:pshared/data/dto/wallet/wallet.dart';
|
import 'package:pshared/data/dto/wallet/wallet.dart';
|
||||||
import 'package:pshared/data/mapper/wallet/balance.dart';
|
import 'package:pshared/data/mapper/wallet/balance.dart';
|
||||||
import 'package:pshared/data/mapper/wallet/money.dart';
|
import 'package:pshared/data/mapper/wallet/money.dart';
|
||||||
|
import 'package:pshared/models/describable.dart';
|
||||||
import 'package:pshared/models/wallet/wallet.dart';
|
import 'package:pshared/models/wallet/wallet.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -22,5 +23,9 @@ extension WalletDTOMapper on WalletDTO {
|
|||||||
updatedAt: (updatedAt == null || updatedAt!.isEmpty) ? null : DateTime.tryParse(updatedAt!),
|
updatedAt: (updatedAt == null || updatedAt!.isEmpty) ? null : DateTime.tryParse(updatedAt!),
|
||||||
balance: balance?.toDomain(),
|
balance: balance?.toDomain(),
|
||||||
availableMoney: balance?.available?.toDomain(),
|
availableMoney: balance?.available?.toDomain(),
|
||||||
|
describable: newDescribable(
|
||||||
|
name: metadata?['name'] ?? 'Crypto Wallet',
|
||||||
|
description: metadata?['description'],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
18
frontend/pshared/lib/models/asset.dart
Normal file
18
frontend/pshared/lib/models/asset.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import 'package:pshared/models/currency.dart';
|
||||||
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class Asset {
|
||||||
|
final Currency currency;
|
||||||
|
final double amount;
|
||||||
|
|
||||||
|
const Asset({
|
||||||
|
required this.currency,
|
||||||
|
required this.amount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Asset createAsset(String currencyCode, String amount) => Asset(
|
||||||
|
currency: currencyStringToCode(currencyCode),
|
||||||
|
amount: double.parse(amount),
|
||||||
|
);
|
||||||
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,5 +1,6 @@
|
|||||||
import 'package:pshared/models/wallet/balance.dart';
|
import 'package:pshared/models/wallet/balance.dart';
|
||||||
import 'package:pshared/models/wallet/money.dart';
|
import 'package:pshared/models/wallet/money.dart';
|
||||||
|
import 'package:pshared/models/describable.dart';
|
||||||
|
|
||||||
|
|
||||||
class WalletAsset {
|
class WalletAsset {
|
||||||
@@ -14,7 +15,7 @@ class WalletAsset {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class WalletModel {
|
class WalletModel implements Describable {
|
||||||
final String walletRef;
|
final String walletRef;
|
||||||
final String organizationRef;
|
final String organizationRef;
|
||||||
final String ownerRef;
|
final String ownerRef;
|
||||||
@@ -26,6 +27,13 @@ class WalletModel {
|
|||||||
final DateTime? updatedAt;
|
final DateTime? updatedAt;
|
||||||
final WalletBalance? balance;
|
final WalletBalance? balance;
|
||||||
final WalletMoney? availableMoney;
|
final WalletMoney? availableMoney;
|
||||||
|
final Describable describable;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => describable.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get description => describable.description;
|
||||||
|
|
||||||
const WalletModel({
|
const WalletModel({
|
||||||
required this.walletRef,
|
required this.walletRef,
|
||||||
@@ -39,11 +47,13 @@ class WalletModel {
|
|||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
this.balance,
|
this.balance,
|
||||||
this.availableMoney,
|
this.availableMoney,
|
||||||
|
required this.describable,
|
||||||
});
|
});
|
||||||
|
|
||||||
WalletModel copyWith({
|
WalletModel copyWith({
|
||||||
WalletBalance? balance,
|
WalletBalance? balance,
|
||||||
WalletMoney? availableMoney,
|
WalletMoney? availableMoney,
|
||||||
|
Describable? describable,
|
||||||
}) {
|
}) {
|
||||||
return WalletModel(
|
return WalletModel(
|
||||||
walletRef: walletRef,
|
walletRef: walletRef,
|
||||||
@@ -57,6 +67,7 @@ class WalletModel {
|
|||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
balance: balance ?? this.balance,
|
balance: balance ?? this.balance,
|
||||||
availableMoney: availableMoney ?? this.availableMoney,
|
availableMoney: availableMoney ?? this.availableMoney,
|
||||||
|
describable: describable ?? this.describable,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
frontend/pshared/lib/provider/payment/amount.dart
Normal file
20
frontend/pshared/lib/provider/payment/amount.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentAmountProvider with ChangeNotifier {
|
||||||
|
double _amount = 10.0;
|
||||||
|
bool _payerCoversFee = true;
|
||||||
|
|
||||||
|
double get amount => _amount;
|
||||||
|
bool get payerCoversFee => _payerCoversFee;
|
||||||
|
|
||||||
|
void setAmount(double value) {
|
||||||
|
_amount = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPayerCoversFee(bool value) {
|
||||||
|
_payerCoversFee = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pshared/models/asset.dart';
|
||||||
|
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/payment/currency_pair.dart';
|
||||||
|
import 'package:pshared/models/payment/fx/intent.dart';
|
||||||
|
import 'package:pshared/models/payment/fx/side.dart';
|
||||||
|
import 'package:pshared/models/payment/kind.dart';
|
||||||
|
import 'package:pshared/models/payment/methods/card.dart';
|
||||||
|
import 'package:pshared/models/payment/methods/managed_wallet.dart';
|
||||||
|
import 'package:pshared/models/payment/money.dart';
|
||||||
|
import 'package:pshared/models/payment/settlement_mode.dart';
|
||||||
|
import 'package:pshared/provider/payment/amount.dart';
|
||||||
import 'package:pshared/api/requests/payment/quote.dart';
|
import 'package:pshared/api/requests/payment/quote.dart';
|
||||||
import 'package:pshared/data/mapper/payment/intent/payment.dart';
|
import 'package:pshared/data/mapper/payment/intent/payment.dart';
|
||||||
import 'package:pshared/models/payment/intent.dart';
|
import 'package:pshared/models/payment/intent.dart';
|
||||||
@@ -7,7 +19,6 @@ import 'package:pshared/models/payment/quote.dart';
|
|||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
import 'package:pshared/provider/resource.dart';
|
import 'package:pshared/provider/resource.dart';
|
||||||
import 'package:pshared/service/payment/quotation.dart';
|
import 'package:pshared/service/payment/quotation.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class QuotationProvider extends ChangeNotifier {
|
class QuotationProvider extends ChangeNotifier {
|
||||||
@@ -15,14 +26,46 @@ class QuotationProvider extends ChangeNotifier {
|
|||||||
late OrganizationsProvider _organizations;
|
late OrganizationsProvider _organizations;
|
||||||
bool _isLoaded = false;
|
bool _isLoaded = false;
|
||||||
|
|
||||||
void update(OrganizationsProvider venue) {
|
void update(OrganizationsProvider venue, PaymentAmountProvider payment) {
|
||||||
_organizations = venue;
|
_organizations = venue;
|
||||||
|
getQuotation(PaymentIntent(
|
||||||
|
kind: PaymentKind.payout,
|
||||||
|
amount: Money(
|
||||||
|
amount: payment.amount.toString(),
|
||||||
|
currency: 'USDT',
|
||||||
|
),
|
||||||
|
destination: CardPaymentMethod(
|
||||||
|
pan: '4000000000000077',
|
||||||
|
firstName: 'John',
|
||||||
|
lastName: 'Doe',
|
||||||
|
),
|
||||||
|
source: ManagedWalletPaymentMethod(
|
||||||
|
managedWalletRef: '',
|
||||||
|
),
|
||||||
|
fx: FxIntent(
|
||||||
|
pair: CurrencyPair(
|
||||||
|
base: 'USDT',
|
||||||
|
quote: 'RUB',
|
||||||
|
),
|
||||||
|
side: FxSide.sellBaseBuyQuote,
|
||||||
|
),
|
||||||
|
settlementMode: payment.payerCoversFee ? SettlementMode.fixReceived : SettlementMode.fixSource,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
PaymentQuote? get quotation => _quotation.data;
|
PaymentQuote? get quotation => _quotation.data;
|
||||||
|
|
||||||
bool get isReady => _isLoaded && !_quotation.isLoading && _quotation.error == null;
|
bool get isReady => _isLoaded && !_quotation.isLoading && _quotation.error == null;
|
||||||
|
|
||||||
|
Asset? get fee => quotation == null ? null : createAsset(quotation!.expectedFeeTotal!.currency, quotation!.expectedFeeTotal!.amount);
|
||||||
|
Asset? get total => quotation == null ? null : createAsset(quotation!.debitAmount!.currency, quotation!.debitAmount!.amount);
|
||||||
|
Asset? get recipientGets => quotation == null ? null : createAsset(quotation!.expectedSettlementAmount!.currency, quotation!.expectedSettlementAmount!.amount);
|
||||||
|
|
||||||
|
void _setResource(Resource<PaymentQuote> quotation) {
|
||||||
|
_quotation = quotation;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
Future<PaymentQuote?> getQuotation(PaymentIntent intent) async {
|
Future<PaymentQuote?> getQuotation(PaymentIntent intent) async {
|
||||||
if (!_organizations.isOrganizationSet) throw StateError('Organization is not set');
|
if (!_organizations.isOrganizationSet) throw StateError('Organization is not set');
|
||||||
try {
|
try {
|
||||||
@@ -35,19 +78,20 @@ class QuotationProvider extends ChangeNotifier {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
_isLoaded = true;
|
_isLoaded = true;
|
||||||
_quotation = _quotation.copyWith(data: response, isLoading: false);
|
_setResource(_quotation.copyWith(data: response, isLoading: false, error: null));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_quotation = _quotation.copyWith(
|
_setResource(_quotation.copyWith(
|
||||||
|
data: null,
|
||||||
error: e is Exception ? e : Exception(e.toString()),
|
error: e is Exception ? e : Exception(e.toString()),
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return _quotation.data;
|
return _quotation.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
_quotation = Resource(data: null, isLoading: false, error: null);
|
_setResource(Resource(data: null, isLoading: false, error: null));
|
||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import 'package:pshared/api/requests/payment/quote.dart';
|
|||||||
import 'package:pshared/api/responses/payment/quotation.dart';
|
import 'package:pshared/api/responses/payment/quotation.dart';
|
||||||
import 'package:pshared/data/mapper/payment/payment_quote.dart';
|
import 'package:pshared/data/mapper/payment/payment_quote.dart';
|
||||||
import 'package:pshared/models/payment/quote.dart';
|
import 'package:pshared/models/payment/quote.dart';
|
||||||
|
import 'package:pshared/service/authorization/service.dart';
|
||||||
import 'package:pshared/service/services.dart';
|
import 'package:pshared/service/services.dart';
|
||||||
import 'package:pshared/utils/http/requests.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class QuotationService {
|
class QuotationService {
|
||||||
@@ -14,7 +14,11 @@ class QuotationService {
|
|||||||
|
|
||||||
static Future<PaymentQuote> getQuotation(String organizationRef, QuotePaymentRequest request) async {
|
static Future<PaymentQuote> getQuotation(String organizationRef, QuotePaymentRequest request) async {
|
||||||
_logger.fine('Quoting payment for organization $organizationRef');
|
_logger.fine('Quoting payment for organization $organizationRef');
|
||||||
final response = await getPOSTResponse(_objectType, '/quote/$organizationRef', request.toJson());
|
final response = await AuthorizationService.getPOSTResponse(
|
||||||
|
_objectType,
|
||||||
|
'/quote/$organizationRef',
|
||||||
|
request.toJson(),
|
||||||
|
);
|
||||||
return PaymentQuoteResponse.fromJson(response).quote.toDomain();
|
return PaymentQuoteResponse.fromJson(response).quote.toDomain();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,64 @@
|
|||||||
String currencyCodeToSymbol(String currencyCode) {
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/asset.dart';
|
||||||
|
import 'package:pshared/models/currency.dart';
|
||||||
|
|
||||||
|
|
||||||
|
String currencyCodeToSymbol(Currency currencyCode) {
|
||||||
switch (currencyCode) {
|
switch (currencyCode) {
|
||||||
case 'USD':
|
case Currency.usd:
|
||||||
return '\$';
|
return '\$';
|
||||||
case 'PLN':
|
case Currency.usdt:
|
||||||
return 'zł';
|
return '₮';
|
||||||
case 'EUR':
|
case Currency.usdc:
|
||||||
return '€';
|
return '\$';
|
||||||
case 'GBP':
|
case Currency.rub:
|
||||||
return '£';
|
|
||||||
case 'HUF':
|
|
||||||
return 'Ft';
|
|
||||||
case 'RUB':
|
|
||||||
return '₽';
|
return '₽';
|
||||||
default:
|
case Currency.eur:
|
||||||
return currencyCode;
|
return '€';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String currencyToString(String currencyCode, double amount) {
|
String amountToString(double amount) {
|
||||||
return '${currencyCodeToSymbol(currencyCode)}\u00A0${amount.toStringAsFixed(2)}';
|
return amount.toStringAsFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
String currencyToString(Currency currencyCode, double amount) {
|
||||||
|
return '${currencyCodeToSymbol(currencyCode)}\u00A0${amountToString(amount)}';
|
||||||
|
}
|
||||||
|
|
||||||
|
String assetToString(Asset asset) {
|
||||||
|
return currencyToString(asset.currency, asset.amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
Currency currencyStringToCode(String currencyCode) {
|
||||||
|
switch (currencyCode) {
|
||||||
|
case 'USD':
|
||||||
|
return Currency.usd;
|
||||||
|
case 'USDT':
|
||||||
|
return Currency.usdt;
|
||||||
|
case 'USDC':
|
||||||
|
return Currency.usdc;
|
||||||
|
case 'RUB':
|
||||||
|
return Currency.rub;
|
||||||
|
case 'EUR':
|
||||||
|
return Currency.eur;
|
||||||
|
default:
|
||||||
|
throw ArgumentError('Unknown currency code: $currencyCode');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconData iconForCurrencyType(Currency currencyCode) {
|
||||||
|
switch (currencyCode) {
|
||||||
|
case Currency.usd:
|
||||||
|
return Icons.currency_exchange;
|
||||||
|
case Currency.eur:
|
||||||
|
return Icons.currency_exchange;
|
||||||
|
case Currency.rub:
|
||||||
|
return Icons.currency_ruble;
|
||||||
|
case Currency.usdt:
|
||||||
|
return Icons.currency_exchange;
|
||||||
|
case Currency.usdc:
|
||||||
|
return Icons.money;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:pshared/models/wallet/wallet.dart' as domain;
|
import 'package:pshared/models/wallet/wallet.dart' as domain;
|
||||||
|
|
||||||
import 'package:pweb/models/currency.dart';
|
import 'package:pshared/models/currency.dart';
|
||||||
|
import 'package:pshared/models/describable.dart';
|
||||||
|
|
||||||
import 'package:pweb/models/wallet.dart';
|
import 'package:pweb/models/wallet.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -16,7 +18,6 @@ extension WalletUiMapper on domain.WalletModel {
|
|||||||
return Wallet(
|
return Wallet(
|
||||||
id: walletRef,
|
id: walletRef,
|
||||||
walletUserID: walletRef,
|
walletUserID: walletRef,
|
||||||
name: metadata?['name'] ?? walletRef,
|
|
||||||
balance: parsedAmount,
|
balance: parsedAmount,
|
||||||
currency: currency,
|
currency: currency,
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
@@ -25,6 +26,10 @@ extension WalletUiMapper on domain.WalletModel {
|
|||||||
network: asset.chain,
|
network: asset.chain,
|
||||||
tokenSymbol: asset.tokenSymbol,
|
tokenSymbol: asset.tokenSymbol,
|
||||||
contractAddress: asset.contractAddress,
|
contractAddress: asset.contractAddress,
|
||||||
|
describable: newDescribable(
|
||||||
|
name: metadata?['name'] ?? 'Crypto Wallet',
|
||||||
|
description: metadata?['description'],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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",
|
||||||
@@ -311,6 +317,7 @@
|
|||||||
"paymentTypeBankAccount": "Russian Bank Account",
|
"paymentTypeBankAccount": "Russian Bank Account",
|
||||||
"paymentTypeIban": "IBAN",
|
"paymentTypeIban": "IBAN",
|
||||||
"paymentTypeWallet": "Wallet",
|
"paymentTypeWallet": "Wallet",
|
||||||
|
"paymentTypeCryptoWallet": "Crypto Wallet",
|
||||||
"paymentTypeCryptoAddress": "Crypto address",
|
"paymentTypeCryptoAddress": "Crypto address",
|
||||||
"paymentTypeLedger": "Ledger account",
|
"paymentTypeLedger": "Ledger account",
|
||||||
"paymentTypeManagedWallet": "Managed wallet",
|
"paymentTypeManagedWallet": "Managed wallet",
|
||||||
@@ -378,7 +385,7 @@
|
|||||||
"send": "Send Payout",
|
"send": "Send Payout",
|
||||||
"recipientPaysFee": "Recipient pays the fee",
|
"recipientPaysFee": "Recipient pays the fee",
|
||||||
|
|
||||||
"sentAmount": "Sent amount: ${amount}",
|
"sentAmount": "Sent amount: {amount}",
|
||||||
"@sentAmount": {
|
"@sentAmount": {
|
||||||
"description": "Label showing the amount sent",
|
"description": "Label showing the amount sent",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -388,7 +395,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"fee": "Fee: ${fee}",
|
"fee": "Fee: {fee}",
|
||||||
"@fee": {
|
"@fee": {
|
||||||
"description": "Label showing the transaction fee",
|
"description": "Label showing the transaction fee",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -398,7 +405,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"recipientWillReceive": "Recipient will receive: ${amount}",
|
"recipientWillReceive": "Recipient will receive: {amount}",
|
||||||
"@recipientWillReceive": {
|
"@recipientWillReceive": {
|
||||||
"description": "Label showing how much the recipient will receive",
|
"description": "Label showing how much the recipient will receive",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -408,7 +415,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"total": "Total: ${total}",
|
"total": "Total: {total}",
|
||||||
"@total": {
|
"@total": {
|
||||||
"description": "Label showing the total amount of the transaction",
|
"description": "Label showing the total amount of the transaction",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -425,9 +432,13 @@
|
|||||||
"addRecipient": "Add Recipient",
|
"addRecipient": "Add Recipient",
|
||||||
"editRecipient": "Edit Recipient",
|
"editRecipient": "Edit Recipient",
|
||||||
"saveRecipient": "Save Recipient",
|
"saveRecipient": "Save Recipient",
|
||||||
|
|
||||||
"choosePaymentMethod": "Payment Methods (choose at least 1)",
|
"choosePaymentMethod": "Payment Methods (choose at least 1)",
|
||||||
"recipientFormRule": "Recipient must have at least one payment method",
|
"recipientFormRule": "Recipient must have at least one payment method",
|
||||||
|
"recipientFormValidationError": "Fill in the required fields to save the recipient",
|
||||||
|
"@recipientFormValidationError": {
|
||||||
|
"description": "Error message shown when recipient form fields are invalid or missing"
|
||||||
|
},
|
||||||
|
|
||||||
"allStatus": "All",
|
"allStatus": "All",
|
||||||
"readyStatus": "Ready",
|
"readyStatus": "Ready",
|
||||||
@@ -443,6 +454,10 @@
|
|||||||
"@errorSaveRecipient": {
|
"@errorSaveRecipient": {
|
||||||
"description": "Error message displayed when saving a recipient fails"
|
"description": "Error message displayed when saving a recipient fails"
|
||||||
},
|
},
|
||||||
|
"recipientSavedSuccessfully": "Recipient saved successfully",
|
||||||
|
"@recipientSavedSuccessfully": {
|
||||||
|
"description": "Success message displayed when a recipient is saved"
|
||||||
|
},
|
||||||
"recipientDeletedSuccessfully": "Recipient deleted successfully",
|
"recipientDeletedSuccessfully": "Recipient deleted successfully",
|
||||||
"@recipientDeletedSuccessfully": {
|
"@recipientDeletedSuccessfully": {
|
||||||
"description": "Success message displayed when a recipient is deleted"
|
"description": "Success message displayed when a recipient is deleted"
|
||||||
|
|||||||
@@ -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": "содержит строчную букву",
|
||||||
@@ -311,6 +317,7 @@
|
|||||||
"paymentTypeBankAccount": "Российский банковский счет",
|
"paymentTypeBankAccount": "Российский банковский счет",
|
||||||
"paymentTypeIban": "IBAN",
|
"paymentTypeIban": "IBAN",
|
||||||
"paymentTypeWallet": "Кошелек",
|
"paymentTypeWallet": "Кошелек",
|
||||||
|
"paymentTypeCryptoWallet": "Криптокошелек",
|
||||||
"paymentTypeCryptoAddress": "Крипто-адрес",
|
"paymentTypeCryptoAddress": "Крипто-адрес",
|
||||||
"paymentTypeLedger": "Леджер счет",
|
"paymentTypeLedger": "Леджер счет",
|
||||||
"paymentTypeManagedWallet": "Управляемый кошелек",
|
"paymentTypeManagedWallet": "Управляемый кошелек",
|
||||||
@@ -378,7 +385,7 @@
|
|||||||
"send": "Отправить выплату",
|
"send": "Отправить выплату",
|
||||||
"recipientPaysFee": "Получатель оплачивает комиссию",
|
"recipientPaysFee": "Получатель оплачивает комиссию",
|
||||||
|
|
||||||
"sentAmount": "Отправленная сумма: ${amount}",
|
"sentAmount": "Отправленная сумма: {amount}",
|
||||||
"@sentAmount": {
|
"@sentAmount": {
|
||||||
"description": "Метка, показывающая отправленную сумму",
|
"description": "Метка, показывающая отправленную сумму",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -388,7 +395,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"fee": "Комиссия: ${fee}",
|
"fee": "Комиссия: {fee}",
|
||||||
"@fee": {
|
"@fee": {
|
||||||
"description": "Метка, показывающая комиссию за транзакцию",
|
"description": "Метка, показывающая комиссию за транзакцию",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -398,7 +405,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"recipientWillReceive": "Получатель получит: ${amount}",
|
"recipientWillReceive": "Получатель получит: {amount}",
|
||||||
"@recipientWillReceive": {
|
"@recipientWillReceive": {
|
||||||
"description": "Метка, показывающая, сколько получит получатель",
|
"description": "Метка, показывающая, сколько получит получатель",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -408,7 +415,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"total": "Итого: ${total}",
|
"total": "Итого: {total}",
|
||||||
"@total": {
|
"@total": {
|
||||||
"description": "Метка, показывающая общую сумму транзакции",
|
"description": "Метка, показывающая общую сумму транзакции",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@@ -425,9 +432,13 @@
|
|||||||
"addRecipient": "Добавить получателя",
|
"addRecipient": "Добавить получателя",
|
||||||
"editRecipient": "Редактировать получателя",
|
"editRecipient": "Редактировать получателя",
|
||||||
"saveRecipient": "Сохранить получателя",
|
"saveRecipient": "Сохранить получателя",
|
||||||
|
|
||||||
"choosePaymentMethod": "Способы оплаты (выберите хотя бы 1)",
|
"choosePaymentMethod": "Способы оплаты (выберите хотя бы 1)",
|
||||||
"recipientFormRule": "Получатель должен иметь хотя бы один способ оплаты",
|
"recipientFormRule": "Получатель должен иметь хотя бы один способ оплаты",
|
||||||
|
"recipientFormValidationError": "Заполните обязательные поля, чтобы сохранить получателя",
|
||||||
|
"@recipientFormValidationError": {
|
||||||
|
"description": "Сообщение об ошибке, если обязательные поля формы получателя не заполнены"
|
||||||
|
},
|
||||||
|
|
||||||
"allStatus": "Все",
|
"allStatus": "Все",
|
||||||
"readyStatus": "Готов",
|
"readyStatus": "Готов",
|
||||||
@@ -443,6 +454,10 @@
|
|||||||
"@errorSaveRecipient": {
|
"@errorSaveRecipient": {
|
||||||
"description": "Сообщение об ошибке при неудачном сохранении получателя"
|
"description": "Сообщение об ошибке при неудачном сохранении получателя"
|
||||||
},
|
},
|
||||||
|
"recipientSavedSuccessfully": "Получатель успешно сохранен",
|
||||||
|
"@recipientSavedSuccessfully": {
|
||||||
|
"description": "Сообщение об успешном сохранении получателя"
|
||||||
|
},
|
||||||
"recipientDeletedSuccessfully": "Получатель успешно удален",
|
"recipientDeletedSuccessfully": "Получатель успешно удален",
|
||||||
"@recipientDeletedSuccessfully": {
|
"@recipientDeletedSuccessfully": {
|
||||||
"description": "Сообщение об успешном удалении получателя"
|
"description": "Сообщение об успешном удалении получателя"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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/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';
|
||||||
@@ -27,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() {
|
||||||
@@ -40,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();
|
||||||
@@ -57,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>(
|
||||||
@@ -70,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(),
|
||||||
),
|
),
|
||||||
@@ -91,12 +93,16 @@ void main() async {
|
|||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => MockPaymentProvider(),
|
create: (_) => MockPaymentProvider(),
|
||||||
),
|
),
|
||||||
|
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => OperationProvider(OperationService())..loadOperations(),
|
create: (_) => OperationProvider(OperationService())..loadOperations(),
|
||||||
),
|
),
|
||||||
ChangeNotifierProxyProvider<OrganizationsProvider, QuotationProvider>(
|
ChangeNotifierProvider(
|
||||||
|
create: (_) => PaymentAmountProvider(),
|
||||||
|
),
|
||||||
|
ChangeNotifierProxyProvider2<OrganizationsProvider, PaymentAmountProvider, QuotationProvider>(
|
||||||
create: (_) => QuotationProvider(),
|
create: (_) => QuotationProvider(),
|
||||||
update: (context, orgnization, provider) => provider!..update(orgnization),
|
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 }
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'package:pweb/models/currency.dart';
|
import 'package:pshared/models/currency.dart';
|
||||||
|
import 'package:pshared/models/describable.dart';
|
||||||
|
|
||||||
|
|
||||||
class Wallet {
|
class Wallet implements Describable {
|
||||||
final String id;
|
final String id;
|
||||||
final String walletUserID; // ID or number that we show the user
|
final String walletUserID; // ID or number that we show the user
|
||||||
final String name;
|
|
||||||
final double balance;
|
final double balance;
|
||||||
final Currency currency;
|
final Currency currency;
|
||||||
final bool isHidden;
|
final bool isHidden;
|
||||||
@@ -13,14 +13,21 @@ class Wallet {
|
|||||||
final String? network;
|
final String? network;
|
||||||
final String? tokenSymbol;
|
final String? tokenSymbol;
|
||||||
final String? contractAddress;
|
final String? contractAddress;
|
||||||
|
final Describable describable;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => describable.name;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get description => describable.description;
|
||||||
|
|
||||||
Wallet({
|
Wallet({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.walletUserID,
|
required this.walletUserID,
|
||||||
required this.name,
|
|
||||||
required this.balance,
|
required this.balance,
|
||||||
required this.currency,
|
required this.currency,
|
||||||
required this.calculatedAt,
|
required this.calculatedAt,
|
||||||
|
required this.describable,
|
||||||
this.isHidden = true,
|
this.isHidden = true,
|
||||||
this.depositAddress,
|
this.depositAddress,
|
||||||
this.network,
|
this.network,
|
||||||
@@ -30,7 +37,6 @@ class Wallet {
|
|||||||
|
|
||||||
Wallet copyWith({
|
Wallet copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
String? name,
|
|
||||||
double? balance,
|
double? balance,
|
||||||
Currency? currency,
|
Currency? currency,
|
||||||
String? walletUserID,
|
String? walletUserID,
|
||||||
@@ -39,9 +45,11 @@ class Wallet {
|
|||||||
String? network,
|
String? network,
|
||||||
String? tokenSymbol,
|
String? tokenSymbol,
|
||||||
String? contractAddress,
|
String? contractAddress,
|
||||||
|
Describable? describable,
|
||||||
|
String? name,
|
||||||
|
String? Function()? description,
|
||||||
}) => Wallet(
|
}) => Wallet(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
name: name ?? this.name,
|
|
||||||
balance: balance ?? this.balance,
|
balance: balance ?? this.balance,
|
||||||
currency: currency ?? this.currency,
|
currency: currency ?? this.currency,
|
||||||
walletUserID: walletUserID ?? this.walletUserID,
|
walletUserID: walletUserID ?? this.walletUserID,
|
||||||
@@ -51,5 +59,9 @@ class Wallet {
|
|||||||
network: network ?? this.network,
|
network: network ?? this.network,
|
||||||
tokenSymbol: tokenSymbol ?? this.tokenSymbol,
|
tokenSymbol: tokenSymbol ?? this.tokenSymbol,
|
||||||
contractAddress: contractAddress ?? this.contractAddress,
|
contractAddress: contractAddress ?? this.contractAddress,
|
||||||
|
describable: describable
|
||||||
|
?? (name != null || description != null
|
||||||
|
? this.describable.copyWith(name: name, description: description)
|
||||||
|
: this.describable),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
|
|
||||||
import 'package:pshared/models/payment/status.dart';
|
import 'package:pshared/models/payment/status.dart';
|
||||||
|
|
||||||
import 'package:pweb/models/currency.dart';
|
import 'package:pshared/models/currency.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -101,21 +104,28 @@ class _AdressBookRecipientFormState extends State<AdressBookRecipientForm> {
|
|||||||
|
|
||||||
//TODO: Change when registration is ready
|
//TODO: Change when registration is ready
|
||||||
Future<void> _save() async {
|
Future<void> _save() async {
|
||||||
if (!_formKey.currentState!.validate() || _methods.isEmpty) {
|
final l10n = AppLocalizations.of(context)!;
|
||||||
notifyUser(context, AppLocalizations.of(context)!.errorSaveRecipient);
|
|
||||||
|
if (!_formKey.currentState!.validate()) {
|
||||||
|
notifyUser(context, l10n.recipientFormValidationError);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AmplitudeService.recipientAddCompleted(
|
if (_methods.isEmpty) {
|
||||||
// _type,
|
notifyUser(context, l10n.recipientFormRule);
|
||||||
// _status,
|
return;
|
||||||
// _methods.keys.toSet(),
|
}
|
||||||
// );
|
|
||||||
|
unawaited(PosthogService.recipientAddCompleted(
|
||||||
|
_type,
|
||||||
|
_status,
|
||||||
|
_methods.keys.toSet(),
|
||||||
|
));
|
||||||
final recipient = await executeActionWithNotification(
|
final recipient = await executeActionWithNotification(
|
||||||
context: context,
|
context: context,
|
||||||
action: _doSave,
|
action: _doSave,
|
||||||
errorMessage: AppLocalizations.of(context)!.errorSaveRecipient,
|
errorMessage: l10n.errorSaveRecipient,
|
||||||
successMessage: AppLocalizations.of(context)!.recipientFormRule,
|
successMessage: l10n.recipientSavedSuccessfully,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
|
||||||
import 'package:pweb/models/wallet.dart';
|
import 'package:pweb/models/wallet.dart';
|
||||||
import 'package:pweb/utils/currency.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class BalanceAmount extends StatelessWidget {
|
class BalanceAmount extends StatelessWidget {
|
||||||
@@ -25,7 +27,7 @@ class BalanceAmount extends StatelessWidget {
|
|||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
wallet.isHidden ? '•••• $currencyBalance' : '${wallet.balance.toStringAsFixed(2)} $currencyBalance',
|
wallet.isHidden ? '•••• $currencyBalance' : '${amountToString(wallet.balance)} $currencyBalance',
|
||||||
style: textTheme.headlineSmall?.copyWith(
|
style: textTheme.headlineSmall?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: colorScheme.onSurface,
|
color: colorScheme.onSurface,
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ class WalletCard extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
BalanceHeader(
|
BalanceHeader(
|
||||||
walletName: wallet.name,
|
walletNetwork: wallet.network,
|
||||||
walletId: wallet.walletUserID,
|
tokenSymbol: wallet.tokenSymbol,
|
||||||
),
|
),
|
||||||
BalanceAmount(
|
BalanceAmount(
|
||||||
wallet: wallet,
|
wallet: wallet,
|
||||||
|
|||||||
@@ -2,37 +2,46 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
|
|
||||||
class BalanceHeader extends StatelessWidget {
|
class BalanceHeader extends StatelessWidget {
|
||||||
final String walletName;
|
final String? walletNetwork;
|
||||||
final String walletId;
|
final String? tokenSymbol;
|
||||||
|
|
||||||
const BalanceHeader({
|
const BalanceHeader({
|
||||||
super.key,
|
super.key,
|
||||||
required this.walletName,
|
this.walletNetwork,
|
||||||
required this.walletId,
|
this.tokenSymbol,
|
||||||
});
|
});
|
||||||
|
|
||||||
static const double _spacing = 8.0;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
final symbol = tokenSymbol?.trim();
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
walletName,
|
'Crypto Wallet',
|
||||||
style: textTheme.titleMedium?.copyWith(
|
style: textTheme.titleMedium?.copyWith(
|
||||||
color: colorScheme.onSurface,
|
color: colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: _spacing),
|
if (symbol != null && symbol.isNotEmpty) ...[
|
||||||
Text(
|
const SizedBox(width: 8),
|
||||||
walletId,
|
Container(
|
||||||
style: textTheme.bodySmall?.copyWith(
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||||
color: colorScheme.onSurface,
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.primaryContainer,
|
||||||
|
borderRadius: BorderRadius.circular(999),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
symbol,
|
||||||
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
|
color: colorScheme.onPrimaryContainer,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
74
frontend/pweb/lib/pages/dashboard/payouts/amount.dart
Normal file
74
frontend/pweb/lib/pages/dashboard/payouts/amount.dart
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/payment/amount.dart';
|
||||||
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentAmountWidget extends StatefulWidget {
|
||||||
|
const PaymentAmountWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PaymentAmountWidget> createState() => _PaymentAmountWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PaymentAmountWidgetState extends State<PaymentAmountWidget> {
|
||||||
|
late final TextEditingController _controller;
|
||||||
|
bool _isSyncingText = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final initialAmount = context.read<PaymentAmountProvider>().amount;
|
||||||
|
_controller = TextEditingController(text: amountToString(initialAmount));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
double? _parseAmount(String value) => double.tryParse(value.replaceAll(',', '.'));
|
||||||
|
|
||||||
|
void _syncTextWithAmount(double amount) {
|
||||||
|
final parsedText = _parseAmount(_controller.text);
|
||||||
|
if (parsedText != null && parsedText == amount) return;
|
||||||
|
|
||||||
|
final nextText = amountToString(amount);
|
||||||
|
_isSyncingText = true;
|
||||||
|
_controller.value = TextEditingValue(
|
||||||
|
text: nextText,
|
||||||
|
selection: TextSelection.collapsed(offset: nextText.length),
|
||||||
|
);
|
||||||
|
_isSyncingText = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onChanged(String value) {
|
||||||
|
if (_isSyncingText) return;
|
||||||
|
|
||||||
|
final parsed = _parseAmount(value);
|
||||||
|
if (parsed != null) {
|
||||||
|
context.read<PaymentAmountProvider>().setAmount(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final amount = context.select<PaymentAmountProvider, double>((provider) => provider.amount);
|
||||||
|
_syncTextWithAmount(amount);
|
||||||
|
|
||||||
|
return TextField(
|
||||||
|
controller: _controller,
|
||||||
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: AppLocalizations.of(context)!.amount,
|
||||||
|
border: const OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
onChanged: _onChanged,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
frontend/pweb/lib/pages/dashboard/payouts/fee_payer.dart
Normal file
29
frontend/pweb/lib/pages/dashboard/payouts/fee_payer.dart
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/payment/amount.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class FeePayerSwitch extends StatelessWidget {
|
||||||
|
final double spacing;
|
||||||
|
final TextStyle? style;
|
||||||
|
|
||||||
|
const FeePayerSwitch({super.key, required this.spacing, TextStyle? this.style});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Consumer<PaymentAmountProvider>(
|
||||||
|
builder: (context, provider, _) => Row(
|
||||||
|
spacing: spacing,
|
||||||
|
children: [
|
||||||
|
Text(AppLocalizations.of(context)!.recipientPaysFee, style: style),
|
||||||
|
Switch(
|
||||||
|
value: !provider.payerCoversFee,
|
||||||
|
onChanged: (val) => provider.setPayerCoversFee(!val),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
43
frontend/pweb/lib/pages/dashboard/payouts/form.dart
Normal file
43
frontend/pweb/lib/pages/dashboard/payouts/form.dart
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/amount.dart';
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/fee_payer.dart';
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/summary/widget.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentFormWidget extends StatelessWidget {
|
||||||
|
const PaymentFormWidget({super.key});
|
||||||
|
|
||||||
|
static const double _smallSpacing = 5;
|
||||||
|
static const double _mediumSpacing = 10;
|
||||||
|
static const double _largeSpacing = 16;
|
||||||
|
static const double _extraSpacing = 15;
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(loc.details, style: theme.textTheme.titleMedium),
|
||||||
|
const SizedBox(height: _smallSpacing),
|
||||||
|
|
||||||
|
const PaymentAmountWidget(),
|
||||||
|
|
||||||
|
const SizedBox(height: _mediumSpacing),
|
||||||
|
|
||||||
|
FeePayerSwitch(spacing: _mediumSpacing, style: theme.textTheme.titleMedium),
|
||||||
|
|
||||||
|
const SizedBox(height: _largeSpacing),
|
||||||
|
|
||||||
|
const PaymentSummary(spacing: _extraSpacing),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/providers/mock_payment.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
|
||||||
|
|
||||||
|
|
||||||
class PaymentFormWidget extends StatelessWidget {
|
|
||||||
const PaymentFormWidget({super.key});
|
|
||||||
|
|
||||||
static const double _smallSpacing = 5;
|
|
||||||
static const double _mediumSpacing = 10;
|
|
||||||
static const double _largeSpacing = 16;
|
|
||||||
static const double _extraSpacing = 15;
|
|
||||||
|
|
||||||
String _formatAmount(double amount) => amount.toStringAsFixed(2);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final provider = Provider.of<MockPaymentProvider>(context);
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
final loc = AppLocalizations.of(context)!;
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(loc.details, style: theme.textTheme.titleMedium),
|
|
||||||
const SizedBox(height: _smallSpacing),
|
|
||||||
|
|
||||||
TextField(
|
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: loc.amount,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
onChanged: (val) {
|
|
||||||
final parsed = double.tryParse(val.replaceAll(',', '.')) ?? 0.0;
|
|
||||||
provider.setAmount(parsed);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: _mediumSpacing),
|
|
||||||
|
|
||||||
Row(
|
|
||||||
spacing: _mediumSpacing,
|
|
||||||
children: [
|
|
||||||
Text(loc.recipientPaysFee, style: theme.textTheme.titleMedium),
|
|
||||||
Switch(
|
|
||||||
value: !provider.payerCoversFee,
|
|
||||||
onChanged: (val) => provider.setPayerCoversFee(!val),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
const SizedBox(height: _largeSpacing),
|
|
||||||
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
_SummaryRow(label: loc.sentAmount(_formatAmount(provider.amount)), style: theme.textTheme.titleMedium),
|
|
||||||
_SummaryRow(label: loc.fee(_formatAmount(provider.fee)), style: theme.textTheme.titleMedium),
|
|
||||||
_SummaryRow(label: loc.recipientWillReceive(_formatAmount(provider.recipientGets)), style: theme.textTheme.titleMedium),
|
|
||||||
|
|
||||||
const SizedBox(height: _extraSpacing),
|
|
||||||
|
|
||||||
_SummaryRow(
|
|
||||||
label: loc.total(_formatAmount(provider.total)),
|
|
||||||
style: theme.textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SummaryRow extends StatelessWidget {
|
|
||||||
final String label;
|
|
||||||
final TextStyle? style;
|
|
||||||
|
|
||||||
const _SummaryRow({required this.label, this.style});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Text(label, style: style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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),
|
||||||
|
|||||||
23
frontend/pweb/lib/pages/dashboard/payouts/summary/fee.dart
Normal file
23
frontend/pweb/lib/pages/dashboard/payouts/summary/fee.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/payment/quotation.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentFeeRow extends StatelessWidget {
|
||||||
|
const PaymentFeeRow({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Consumer<QuotationProvider>(
|
||||||
|
builder: (context, provider, _) => PaymentSummaryRow(
|
||||||
|
labelFactory: AppLocalizations.of(context)!.fee,
|
||||||
|
asset: provider.fee,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/payment/quotation.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentRecipientReceivesRow extends StatelessWidget {
|
||||||
|
const PaymentRecipientReceivesRow({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Consumer<QuotationProvider>(
|
||||||
|
builder: (context, provider, _) => PaymentSummaryRow(
|
||||||
|
labelFactory: AppLocalizations.of(context)!.recipientWillReceive,
|
||||||
|
asset: provider.recipientGets,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
24
frontend/pweb/lib/pages/dashboard/payouts/summary/row.dart
Normal file
24
frontend/pweb/lib/pages/dashboard/payouts/summary/row.dart
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/asset.dart';
|
||||||
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentSummaryRow extends StatelessWidget {
|
||||||
|
final String Function(String) labelFactory;
|
||||||
|
final Asset? asset;
|
||||||
|
final TextStyle? style;
|
||||||
|
|
||||||
|
const PaymentSummaryRow({
|
||||||
|
super.key,
|
||||||
|
required this.labelFactory,
|
||||||
|
required this.asset,
|
||||||
|
this.style,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Text(
|
||||||
|
labelFactory(asset == null ? 'N/A' : assetToString(asset!)),
|
||||||
|
style: style,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/asset.dart';
|
||||||
|
import 'package:pshared/models/currency.dart';
|
||||||
|
import 'package:pshared/provider/payment/amount.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentSentAmountRow extends StatelessWidget {
|
||||||
|
final Currency currency;
|
||||||
|
const PaymentSentAmountRow({super.key, required this.currency});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Consumer<PaymentAmountProvider>(
|
||||||
|
builder: (context, provider, _) => PaymentSummaryRow(
|
||||||
|
labelFactory: AppLocalizations.of(context)!.sentAmount,
|
||||||
|
asset: Asset(currency: currency, amount: provider.amount),
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
23
frontend/pweb/lib/pages/dashboard/payouts/summary/total.dart
Normal file
23
frontend/pweb/lib/pages/dashboard/payouts/summary/total.dart
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/payment/quotation.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentTotalRow extends StatelessWidget {
|
||||||
|
const PaymentTotalRow({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Consumer<QuotationProvider>(
|
||||||
|
builder: (context, provider, _) => PaymentSummaryRow(
|
||||||
|
labelFactory: AppLocalizations.of(context)!.total,
|
||||||
|
asset: provider.total,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/utils/currency.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/summary/fee.dart';
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/summary/recipient_receives.dart';
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/summary/sent_amount.dart';
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/summary/total.dart';
|
||||||
|
import 'package:pweb/providers/wallets.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentSummary extends StatelessWidget {
|
||||||
|
final double spacing;
|
||||||
|
|
||||||
|
const PaymentSummary({super.key, required this.spacing});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
PaymentSentAmountRow(currency: currencyStringToCode(context.read<WalletsProvider>().selectedWallet?.tokenSymbol ?? 'USDT')),
|
||||||
|
const PaymentFeeRow(),
|
||||||
|
const PaymentRecipientReceivesRow(),
|
||||||
|
SizedBox(height: spacing),
|
||||||
|
const PaymentTotalRow(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
28
frontend/pweb/lib/pages/dashboard/payouts/widget.dart
Normal file
28
frontend/pweb/lib/pages/dashboard/payouts/widget.dart
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/organizations.dart';
|
||||||
|
import 'package:pshared/provider/payment/amount.dart';
|
||||||
|
import 'package:pshared/provider/payment/quotation.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/dashboard/payouts/form.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentFromWrappingWidget extends StatelessWidget {
|
||||||
|
const PaymentFromWrappingWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (_) => PaymentAmountProvider(),
|
||||||
|
),
|
||||||
|
ChangeNotifierProxyProvider2<OrganizationsProvider, PaymentAmountProvider, QuotationProvider>(
|
||||||
|
create: (_) => QuotationProvider(),
|
||||||
|
update: (context, orgnization, payment, provider) => provider!..update(orgnization, payment),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: const PaymentFormWidget(),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user