Merge pull request 'fixed quotation currency inference' (#630) from pq-626 into main
All checks were successful
ci/woodpecker/push/billing_documents Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/callbacks Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/gateway_tron Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_methods Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/payments_quotation Pipeline was successful
All checks were successful
ci/woodpecker/push/billing_documents Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/callbacks Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/gateway_tron Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_methods Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/payments_quotation Pipeline was successful
Reviewed-on: #630
This commit was merged in pull request #630.
This commit is contained in:
@@ -5,10 +5,10 @@ go 1.25.7
|
||||
replace github.com/tech/sendico/pkg => ../../pkg
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.10
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.3
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.11
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.11
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3
|
||||
github.com/jung-kurt/gofpdf v1.16.2
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/shopspring/decimal v1.4.0
|
||||
@@ -20,20 +20,20 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.8 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
||||
|
||||
@@ -4,42 +4,42 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10 h1:fJvQ5mIBVfKtiyx0AHY6HeWcRX5LGANLpq8SVR+Uazs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2 h1:M1A9AjcFwlxTLuf0Faj88L8Iqw0n/AJHjpZTQzMMsSc=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2/go.mod h1:KsdTV6Q9WKUZm2mNJnUFmIoXfZux91M3sr/a4REX8e0=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.3/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6 h1:N4lRUXZpZ1KVEUn6hxtco/1d2lgYhNn1fHkkl8WhlyQ=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.11 h1:ftxI5sgz8jZkckuUHXfC/wMUc8u3fG1vQS0plr2F2Zs=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.11/go.mod h1:twF11+6ps9aNRKEDimksp923o44w/Thk9+8YIlzWMmo=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 h1:NdV8cwCcAXrCWyxArt58BrvZJ9pZ9Fhf9w6Uh5W3Uyc=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.11/go.mod h1:30yY2zqkMPdrvxBqzI9xQCM+WrlrZKSOpSJEsylVU+8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19 h1:INUvJxmhdEbVulJYHI061k4TVuS3jzzthNvjqvVvTKM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19/go.mod h1:FpZN2QISLdEBWkayloda+sZjVJL+e9Gl0k1SyTgcswU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 h1:/sECfyq2JTifMI2JPyZ4bdRN77zJmr6SrS1eL3augIA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19/go.mod h1:dMf8A5oAqr9/oxOfLkC/c2LU/uMcALP0Rgn2BD5LWn0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 h1:AWeJMk33GTBf6J20XJe6qZoRSJo0WfUhsMdUKhoODXE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 h1:clHU5fm//kWS1C2HgtgWxfQbFbx4b6rx+5jzhgX9HrI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19 h1:3Y4oma5TiV7tT9wa8zRcdoXwZkGz9Q/wxbEUK7cMuAM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19/go.mod h1:V1K+TeJVD5JOk3D9e5tsX2KUdL7BlB+FV6cBhdobN8c=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 h1:XAq62tBTJP/85lFD5oqOOe7YYgWxY9LvWq8plyDvDVg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 h1:BYf7XNsJMzl4mObARUBUib+j2tf0U//JAAtTnYqvqCw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11/go.mod h1:aEUS4WrNk/+FxkBZZa7tVgp4pGH+kFGW40Y8rCPqt5g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 h1:X1Tow7suZk9UCJHE1Iw9GMZJJl0dAnKXXP1NaSDHwmw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 h1:JnQeStZvPHFHeyky/7LbMlyQjUa+jIBj36OlWm0pzIk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19/go.mod h1:HGyasyHvYdFQeJhvDHfH7HXkHh57htcJGKDZ+7z+I24=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3 h1:+d0SsTvxtIJt4tSJ6wr+jrxEMDa6XeupjRv8H7Qitkk=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3/go.mod h1:ROUNFvFWPwBlOu687WJNQ9cPvd2ccpFrnCiA1YGz50o=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12/go.mod h1:fEWYKTRGoZNl8tZ77i61/ccwOMJdGxwOhWCkp6TXAr0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16 h1:EnUdUqRP1CNzt2DkV67tJx6XDN4xlfBFm+bzeNOQVb0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16/go.mod h1:Jic/xv0Rq/pFNCh3WwpH4BEqdbSAl+IyHro8LbibHD8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.8 h1:XQTQTF75vnug2TXS8m7CVJfC2nniYPZnO1D4Np761Oo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.8/go.mod h1:Xgx+PR1NUOjNmQY+tRMnouRp83JRM8pRMw/vCaVhPkI=
|
||||
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
|
||||
@@ -15,10 +15,10 @@ replace github.com/tech/sendico/payments/storage => ../../payments/storage
|
||||
replace github.com/tech/sendico/gateway/tron => ../../gateway/tron
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.10
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.3
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.11
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.11
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/go-chi/jwtauth/v5 v5.4.0
|
||||
@@ -54,20 +54,20 @@ require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.8 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/casbin/mongodb-adapter/v4 v4.3.0 // indirect
|
||||
|
||||
@@ -6,42 +6,42 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10 h1:fJvQ5mIBVfKtiyx0AHY6HeWcRX5LGANLpq8SVR+Uazs=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2 h1:M1A9AjcFwlxTLuf0Faj88L8Iqw0n/AJHjpZTQzMMsSc=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2/go.mod h1:KsdTV6Q9WKUZm2mNJnUFmIoXfZux91M3sr/a4REX8e0=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.3/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6 h1:N4lRUXZpZ1KVEUn6hxtco/1d2lgYhNn1fHkkl8WhlyQ=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.6/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.11 h1:ftxI5sgz8jZkckuUHXfC/wMUc8u3fG1vQS0plr2F2Zs=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.11/go.mod h1:twF11+6ps9aNRKEDimksp923o44w/Thk9+8YIlzWMmo=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 h1:NdV8cwCcAXrCWyxArt58BrvZJ9pZ9Fhf9w6Uh5W3Uyc=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.11/go.mod h1:30yY2zqkMPdrvxBqzI9xQCM+WrlrZKSOpSJEsylVU+8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19 h1:INUvJxmhdEbVulJYHI061k4TVuS3jzzthNvjqvVvTKM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19/go.mod h1:FpZN2QISLdEBWkayloda+sZjVJL+e9Gl0k1SyTgcswU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 h1:/sECfyq2JTifMI2JPyZ4bdRN77zJmr6SrS1eL3augIA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19/go.mod h1:dMf8A5oAqr9/oxOfLkC/c2LU/uMcALP0Rgn2BD5LWn0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 h1:AWeJMk33GTBf6J20XJe6qZoRSJo0WfUhsMdUKhoODXE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 h1:clHU5fm//kWS1C2HgtgWxfQbFbx4b6rx+5jzhgX9HrI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19 h1:3Y4oma5TiV7tT9wa8zRcdoXwZkGz9Q/wxbEUK7cMuAM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19/go.mod h1:V1K+TeJVD5JOk3D9e5tsX2KUdL7BlB+FV6cBhdobN8c=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 h1:XAq62tBTJP/85lFD5oqOOe7YYgWxY9LvWq8plyDvDVg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 h1:BYf7XNsJMzl4mObARUBUib+j2tf0U//JAAtTnYqvqCw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11/go.mod h1:aEUS4WrNk/+FxkBZZa7tVgp4pGH+kFGW40Y8rCPqt5g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 h1:X1Tow7suZk9UCJHE1Iw9GMZJJl0dAnKXXP1NaSDHwmw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 h1:JnQeStZvPHFHeyky/7LbMlyQjUa+jIBj36OlWm0pzIk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19/go.mod h1:HGyasyHvYdFQeJhvDHfH7HXkHh57htcJGKDZ+7z+I24=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3 h1:+d0SsTvxtIJt4tSJ6wr+jrxEMDa6XeupjRv8H7Qitkk=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3/go.mod h1:ROUNFvFWPwBlOu687WJNQ9cPvd2ccpFrnCiA1YGz50o=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12/go.mod h1:fEWYKTRGoZNl8tZ77i61/ccwOMJdGxwOhWCkp6TXAr0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16 h1:EnUdUqRP1CNzt2DkV67tJx6XDN4xlfBFm+bzeNOQVb0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16/go.mod h1:Jic/xv0Rq/pFNCh3WwpH4BEqdbSAl+IyHro8LbibHD8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.8 h1:XQTQTF75vnug2TXS8m7CVJfC2nniYPZnO1D4Np761Oo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.8/go.mod h1:Xgx+PR1NUOjNmQY+tRMnouRp83JRM8pRMw/vCaVhPkI=
|
||||
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
|
||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
||||
sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
|
||||
"github.com/tech/sendico/server/interface/api/srequest"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
@@ -59,7 +60,7 @@ func mapQuoteIntent(intent *srequest.PaymentIntent) (*quotationv2.QuoteIntent, e
|
||||
SettlementMode: resolvedSettlementMode,
|
||||
FeeTreatment: resolvedFeeTreatment,
|
||||
SettlementCurrency: settlementCurrency,
|
||||
FxSide: mapFXSide(intent),
|
||||
Fx: mapFXIntent(intent),
|
||||
}
|
||||
if comment := strings.TrimSpace(intent.Attributes["comment"]); comment != "" {
|
||||
quoteIntent.Comment = comment
|
||||
@@ -67,17 +68,30 @@ func mapQuoteIntent(intent *srequest.PaymentIntent) (*quotationv2.QuoteIntent, e
|
||||
return quoteIntent, nil
|
||||
}
|
||||
|
||||
func mapFXSide(intent *srequest.PaymentIntent) fxv1.Side {
|
||||
if intent == nil || intent.FX == nil {
|
||||
return fxv1.Side_SIDE_UNSPECIFIED
|
||||
func mapFXIntent(intent *srequest.PaymentIntent) *sharedv1.FXIntent {
|
||||
if intent == nil || intent.FX == nil || intent.FX.Pair == nil {
|
||||
return nil
|
||||
}
|
||||
side := fxv1.Side_SIDE_UNSPECIFIED
|
||||
switch strings.TrimSpace(string(intent.FX.Side)) {
|
||||
case string(srequest.FXSideBuyBaseSellQuote):
|
||||
return fxv1.Side_BUY_BASE_SELL_QUOTE
|
||||
side = fxv1.Side_BUY_BASE_SELL_QUOTE
|
||||
case string(srequest.FXSideSellBaseBuyQuote):
|
||||
return fxv1.Side_SELL_BASE_BUY_QUOTE
|
||||
default:
|
||||
return fxv1.Side_SIDE_UNSPECIFIED
|
||||
side = fxv1.Side_SELL_BASE_BUY_QUOTE
|
||||
}
|
||||
if side == fxv1.Side_SIDE_UNSPECIFIED {
|
||||
side = fxv1.Side_SELL_BASE_BUY_QUOTE
|
||||
}
|
||||
return &sharedv1.FXIntent{
|
||||
Pair: &fxv1.CurrencyPair{
|
||||
Base: strings.ToUpper(strings.TrimSpace(intent.FX.Pair.Base)),
|
||||
Quote: strings.ToUpper(strings.TrimSpace(intent.FX.Pair.Quote)),
|
||||
},
|
||||
Side: side,
|
||||
Firm: intent.FX.Firm,
|
||||
TtlMs: intent.FX.TTLms,
|
||||
PreferredProvider: strings.TrimSpace(intent.FX.PreferredProvider),
|
||||
MaxAgeMs: intent.FX.MaxAgeMs,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -202,8 +202,14 @@ func TestMapQuoteIntent_DerivesSettlementCurrencyFromFX(t *testing.T) {
|
||||
if got.GetSettlementCurrency() != "RUB" {
|
||||
t.Fatalf("unexpected settlement currency: got=%q", got.GetSettlementCurrency())
|
||||
}
|
||||
if got.GetFxSide() != fxv1.Side_SELL_BASE_BUY_QUOTE {
|
||||
t.Fatalf("unexpected fx_side: got=%s", got.GetFxSide().String())
|
||||
if got.GetFx() == nil || got.GetFx().GetPair() == nil {
|
||||
t.Fatalf("expected fx intent")
|
||||
}
|
||||
if got.GetFx().GetSide() != fxv1.Side_SELL_BASE_BUY_QUOTE {
|
||||
t.Fatalf("unexpected fx side: got=%s", got.GetFx().GetSide().String())
|
||||
}
|
||||
if got.GetFx().GetPair().GetBase() != "USDT" || got.GetFx().GetPair().GetQuote() != "RUB" {
|
||||
t.Fatalf("unexpected fx pair: got=%s/%s", got.GetFx().GetPair().GetBase(), got.GetFx().GetPair().GetQuote())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,8 +252,11 @@ func TestMapQuoteIntent_PropagatesFXSideBuyBaseSellQuote(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got.GetFxSide() != fxv1.Side_BUY_BASE_SELL_QUOTE {
|
||||
t.Fatalf("unexpected fx_side: got=%s", got.GetFxSide().String())
|
||||
if got.GetFx() == nil || got.GetFx().GetPair() == nil {
|
||||
t.Fatalf("expected fx intent")
|
||||
}
|
||||
if got.GetFx().GetSide() != fxv1.Side_BUY_BASE_SELL_QUOTE {
|
||||
t.Fatalf("unexpected fx side: got=%s", got.GetFx().GetSide().String())
|
||||
}
|
||||
if got.GetSettlementCurrency() != "RUB" {
|
||||
t.Fatalf("unexpected settlement currency: got=%q", got.GetSettlementCurrency())
|
||||
|
||||
@@ -7,7 +7,7 @@ replace github.com/tech/sendico/pkg => ../../pkg
|
||||
replace github.com/tech/sendico/gateway/common => ../common
|
||||
|
||||
require (
|
||||
github.com/ethereum/go-ethereum v1.17.0
|
||||
github.com/ethereum/go-ethereum v1.17.1
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/shopspring/decimal v1.4.0
|
||||
|
||||
@@ -76,8 +76,8 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn2
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
||||
github.com/ethereum/go-ethereum v1.17.0 h1:2D+1Fe23CwZ5tQoAS5DfwKFNI1HGcTwi65/kRlAVxes=
|
||||
github.com/ethereum/go-ethereum v1.17.0/go.mod h1:2W3msvdosS/MCWytpqTcqgFiRYbTH59FxDJzqah120o=
|
||||
github.com/ethereum/go-ethereum v1.17.1 h1:IjlQDjgxg2uL+GzPRkygGULPMLzcYWncEI7wbaizvho=
|
||||
github.com/ethereum/go-ethereum v1.17.1/go.mod h1:7UWOVHL7K3b8RfVRea022btnzLCaanwHtBuH1jUCH/I=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
|
||||
@@ -43,8 +43,7 @@ func NewPayouts(logger mlogger.Logger, db *mongo.Database) (*Payouts, error) {
|
||||
Sparse: true,
|
||||
}); err != nil {
|
||||
logger.Error("Failed to create payouts operation index",
|
||||
zap.Error(err),
|
||||
zap.String("index_field", payoutOpField))
|
||||
zap.Error(err), zap.String("index_field", payoutOpField))
|
||||
return nil, err
|
||||
}
|
||||
if err := repo.CreateIndex(&ri.Definition{
|
||||
@@ -52,8 +51,7 @@ func NewPayouts(logger mlogger.Logger, db *mongo.Database) (*Payouts, error) {
|
||||
Unique: true,
|
||||
}); err != nil {
|
||||
logger.Error("Failed to create payouts idempotency index",
|
||||
zap.Error(err),
|
||||
zap.String("index_field", payoutIdemField))
|
||||
zap.Error(err), zap.String("index_field", payoutIdemField))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -80,8 +80,6 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn2
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
||||
github.com/ethereum/go-ethereum v1.17.0 h1:2D+1Fe23CwZ5tQoAS5DfwKFNI1HGcTwi65/kRlAVxes=
|
||||
github.com/ethereum/go-ethereum v1.17.0/go.mod h1:2W3msvdosS/MCWytpqTcqgFiRYbTH59FxDJzqah120o=
|
||||
github.com/ethereum/go-ethereum v1.17.1 h1:IjlQDjgxg2uL+GzPRkygGULPMLzcYWncEI7wbaizvho=
|
||||
github.com/ethereum/go-ethereum v1.17.1/go.mod h1:7UWOVHL7K3b8RfVRea022btnzLCaanwHtBuH1jUCH/I=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
|
||||
@@ -51,6 +51,12 @@ func (i *Imp) initDependencies(cfg *config) *clientDependencies {
|
||||
i.discoveryClients = newDiscoveryClientResolver(i.logger, i.discoveryReg)
|
||||
deps.gatewayResolver = discoveryChainGatewayResolver{resolver: i.discoveryClients}
|
||||
deps.gatewayInvokeResolver = discoveryGatewayInvokeResolver{resolver: i.discoveryClients}
|
||||
ledgerClient, ledgerErr := i.discoveryClients.LedgerClient(context.Background())
|
||||
if ledgerErr != nil {
|
||||
i.logger.Warn("Failed to initialise ledger client from discovery", zap.Error(ledgerErr))
|
||||
} else {
|
||||
deps.ledgerClient = ledgerClient
|
||||
}
|
||||
} else if i != nil && i.logger != nil {
|
||||
i.logger.Warn("Discovery registry unavailable; chain gateway clients disabled")
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@ import (
|
||||
"time"
|
||||
|
||||
chainclient "github.com/tech/sendico/gateway/chain/client"
|
||||
ledgerclient "github.com/tech/sendico/ledger/client"
|
||||
"github.com/tech/sendico/pkg/discovery"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -36,6 +38,8 @@ type discoveryClientResolver struct {
|
||||
mu sync.Mutex
|
||||
|
||||
chainClients map[string]chainclient.Client
|
||||
ledgerClient ledgerclient.Client
|
||||
ledgerEndpoint discoveryEndpoint
|
||||
|
||||
lastSelection map[string]string
|
||||
lastMissing map[string]time.Time
|
||||
@@ -66,6 +70,10 @@ func (r *discoveryClientResolver) Close() {
|
||||
}
|
||||
delete(r.chainClients, key)
|
||||
}
|
||||
if r.ledgerClient != nil {
|
||||
_ = r.ledgerClient.Close()
|
||||
r.ledgerClient = nil
|
||||
}
|
||||
}
|
||||
|
||||
type discoveryGatewayInvokeResolver struct {
|
||||
@@ -130,6 +138,43 @@ func (r *discoveryClientResolver) ChainClientByNetwork(ctx context.Context, netw
|
||||
return r.ChainClientByInvokeURI(ctx, entry.InvokeURI)
|
||||
}
|
||||
|
||||
func (r *discoveryClientResolver) LedgerClient(ctx context.Context) (ledgerclient.Client, error) {
|
||||
entry, ok := r.findLedgerEntry()
|
||||
if !ok {
|
||||
return nil, merrors.NoData("discovery: ledger service unavailable")
|
||||
}
|
||||
endpoint, err := parseDiscoveryEndpoint(entry.InvokeURI)
|
||||
if err != nil {
|
||||
r.logMissing("ledger", "invalid ledger invoke uri", entry.InvokeURI, err)
|
||||
return nil, err
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if r.ledgerClient == nil || r.ledgerEndpoint.key() != endpoint.key() || r.ledgerEndpoint.address != endpoint.address {
|
||||
if r.ledgerClient != nil {
|
||||
_ = r.ledgerClient.Close()
|
||||
r.ledgerClient = nil
|
||||
}
|
||||
client, dialErr := ledgerclient.New(ctx, ledgerclient.Config{
|
||||
Address: endpoint.address,
|
||||
Insecure: endpoint.insecure,
|
||||
})
|
||||
if dialErr != nil {
|
||||
r.logMissing("ledger", "failed to dial ledger service", endpoint.raw, dialErr)
|
||||
return nil, dialErr
|
||||
}
|
||||
r.ledgerClient = client
|
||||
r.ledgerEndpoint = endpoint
|
||||
}
|
||||
|
||||
return r.ledgerClient, nil
|
||||
}
|
||||
|
||||
func (r *discoveryClientResolver) findChainEntry(network string) (*discovery.RegistryEntry, bool) {
|
||||
if r == nil || r.registry == nil {
|
||||
r.logMissing("chain", "discovery registry unavailable", "", nil)
|
||||
@@ -172,6 +217,44 @@ func (r *discoveryClientResolver) findChainEntry(network string) (*discovery.Reg
|
||||
return &entry, true
|
||||
}
|
||||
|
||||
func (r *discoveryClientResolver) findLedgerEntry() (*discovery.RegistryEntry, bool) {
|
||||
if r == nil || r.registry == nil {
|
||||
r.logMissing("ledger", "discovery registry unavailable", "", nil)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
entries := r.registry.List(time.Now(), true)
|
||||
matches := make([]discovery.RegistryEntry, 0)
|
||||
for _, entry := range entries {
|
||||
if !strings.EqualFold(strings.TrimSpace(entry.Service), string(mservice.Ledger)) {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(entry.InvokeURI) == "" {
|
||||
continue
|
||||
}
|
||||
matches = append(matches, entry)
|
||||
}
|
||||
if len(matches) == 0 {
|
||||
r.logMissing("ledger", "discovery ledger entry missing", "", nil)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
sort.Slice(matches, func(i, j int) bool {
|
||||
if matches[i].RoutingPriority != matches[j].RoutingPriority {
|
||||
return matches[i].RoutingPriority > matches[j].RoutingPriority
|
||||
}
|
||||
if matches[i].ID != matches[j].ID {
|
||||
return matches[i].ID < matches[j].ID
|
||||
}
|
||||
return matches[i].InstanceID < matches[j].InstanceID
|
||||
})
|
||||
|
||||
entry := matches[0]
|
||||
entryKey := discoveryEntryKey(entry)
|
||||
r.logSelection("ledger", entryKey, entry)
|
||||
return &entry, true
|
||||
}
|
||||
|
||||
func (r *discoveryClientResolver) logSelection(key, entryKey string, entry discovery.RegistryEntry) {
|
||||
if r == nil {
|
||||
return
|
||||
|
||||
@@ -51,6 +51,9 @@ func (i *Imp) Start() error {
|
||||
if i.deps.oracleClient != nil {
|
||||
opts = append(opts, quotesvc.WithOracleClient(i.deps.oracleClient))
|
||||
}
|
||||
if i.deps.ledgerClient != nil {
|
||||
opts = append(opts, quotesvc.WithLedgerClient(i.deps.ledgerClient))
|
||||
}
|
||||
if i.deps.gatewayResolver != nil {
|
||||
opts = append(opts, quotesvc.WithChainGatewayResolver(i.deps.gatewayResolver))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package serverimp
|
||||
|
||||
import (
|
||||
oracleclient "github.com/tech/sendico/fx/oracle/client"
|
||||
ledgerclient "github.com/tech/sendico/ledger/client"
|
||||
quotesvc "github.com/tech/sendico/payments/quotation/internal/service/quotation"
|
||||
"github.com/tech/sendico/payments/storage"
|
||||
"github.com/tech/sendico/pkg/discovery"
|
||||
@@ -20,6 +21,7 @@ type clientDependencies struct {
|
||||
feesConn *grpc.ClientConn
|
||||
feesClient feesv1.FeeEngineClient
|
||||
oracleClient oracleclient.Client
|
||||
ledgerClient ledgerclient.Client
|
||||
gatewayResolver quotesvc.ChainGatewayResolver
|
||||
gatewayInvokeResolver quotesvc.GatewayInvokeResolver
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package quotation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ledgerAccountCurrencyResolver struct {
|
||||
client ledgerClientForCurrency
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
type ledgerClientForCurrency interface {
|
||||
GetBalance(ctx context.Context, req *ledgerv1.GetBalanceRequest) (*ledgerv1.BalanceResponse, error)
|
||||
ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error)
|
||||
}
|
||||
|
||||
func newLedgerAccountCurrencyResolver(core *Service) *ledgerAccountCurrencyResolver {
|
||||
if core == nil || core.deps.ledger == nil {
|
||||
return nil
|
||||
}
|
||||
logger := core.logger
|
||||
if logger == nil {
|
||||
logger = zap.NewNop()
|
||||
}
|
||||
return &ledgerAccountCurrencyResolver{
|
||||
client: core.deps.ledger,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ledgerAccountCurrencyResolver) ResolveLedgerAccountCurrency(
|
||||
ctx context.Context,
|
||||
organizationRef string,
|
||||
ledgerAccountRef string,
|
||||
) (string, error) {
|
||||
if r == nil || r.client == nil {
|
||||
return "", merrors.NoData("ledger client unavailable")
|
||||
}
|
||||
|
||||
accountRef := strings.TrimSpace(ledgerAccountRef)
|
||||
if accountRef == "" {
|
||||
return "", merrors.InvalidArgument("ledger_account_ref is required")
|
||||
}
|
||||
|
||||
balanceResp, err := r.client.GetBalance(ctx, &ledgerv1.GetBalanceRequest{
|
||||
LedgerAccountRef: accountRef,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if balance := balanceResp.GetBalance(); balance != nil {
|
||||
if currency := strings.ToUpper(strings.TrimSpace(balance.GetCurrency())); currency != "" {
|
||||
return currency, nil
|
||||
}
|
||||
}
|
||||
|
||||
orgRef := strings.TrimSpace(organizationRef)
|
||||
if orgRef == "" {
|
||||
return "", merrors.NoData("ledger account currency is missing")
|
||||
}
|
||||
|
||||
listResp, err := r.client.ListAccounts(ctx, &ledgerv1.ListAccountsRequest{
|
||||
OrganizationRef: orgRef,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, account := range listResp.GetAccounts() {
|
||||
if strings.TrimSpace(account.GetLedgerAccountRef()) != accountRef {
|
||||
continue
|
||||
}
|
||||
if currency := strings.ToUpper(strings.TrimSpace(account.GetCurrency())); currency != "" {
|
||||
return currency, nil
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if r.logger != nil {
|
||||
r.logger.Warn("Failed to resolve ledger account currency",
|
||||
zap.String("organization_ref", orgRef),
|
||||
zap.String("ledger_account_ref", accountRef),
|
||||
)
|
||||
}
|
||||
return "", merrors.NoData("ledger account currency is missing")
|
||||
}
|
||||
@@ -159,9 +159,13 @@ func WithClock(clock clockpkg.Clock) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithLedgerClient is retained for backward compatibility and is currently a no-op.
|
||||
func WithLedgerClient(_ ledgerclient.Client) Option {
|
||||
return func(*Service) {}
|
||||
// WithLedgerClient wires the ledger client used for account-currency inference.
|
||||
func WithLedgerClient(client ledgerclient.Client) Option {
|
||||
return func(s *Service) {
|
||||
if s != nil && client != nil {
|
||||
s.deps.ledger = client
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithProviderSettlementGatewayClient is retained for backward compatibility and is currently a no-op.
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
|
||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
||||
sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -29,6 +30,9 @@ type quoteIntentLogSummary struct {
|
||||
SettlementMode string `json:"settlementMode,omitempty"`
|
||||
FeeTreatment string `json:"feeTreatment,omitempty"`
|
||||
SettlementCurrency string `json:"settlementCurrency,omitempty"`
|
||||
HasFX bool `json:"hasFx"`
|
||||
FXPair string `json:"fxPair,omitempty"`
|
||||
FXSide string `json:"fxSide,omitempty"`
|
||||
HasComment bool `json:"hasComment"`
|
||||
}
|
||||
|
||||
@@ -59,6 +63,7 @@ func summarizeQuoteIntent(intent *quotationv2.QuoteIntent) *quoteIntentLogSummar
|
||||
if intent == nil {
|
||||
return nil
|
||||
}
|
||||
fxPair, fxSide, hasFX := summarizeFXIntent(intent.GetFx())
|
||||
return "eIntentLogSummary{
|
||||
Source: summarizeEndpoint(intent.GetSource()),
|
||||
Destination: summarizeEndpoint(intent.GetDestination()),
|
||||
@@ -66,6 +71,9 @@ func summarizeQuoteIntent(intent *quotationv2.QuoteIntent) *quoteIntentLogSummar
|
||||
SettlementMode: enumLogValue(intent.GetSettlementMode().String()),
|
||||
FeeTreatment: enumLogValue(intent.GetFeeTreatment().String()),
|
||||
SettlementCurrency: strings.ToUpper(strings.TrimSpace(intent.GetSettlementCurrency())),
|
||||
HasFX: hasFX,
|
||||
FXPair: fxPair,
|
||||
FXSide: fxSide,
|
||||
HasComment: strings.TrimSpace(intent.GetComment()) != "",
|
||||
}
|
||||
}
|
||||
@@ -142,3 +150,15 @@ func moneyLogValue(m *moneyv1.Money) string {
|
||||
func enumLogValue(value string) string {
|
||||
return strings.ToLower(strings.TrimSpace(value))
|
||||
}
|
||||
|
||||
func summarizeFXIntent(fx *sharedv1.FXIntent) (string, string, bool) {
|
||||
if fx == nil || fx.GetPair() == nil {
|
||||
return "", "", false
|
||||
}
|
||||
base := strings.ToUpper(strings.TrimSpace(fx.GetPair().GetBase()))
|
||||
quote := strings.ToUpper(strings.TrimSpace(fx.GetPair().GetQuote()))
|
||||
if base == "" && quote == "" {
|
||||
return "", enumLogValue(fx.GetSide().String()), false
|
||||
}
|
||||
return strings.TrimSpace(base + "/" + quote), enumLogValue(fx.GetSide().String()), true
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@ func newQuoteComputationService(core *Service) *quote_computation_service.QuoteC
|
||||
if resolver := newManagedWalletNetworkResolver(core); resolver != nil {
|
||||
opts = append(opts, quote_computation_service.WithManagedWalletNetworkResolver(resolver))
|
||||
}
|
||||
if resolver := newLedgerAccountCurrencyResolver(core); resolver != nil {
|
||||
opts = append(opts, quote_computation_service.WithLedgerAccountCurrencyResolver(resolver))
|
||||
}
|
||||
if resolver := fundingProfileResolver(core); resolver != nil {
|
||||
opts = append(opts, quote_computation_service.WithFundingProfileResolver(resolver))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package quote_computation_service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/payments/storage/model"
|
||||
)
|
||||
|
||||
type endpointCurrencyInference struct {
|
||||
SourceCurrency string
|
||||
DestinationCurrency string
|
||||
SourceInferred bool
|
||||
DestinationInferred bool
|
||||
}
|
||||
|
||||
func (s *QuoteComputationService) inferEndpointCurrencies(
|
||||
ctx context.Context,
|
||||
organizationRef string,
|
||||
intent model.PaymentIntent,
|
||||
ledgerCurrencyCache map[string]string,
|
||||
) (endpointCurrencyInference, error) {
|
||||
sourceCurrency, sourceInferred, err := s.inferEndpointCurrency(
|
||||
ctx,
|
||||
organizationRef,
|
||||
intent.Source,
|
||||
ledgerCurrencyCache,
|
||||
)
|
||||
if err != nil {
|
||||
return endpointCurrencyInference{}, err
|
||||
}
|
||||
destinationCurrency, destinationInferred, err := s.inferEndpointCurrency(
|
||||
ctx,
|
||||
organizationRef,
|
||||
intent.Destination,
|
||||
ledgerCurrencyCache,
|
||||
)
|
||||
if err != nil {
|
||||
return endpointCurrencyInference{}, err
|
||||
}
|
||||
return endpointCurrencyInference{
|
||||
SourceCurrency: sourceCurrency,
|
||||
DestinationCurrency: destinationCurrency,
|
||||
SourceInferred: sourceInferred,
|
||||
DestinationInferred: destinationInferred,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *QuoteComputationService) inferEndpointCurrency(
|
||||
ctx context.Context,
|
||||
organizationRef string,
|
||||
endpoint model.PaymentEndpoint,
|
||||
ledgerCurrencyCache map[string]string,
|
||||
) (string, bool, error) {
|
||||
if token := sourceAssetToken(endpoint); token != "" {
|
||||
return token, true, nil
|
||||
}
|
||||
if endpoint.Ledger == nil {
|
||||
return "", false, nil
|
||||
}
|
||||
ledgerAccountRef := strings.TrimSpace(endpoint.Ledger.LedgerAccountRef)
|
||||
if ledgerAccountRef == "" {
|
||||
return "", false, nil
|
||||
}
|
||||
if cached := normalizeAsset(ledgerCurrencyCache[ledgerAccountRef]); cached != "" {
|
||||
return cached, true, nil
|
||||
}
|
||||
if s == nil || s.ledgerAccountCurrencyResolver == nil {
|
||||
return "", false, nil
|
||||
}
|
||||
currency, err := s.ledgerAccountCurrencyResolver.ResolveLedgerAccountCurrency(
|
||||
ctx,
|
||||
strings.TrimSpace(organizationRef),
|
||||
ledgerAccountRef,
|
||||
)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
currency = normalizeAsset(currency)
|
||||
if currency == "" {
|
||||
return "", false, nil
|
||||
}
|
||||
if ledgerCurrencyCache != nil {
|
||||
ledgerCurrencyCache[ledgerAccountRef] = currency
|
||||
}
|
||||
return currency, true, nil
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package quote_computation_service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/tech/sendico/payments/storage/model"
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
)
|
||||
|
||||
func TestInferEndpointCurrencies_UsesEndpointAssets(t *testing.T) {
|
||||
svc := New(nil)
|
||||
out, err := svc.inferEndpointCurrencies(context.Background(), "org-1", model.PaymentIntent{
|
||||
Source: model.PaymentEndpoint{
|
||||
Type: model.EndpointTypeManagedWallet,
|
||||
ManagedWallet: &model.ManagedWalletEndpoint{
|
||||
ManagedWalletRef: "mw-src",
|
||||
Asset: &paymenttypes.Asset{TokenSymbol: "USDT"},
|
||||
},
|
||||
},
|
||||
Destination: model.PaymentEndpoint{
|
||||
Type: model.EndpointTypeExternalChain,
|
||||
ExternalChain: &model.ExternalChainEndpoint{
|
||||
Asset: &paymenttypes.Asset{TokenSymbol: "RUB"},
|
||||
},
|
||||
},
|
||||
}, map[string]string{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got, want := out.SourceCurrency, "USDT"; got != want {
|
||||
t.Fatalf("unexpected source currency: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := out.DestinationCurrency, "RUB"; got != want {
|
||||
t.Fatalf("unexpected destination currency: got=%q want=%q", got, want)
|
||||
}
|
||||
if !out.SourceInferred || !out.DestinationInferred {
|
||||
t.Fatalf("expected both currencies inferred: %#v", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInferEndpointCurrencies_UsesLedgerResolver(t *testing.T) {
|
||||
resolver := &fakeLedgerCurrencyResolver{
|
||||
currencies: map[string]string{
|
||||
"ledger-src": "USDT",
|
||||
"ledger-dst": "RUB",
|
||||
},
|
||||
}
|
||||
svc := New(nil, WithLedgerAccountCurrencyResolver(resolver))
|
||||
out, err := svc.inferEndpointCurrencies(context.Background(), "org-1", model.PaymentIntent{
|
||||
Source: model.PaymentEndpoint{
|
||||
Type: model.EndpointTypeLedger,
|
||||
Ledger: &model.LedgerEndpoint{
|
||||
LedgerAccountRef: "ledger-src",
|
||||
},
|
||||
},
|
||||
Destination: model.PaymentEndpoint{
|
||||
Type: model.EndpointTypeLedger,
|
||||
Ledger: &model.LedgerEndpoint{
|
||||
LedgerAccountRef: "ledger-dst",
|
||||
},
|
||||
},
|
||||
}, map[string]string{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got, want := out.SourceCurrency, "USDT"; got != want {
|
||||
t.Fatalf("unexpected source currency: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := out.DestinationCurrency, "RUB"; got != want {
|
||||
t.Fatalf("unexpected destination currency: got=%q want=%q", got, want)
|
||||
}
|
||||
if resolver.calls != 2 {
|
||||
t.Fatalf("unexpected resolver calls: got=%d want=%d", resolver.calls, 2)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeLedgerCurrencyResolver struct {
|
||||
currencies map[string]string
|
||||
calls int
|
||||
}
|
||||
|
||||
func (f *fakeLedgerCurrencyResolver) ResolveLedgerAccountCurrency(_ context.Context, _ string, ledgerAccountRef string) (string, error) {
|
||||
f.calls++
|
||||
return f.currencies[ledgerAccountRef], nil
|
||||
}
|
||||
@@ -8,6 +8,15 @@ import (
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
)
|
||||
|
||||
type fxDerivationResult struct {
|
||||
InferredSourceCurrency string
|
||||
InferredDestinationCurrency string
|
||||
EffectiveSourceCurrency string
|
||||
EffectiveDestinationCurrency string
|
||||
ExplicitOverrideApplied bool
|
||||
RequiresFXInferred bool
|
||||
}
|
||||
|
||||
func modelIntentFromQuoteIntent(src *transfer_intent_hydrator.QuoteIntent) model.PaymentIntent {
|
||||
if src == nil {
|
||||
return model.PaymentIntent{}
|
||||
@@ -33,18 +42,44 @@ func modelIntentFromQuoteIntent(src *transfer_intent_hydrator.QuoteIntent) model
|
||||
}
|
||||
|
||||
func fxIntentFromHydratedIntent(src *transfer_intent_hydrator.QuoteIntent) *model.FXIntent {
|
||||
if src == nil {
|
||||
if src == nil || src.FX == nil {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(string(src.FXSide)) == "" || src.FXSide == paymenttypes.FXSideUnspecified {
|
||||
result := &model.FXIntent{
|
||||
Side: src.FX.Side,
|
||||
Firm: src.FX.Firm,
|
||||
TTLMillis: src.FX.TTLms,
|
||||
PreferredProvider: strings.TrimSpace(src.FX.PreferredProvider),
|
||||
MaxAgeMillis: src.FX.MaxAgeMs,
|
||||
}
|
||||
if src.FX.Pair != nil {
|
||||
result.Pair = &paymenttypes.CurrencyPair{
|
||||
Base: normalizeAsset(src.FX.Pair.GetBase()),
|
||||
Quote: normalizeAsset(src.FX.Pair.GetQuote()),
|
||||
}
|
||||
}
|
||||
if result.Side == paymenttypes.FXSideUnspecified &&
|
||||
result.Pair == nil &&
|
||||
!result.Firm &&
|
||||
result.TTLMillis == 0 &&
|
||||
result.PreferredProvider == "" &&
|
||||
result.MaxAgeMillis == 0 {
|
||||
return nil
|
||||
}
|
||||
return &model.FXIntent{Side: src.FXSide}
|
||||
return result
|
||||
}
|
||||
|
||||
func ensureDerivedFXIntent(intent *model.PaymentIntent) {
|
||||
func ensureDerivedFXIntent(
|
||||
intent *model.PaymentIntent,
|
||||
inferredSourceCurrency string,
|
||||
inferredDestinationCurrency string,
|
||||
) fxDerivationResult {
|
||||
result := fxDerivationResult{
|
||||
InferredSourceCurrency: normalizeAsset(inferredSourceCurrency),
|
||||
InferredDestinationCurrency: normalizeAsset(inferredDestinationCurrency),
|
||||
}
|
||||
if intent == nil {
|
||||
return
|
||||
return result
|
||||
}
|
||||
|
||||
amountCurrency := ""
|
||||
@@ -52,6 +87,9 @@ func ensureDerivedFXIntent(intent *model.PaymentIntent) {
|
||||
amountCurrency = normalizeAsset(intent.Amount.GetCurrency())
|
||||
}
|
||||
settlementCurrency := normalizeAsset(intent.SettlementCurrency)
|
||||
if result.InferredDestinationCurrency != "" {
|
||||
settlementCurrency = result.InferredDestinationCurrency
|
||||
}
|
||||
if settlementCurrency == "" {
|
||||
settlementCurrency = amountCurrency
|
||||
}
|
||||
@@ -59,42 +97,98 @@ func ensureDerivedFXIntent(intent *model.PaymentIntent) {
|
||||
intent.SettlementCurrency = settlementCurrency
|
||||
}
|
||||
|
||||
sourceCurrency := sourceAssetToken(intent.Source)
|
||||
sourceCurrency := firstNonEmpty(result.InferredSourceCurrency, sourceAssetToken(intent.Source))
|
||||
sourceCurrencyBeforeExplicit := sourceCurrency
|
||||
settlementCurrencyBeforeExplicit := settlementCurrency
|
||||
requiresFXBeforeExplicit := intent.RequiresFX
|
||||
|
||||
// For FIX_RECEIVED, destination amounts can be provided in payout currency.
|
||||
// Derive FX necessity from source asset currency when available.
|
||||
if !intent.RequiresFX &&
|
||||
intent.SettlementMode == model.SettlementModeFixReceived &&
|
||||
explicitSourceCurrency, explicitDestinationCurrency := fxTradeCurrencies(intent.FX)
|
||||
if explicitSourceCurrency != "" && explicitDestinationCurrency != "" {
|
||||
sourceCurrency = explicitSourceCurrency
|
||||
settlementCurrency = explicitDestinationCurrency
|
||||
intent.SettlementCurrency = settlementCurrency
|
||||
intent.RequiresFX = true
|
||||
if sourceCurrencyBeforeExplicit == "" ||
|
||||
settlementCurrencyBeforeExplicit == "" ||
|
||||
!strings.EqualFold(sourceCurrencyBeforeExplicit, explicitSourceCurrency) ||
|
||||
!strings.EqualFold(settlementCurrencyBeforeExplicit, explicitDestinationCurrency) ||
|
||||
!requiresFXBeforeExplicit {
|
||||
result.ExplicitOverrideApplied = true
|
||||
}
|
||||
} else if !intent.RequiresFX &&
|
||||
sourceCurrency != "" &&
|
||||
settlementCurrency != "" &&
|
||||
!strings.EqualFold(sourceCurrency, settlementCurrency) {
|
||||
intent.RequiresFX = true
|
||||
result.RequiresFXInferred = true
|
||||
}
|
||||
|
||||
if !intent.RequiresFX {
|
||||
return
|
||||
result.EffectiveSourceCurrency = sourceCurrency
|
||||
result.EffectiveDestinationCurrency = settlementCurrency
|
||||
return result
|
||||
}
|
||||
|
||||
baseCurrency := firstNonEmpty(sourceCurrency, amountCurrency)
|
||||
quoteCurrency := settlementCurrency
|
||||
if baseCurrency == "" || quoteCurrency == "" {
|
||||
return
|
||||
result.EffectiveSourceCurrency = sourceCurrency
|
||||
result.EffectiveDestinationCurrency = settlementCurrency
|
||||
return result
|
||||
}
|
||||
|
||||
if intent.FX == nil {
|
||||
intent.FX = &model.FXIntent{}
|
||||
}
|
||||
if strings.TrimSpace(string(intent.FX.Side)) == "" || intent.FX.Side == paymenttypes.FXSideUnspecified {
|
||||
intent.FX.Side = paymenttypes.FXSideSellBaseBuyQuote
|
||||
}
|
||||
if intent.FX.Pair == nil {
|
||||
intent.FX.Pair = &paymenttypes.CurrencyPair{}
|
||||
}
|
||||
desiredBase, desiredQuote := fxPairFromTradeCurrencies(intent.FX.Side, baseCurrency, quoteCurrency)
|
||||
if normalizeAsset(intent.FX.Pair.Base) == "" {
|
||||
intent.FX.Pair.Base = baseCurrency
|
||||
intent.FX.Pair.Base = desiredBase
|
||||
}
|
||||
if normalizeAsset(intent.FX.Pair.Quote) == "" {
|
||||
intent.FX.Pair.Quote = quoteCurrency
|
||||
intent.FX.Pair.Quote = desiredQuote
|
||||
}
|
||||
if strings.TrimSpace(string(intent.FX.Side)) == "" || intent.FX.Side == paymenttypes.FXSideUnspecified {
|
||||
intent.FX.Side = paymenttypes.FXSideSellBaseBuyQuote
|
||||
|
||||
result.EffectiveSourceCurrency, result.EffectiveDestinationCurrency = fxTradeCurrencies(intent.FX)
|
||||
if result.EffectiveSourceCurrency == "" {
|
||||
result.EffectiveSourceCurrency = sourceCurrency
|
||||
}
|
||||
if result.EffectiveDestinationCurrency == "" {
|
||||
result.EffectiveDestinationCurrency = settlementCurrency
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func fxPairFromTradeCurrencies(side paymenttypes.FXSide, sourceCurrency, destinationCurrency string) (string, string) {
|
||||
sourceCurrency = normalizeAsset(sourceCurrency)
|
||||
destinationCurrency = normalizeAsset(destinationCurrency)
|
||||
switch side {
|
||||
case paymenttypes.FXSideBuyBaseSellQuote:
|
||||
return destinationCurrency, sourceCurrency
|
||||
default:
|
||||
return sourceCurrency, destinationCurrency
|
||||
}
|
||||
}
|
||||
|
||||
func fxTradeCurrencies(fx *model.FXIntent) (string, string) {
|
||||
if fx == nil || fx.Pair == nil {
|
||||
return "", ""
|
||||
}
|
||||
base := normalizeAsset(fx.Pair.GetBase())
|
||||
quote := normalizeAsset(fx.Pair.GetQuote())
|
||||
if base == "" || quote == "" {
|
||||
return "", ""
|
||||
}
|
||||
switch fx.Side {
|
||||
case paymenttypes.FXSideBuyBaseSellQuote:
|
||||
return quote, base
|
||||
default:
|
||||
return base, quote
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestEnsureDerivedFXIntent_DefaultsSideWhenEmpty(t *testing.T) {
|
||||
FX: &model.FXIntent{},
|
||||
}
|
||||
|
||||
ensureDerivedFXIntent(intent)
|
||||
ensureDerivedFXIntent(intent, "", "")
|
||||
|
||||
if intent.FX == nil {
|
||||
t.Fatal("expected fx intent")
|
||||
@@ -34,7 +34,7 @@ func TestEnsureDerivedFXIntent_DefaultsSideWhenUnspecified(t *testing.T) {
|
||||
FX: &model.FXIntent{Side: paymenttypes.FXSideUnspecified},
|
||||
}
|
||||
|
||||
ensureDerivedFXIntent(intent)
|
||||
ensureDerivedFXIntent(intent, "", "")
|
||||
|
||||
if intent.FX == nil {
|
||||
t.Fatal("expected fx intent")
|
||||
@@ -56,11 +56,17 @@ func TestEnsureDerivedFXIntent_PreservesExplicitSideFromHydratedIntent(t *testin
|
||||
Amount: &paymenttypes.Money{Amount: "100", Currency: "USDT"},
|
||||
SettlementCurrency: "RUB",
|
||||
RequiresFX: true,
|
||||
FXSide: paymenttypes.FXSideBuyBaseSellQuote,
|
||||
FX: &transfer_intent_hydrator.QuoteFXIntent{
|
||||
Pair: &paymenttypes.CurrencyPair{
|
||||
Base: "RUB",
|
||||
Quote: "USDT",
|
||||
},
|
||||
Side: paymenttypes.FXSideBuyBaseSellQuote,
|
||||
},
|
||||
}
|
||||
|
||||
intent := modelIntentFromQuoteIntent(hydrated)
|
||||
ensureDerivedFXIntent(&intent)
|
||||
ensureDerivedFXIntent(&intent, "", "")
|
||||
|
||||
if intent.FX == nil {
|
||||
t.Fatal("expected fx intent")
|
||||
@@ -69,3 +75,45 @@ func TestEnsureDerivedFXIntent_PreservesExplicitSideFromHydratedIntent(t *testin
|
||||
t.Fatalf("unexpected side: got=%q want=%q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureDerivedFXIntent_ExplicitFXOverridesInferredCurrencies(t *testing.T) {
|
||||
intent := &model.PaymentIntent{
|
||||
RequiresFX: false,
|
||||
SettlementCurrency: "EUR",
|
||||
Amount: &paymenttypes.Money{Amount: "10", Currency: "EUR"},
|
||||
FX: &model.FXIntent{
|
||||
Pair: &paymenttypes.CurrencyPair{
|
||||
Base: "USDT",
|
||||
Quote: "RUB",
|
||||
},
|
||||
Side: paymenttypes.FXSideSellBaseBuyQuote,
|
||||
},
|
||||
}
|
||||
|
||||
out := ensureDerivedFXIntent(intent, "BTC", "EUR")
|
||||
|
||||
if !out.ExplicitOverrideApplied {
|
||||
t.Fatalf("expected explicit override flag")
|
||||
}
|
||||
if !intent.RequiresFX {
|
||||
t.Fatalf("expected requires_fx=true")
|
||||
}
|
||||
if got, want := intent.SettlementCurrency, "RUB"; got != want {
|
||||
t.Fatalf("unexpected settlement currency: got=%q want=%q", got, want)
|
||||
}
|
||||
if intent.FX == nil || intent.FX.Pair == nil {
|
||||
t.Fatalf("expected fx pair")
|
||||
}
|
||||
if got, want := intent.FX.Pair.GetBase(), "USDT"; got != want {
|
||||
t.Fatalf("unexpected base currency: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := intent.FX.Pair.GetQuote(), "RUB"; got != want {
|
||||
t.Fatalf("unexpected quote currency: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := out.EffectiveSourceCurrency, "USDT"; got != want {
|
||||
t.Fatalf("unexpected effective source currency: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := out.EffectiveDestinationCurrency, "RUB"; got != want {
|
||||
t.Fatalf("unexpected effective destination currency: got=%q want=%q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,9 +53,10 @@ func (s *QuoteComputationService) BuildPlan(ctx context.Context, in ComputeInput
|
||||
Items: make([]*QuoteComputationPlanItem, 0, len(in.Intents)),
|
||||
}
|
||||
managedWalletNetworks := map[string]string{}
|
||||
ledgerAccountCurrencies := map[string]string{}
|
||||
|
||||
for i, intent := range in.Intents {
|
||||
item, err := s.buildPlanItem(ctx, in, i, intent, managedWalletNetworks)
|
||||
item, err := s.buildPlanItem(ctx, in, i, intent, managedWalletNetworks, ledgerAccountCurrencies)
|
||||
if err != nil {
|
||||
s.logger.Warn("Computation plan item build failed",
|
||||
zap.String("org_ref", in.OrganizationRef),
|
||||
@@ -84,6 +85,7 @@ func (s *QuoteComputationService) buildPlanItem(
|
||||
index int,
|
||||
intent *transfer_intent_hydrator.QuoteIntent,
|
||||
managedWalletNetworks map[string]string,
|
||||
ledgerAccountCurrencies map[string]string,
|
||||
) (*QuoteComputationPlanItem, error) {
|
||||
if intent == nil {
|
||||
s.logger.Warn("Plan item build failed: intent is nil", zap.Int("index", index))
|
||||
@@ -137,7 +139,59 @@ func (s *QuoteComputationService) buildPlanItem(
|
||||
}
|
||||
modelIntent.Source = clonePaymentEndpoint(source)
|
||||
modelIntent.Destination = clonePaymentEndpoint(destination)
|
||||
ensureDerivedFXIntent(&modelIntent)
|
||||
currencyInference, err := s.inferEndpointCurrencies(
|
||||
ctx,
|
||||
strings.TrimSpace(in.OrganizationRef),
|
||||
modelIntent,
|
||||
ledgerAccountCurrencies,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, merrors.InternalWrap(err, "resolve endpoint currencies")
|
||||
}
|
||||
fxDecision := ensureDerivedFXIntent(
|
||||
&modelIntent,
|
||||
currencyInference.SourceCurrency,
|
||||
currencyInference.DestinationCurrency,
|
||||
)
|
||||
if currencyInference.SourceInferred || currencyInference.DestinationInferred {
|
||||
s.logger.Info("Resolved endpoint currencies for quote intent",
|
||||
zap.Int("index", index),
|
||||
zap.String("intent_ref", strings.TrimSpace(modelIntent.Ref)),
|
||||
zap.String("inferred_source_currency", fxDecision.InferredSourceCurrency),
|
||||
zap.String("inferred_destination_currency", fxDecision.InferredDestinationCurrency),
|
||||
zap.String("effective_source_currency", fxDecision.EffectiveSourceCurrency),
|
||||
zap.String("effective_destination_currency", fxDecision.EffectiveDestinationCurrency),
|
||||
)
|
||||
}
|
||||
if fxDecision.ExplicitOverrideApplied {
|
||||
fxBase, fxQuote, fxSide := "", "", ""
|
||||
if modelIntent.FX != nil {
|
||||
fxSide = strings.TrimSpace(string(modelIntent.FX.Side))
|
||||
if modelIntent.FX.Pair != nil {
|
||||
fxBase = strings.TrimSpace(modelIntent.FX.Pair.GetBase())
|
||||
fxQuote = strings.TrimSpace(modelIntent.FX.Pair.GetQuote())
|
||||
}
|
||||
}
|
||||
s.logger.Info("Applied explicit FX override to inferred endpoint currencies",
|
||||
zap.Int("index", index),
|
||||
zap.String("intent_ref", strings.TrimSpace(modelIntent.Ref)),
|
||||
zap.String("inferred_source_currency", fxDecision.InferredSourceCurrency),
|
||||
zap.String("inferred_destination_currency", fxDecision.InferredDestinationCurrency),
|
||||
zap.String("effective_source_currency", fxDecision.EffectiveSourceCurrency),
|
||||
zap.String("effective_destination_currency", fxDecision.EffectiveDestinationCurrency),
|
||||
zap.String("fx_base_currency", fxBase),
|
||||
zap.String("fx_quote_currency", fxQuote),
|
||||
zap.String("fx_side", fxSide),
|
||||
)
|
||||
}
|
||||
if fxDecision.RequiresFXInferred {
|
||||
s.logger.Info("Inferred FX requirement from endpoint currencies",
|
||||
zap.Int("index", index),
|
||||
zap.String("intent_ref", strings.TrimSpace(modelIntent.Ref)),
|
||||
zap.String("source_currency", fxDecision.EffectiveSourceCurrency),
|
||||
zap.String("destination_currency", fxDecision.EffectiveDestinationCurrency),
|
||||
)
|
||||
}
|
||||
|
||||
sourceRail, sourceNetwork, err := plan.RailFromEndpoint(source, modelIntent.Attributes, true)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,6 +19,10 @@ type ManagedWalletNetworkResolver interface {
|
||||
ResolveManagedWalletNetwork(ctx context.Context, managedWalletRef string) (string, error)
|
||||
}
|
||||
|
||||
type LedgerAccountCurrencyResolver interface {
|
||||
ResolveLedgerAccountCurrency(ctx context.Context, organizationRef, ledgerAccountRef string) (string, error)
|
||||
}
|
||||
|
||||
type Option func(*QuoteComputationService)
|
||||
|
||||
type QuoteComputationService struct {
|
||||
@@ -26,6 +30,7 @@ type QuoteComputationService struct {
|
||||
fundingResolver gateway_funding_profile.FundingProfileResolver
|
||||
gatewayRegistry plan.GatewayRegistry
|
||||
managedWalletNetworkResolver ManagedWalletNetworkResolver
|
||||
ledgerAccountCurrencyResolver LedgerAccountCurrencyResolver
|
||||
routeStore plan.RouteStore
|
||||
pathFinder *graph_path_finder.GraphPathFinder
|
||||
logger mlogger.Logger
|
||||
@@ -69,6 +74,14 @@ func WithManagedWalletNetworkResolver(resolver ManagedWalletNetworkResolver) Opt
|
||||
}
|
||||
}
|
||||
|
||||
func WithLedgerAccountCurrencyResolver(resolver LedgerAccountCurrencyResolver) Option {
|
||||
return func(svc *QuoteComputationService) {
|
||||
if svc != nil {
|
||||
svc.ledgerAccountCurrencyResolver = resolver
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithRouteStore(store plan.RouteStore) Option {
|
||||
return func(svc *QuoteComputationService) {
|
||||
if svc != nil {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package quotation
|
||||
|
||||
import (
|
||||
ledgerclient "github.com/tech/sendico/ledger/client"
|
||||
"github.com/tech/sendico/payments/storage"
|
||||
clockpkg "github.com/tech/sendico/pkg/clock"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
@@ -35,6 +36,7 @@ type serviceDependencies struct {
|
||||
fees feesDependency
|
||||
gateway gatewayDependency
|
||||
oracle oracleDependency
|
||||
ledger ledgerclient.Client
|
||||
gatewayRegistry GatewayRegistry
|
||||
gatewayInvokeResolver GatewayInvokeResolver
|
||||
cardRoutes map[string]CardGatewayRoute
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
|
||||
methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1"
|
||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
||||
sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
@@ -123,7 +124,16 @@ func (h *TransferIntentHydrator) HydrateOne(ctx context.Context, in HydrateOneIn
|
||||
if settlementCurrency == "" {
|
||||
settlementCurrency = strings.ToUpper(strings.TrimSpace(amount.Currency))
|
||||
}
|
||||
requiresFX := !strings.EqualFold(amount.Currency, settlementCurrency)
|
||||
fxIntent := fxIntentFromProto(in.Intent.GetFx())
|
||||
if settlementCurrency == "" {
|
||||
settlementCurrency = settlementCurrencyFromFX(fxIntent)
|
||||
}
|
||||
requiresFX := false
|
||||
if fxIntent != nil && fxIntent.Pair != nil {
|
||||
requiresFX = true
|
||||
} else {
|
||||
requiresFX = !strings.EqualFold(amount.Currency, settlementCurrency)
|
||||
}
|
||||
|
||||
intent := &QuoteIntent{
|
||||
Ref: h.newRef(),
|
||||
@@ -134,7 +144,7 @@ func (h *TransferIntentHydrator) HydrateOne(ctx context.Context, in HydrateOneIn
|
||||
Comment: strings.TrimSpace(in.Intent.GetComment()),
|
||||
SettlementMode: settlementMode,
|
||||
FeeTreatment: feeTreatment,
|
||||
FXSide: fxSideFromProto(in.Intent.GetFxSide()),
|
||||
FX: fxIntent,
|
||||
SettlementCurrency: settlementCurrency,
|
||||
RequiresFX: requiresFX,
|
||||
Attributes: map[string]string{
|
||||
@@ -223,3 +233,55 @@ func fxSideFromProto(side fxv1.Side) paymenttypes.FXSide {
|
||||
return paymenttypes.FXSideUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func fxIntentFromProto(src *sharedv1.FXIntent) *QuoteFXIntent {
|
||||
if src == nil || src.GetPair() == nil {
|
||||
return nil
|
||||
}
|
||||
base := strings.ToUpper(strings.TrimSpace(src.GetPair().GetBase()))
|
||||
quote := strings.ToUpper(strings.TrimSpace(src.GetPair().GetQuote()))
|
||||
if base == "" || quote == "" {
|
||||
return nil
|
||||
}
|
||||
side := fxSideFromProto(src.GetSide())
|
||||
if side == paymenttypes.FXSideUnspecified {
|
||||
side = paymenttypes.FXSideSellBaseBuyQuote
|
||||
}
|
||||
return &QuoteFXIntent{
|
||||
Pair: &paymenttypes.CurrencyPair{
|
||||
Base: base,
|
||||
Quote: quote,
|
||||
},
|
||||
Side: side,
|
||||
Firm: src.GetFirm(),
|
||||
TTLms: src.GetTtlMs(),
|
||||
PreferredProvider: strings.TrimSpace(src.GetPreferredProvider()),
|
||||
MaxAgeMs: src.GetMaxAgeMs(),
|
||||
}
|
||||
}
|
||||
|
||||
func settlementCurrencyFromFX(fx *QuoteFXIntent) string {
|
||||
if fx == nil || fx.Pair == nil {
|
||||
return ""
|
||||
}
|
||||
base := strings.ToUpper(strings.TrimSpace(fx.Pair.GetBase()))
|
||||
quote := strings.ToUpper(strings.TrimSpace(fx.Pair.GetQuote()))
|
||||
switch fx.Side {
|
||||
case paymenttypes.FXSideBuyBaseSellQuote:
|
||||
return firstNonEmpty(base, quote)
|
||||
case paymenttypes.FXSideSellBaseBuyQuote:
|
||||
return firstNonEmpty(quote, base)
|
||||
default:
|
||||
return firstNonEmpty(quote, base)
|
||||
}
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -64,6 +64,15 @@ type QuoteCardEndpoint struct {
|
||||
MaskedPan string
|
||||
}
|
||||
|
||||
type QuoteFXIntent struct {
|
||||
Pair *paymenttypes.CurrencyPair
|
||||
Side paymenttypes.FXSide
|
||||
Firm bool
|
||||
TTLms int64
|
||||
PreferredProvider string
|
||||
MaxAgeMs int32
|
||||
}
|
||||
|
||||
type QuoteEndpoint struct {
|
||||
Type QuoteEndpointType
|
||||
PaymentMethodRef string
|
||||
@@ -84,7 +93,7 @@ type QuoteIntent struct {
|
||||
Comment string
|
||||
SettlementMode QuoteSettlementMode
|
||||
FeeTreatment QuoteFeeTreatment
|
||||
FXSide paymenttypes.FXSide
|
||||
FX *QuoteFXIntent
|
||||
SettlementCurrency string
|
||||
RequiresFX bool
|
||||
Attributes map[string]string
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
|
||||
methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1"
|
||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
||||
sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
@@ -100,7 +101,7 @@ func TestHydrateOne_SuccessWithInlineMethods(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHydrateOne_PropagatesFXSide(t *testing.T) {
|
||||
func TestHydrateOne_PropagatesFXIntent(t *testing.T) {
|
||||
h := New(nil, WithRefFactory(func() string { return "q-intent-fx-side" }))
|
||||
intent := "ationv2.QuoteIntent{
|
||||
Source: &endpointv1.PaymentEndpoint{
|
||||
@@ -126,7 +127,17 @@ func TestHydrateOne_PropagatesFXSide(t *testing.T) {
|
||||
},
|
||||
Amount: newMoney("10", "USDT"),
|
||||
SettlementCurrency: "RUB",
|
||||
FxSide: fxv1.Side_BUY_BASE_SELL_QUOTE,
|
||||
Fx: &sharedv1.FXIntent{
|
||||
Pair: &fxv1.CurrencyPair{
|
||||
Base: "USDT",
|
||||
Quote: "RUB",
|
||||
},
|
||||
Side: fxv1.Side_BUY_BASE_SELL_QUOTE,
|
||||
Firm: true,
|
||||
TtlMs: 12_000,
|
||||
PreferredProvider: "bestfx",
|
||||
MaxAgeMs: 1_000,
|
||||
},
|
||||
}
|
||||
|
||||
got, err := h.HydrateOne(context.Background(), HydrateOneInput{
|
||||
@@ -140,8 +151,65 @@ func TestHydrateOne_PropagatesFXSide(t *testing.T) {
|
||||
if got == nil {
|
||||
t.Fatalf("expected hydrated intent")
|
||||
}
|
||||
if got.FXSide != paymenttypes.FXSideBuyBaseSellQuote {
|
||||
t.Fatalf("unexpected fx side: got=%q", got.FXSide)
|
||||
if got.FX == nil || got.FX.Pair == nil {
|
||||
t.Fatalf("expected hydrated fx intent")
|
||||
}
|
||||
if got.FX.Side != paymenttypes.FXSideBuyBaseSellQuote {
|
||||
t.Fatalf("unexpected fx side: got=%q", got.FX.Side)
|
||||
}
|
||||
if got.FX.Pair.GetBase() != "USDT" || got.FX.Pair.GetQuote() != "RUB" {
|
||||
t.Fatalf("unexpected fx pair: got=%s/%s", got.FX.Pair.GetBase(), got.FX.Pair.GetQuote())
|
||||
}
|
||||
if !got.FX.Firm || got.FX.TTLms != 12_000 || got.FX.PreferredProvider != "bestfx" || got.FX.MaxAgeMs != 1_000 {
|
||||
t.Fatalf("unexpected fx extras: %#v", got.FX)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHydrateOne_RequiresFXWhenExplicitFXProvided(t *testing.T) {
|
||||
h := New(nil, WithRefFactory(func() string { return "q-intent-fx-required" }))
|
||||
intent := "ationv2.QuoteIntent{
|
||||
Source: &endpointv1.PaymentEndpoint{
|
||||
Source: &endpointv1.PaymentEndpoint_PaymentMethod{
|
||||
PaymentMethod: &endpointv1.PaymentMethod{
|
||||
Type: endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_LEDGER,
|
||||
Data: mustMarshalBSON(t, map[string]string{"ledgerAccountRef": "ledger-src"}),
|
||||
},
|
||||
},
|
||||
},
|
||||
Destination: &endpointv1.PaymentEndpoint{
|
||||
Source: &endpointv1.PaymentEndpoint_PaymentMethod{
|
||||
PaymentMethod: &endpointv1.PaymentMethod{
|
||||
Type: endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD,
|
||||
Data: mustMarshalBSON(t, pkgmodel.CardPaymentData{
|
||||
Pan: "4111111111111111",
|
||||
ExpMonth: "12",
|
||||
ExpYear: "2030",
|
||||
Country: "US",
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
Amount: newMoney("10", "RUB"),
|
||||
SettlementCurrency: "RUB",
|
||||
Fx: &sharedv1.FXIntent{
|
||||
Pair: &fxv1.CurrencyPair{Base: "USDT", Quote: "RUB"},
|
||||
Side: fxv1.Side_SELL_BASE_BUY_QUOTE,
|
||||
},
|
||||
}
|
||||
|
||||
got, err := h.HydrateOne(context.Background(), HydrateOneInput{
|
||||
OrganizationRef: bson.NewObjectID().Hex(),
|
||||
InitiatorRef: bson.NewObjectID().Hex(),
|
||||
Intent: intent,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("HydrateOne returned error: %v", err)
|
||||
}
|
||||
if got == nil {
|
||||
t.Fatalf("expected hydrated intent")
|
||||
}
|
||||
if !got.RequiresFX {
|
||||
t.Fatalf("expected requires_fx=true when explicit fx is supplied")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ go 1.25.0
|
||||
require (
|
||||
github.com/casbin/casbin/v2 v2.135.0
|
||||
github.com/casbin/mongodb-adapter/v4 v4.3.0
|
||||
github.com/ethereum/go-ethereum v1.17.0
|
||||
github.com/ethereum/go-ethereum v1.17.1
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/vault/api v1.22.0
|
||||
|
||||
@@ -61,8 +61,8 @@ github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
|
||||
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.6 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn27fRjSls=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw=
|
||||
github.com/ethereum/go-ethereum v1.17.0 h1:2D+1Fe23CwZ5tQoAS5DfwKFNI1HGcTwi65/kRlAVxes=
|
||||
github.com/ethereum/go-ethereum v1.17.0/go.mod h1:2W3msvdosS/MCWytpqTcqgFiRYbTH59FxDJzqah120o=
|
||||
github.com/ethereum/go-ethereum v1.17.1 h1:IjlQDjgxg2uL+GzPRkygGULPMLzcYWncEI7wbaizvho=
|
||||
github.com/ethereum/go-ethereum v1.17.1/go.mod h1:7UWOVHL7K3b8RfVRea022btnzLCaanwHtBuH1jUCH/I=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
|
||||
@@ -6,7 +6,6 @@ option go_package = "github.com/tech/sendico/pkg/proto/payments/quotation/v2;quo
|
||||
|
||||
import "api/proto/payments/shared/v1/shared.proto";
|
||||
import "api/proto/common/money/v1/money.proto";
|
||||
import "api/proto/common/fx/v1/fx.proto";
|
||||
import "api/proto/common/payment/v1/settlement.proto";
|
||||
import "api/proto/payments/endpoint/v1/endpoint.proto";
|
||||
import "api/proto/payments/quotation/v2/interface.proto";
|
||||
@@ -20,7 +19,7 @@ message QuoteIntent {
|
||||
payments.quotation.v2.FeeTreatment fee_treatment = 5;
|
||||
string settlement_currency = 6;
|
||||
string comment = 7;
|
||||
common.fx.v1.Side fx_side = 8;
|
||||
payments.shared.v1.FXIntent fx = 8;
|
||||
}
|
||||
|
||||
// QuotePaymentRequest is the request to quote a single v2 payment.
|
||||
|
||||
@@ -39,8 +39,7 @@ class PaymentProvider extends ChangeNotifier {
|
||||
String? clientPaymentRef,
|
||||
Map<String, String>? metadata,
|
||||
}) async {
|
||||
if (!_organization.isOrganizationSet)
|
||||
throw StateError('Organization is not set');
|
||||
if (!_organization.isOrganizationSet) throw StateError('Organization is not set');
|
||||
final quoteRef = _quotation.quotation?.quoteRef;
|
||||
if (quoteRef == null || quoteRef.isEmpty) {
|
||||
throw StateError('Quotation reference is not set');
|
||||
|
||||
@@ -98,7 +98,7 @@ class QuotationIntentBuilder {
|
||||
}
|
||||
return FxIntent(
|
||||
pair: CurrencyPair(base: base, quote: quote),
|
||||
side: FxSide.buyBaseSellQuote,
|
||||
side: FxSide.sellBaseBuyQuote,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
5
test.csv
5
test.csv
@@ -1,5 +0,0 @@
|
||||
pan;first_name;last_name;exp_month;exp_year;amount
|
||||
2204310159722456;Anastasiia;Limonova;06;2028;100
|
||||
2204320167919754;Anastasiia;Limonova;10;2027;100
|
||||
2200242558874568;Anastasiia;Limonova;07;2032;100
|
||||
2203410113188371;Vladimir;Burmakin;02;2033;100
|
||||
|
Reference in New Issue
Block a user