fixed quotation currency inference
This commit is contained in:
@@ -5,10 +5,10 @@ go 1.25.7
|
|||||||
replace github.com/tech/sendico/pkg => ../../pkg
|
replace github.com/tech/sendico/pkg => ../../pkg
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.2
|
github.com/aws/aws-sdk-go-v2 v1.41.3
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.10
|
github.com/aws/aws-sdk-go-v2/config v1.32.11
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.11
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3
|
||||||
github.com/jung-kurt/gofpdf v1.16.2
|
github.com/jung-kurt/gofpdf v1.16.2
|
||||||
github.com/prometheus/client_golang v1.23.2
|
github.com/prometheus/client_golang v1.23.2
|
||||||
github.com/shopspring/decimal v1.4.0
|
github.com/shopspring/decimal v1.4.0
|
||||||
@@ -20,20 +20,20 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // 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.18 // 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.18 // 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.18 // 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.4 // 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.18 // 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.5 // 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.10 // 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.18 // 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.18 // 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.6 // 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.11 // 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.15 // 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.7 // 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/aws/smithy-go v1.24.2 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bmatcuk/doublestar/v4 v4.10.0 // 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/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 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/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
|
github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
|
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.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
|
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.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
|
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.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
|
github.com/aws/aws-sdk-go-v2/config v1.32.11 h1:ftxI5sgz8jZkckuUHXfC/wMUc8u3fG1vQS0plr2F2Zs=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
|
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.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 h1:NdV8cwCcAXrCWyxArt58BrvZJ9pZ9Fhf9w6Uh5W3Uyc=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
|
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.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
|
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.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
|
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.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
|
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.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
|
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.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
|
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.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
|
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.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
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.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
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.18 h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU=
|
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.18/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4=
|
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.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
|
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.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
|
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.10 h1:fJvQ5mIBVfKtiyx0AHY6HeWcRX5LGANLpq8SVR+Uazs=
|
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.10/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I=
|
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.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
|
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.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
|
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.18 h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg=
|
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.18/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY=
|
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.2 h1:M1A9AjcFwlxTLuf0Faj88L8Iqw0n/AJHjpZTQzMMsSc=
|
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.2/go.mod h1:KsdTV6Q9WKUZm2mNJnUFmIoXfZux91M3sr/a4REX8e0=
|
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.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
|
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.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
|
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.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
|
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.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
|
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.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
|
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.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
|
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.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
|
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.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
|
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 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
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
|
replace github.com/tech/sendico/gateway/tron => ../../gateway/tron
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.2
|
github.com/aws/aws-sdk-go-v2 v1.41.3
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.10
|
github.com/aws/aws-sdk-go-v2/config v1.32.11
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.11
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2
|
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/chi/v5 v5.2.5
|
||||||
github.com/go-chi/cors v1.2.2
|
github.com/go-chi/cors v1.2.2
|
||||||
github.com/go-chi/jwtauth/v5 v5.4.0
|
github.com/go-chi/jwtauth/v5 v5.4.0
|
||||||
@@ -54,20 +54,20 @@ require (
|
|||||||
dario.cat/mergo v1.0.1 // indirect
|
dario.cat/mergo v1.0.1 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // 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/aws/protocol/eventstream v1.7.6 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 // 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.18 // 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.18 // 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.4 // 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.18 // 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.5 // 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.10 // 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.18 // 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.18 // 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.6 // 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.11 // 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.15 // 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.7 // 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/aws/smithy-go v1.24.2 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/casbin/mongodb-adapter/v4 v4.3.0 // 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/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 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/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
|
github.com/aws/aws-sdk-go-v2 v1.41.3 h1:4kQ/fa22KjDt13QCy1+bYADvdgcxpfH18f0zP542kZA=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
|
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.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
|
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.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
|
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.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
|
github.com/aws/aws-sdk-go-v2/config v1.32.11 h1:ftxI5sgz8jZkckuUHXfC/wMUc8u3fG1vQS0plr2F2Zs=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
|
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.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
|
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 h1:NdV8cwCcAXrCWyxArt58BrvZJ9pZ9Fhf9w6Uh5W3Uyc=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
|
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.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
|
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.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
|
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.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
|
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.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
|
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.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
|
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.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
|
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.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
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.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
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.18 h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU=
|
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.18/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4=
|
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.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
|
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.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
|
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.10 h1:fJvQ5mIBVfKtiyx0AHY6HeWcRX5LGANLpq8SVR+Uazs=
|
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.10/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I=
|
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.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
|
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.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
|
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.18 h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg=
|
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.18/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY=
|
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.2 h1:M1A9AjcFwlxTLuf0Faj88L8Iqw0n/AJHjpZTQzMMsSc=
|
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.2/go.mod h1:KsdTV6Q9WKUZm2mNJnUFmIoXfZux91M3sr/a4REX8e0=
|
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.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
|
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.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
|
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.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
|
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.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
|
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.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
|
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.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
|
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.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
|
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.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
|
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 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
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=
|
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"
|
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||||
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
|
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
|
||||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
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"
|
"github.com/tech/sendico/server/interface/api/srequest"
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
)
|
)
|
||||||
@@ -59,7 +60,7 @@ func mapQuoteIntent(intent *srequest.PaymentIntent) (*quotationv2.QuoteIntent, e
|
|||||||
SettlementMode: resolvedSettlementMode,
|
SettlementMode: resolvedSettlementMode,
|
||||||
FeeTreatment: resolvedFeeTreatment,
|
FeeTreatment: resolvedFeeTreatment,
|
||||||
SettlementCurrency: settlementCurrency,
|
SettlementCurrency: settlementCurrency,
|
||||||
FxSide: mapFXSide(intent),
|
Fx: mapFXIntent(intent),
|
||||||
}
|
}
|
||||||
if comment := strings.TrimSpace(intent.Attributes["comment"]); comment != "" {
|
if comment := strings.TrimSpace(intent.Attributes["comment"]); comment != "" {
|
||||||
quoteIntent.Comment = comment
|
quoteIntent.Comment = comment
|
||||||
@@ -67,17 +68,30 @@ func mapQuoteIntent(intent *srequest.PaymentIntent) (*quotationv2.QuoteIntent, e
|
|||||||
return quoteIntent, nil
|
return quoteIntent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapFXSide(intent *srequest.PaymentIntent) fxv1.Side {
|
func mapFXIntent(intent *srequest.PaymentIntent) *sharedv1.FXIntent {
|
||||||
if intent == nil || intent.FX == nil {
|
if intent == nil || intent.FX == nil || intent.FX.Pair == nil {
|
||||||
return fxv1.Side_SIDE_UNSPECIFIED
|
return nil
|
||||||
}
|
}
|
||||||
|
side := fxv1.Side_SIDE_UNSPECIFIED
|
||||||
switch strings.TrimSpace(string(intent.FX.Side)) {
|
switch strings.TrimSpace(string(intent.FX.Side)) {
|
||||||
case string(srequest.FXSideBuyBaseSellQuote):
|
case string(srequest.FXSideBuyBaseSellQuote):
|
||||||
return fxv1.Side_BUY_BASE_SELL_QUOTE
|
side = fxv1.Side_BUY_BASE_SELL_QUOTE
|
||||||
case string(srequest.FXSideSellBaseBuyQuote):
|
case string(srequest.FXSideSellBaseBuyQuote):
|
||||||
return fxv1.Side_SELL_BASE_BUY_QUOTE
|
side = fxv1.Side_SELL_BASE_BUY_QUOTE
|
||||||
default:
|
}
|
||||||
return fxv1.Side_SIDE_UNSPECIFIED
|
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" {
|
if got.GetSettlementCurrency() != "RUB" {
|
||||||
t.Fatalf("unexpected settlement currency: got=%q", got.GetSettlementCurrency())
|
t.Fatalf("unexpected settlement currency: got=%q", got.GetSettlementCurrency())
|
||||||
}
|
}
|
||||||
if got.GetFxSide() != fxv1.Side_SELL_BASE_BUY_QUOTE {
|
if got.GetFx() == nil || got.GetFx().GetPair() == nil {
|
||||||
t.Fatalf("unexpected fx_side: got=%s", got.GetFxSide().String())
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
if got.GetFxSide() != fxv1.Side_BUY_BASE_SELL_QUOTE {
|
if got.GetFx() == nil || got.GetFx().GetPair() == nil {
|
||||||
t.Fatalf("unexpected fx_side: got=%s", got.GetFxSide().String())
|
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" {
|
if got.GetSettlementCurrency() != "RUB" {
|
||||||
t.Fatalf("unexpected settlement currency: got=%q", got.GetSettlementCurrency())
|
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
|
replace github.com/tech/sendico/gateway/common => ../common
|
||||||
|
|
||||||
require (
|
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/mitchellh/mapstructure v1.5.0
|
||||||
github.com/prometheus/client_golang v1.23.2
|
github.com/prometheus/client_golang v1.23.2
|
||||||
github.com/shopspring/decimal v1.4.0
|
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/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 h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
||||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
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.1 h1:IjlQDjgxg2uL+GzPRkygGULPMLzcYWncEI7wbaizvho=
|
||||||
github.com/ethereum/go-ethereum v1.17.0/go.mod h1:2W3msvdosS/MCWytpqTcqgFiRYbTH59FxDJzqah120o=
|
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 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
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,
|
Sparse: true,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
logger.Error("Failed to create payouts operation index",
|
logger.Error("Failed to create payouts operation index",
|
||||||
zap.Error(err),
|
zap.Error(err), zap.String("index_field", payoutOpField))
|
||||||
zap.String("index_field", payoutOpField))
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := repo.CreateIndex(&ri.Definition{
|
if err := repo.CreateIndex(&ri.Definition{
|
||||||
@@ -52,8 +51,7 @@ func NewPayouts(logger mlogger.Logger, db *mongo.Database) (*Payouts, error) {
|
|||||||
Unique: true,
|
Unique: true,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
logger.Error("Failed to create payouts idempotency index",
|
logger.Error("Failed to create payouts idempotency index",
|
||||||
zap.Error(err),
|
zap.Error(err), zap.String("index_field", payoutIdemField))
|
||||||
zap.String("index_field", payoutIdemField))
|
|
||||||
return nil, err
|
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/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 h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
||||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
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 h1:IjlQDjgxg2uL+GzPRkygGULPMLzcYWncEI7wbaizvho=
|
||||||
github.com/ethereum/go-ethereum v1.17.1/go.mod h1:7UWOVHL7K3b8RfVRea022btnzLCaanwHtBuH1jUCH/I=
|
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 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ func (i *Imp) initDependencies(cfg *config) *clientDependencies {
|
|||||||
i.discoveryClients = newDiscoveryClientResolver(i.logger, i.discoveryReg)
|
i.discoveryClients = newDiscoveryClientResolver(i.logger, i.discoveryReg)
|
||||||
deps.gatewayResolver = discoveryChainGatewayResolver{resolver: i.discoveryClients}
|
deps.gatewayResolver = discoveryChainGatewayResolver{resolver: i.discoveryClients}
|
||||||
deps.gatewayInvokeResolver = discoveryGatewayInvokeResolver{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 {
|
} else if i != nil && i.logger != nil {
|
||||||
i.logger.Warn("Discovery registry unavailable; chain gateway clients disabled")
|
i.logger.Warn("Discovery registry unavailable; chain gateway clients disabled")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
chainclient "github.com/tech/sendico/gateway/chain/client"
|
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/discovery"
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,6 +38,8 @@ type discoveryClientResolver struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
|
||||||
chainClients map[string]chainclient.Client
|
chainClients map[string]chainclient.Client
|
||||||
|
ledgerClient ledgerclient.Client
|
||||||
|
ledgerEndpoint discoveryEndpoint
|
||||||
|
|
||||||
lastSelection map[string]string
|
lastSelection map[string]string
|
||||||
lastMissing map[string]time.Time
|
lastMissing map[string]time.Time
|
||||||
@@ -66,6 +70,10 @@ func (r *discoveryClientResolver) Close() {
|
|||||||
}
|
}
|
||||||
delete(r.chainClients, key)
|
delete(r.chainClients, key)
|
||||||
}
|
}
|
||||||
|
if r.ledgerClient != nil {
|
||||||
|
_ = r.ledgerClient.Close()
|
||||||
|
r.ledgerClient = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type discoveryGatewayInvokeResolver struct {
|
type discoveryGatewayInvokeResolver struct {
|
||||||
@@ -130,6 +138,43 @@ func (r *discoveryClientResolver) ChainClientByNetwork(ctx context.Context, netw
|
|||||||
return r.ChainClientByInvokeURI(ctx, entry.InvokeURI)
|
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) {
|
func (r *discoveryClientResolver) findChainEntry(network string) (*discovery.RegistryEntry, bool) {
|
||||||
if r == nil || r.registry == nil {
|
if r == nil || r.registry == nil {
|
||||||
r.logMissing("chain", "discovery registry unavailable", "", nil)
|
r.logMissing("chain", "discovery registry unavailable", "", nil)
|
||||||
@@ -172,6 +217,44 @@ func (r *discoveryClientResolver) findChainEntry(network string) (*discovery.Reg
|
|||||||
return &entry, true
|
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) {
|
func (r *discoveryClientResolver) logSelection(key, entryKey string, entry discovery.RegistryEntry) {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ func (i *Imp) Start() error {
|
|||||||
if i.deps.oracleClient != nil {
|
if i.deps.oracleClient != nil {
|
||||||
opts = append(opts, quotesvc.WithOracleClient(i.deps.oracleClient))
|
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 {
|
if i.deps.gatewayResolver != nil {
|
||||||
opts = append(opts, quotesvc.WithChainGatewayResolver(i.deps.gatewayResolver))
|
opts = append(opts, quotesvc.WithChainGatewayResolver(i.deps.gatewayResolver))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package serverimp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
oracleclient "github.com/tech/sendico/fx/oracle/client"
|
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"
|
quotesvc "github.com/tech/sendico/payments/quotation/internal/service/quotation"
|
||||||
"github.com/tech/sendico/payments/storage"
|
"github.com/tech/sendico/payments/storage"
|
||||||
"github.com/tech/sendico/pkg/discovery"
|
"github.com/tech/sendico/pkg/discovery"
|
||||||
@@ -20,6 +21,7 @@ type clientDependencies struct {
|
|||||||
feesConn *grpc.ClientConn
|
feesConn *grpc.ClientConn
|
||||||
feesClient feesv1.FeeEngineClient
|
feesClient feesv1.FeeEngineClient
|
||||||
oracleClient oracleclient.Client
|
oracleClient oracleclient.Client
|
||||||
|
ledgerClient ledgerclient.Client
|
||||||
gatewayResolver quotesvc.ChainGatewayResolver
|
gatewayResolver quotesvc.ChainGatewayResolver
|
||||||
gatewayInvokeResolver quotesvc.GatewayInvokeResolver
|
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.
|
// WithLedgerClient wires the ledger client used for account-currency inference.
|
||||||
func WithLedgerClient(_ ledgerclient.Client) Option {
|
func WithLedgerClient(client ledgerclient.Client) Option {
|
||||||
return func(*Service) {}
|
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.
|
// 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"
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
|
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
|
||||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
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.mongodb.org/mongo-driver/v2/bson"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@@ -29,6 +30,9 @@ type quoteIntentLogSummary struct {
|
|||||||
SettlementMode string `json:"settlementMode,omitempty"`
|
SettlementMode string `json:"settlementMode,omitempty"`
|
||||||
FeeTreatment string `json:"feeTreatment,omitempty"`
|
FeeTreatment string `json:"feeTreatment,omitempty"`
|
||||||
SettlementCurrency string `json:"settlementCurrency,omitempty"`
|
SettlementCurrency string `json:"settlementCurrency,omitempty"`
|
||||||
|
HasFX bool `json:"hasFx"`
|
||||||
|
FXPair string `json:"fxPair,omitempty"`
|
||||||
|
FXSide string `json:"fxSide,omitempty"`
|
||||||
HasComment bool `json:"hasComment"`
|
HasComment bool `json:"hasComment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +63,7 @@ func summarizeQuoteIntent(intent *quotationv2.QuoteIntent) *quoteIntentLogSummar
|
|||||||
if intent == nil {
|
if intent == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
fxPair, fxSide, hasFX := summarizeFXIntent(intent.GetFx())
|
||||||
return "eIntentLogSummary{
|
return "eIntentLogSummary{
|
||||||
Source: summarizeEndpoint(intent.GetSource()),
|
Source: summarizeEndpoint(intent.GetSource()),
|
||||||
Destination: summarizeEndpoint(intent.GetDestination()),
|
Destination: summarizeEndpoint(intent.GetDestination()),
|
||||||
@@ -66,6 +71,9 @@ func summarizeQuoteIntent(intent *quotationv2.QuoteIntent) *quoteIntentLogSummar
|
|||||||
SettlementMode: enumLogValue(intent.GetSettlementMode().String()),
|
SettlementMode: enumLogValue(intent.GetSettlementMode().String()),
|
||||||
FeeTreatment: enumLogValue(intent.GetFeeTreatment().String()),
|
FeeTreatment: enumLogValue(intent.GetFeeTreatment().String()),
|
||||||
SettlementCurrency: strings.ToUpper(strings.TrimSpace(intent.GetSettlementCurrency())),
|
SettlementCurrency: strings.ToUpper(strings.TrimSpace(intent.GetSettlementCurrency())),
|
||||||
|
HasFX: hasFX,
|
||||||
|
FXPair: fxPair,
|
||||||
|
FXSide: fxSide,
|
||||||
HasComment: strings.TrimSpace(intent.GetComment()) != "",
|
HasComment: strings.TrimSpace(intent.GetComment()) != "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,3 +150,15 @@ func moneyLogValue(m *moneyv1.Money) string {
|
|||||||
func enumLogValue(value string) string {
|
func enumLogValue(value string) string {
|
||||||
return strings.ToLower(strings.TrimSpace(value))
|
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 {
|
if resolver := newManagedWalletNetworkResolver(core); resolver != nil {
|
||||||
opts = append(opts, quote_computation_service.WithManagedWalletNetworkResolver(resolver))
|
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 {
|
if resolver := fundingProfileResolver(core); resolver != nil {
|
||||||
opts = append(opts, quote_computation_service.WithFundingProfileResolver(resolver))
|
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"
|
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 {
|
func modelIntentFromQuoteIntent(src *transfer_intent_hydrator.QuoteIntent) model.PaymentIntent {
|
||||||
if src == nil {
|
if src == nil {
|
||||||
return model.PaymentIntent{}
|
return model.PaymentIntent{}
|
||||||
@@ -33,18 +42,44 @@ func modelIntentFromQuoteIntent(src *transfer_intent_hydrator.QuoteIntent) model
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fxIntentFromHydratedIntent(src *transfer_intent_hydrator.QuoteIntent) *model.FXIntent {
|
func fxIntentFromHydratedIntent(src *transfer_intent_hydrator.QuoteIntent) *model.FXIntent {
|
||||||
if src == nil {
|
if src == nil || src.FX == nil {
|
||||||
return 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 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 {
|
if intent == nil {
|
||||||
return
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
amountCurrency := ""
|
amountCurrency := ""
|
||||||
@@ -52,6 +87,9 @@ func ensureDerivedFXIntent(intent *model.PaymentIntent) {
|
|||||||
amountCurrency = normalizeAsset(intent.Amount.GetCurrency())
|
amountCurrency = normalizeAsset(intent.Amount.GetCurrency())
|
||||||
}
|
}
|
||||||
settlementCurrency := normalizeAsset(intent.SettlementCurrency)
|
settlementCurrency := normalizeAsset(intent.SettlementCurrency)
|
||||||
|
if result.InferredDestinationCurrency != "" {
|
||||||
|
settlementCurrency = result.InferredDestinationCurrency
|
||||||
|
}
|
||||||
if settlementCurrency == "" {
|
if settlementCurrency == "" {
|
||||||
settlementCurrency = amountCurrency
|
settlementCurrency = amountCurrency
|
||||||
}
|
}
|
||||||
@@ -59,42 +97,98 @@ func ensureDerivedFXIntent(intent *model.PaymentIntent) {
|
|||||||
intent.SettlementCurrency = settlementCurrency
|
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.
|
explicitSourceCurrency, explicitDestinationCurrency := fxTradeCurrencies(intent.FX)
|
||||||
// Derive FX necessity from source asset currency when available.
|
if explicitSourceCurrency != "" && explicitDestinationCurrency != "" {
|
||||||
if !intent.RequiresFX &&
|
sourceCurrency = explicitSourceCurrency
|
||||||
intent.SettlementMode == model.SettlementModeFixReceived &&
|
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 != "" &&
|
sourceCurrency != "" &&
|
||||||
settlementCurrency != "" &&
|
settlementCurrency != "" &&
|
||||||
!strings.EqualFold(sourceCurrency, settlementCurrency) {
|
!strings.EqualFold(sourceCurrency, settlementCurrency) {
|
||||||
intent.RequiresFX = true
|
intent.RequiresFX = true
|
||||||
|
result.RequiresFXInferred = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !intent.RequiresFX {
|
if !intent.RequiresFX {
|
||||||
return
|
result.EffectiveSourceCurrency = sourceCurrency
|
||||||
|
result.EffectiveDestinationCurrency = settlementCurrency
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
baseCurrency := firstNonEmpty(sourceCurrency, amountCurrency)
|
baseCurrency := firstNonEmpty(sourceCurrency, amountCurrency)
|
||||||
quoteCurrency := settlementCurrency
|
quoteCurrency := settlementCurrency
|
||||||
if baseCurrency == "" || quoteCurrency == "" {
|
if baseCurrency == "" || quoteCurrency == "" {
|
||||||
return
|
result.EffectiveSourceCurrency = sourceCurrency
|
||||||
|
result.EffectiveDestinationCurrency = settlementCurrency
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
if intent.FX == nil {
|
if intent.FX == nil {
|
||||||
intent.FX = &model.FXIntent{}
|
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 {
|
if intent.FX.Pair == nil {
|
||||||
intent.FX.Pair = &paymenttypes.CurrencyPair{}
|
intent.FX.Pair = &paymenttypes.CurrencyPair{}
|
||||||
}
|
}
|
||||||
|
desiredBase, desiredQuote := fxPairFromTradeCurrencies(intent.FX.Side, baseCurrency, quoteCurrency)
|
||||||
if normalizeAsset(intent.FX.Pair.Base) == "" {
|
if normalizeAsset(intent.FX.Pair.Base) == "" {
|
||||||
intent.FX.Pair.Base = baseCurrency
|
intent.FX.Pair.Base = desiredBase
|
||||||
}
|
}
|
||||||
if normalizeAsset(intent.FX.Pair.Quote) == "" {
|
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{},
|
FX: &model.FXIntent{},
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureDerivedFXIntent(intent)
|
ensureDerivedFXIntent(intent, "", "")
|
||||||
|
|
||||||
if intent.FX == nil {
|
if intent.FX == nil {
|
||||||
t.Fatal("expected fx intent")
|
t.Fatal("expected fx intent")
|
||||||
@@ -34,7 +34,7 @@ func TestEnsureDerivedFXIntent_DefaultsSideWhenUnspecified(t *testing.T) {
|
|||||||
FX: &model.FXIntent{Side: paymenttypes.FXSideUnspecified},
|
FX: &model.FXIntent{Side: paymenttypes.FXSideUnspecified},
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureDerivedFXIntent(intent)
|
ensureDerivedFXIntent(intent, "", "")
|
||||||
|
|
||||||
if intent.FX == nil {
|
if intent.FX == nil {
|
||||||
t.Fatal("expected fx intent")
|
t.Fatal("expected fx intent")
|
||||||
@@ -56,11 +56,17 @@ func TestEnsureDerivedFXIntent_PreservesExplicitSideFromHydratedIntent(t *testin
|
|||||||
Amount: &paymenttypes.Money{Amount: "100", Currency: "USDT"},
|
Amount: &paymenttypes.Money{Amount: "100", Currency: "USDT"},
|
||||||
SettlementCurrency: "RUB",
|
SettlementCurrency: "RUB",
|
||||||
RequiresFX: true,
|
RequiresFX: true,
|
||||||
FXSide: paymenttypes.FXSideBuyBaseSellQuote,
|
FX: &transfer_intent_hydrator.QuoteFXIntent{
|
||||||
|
Pair: &paymenttypes.CurrencyPair{
|
||||||
|
Base: "RUB",
|
||||||
|
Quote: "USDT",
|
||||||
|
},
|
||||||
|
Side: paymenttypes.FXSideBuyBaseSellQuote,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
intent := modelIntentFromQuoteIntent(hydrated)
|
intent := modelIntentFromQuoteIntent(hydrated)
|
||||||
ensureDerivedFXIntent(&intent)
|
ensureDerivedFXIntent(&intent, "", "")
|
||||||
|
|
||||||
if intent.FX == nil {
|
if intent.FX == nil {
|
||||||
t.Fatal("expected fx intent")
|
t.Fatal("expected fx intent")
|
||||||
@@ -69,3 +75,45 @@ func TestEnsureDerivedFXIntent_PreservesExplicitSideFromHydratedIntent(t *testin
|
|||||||
t.Fatalf("unexpected side: got=%q want=%q", got, want)
|
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)),
|
Items: make([]*QuoteComputationPlanItem, 0, len(in.Intents)),
|
||||||
}
|
}
|
||||||
managedWalletNetworks := map[string]string{}
|
managedWalletNetworks := map[string]string{}
|
||||||
|
ledgerAccountCurrencies := map[string]string{}
|
||||||
|
|
||||||
for i, intent := range in.Intents {
|
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 {
|
if err != nil {
|
||||||
s.logger.Warn("Computation plan item build failed",
|
s.logger.Warn("Computation plan item build failed",
|
||||||
zap.String("org_ref", in.OrganizationRef),
|
zap.String("org_ref", in.OrganizationRef),
|
||||||
@@ -84,6 +85,7 @@ func (s *QuoteComputationService) buildPlanItem(
|
|||||||
index int,
|
index int,
|
||||||
intent *transfer_intent_hydrator.QuoteIntent,
|
intent *transfer_intent_hydrator.QuoteIntent,
|
||||||
managedWalletNetworks map[string]string,
|
managedWalletNetworks map[string]string,
|
||||||
|
ledgerAccountCurrencies map[string]string,
|
||||||
) (*QuoteComputationPlanItem, error) {
|
) (*QuoteComputationPlanItem, error) {
|
||||||
if intent == nil {
|
if intent == nil {
|
||||||
s.logger.Warn("Plan item build failed: intent is nil", zap.Int("index", index))
|
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.Source = clonePaymentEndpoint(source)
|
||||||
modelIntent.Destination = clonePaymentEndpoint(destination)
|
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)
|
sourceRail, sourceNetwork, err := plan.RailFromEndpoint(source, modelIntent.Attributes, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ type ManagedWalletNetworkResolver interface {
|
|||||||
ResolveManagedWalletNetwork(ctx context.Context, managedWalletRef string) (string, error)
|
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 Option func(*QuoteComputationService)
|
||||||
|
|
||||||
type QuoteComputationService struct {
|
type QuoteComputationService struct {
|
||||||
@@ -26,6 +30,7 @@ type QuoteComputationService struct {
|
|||||||
fundingResolver gateway_funding_profile.FundingProfileResolver
|
fundingResolver gateway_funding_profile.FundingProfileResolver
|
||||||
gatewayRegistry plan.GatewayRegistry
|
gatewayRegistry plan.GatewayRegistry
|
||||||
managedWalletNetworkResolver ManagedWalletNetworkResolver
|
managedWalletNetworkResolver ManagedWalletNetworkResolver
|
||||||
|
ledgerAccountCurrencyResolver LedgerAccountCurrencyResolver
|
||||||
routeStore plan.RouteStore
|
routeStore plan.RouteStore
|
||||||
pathFinder *graph_path_finder.GraphPathFinder
|
pathFinder *graph_path_finder.GraphPathFinder
|
||||||
logger mlogger.Logger
|
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 {
|
func WithRouteStore(store plan.RouteStore) Option {
|
||||||
return func(svc *QuoteComputationService) {
|
return func(svc *QuoteComputationService) {
|
||||||
if svc != nil {
|
if svc != nil {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package quotation
|
package quotation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
ledgerclient "github.com/tech/sendico/ledger/client"
|
||||||
"github.com/tech/sendico/payments/storage"
|
"github.com/tech/sendico/payments/storage"
|
||||||
clockpkg "github.com/tech/sendico/pkg/clock"
|
clockpkg "github.com/tech/sendico/pkg/clock"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
@@ -35,6 +36,7 @@ type serviceDependencies struct {
|
|||||||
fees feesDependency
|
fees feesDependency
|
||||||
gateway gatewayDependency
|
gateway gatewayDependency
|
||||||
oracle oracleDependency
|
oracle oracleDependency
|
||||||
|
ledger ledgerclient.Client
|
||||||
gatewayRegistry GatewayRegistry
|
gatewayRegistry GatewayRegistry
|
||||||
gatewayInvokeResolver GatewayInvokeResolver
|
gatewayInvokeResolver GatewayInvokeResolver
|
||||||
cardRoutes map[string]CardGatewayRoute
|
cardRoutes map[string]CardGatewayRoute
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
|
paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
|
||||||
methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1"
|
methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1"
|
||||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
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.mongodb.org/mongo-driver/v2/bson"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
@@ -123,7 +124,16 @@ func (h *TransferIntentHydrator) HydrateOne(ctx context.Context, in HydrateOneIn
|
|||||||
if settlementCurrency == "" {
|
if settlementCurrency == "" {
|
||||||
settlementCurrency = strings.ToUpper(strings.TrimSpace(amount.Currency))
|
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{
|
intent := &QuoteIntent{
|
||||||
Ref: h.newRef(),
|
Ref: h.newRef(),
|
||||||
@@ -134,7 +144,7 @@ func (h *TransferIntentHydrator) HydrateOne(ctx context.Context, in HydrateOneIn
|
|||||||
Comment: strings.TrimSpace(in.Intent.GetComment()),
|
Comment: strings.TrimSpace(in.Intent.GetComment()),
|
||||||
SettlementMode: settlementMode,
|
SettlementMode: settlementMode,
|
||||||
FeeTreatment: feeTreatment,
|
FeeTreatment: feeTreatment,
|
||||||
FXSide: fxSideFromProto(in.Intent.GetFxSide()),
|
FX: fxIntent,
|
||||||
SettlementCurrency: settlementCurrency,
|
SettlementCurrency: settlementCurrency,
|
||||||
RequiresFX: requiresFX,
|
RequiresFX: requiresFX,
|
||||||
Attributes: map[string]string{
|
Attributes: map[string]string{
|
||||||
@@ -223,3 +233,55 @@ func fxSideFromProto(side fxv1.Side) paymenttypes.FXSide {
|
|||||||
return paymenttypes.FXSideUnspecified
|
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
|
MaskedPan string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type QuoteFXIntent struct {
|
||||||
|
Pair *paymenttypes.CurrencyPair
|
||||||
|
Side paymenttypes.FXSide
|
||||||
|
Firm bool
|
||||||
|
TTLms int64
|
||||||
|
PreferredProvider string
|
||||||
|
MaxAgeMs int32
|
||||||
|
}
|
||||||
|
|
||||||
type QuoteEndpoint struct {
|
type QuoteEndpoint struct {
|
||||||
Type QuoteEndpointType
|
Type QuoteEndpointType
|
||||||
PaymentMethodRef string
|
PaymentMethodRef string
|
||||||
@@ -84,7 +93,7 @@ type QuoteIntent struct {
|
|||||||
Comment string
|
Comment string
|
||||||
SettlementMode QuoteSettlementMode
|
SettlementMode QuoteSettlementMode
|
||||||
FeeTreatment QuoteFeeTreatment
|
FeeTreatment QuoteFeeTreatment
|
||||||
FXSide paymenttypes.FXSide
|
FX *QuoteFXIntent
|
||||||
SettlementCurrency string
|
SettlementCurrency string
|
||||||
RequiresFX bool
|
RequiresFX bool
|
||||||
Attributes map[string]string
|
Attributes map[string]string
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
|
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
|
||||||
methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1"
|
methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1"
|
||||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
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.mongodb.org/mongo-driver/v2/bson"
|
||||||
"google.golang.org/grpc"
|
"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" }))
|
h := New(nil, WithRefFactory(func() string { return "q-intent-fx-side" }))
|
||||||
intent := "ationv2.QuoteIntent{
|
intent := "ationv2.QuoteIntent{
|
||||||
Source: &endpointv1.PaymentEndpoint{
|
Source: &endpointv1.PaymentEndpoint{
|
||||||
@@ -126,7 +127,17 @@ func TestHydrateOne_PropagatesFXSide(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Amount: newMoney("10", "USDT"),
|
Amount: newMoney("10", "USDT"),
|
||||||
SettlementCurrency: "RUB",
|
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{
|
got, err := h.HydrateOne(context.Background(), HydrateOneInput{
|
||||||
@@ -140,8 +151,65 @@ func TestHydrateOne_PropagatesFXSide(t *testing.T) {
|
|||||||
if got == nil {
|
if got == nil {
|
||||||
t.Fatalf("expected hydrated intent")
|
t.Fatalf("expected hydrated intent")
|
||||||
}
|
}
|
||||||
if got.FXSide != paymenttypes.FXSideBuyBaseSellQuote {
|
if got.FX == nil || got.FX.Pair == nil {
|
||||||
t.Fatalf("unexpected fx side: got=%q", got.FXSide)
|
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 (
|
require (
|
||||||
github.com/casbin/casbin/v2 v2.135.0
|
github.com/casbin/casbin/v2 v2.135.0
|
||||||
github.com/casbin/mongodb-adapter/v4 v4.3.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/go-chi/chi/v5 v5.2.5
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/vault/api v1.22.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/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 h1:xQymkKCT5E2Jiaoqf3v4wsNgjZLY0lRSkZn27fRjSls=
|
||||||
github.com/ethereum/c-kzg-4844/v2 v2.1.6/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw=
|
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.1 h1:IjlQDjgxg2uL+GzPRkygGULPMLzcYWncEI7wbaizvho=
|
||||||
github.com/ethereum/go-ethereum v1.17.0/go.mod h1:2W3msvdosS/MCWytpqTcqgFiRYbTH59FxDJzqah120o=
|
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 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
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/payments/shared/v1/shared.proto";
|
||||||
import "api/proto/common/money/v1/money.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/common/payment/v1/settlement.proto";
|
||||||
import "api/proto/payments/endpoint/v1/endpoint.proto";
|
import "api/proto/payments/endpoint/v1/endpoint.proto";
|
||||||
import "api/proto/payments/quotation/v2/interface.proto";
|
import "api/proto/payments/quotation/v2/interface.proto";
|
||||||
@@ -20,7 +19,7 @@ message QuoteIntent {
|
|||||||
payments.quotation.v2.FeeTreatment fee_treatment = 5;
|
payments.quotation.v2.FeeTreatment fee_treatment = 5;
|
||||||
string settlement_currency = 6;
|
string settlement_currency = 6;
|
||||||
string comment = 7;
|
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.
|
// QuotePaymentRequest is the request to quote a single v2 payment.
|
||||||
|
|||||||
@@ -39,8 +39,7 @@ class PaymentProvider extends ChangeNotifier {
|
|||||||
String? clientPaymentRef,
|
String? clientPaymentRef,
|
||||||
Map<String, String>? metadata,
|
Map<String, String>? metadata,
|
||||||
}) async {
|
}) async {
|
||||||
if (!_organization.isOrganizationSet)
|
if (!_organization.isOrganizationSet) throw StateError('Organization is not set');
|
||||||
throw StateError('Organization is not set');
|
|
||||||
final quoteRef = _quotation.quotation?.quoteRef;
|
final quoteRef = _quotation.quotation?.quoteRef;
|
||||||
if (quoteRef == null || quoteRef.isEmpty) {
|
if (quoteRef == null || quoteRef.isEmpty) {
|
||||||
throw StateError('Quotation reference is not set');
|
throw StateError('Quotation reference is not set');
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class QuotationIntentBuilder {
|
|||||||
}
|
}
|
||||||
return FxIntent(
|
return FxIntent(
|
||||||
pair: CurrencyPair(base: base, quote: quote),
|
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