Compare commits
10 Commits
97d1470515
...
SEND005
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83e3af9a42 | ||
| 97f71d125e | |||
|
|
8db2f3926c | ||
|
|
2b68b59eca | ||
| d07e64fc4f | |||
|
|
8e40e6247b | ||
| 779cb0ead9 | |||
|
|
2e0057f839 | ||
| 25080ae168 | |||
|
|
e6b001dc61 |
@@ -8,9 +8,9 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/tech/sendico/fx/storage/model"
|
"github.com/tech/sendico/fx/storage/model"
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
|
smodel "github.com/tech/sendico/pkg/model"
|
||||||
fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
|
fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
|
||||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
tracev1 "github.com/tech/sendico/pkg/proto/common/trace/v1"
|
|
||||||
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
|
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
)
|
)
|
||||||
@@ -138,11 +138,11 @@ func (qc *quoteComputation) buildModelQuote(firm bool, expiryMillis int64, req *
|
|||||||
Pair: qc.pair.Pair,
|
Pair: qc.pair.Pair,
|
||||||
Side: qc.sideModel,
|
Side: qc.sideModel,
|
||||||
Price: formatRat(qc.priceRounded, qc.priceScale),
|
Price: formatRat(qc.priceRounded, qc.priceScale),
|
||||||
BaseAmount: model.Money{
|
BaseAmount: smodel.Money{
|
||||||
Currency: qc.pair.Pair.Base,
|
Currency: qc.pair.Pair.Base,
|
||||||
Amount: formatRat(qc.baseRounded, qc.baseScale),
|
Amount: formatRat(qc.baseRounded, qc.baseScale),
|
||||||
},
|
},
|
||||||
QuoteAmount: model.Money{
|
QuoteAmount: smodel.Money{
|
||||||
Currency: qc.pair.Pair.Quote,
|
Currency: qc.pair.Pair.Quote,
|
||||||
Amount: formatRat(qc.quoteRounded, qc.quoteScale),
|
Amount: formatRat(qc.quoteRounded, qc.quoteScale),
|
||||||
},
|
},
|
||||||
@@ -170,10 +170,13 @@ func buildQuoteMeta(meta *oraclev1.RequestMeta) *model.QuoteMeta {
|
|||||||
}
|
}
|
||||||
trace := meta.GetTrace()
|
trace := meta.GetTrace()
|
||||||
qm := &model.QuoteMeta{
|
qm := &model.QuoteMeta{
|
||||||
RequestRef: deriveRequestRef(meta, trace),
|
|
||||||
TenantRef: meta.GetTenantRef(),
|
TenantRef: meta.GetTenantRef(),
|
||||||
TraceRef: deriveTraceRef(meta, trace),
|
}
|
||||||
IdempotencyKey: deriveIdempotencyKey(meta, trace),
|
|
||||||
|
if trace != nil {
|
||||||
|
qm.RequestRef = trace.GetRequestRef()
|
||||||
|
qm.TraceRef = trace.GetTraceRef()
|
||||||
|
qm.IdempotencyKey = trace.GetIdempotencyKey()
|
||||||
}
|
}
|
||||||
if org := strings.TrimSpace(meta.GetOrganizationRef()); org != "" {
|
if org := strings.TrimSpace(meta.GetOrganizationRef()); org != "" {
|
||||||
if objID, err := primitive.ObjectIDFromHex(org); err == nil {
|
if objID, err := primitive.ObjectIDFromHex(org); err == nil {
|
||||||
@@ -200,24 +203,3 @@ func computeExpiry(now time.Time, ttlMs int64) (int64, error) {
|
|||||||
}
|
}
|
||||||
return now.Add(time.Duration(ttlMs) * time.Millisecond).UnixMilli(), nil
|
return now.Add(time.Duration(ttlMs) * time.Millisecond).UnixMilli(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deriveRequestRef(meta *oraclev1.RequestMeta, trace *tracev1.TraceContext) string {
|
|
||||||
if trace != nil && trace.GetRequestRef() != "" {
|
|
||||||
return trace.GetRequestRef()
|
|
||||||
}
|
|
||||||
return meta.GetRequestRef()
|
|
||||||
}
|
|
||||||
|
|
||||||
func deriveTraceRef(meta *oraclev1.RequestMeta, trace *tracev1.TraceContext) string {
|
|
||||||
if trace != nil && trace.GetTraceRef() != "" {
|
|
||||||
return trace.GetTraceRef()
|
|
||||||
}
|
|
||||||
return meta.GetTraceRef()
|
|
||||||
}
|
|
||||||
|
|
||||||
func deriveIdempotencyKey(meta *oraclev1.RequestMeta, trace *tracev1.TraceContext) string {
|
|
||||||
if trace != nil && trace.GetIdempotencyKey() != "" {
|
|
||||||
return trace.GetIdempotencyKey()
|
|
||||||
}
|
|
||||||
return meta.GetIdempotencyKey()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package oracle
|
|||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tech/sendico/fx/storage/model"
|
"github.com/tech/sendico/fx/storage/model"
|
||||||
"github.com/tech/sendico/pkg/decimal"
|
"github.com/tech/sendico/pkg/decimal"
|
||||||
@@ -61,7 +60,3 @@ func priceFromRate(rate *model.RateSnapshot, side fxv1.Side) (*big.Rat, error) {
|
|||||||
|
|
||||||
return ratFromString(priceStr)
|
return ratFromString(priceStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func timeFromUnixMilli(ms int64) time.Time {
|
|
||||||
return time.Unix(0, ms*int64(time.Millisecond))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/tech/sendico/fx/storage"
|
"github.com/tech/sendico/fx/storage"
|
||||||
"github.com/tech/sendico/fx/storage/model"
|
"github.com/tech/sendico/fx/storage/model"
|
||||||
"github.com/tech/sendico/pkg/merrors"
|
"github.com/tech/sendico/pkg/merrors"
|
||||||
|
smodel "github.com/tech/sendico/pkg/model"
|
||||||
fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
|
fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
|
||||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
tracev1 "github.com/tech/sendico/pkg/proto/common/trace/v1"
|
tracev1 "github.com/tech/sendico/pkg/proto/common/trace/v1"
|
||||||
@@ -381,8 +382,8 @@ func TestServiceValidateQuote(t *testing.T) {
|
|||||||
Pair: model.CurrencyPair{Base: "USD", Quote: "EUR"},
|
Pair: model.CurrencyPair{Base: "USD", Quote: "EUR"},
|
||||||
Side: model.QuoteSideBuyBaseSellQuote,
|
Side: model.QuoteSideBuyBaseSellQuote,
|
||||||
Price: "1.10",
|
Price: "1.10",
|
||||||
BaseAmount: model.Money{Currency: "USD", Amount: "100"},
|
BaseAmount: smodel.Money{Currency: "USD", Amount: "100"},
|
||||||
QuoteAmount: model.Money{Currency: "EUR", Amount: "110"},
|
QuoteAmount: smodel.Money{Currency: "EUR", Amount: "110"},
|
||||||
ExpiresAtUnixMs: now.UnixMilli(),
|
ExpiresAtUnixMs: now.UnixMilli(),
|
||||||
Status: model.QuoteStatusIssued,
|
Status: model.QuoteStatusIssued,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tech/sendico/fx/storage/model"
|
"github.com/tech/sendico/fx/storage/model"
|
||||||
|
smodel "github.com/tech/sendico/pkg/model"
|
||||||
fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
|
fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
|
||||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||||
tracev1 "github.com/tech/sendico/pkg/proto/common/trace/v1"
|
|
||||||
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
|
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,18 +15,11 @@ func buildResponseMeta(meta *oraclev1.RequestMeta) *oraclev1.ResponseMeta {
|
|||||||
if meta == nil {
|
if meta == nil {
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
resp.RequestRef = meta.GetRequestRef()
|
|
||||||
resp.TraceRef = meta.GetTraceRef()
|
|
||||||
|
|
||||||
trace := meta.GetTrace()
|
trace := meta.GetTrace()
|
||||||
if trace == nil {
|
if trace != nil {
|
||||||
trace = &tracev1.TraceContext{
|
|
||||||
RequestRef: meta.GetRequestRef(),
|
|
||||||
IdempotencyKey: meta.GetIdempotencyKey(),
|
|
||||||
TraceRef: meta.GetTraceRef(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
resp.Trace = trace
|
resp.Trace = trace
|
||||||
|
}
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +42,7 @@ func quoteModelToProto(q *model.Quote) *oraclev1.Quote {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func moneyModelToProto(m *model.Money) *moneyv1.Money {
|
func moneyModelToProto(m *smodel.Money) *moneyv1.Money {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ func quoteSnapshotToModel(src *orchestratorv1.PaymentQuote) *model.PaymentQuoteS
|
|||||||
FeeRules: cloneFeeRules(src.GetFeeRules()),
|
FeeRules: cloneFeeRules(src.GetFeeRules()),
|
||||||
FXQuote: cloneFXQuote(src.GetFxQuote()),
|
FXQuote: cloneFXQuote(src.GetFxQuote()),
|
||||||
NetworkFee: cloneNetworkEstimate(src.GetNetworkFee()),
|
NetworkFee: cloneNetworkEstimate(src.GetNetworkFee()),
|
||||||
FeeQuoteToken: strings.TrimSpace(src.GetFeeQuoteToken()),
|
|
||||||
QuoteRef: strings.TrimSpace(src.GetQuoteRef()),
|
QuoteRef: strings.TrimSpace(src.GetQuoteRef()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,7 +263,6 @@ func modelQuoteToProto(src *model.PaymentQuoteSnapshot) *orchestratorv1.PaymentQ
|
|||||||
FeeRules: cloneFeeRules(src.FeeRules),
|
FeeRules: cloneFeeRules(src.FeeRules),
|
||||||
FxQuote: cloneFXQuote(src.FXQuote),
|
FxQuote: cloneFXQuote(src.FXQuote),
|
||||||
NetworkFee: cloneNetworkEstimate(src.NetworkFee),
|
NetworkFee: cloneNetworkEstimate(src.NetworkFee),
|
||||||
FeeQuoteToken: src.FeeQuoteToken,
|
|
||||||
QuoteRef: strings.TrimSpace(src.QuoteRef),
|
QuoteRef: strings.TrimSpace(src.QuoteRef),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ func (h *initiatePaymentCommand) Execute(ctx context.Context, req *orchestratorv
|
|||||||
Meta: req.GetMeta(),
|
Meta: req.GetMeta(),
|
||||||
Intent: intent,
|
Intent: intent,
|
||||||
QuoteRef: req.GetQuoteRef(),
|
QuoteRef: req.GetQuoteRef(),
|
||||||
FeeQuoteToken: req.GetFeeQuoteToken(),
|
|
||||||
IdempotencyKey: req.GetIdempotencyKey(),
|
IdempotencyKey: req.GetIdempotencyKey(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ func (s *Service) buildPaymentQuote(ctx context.Context, orgRef string, req *orc
|
|||||||
FeeRules: cloneFeeRules(feeQuote.GetApplied()),
|
FeeRules: cloneFeeRules(feeQuote.GetApplied()),
|
||||||
FxQuote: fxQuote,
|
FxQuote: fxQuote,
|
||||||
NetworkFee: networkFee,
|
NetworkFee: networkFee,
|
||||||
FeeQuoteToken: feeQuote.GetFeeQuoteToken(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expiresAt := quoteExpiry(s.clock.Now(), feeQuote, fxQuote)
|
expiresAt := quoteExpiry(s.clock.Now(), feeQuote, fxQuote)
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ type quoteResolutionInput struct {
|
|||||||
Meta *orchestratorv1.RequestMeta
|
Meta *orchestratorv1.RequestMeta
|
||||||
Intent *orchestratorv1.PaymentIntent
|
Intent *orchestratorv1.PaymentIntent
|
||||||
QuoteRef string
|
QuoteRef string
|
||||||
FeeQuoteToken string
|
|
||||||
IdempotencyKey string
|
IdempotencyKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,10 +130,6 @@ func (s *Service) resolvePaymentQuote(ctx context.Context, in quoteResolutionInp
|
|||||||
return quote, nil
|
return quote, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if token := strings.TrimSpace(in.FeeQuoteToken); token != "" {
|
|
||||||
return &orchestratorv1.PaymentQuote{FeeQuoteToken: token}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &orchestratorv1.QuotePaymentRequest{
|
req := &orchestratorv1.QuotePaymentRequest{
|
||||||
Meta: in.Meta,
|
Meta: in.Meta,
|
||||||
IdempotencyKey: in.IdempotencyKey,
|
IdempotencyKey: in.IdempotencyKey,
|
||||||
|
|||||||
@@ -106,25 +106,6 @@ func TestResolvePaymentQuote_Expired(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolvePaymentQuote_FeeToken(t *testing.T) {
|
|
||||||
org := primitive.NewObjectID()
|
|
||||||
intent := &orchestratorv1.PaymentIntent{Amount: &moneyv1.Money{Currency: "USD", Amount: "1"}}
|
|
||||||
svc := &Service{clock: clockpkg.NewSystem()}
|
|
||||||
quote, err := svc.resolvePaymentQuote(context.Background(), quoteResolutionInput{
|
|
||||||
OrgRef: org.Hex(),
|
|
||||||
OrgID: org,
|
|
||||||
Meta: &orchestratorv1.RequestMeta{OrganizationRef: org.Hex()},
|
|
||||||
Intent: intent,
|
|
||||||
FeeQuoteToken: "token",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if quote.GetFeeQuoteToken() != "token" {
|
|
||||||
t.Fatalf("expected fee token preserved")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInitiatePaymentIdempotency(t *testing.T) {
|
func TestInitiatePaymentIdempotency(t *testing.T) {
|
||||||
logger := mloggerfactory.NewLogger(false)
|
logger := mloggerfactory.NewLogger(false)
|
||||||
org := primitive.NewObjectID()
|
org := primitive.NewObjectID()
|
||||||
@@ -141,7 +122,6 @@ func TestInitiatePaymentIdempotency(t *testing.T) {
|
|||||||
Meta: &orchestratorv1.RequestMeta{OrganizationRef: org.Hex()},
|
Meta: &orchestratorv1.RequestMeta{OrganizationRef: org.Hex()},
|
||||||
Intent: intent,
|
Intent: intent,
|
||||||
IdempotencyKey: "k1",
|
IdempotencyKey: "k1",
|
||||||
FeeQuoteToken: "fq",
|
|
||||||
}
|
}
|
||||||
resp, err := svc.h.commands.InitiatePayment().Execute(context.Background(), req)(context.Background())
|
resp, err := svc.h.commands.InitiatePayment().Execute(context.Background(), req)(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -143,7 +143,6 @@ type PaymentQuoteSnapshot struct {
|
|||||||
FeeRules []*feesv1.AppliedRule `bson:"feeRules,omitempty" json:"feeRules,omitempty"`
|
FeeRules []*feesv1.AppliedRule `bson:"feeRules,omitempty" json:"feeRules,omitempty"`
|
||||||
FXQuote *oraclev1.Quote `bson:"fxQuote,omitempty" json:"fxQuote,omitempty"`
|
FXQuote *oraclev1.Quote `bson:"fxQuote,omitempty" json:"fxQuote,omitempty"`
|
||||||
NetworkFee *chainv1.EstimateTransferFeeResponse `bson:"networkFee,omitempty" json:"networkFee,omitempty"`
|
NetworkFee *chainv1.EstimateTransferFeeResponse `bson:"networkFee,omitempty" json:"networkFee,omitempty"`
|
||||||
FeeQuoteToken string `bson:"feeQuoteToken,omitempty" json:"feeQuoteToken,omitempty"`
|
|
||||||
QuoteRef string `bson:"quoteRef,omitempty" json:"quoteRef,omitempty"`
|
QuoteRef string `bson:"quoteRef,omitempty" json:"quoteRef,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,17 +20,18 @@ message RateSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message RequestMeta {
|
message RequestMeta {
|
||||||
string request_ref = 1 [deprecated = true];
|
reserved 1, 4, 5;
|
||||||
|
reserved "request_ref", "idempotency_key", "trace_ref";
|
||||||
|
|
||||||
string tenant_ref = 2;
|
string tenant_ref = 2;
|
||||||
string organization_ref = 3;
|
string organization_ref = 3;
|
||||||
string idempotency_key = 4 [deprecated = true];
|
|
||||||
string trace_ref = 5 [deprecated = true];
|
|
||||||
common.trace.v1.TraceContext trace = 6;
|
common.trace.v1.TraceContext trace = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ResponseMeta {
|
message ResponseMeta {
|
||||||
string request_ref = 1 [deprecated = true];
|
reserved 1, 2;
|
||||||
string trace_ref = 2 [deprecated = true];
|
reserved "request_ref", "trace_ref";
|
||||||
|
|
||||||
common.trace.v1.TraceContext trace = 3;
|
common.trace.v1.TraceContext trace = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,8 +122,7 @@ message PaymentQuote {
|
|||||||
repeated fees.v1.AppliedRule fee_rules = 5;
|
repeated fees.v1.AppliedRule fee_rules = 5;
|
||||||
oracle.v1.Quote fx_quote = 6;
|
oracle.v1.Quote fx_quote = 6;
|
||||||
chain.gateway.v1.EstimateTransferFeeResponse network_fee = 7;
|
chain.gateway.v1.EstimateTransferFeeResponse network_fee = 7;
|
||||||
string fee_quote_token = 8;
|
string quote_ref = 8;
|
||||||
string quote_ref = 9;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ExecutionRefs {
|
message ExecutionRefs {
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ type PaymentQuote struct {
|
|||||||
DebitAmount *model.Money `json:"debitAmount,omitempty"`
|
DebitAmount *model.Money `json:"debitAmount,omitempty"`
|
||||||
ExpectedSettlementAmount *model.Money `json:"expectedSettlementAmount,omitempty"`
|
ExpectedSettlementAmount *model.Money `json:"expectedSettlementAmount,omitempty"`
|
||||||
ExpectedFeeTotal *model.Money `json:"expectedFeeTotal,omitempty"`
|
ExpectedFeeTotal *model.Money `json:"expectedFeeTotal,omitempty"`
|
||||||
FeeQuoteToken string `json:"feeQuoteToken,omitempty"`
|
|
||||||
FeeLines []FeeLine `json:"feeLines,omitempty"`
|
FeeLines []FeeLine `json:"feeLines,omitempty"`
|
||||||
NetworkFee *NetworkFee `json:"networkFee,omitempty"`
|
NetworkFee *NetworkFee `json:"networkFee,omitempty"`
|
||||||
FxQuote *FxQuote `json:"fxQuote,omitempty"`
|
FxQuote *FxQuote `json:"fxQuote,omitempty"`
|
||||||
@@ -153,7 +152,6 @@ func toPaymentQuote(q *orchestratorv1.PaymentQuote) *PaymentQuote {
|
|||||||
DebitAmount: toMoney(q.GetDebitAmount()),
|
DebitAmount: toMoney(q.GetDebitAmount()),
|
||||||
ExpectedSettlementAmount: toMoney(q.GetExpectedSettlementAmount()),
|
ExpectedSettlementAmount: toMoney(q.GetExpectedSettlementAmount()),
|
||||||
ExpectedFeeTotal: toMoney(q.GetExpectedFeeTotal()),
|
ExpectedFeeTotal: toMoney(q.GetExpectedFeeTotal()),
|
||||||
FeeQuoteToken: q.GetFeeQuoteToken(),
|
|
||||||
FeeLines: toFeeLines(q.GetFeeLines()),
|
FeeLines: toFeeLines(q.GetFeeLines()),
|
||||||
NetworkFee: toNetworkFee(q.GetNetworkFee()),
|
NetworkFee: toNetworkFee(q.GetNetworkFee()),
|
||||||
FxQuote: toFxQuote(q.GetFxQuote()),
|
FxQuote: toFxQuote(q.GetFxQuote()),
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:pshared/api/responses/base.dart';
|
|||||||
import 'package:pshared/api/responses/token.dart';
|
import 'package:pshared/api/responses/token.dart';
|
||||||
import 'package:pshared/data/dto/payment/method.dart';
|
import 'package:pshared/data/dto/payment/method.dart';
|
||||||
|
|
||||||
part 'payment_method.g.dart';
|
part 'method.g.dart';
|
||||||
|
|
||||||
|
|
||||||
@JsonSerializable(explicitToJson: true)
|
@JsonSerializable(explicitToJson: true)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ class CommonConstants {
|
|||||||
static String apiEndpoint = '/api/v1';
|
static String apiEndpoint = '/api/v1';
|
||||||
static String amplitudeSecret = 'c3d75b3e2520d708440acbb16b923e79';
|
static String amplitudeSecret = 'c3d75b3e2520d708440acbb16b923e79';
|
||||||
static String amplitudeServerZone = 'EU';
|
static String amplitudeServerZone = 'EU';
|
||||||
|
static String posthogApiKey = '';
|
||||||
|
static String posthogHost = 'https://eu.i.posthog.com';
|
||||||
static Locale defaultLocale = const Locale('en');
|
static Locale defaultLocale = const Locale('en');
|
||||||
static String defaultCurrency = 'EUR';
|
static String defaultCurrency = 'EUR';
|
||||||
static int defaultDimensionLength = 500;
|
static int defaultDimensionLength = 500;
|
||||||
@@ -36,6 +38,8 @@ class CommonConstants {
|
|||||||
apiEndpoint = configJson['apiEndpoint'] ?? apiEndpoint;
|
apiEndpoint = configJson['apiEndpoint'] ?? apiEndpoint;
|
||||||
amplitudeSecret = configJson['amplitudeSecret'] ?? amplitudeSecret;
|
amplitudeSecret = configJson['amplitudeSecret'] ?? amplitudeSecret;
|
||||||
amplitudeServerZone = configJson['amplitudeServerZone'] ?? amplitudeServerZone;
|
amplitudeServerZone = configJson['amplitudeServerZone'] ?? amplitudeServerZone;
|
||||||
|
posthogApiKey = configJson['posthogApiKey'] ?? posthogApiKey;
|
||||||
|
posthogHost = configJson['posthogHost'] ?? posthogHost;
|
||||||
defaultLocale = Locale(configJson['defaultLocale'] ?? defaultLocale.languageCode);
|
defaultLocale = Locale(configJson['defaultLocale'] ?? defaultLocale.languageCode);
|
||||||
defaultCurrency = configJson['defaultCurrency'] ?? defaultCurrency;
|
defaultCurrency = configJson['defaultCurrency'] ?? defaultCurrency;
|
||||||
wsProto = configJson['wsProto'] ?? wsProto;
|
wsProto = configJson['wsProto'] ?? wsProto;
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ class Constants extends CommonConstants {
|
|||||||
static String get currentOrgKey => CommonConstants.currentOrgKey;
|
static String get currentOrgKey => CommonConstants.currentOrgKey;
|
||||||
static String get apiUrl => CommonConstants.apiUrl;
|
static String get apiUrl => CommonConstants.apiUrl;
|
||||||
static String get serviceUrl => CommonConstants.serviceUrl;
|
static String get serviceUrl => CommonConstants.serviceUrl;
|
||||||
|
static String get posthogApiKey => CommonConstants.posthogApiKey;
|
||||||
|
static String get posthogHost => CommonConstants.posthogHost;
|
||||||
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
|
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
|
||||||
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
|
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
|
||||||
static String get nilObjectRef => CommonConstants.nilObjectRef;
|
static String get nilObjectRef => CommonConstants.nilObjectRef;
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ extension AppConfigExtension on AppConfig {
|
|||||||
external String? get apiEndpoint;
|
external String? get apiEndpoint;
|
||||||
external String? get amplitudeSecret;
|
external String? get amplitudeSecret;
|
||||||
external String? get amplitudeServerZone;
|
external String? get amplitudeServerZone;
|
||||||
|
external String? get posthogApiKey;
|
||||||
|
external String? get posthogHost;
|
||||||
external String? get defaultLocale;
|
external String? get defaultLocale;
|
||||||
external String? get wsProto;
|
external String? get wsProto;
|
||||||
external String? get wsEndpoint;
|
external String? get wsEndpoint;
|
||||||
@@ -40,6 +42,8 @@ class Constants extends CommonConstants {
|
|||||||
static String get currentOrgKey => CommonConstants.currentOrgKey;
|
static String get currentOrgKey => CommonConstants.currentOrgKey;
|
||||||
static String get apiUrl => CommonConstants.apiUrl;
|
static String get apiUrl => CommonConstants.apiUrl;
|
||||||
static String get serviceUrl => CommonConstants.serviceUrl;
|
static String get serviceUrl => CommonConstants.serviceUrl;
|
||||||
|
static String get posthogApiKey => CommonConstants.posthogApiKey;
|
||||||
|
static String get posthogHost => CommonConstants.posthogHost;
|
||||||
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
|
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
|
||||||
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
|
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
|
||||||
static String get nilObjectRef => CommonConstants.nilObjectRef;
|
static String get nilObjectRef => CommonConstants.nilObjectRef;
|
||||||
@@ -57,6 +61,8 @@ class Constants extends CommonConstants {
|
|||||||
'apiEndpoint': config.apiEndpoint,
|
'apiEndpoint': config.apiEndpoint,
|
||||||
'amplitudeSecret': config.amplitudeSecret,
|
'amplitudeSecret': config.amplitudeSecret,
|
||||||
'amplitudeServerZone': config.amplitudeServerZone,
|
'amplitudeServerZone': config.amplitudeServerZone,
|
||||||
|
'posthogApiKey': config.posthogApiKey,
|
||||||
|
'posthogHost': config.posthogHost,
|
||||||
'defaultLocale': config.defaultLocale,
|
'defaultLocale': config.defaultLocale,
|
||||||
'wsProto': config.wsProto,
|
'wsProto': config.wsProto,
|
||||||
'wsEndpoint': config.wsEndpoint,
|
'wsEndpoint': config.wsEndpoint,
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ class PaymentQuoteDTO {
|
|||||||
final MoneyDTO? debitAmount;
|
final MoneyDTO? debitAmount;
|
||||||
final MoneyDTO? expectedSettlementAmount;
|
final MoneyDTO? expectedSettlementAmount;
|
||||||
final MoneyDTO? expectedFeeTotal;
|
final MoneyDTO? expectedFeeTotal;
|
||||||
final String? feeQuoteToken;
|
|
||||||
final List<FeeLineDTO>? feeLines;
|
final List<FeeLineDTO>? feeLines;
|
||||||
final NetworkFeeDTO? networkFee;
|
final NetworkFeeDTO? networkFee;
|
||||||
final FxQuoteDTO? fxQuote;
|
final FxQuoteDTO? fxQuote;
|
||||||
@@ -24,7 +23,6 @@ class PaymentQuoteDTO {
|
|||||||
this.debitAmount,
|
this.debitAmount,
|
||||||
this.expectedSettlementAmount,
|
this.expectedSettlementAmount,
|
||||||
this.expectedFeeTotal,
|
this.expectedFeeTotal,
|
||||||
this.feeQuoteToken,
|
|
||||||
this.feeLines,
|
this.feeLines,
|
||||||
this.networkFee,
|
this.networkFee,
|
||||||
this.fxQuote,
|
this.fxQuote,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ extension PaymentQuoteDTOMapper on PaymentQuoteDTO {
|
|||||||
debitAmount: debitAmount?.toDomain(),
|
debitAmount: debitAmount?.toDomain(),
|
||||||
expectedSettlementAmount: expectedSettlementAmount?.toDomain(),
|
expectedSettlementAmount: expectedSettlementAmount?.toDomain(),
|
||||||
expectedFeeTotal: expectedFeeTotal?.toDomain(),
|
expectedFeeTotal: expectedFeeTotal?.toDomain(),
|
||||||
feeQuoteToken: feeQuoteToken,
|
|
||||||
feeLines: feeLines?.map((line) => line.toDomain()).toList(),
|
feeLines: feeLines?.map((line) => line.toDomain()).toList(),
|
||||||
networkFee: networkFee?.toDomain(),
|
networkFee: networkFee?.toDomain(),
|
||||||
fxQuote: fxQuote?.toDomain(),
|
fxQuote: fxQuote?.toDomain(),
|
||||||
@@ -25,7 +24,6 @@ extension PaymentQuoteMapper on PaymentQuote {
|
|||||||
debitAmount: debitAmount?.toDTO(),
|
debitAmount: debitAmount?.toDTO(),
|
||||||
expectedSettlementAmount: expectedSettlementAmount?.toDTO(),
|
expectedSettlementAmount: expectedSettlementAmount?.toDTO(),
|
||||||
expectedFeeTotal: expectedFeeTotal?.toDTO(),
|
expectedFeeTotal: expectedFeeTotal?.toDTO(),
|
||||||
feeQuoteToken: feeQuoteToken,
|
|
||||||
feeLines: feeLines?.map((line) => line.toDTO()).toList(),
|
feeLines: feeLines?.map((line) => line.toDTO()).toList(),
|
||||||
networkFee: networkFee?.toDTO(),
|
networkFee: networkFee?.toDTO(),
|
||||||
fxQuote: fxQuote?.toDTO(),
|
fxQuote: fxQuote?.toDTO(),
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ class PaymentQuote {
|
|||||||
final Money? debitAmount;
|
final Money? debitAmount;
|
||||||
final Money? expectedSettlementAmount;
|
final Money? expectedSettlementAmount;
|
||||||
final Money? expectedFeeTotal;
|
final Money? expectedFeeTotal;
|
||||||
final String? feeQuoteToken;
|
|
||||||
final List<FeeLine>? feeLines;
|
final List<FeeLine>? feeLines;
|
||||||
final NetworkFee? networkFee;
|
final NetworkFee? networkFee;
|
||||||
final FxQuote? fxQuote;
|
final FxQuote? fxQuote;
|
||||||
@@ -19,7 +18,6 @@ class PaymentQuote {
|
|||||||
required this.debitAmount,
|
required this.debitAmount,
|
||||||
required this.expectedSettlementAmount,
|
required this.expectedSettlementAmount,
|
||||||
required this.expectedFeeTotal,
|
required this.expectedFeeTotal,
|
||||||
required this.feeQuoteToken,
|
|
||||||
required this.feeLines,
|
required this.feeLines,
|
||||||
required this.networkFee,
|
required this.networkFee,
|
||||||
required this.fxQuote,
|
required this.fxQuote,
|
||||||
|
|||||||
@@ -79,6 +79,10 @@ enum ResourceType {
|
|||||||
@JsonValue('payments')
|
@JsonValue('payments')
|
||||||
payments,
|
payments,
|
||||||
|
|
||||||
|
/// Represents payment orchestration service
|
||||||
|
@JsonValue('payment_orchestrator')
|
||||||
|
paymentOrchestrator,
|
||||||
|
|
||||||
@JsonValue('payment_methods')
|
@JsonValue('payment_methods')
|
||||||
paymentMethods,
|
paymentMethods,
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
@@ -20,6 +22,9 @@ import 'package:pshared/utils/exception.dart';
|
|||||||
|
|
||||||
|
|
||||||
class AccountProvider extends ChangeNotifier {
|
class AccountProvider extends ChangeNotifier {
|
||||||
|
AccountProvider({Future<void> Function(Account?)? onAccountChanged})
|
||||||
|
: _onAccountChanged = onAccountChanged;
|
||||||
|
|
||||||
static String get currentUserRef => Constants.nilObjectRef;
|
static String get currentUserRef => Constants.nilObjectRef;
|
||||||
|
|
||||||
// The resource now wraps our Account? state along with its loading/error state.
|
// The resource now wraps our Account? state along with its loading/error state.
|
||||||
@@ -27,6 +32,8 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
Resource<Account?> get resource => _resource;
|
Resource<Account?> get resource => _resource;
|
||||||
late LocaleProvider _localeProvider;
|
late LocaleProvider _localeProvider;
|
||||||
PendingLogin? _pendingLogin;
|
PendingLogin? _pendingLogin;
|
||||||
|
Future<void>? _restoreFuture;
|
||||||
|
Future<void> Function(Account?)? _onAccountChanged;
|
||||||
|
|
||||||
Account? get account => _resource.data;
|
Account? get account => _resource.data;
|
||||||
PendingLogin? get pendingLogin => _pendingLogin;
|
PendingLogin? get pendingLogin => _pendingLogin;
|
||||||
@@ -53,11 +60,21 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Private helper to update the resource and notify listeners.
|
// Private helper to update the resource and notify listeners.
|
||||||
|
void setAccountChangedListener(Future<void> Function(Account?)? listener) => _onAccountChanged = listener;
|
||||||
|
|
||||||
void _setResource(Resource<Account?> newResource) {
|
void _setResource(Resource<Account?> newResource) {
|
||||||
|
final previousAccount = _resource.data;
|
||||||
_resource = newResource;
|
_resource = newResource;
|
||||||
|
_notifyAccountChanged(previousAccount, newResource.data);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _notifyAccountChanged(Account? previous, Account? current) {
|
||||||
|
if (previous == current) return;
|
||||||
|
final handler = _onAccountChanged;
|
||||||
|
if (handler != null) unawaited(handler(current));
|
||||||
|
}
|
||||||
|
|
||||||
void updateProvider(LocaleProvider localeProvider) => _localeProvider = localeProvider;
|
void updateProvider(LocaleProvider localeProvider) => _localeProvider = localeProvider;
|
||||||
|
|
||||||
void _pickupLocale(String locale) => _localeProvider.setLocale(Locale(locale));
|
void _pickupLocale(String locale) => _localeProvider.setLocale(Locale(locale));
|
||||||
@@ -220,4 +237,11 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> restoreIfPossible() {
|
||||||
|
return _restoreFuture ??= AuthorizationService.isAuthorizationStored().then<void>((hasAuth) async {
|
||||||
|
if (!hasAuth) return;
|
||||||
|
await restore();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class PermissionsProvider extends ChangeNotifier {
|
|||||||
Resource<UserAccess> _userAccess = Resource(data: null, isLoading: false, error: null);
|
Resource<UserAccess> _userAccess = Resource(data: null, isLoading: false, error: null);
|
||||||
late OrganizationsProvider _organizations;
|
late OrganizationsProvider _organizations;
|
||||||
bool _isLoaded = false;
|
bool _isLoaded = false;
|
||||||
|
bool _errorHandled = false;
|
||||||
|
|
||||||
void update(OrganizationsProvider venue) {
|
void update(OrganizationsProvider venue) {
|
||||||
_organizations = venue;
|
_organizations = venue;
|
||||||
@@ -44,6 +45,7 @@ class PermissionsProvider extends ChangeNotifier {
|
|||||||
/// Load the [UserAccess] for the current venue.
|
/// Load the [UserAccess] for the current venue.
|
||||||
Future<UserAccess?> load() async {
|
Future<UserAccess?> load() async {
|
||||||
_userAccess = _userAccess.copyWith(isLoading: true, error: null);
|
_userAccess = _userAccess.copyWith(isLoading: true, error: null);
|
||||||
|
_errorHandled = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -67,6 +69,12 @@ class PermissionsProvider extends ChangeNotifier {
|
|||||||
return _userAccess.data;
|
return _userAccess.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get hasUnhandledError => error != null && !_errorHandled;
|
||||||
|
|
||||||
|
void markErrorHandled() {
|
||||||
|
_errorHandled = true;
|
||||||
|
}
|
||||||
|
|
||||||
Future<UserAccess?> changeRole(String accountRef, String newRoleDescRef) async {
|
Future<UserAccess?> changeRole(String accountRef, String newRoleDescRef) async {
|
||||||
final currentRole = roles.firstWhereOrNull((r) => r.accountRef == accountRef);
|
final currentRole = roles.firstWhereOrNull((r) => r.accountRef == accountRef);
|
||||||
final currentDesc = currentRole != null
|
final currentDesc = currentRole != null
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import 'package:pshared/api/responses/payment_method.dart';
|
import 'package:pshared/api/responses/payment/method.dart';
|
||||||
import 'package:pshared/data/mapper/payment/method.dart';
|
import 'package:pshared/data/mapper/payment/method.dart';
|
||||||
import 'package:pshared/models/payment/methods/type.dart';
|
import 'package:pshared/models/payment/methods/type.dart';
|
||||||
import 'package:pshared/service/services.dart';
|
import 'package:pshared/service/services.dart';
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import 'package:pweb/providers/wallets.dart';
|
|||||||
import 'package:pweb/providers/wallet_transactions.dart';
|
import 'package:pweb/providers/wallet_transactions.dart';
|
||||||
import 'package:pweb/services/operations.dart';
|
import 'package:pweb/services/operations.dart';
|
||||||
import 'package:pweb/services/payments/history.dart';
|
import 'package:pweb/services/payments/history.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
import 'package:pweb/services/wallet_transactions.dart';
|
import 'package:pweb/services/wallet_transactions.dart';
|
||||||
import 'package:pweb/services/wallets.dart';
|
import 'package:pweb/services/wallets.dart';
|
||||||
|
|
||||||
@@ -40,11 +41,9 @@ void _setupLogging() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
await Constants.initialize();
|
|
||||||
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await Constants.initialize();
|
||||||
// await AmplitudeService.initialize();
|
await PosthogService.initialize();
|
||||||
|
|
||||||
|
|
||||||
_setupLogging();
|
_setupLogging();
|
||||||
@@ -57,7 +56,12 @@ void main() async {
|
|||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (_) => LocaleProvider(null)),
|
ChangeNotifierProvider(create: (_) => LocaleProvider(null)),
|
||||||
ChangeNotifierProxyProvider<LocaleProvider, AccountProvider>(
|
ChangeNotifierProxyProvider<LocaleProvider, AccountProvider>(
|
||||||
create: (_) => AccountProvider(),
|
create: (_) => AccountProvider(
|
||||||
|
onAccountChanged: (account) {
|
||||||
|
if (account == null) return Future<void>.value();
|
||||||
|
return PosthogService.identify(account);
|
||||||
|
},
|
||||||
|
),
|
||||||
update: (context, localeProvider, provider) => provider!..updateProvider(localeProvider),
|
update: (context, localeProvider, provider) => provider!..updateProvider(localeProvider),
|
||||||
),
|
),
|
||||||
ChangeNotifierProxyProvider<AccountProvider, TwoFactorProvider>(
|
ChangeNotifierProxyProvider<AccountProvider, TwoFactorProvider>(
|
||||||
@@ -70,6 +74,7 @@ void main() async {
|
|||||||
update: (context, orgnization, provider) => provider!..update(orgnization),
|
update: (context, orgnization, provider) => provider!..update(orgnization),
|
||||||
),
|
),
|
||||||
ChangeNotifierProvider(create: (_) => CarouselIndexProvider()),
|
ChangeNotifierProvider(create: (_) => CarouselIndexProvider()),
|
||||||
|
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => UploadHistoryProvider(service: MockUploadHistoryService())..load(),
|
create: (_) => UploadHistoryProvider(service: MockUploadHistoryService())..load(),
|
||||||
),
|
),
|
||||||
@@ -91,6 +96,7 @@ void main() async {
|
|||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => MockPaymentProvider(),
|
create: (_) => MockPaymentProvider(),
|
||||||
),
|
),
|
||||||
|
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => OperationProvider(OperationService())..loadOperations(),
|
create: (_) => OperationProvider(OperationService())..loadOperations(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@@ -14,6 +16,7 @@ import 'package:pshared/provider/recipient/pmethods.dart';
|
|||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/address_book/form/view.dart';
|
import 'package:pweb/pages/address_book/form/view.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
import 'package:pweb/utils/error/snackbar.dart';
|
import 'package:pweb/utils/error/snackbar.dart';
|
||||||
import 'package:pweb/utils/payment/label.dart';
|
import 'package:pweb/utils/payment/label.dart';
|
||||||
import 'package:pweb/utils/snackbar.dart';
|
import 'package:pweb/utils/snackbar.dart';
|
||||||
@@ -106,11 +109,11 @@ class _AdressBookRecipientFormState extends State<AdressBookRecipientForm> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AmplitudeService.recipientAddCompleted(
|
unawaited(PosthogService.recipientAddCompleted(
|
||||||
// _type,
|
_type,
|
||||||
// _status,
|
_status,
|
||||||
// _methods.keys.toSet(),
|
_methods.keys.toSet(),
|
||||||
// );
|
));
|
||||||
final recipient = await executeActionWithNotification(
|
final recipient = await executeActionWithNotification(
|
||||||
context: context,
|
context: context,
|
||||||
action: _doSave,
|
action: _doSave,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
@@ -10,26 +11,50 @@ import 'package:pweb/widgets/error/snackbar.dart';
|
|||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class AccountLoader extends StatelessWidget {
|
class AccountLoader extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const AccountLoader({super.key, required this.child});
|
const AccountLoader({super.key, required this.child});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Consumer<AccountProvider>(builder: (context, provider, _) {
|
State<AccountLoader> createState() => _AccountLoaderState();
|
||||||
if (provider.isLoading) return const Center(child: CircularProgressIndicator());
|
}
|
||||||
|
|
||||||
|
class _AccountLoaderState extends State<AccountLoader> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final provider = Provider.of<AccountProvider>(context, listen: false);
|
||||||
|
if (provider.account == null) {
|
||||||
|
provider.restoreIfPossible().catchError((error, stack) {
|
||||||
|
Logger('Account restore failed: $error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<AccountProvider>(builder: (context, provider, _) {
|
||||||
|
if (provider.account != null) {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
|
||||||
if (provider.error != null) {
|
if (provider.error != null) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
postNotifyUserOfErrorX(
|
postNotifyUserOfErrorX(
|
||||||
context: context,
|
context: context,
|
||||||
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
||||||
exception: provider.error!,
|
exception: provider.error!,
|
||||||
);
|
);
|
||||||
navigateAndReplace(context, Pages.login);
|
navigateAndReplace(context, Pages.login);
|
||||||
}
|
});
|
||||||
if (provider.account == null) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => navigateAndReplace(context, Pages.login));
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
return child;
|
|
||||||
|
if (provider.isLoading) return const Center(child: CircularProgressIndicator());
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => navigateAndReplace(context, Pages.login));
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,20 +21,29 @@ class PermissionsLoader extends StatelessWidget {
|
|||||||
if (provider.isLoading) {
|
if (provider.isLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider.error != null) {
|
if (provider.error != null) {
|
||||||
|
if (provider.hasUnhandledError) {
|
||||||
|
provider.markErrorHandled();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
postNotifyUserOfErrorX(
|
postNotifyUserOfErrorX(
|
||||||
context: context,
|
context: context,
|
||||||
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
||||||
exception: provider.error!,
|
exception: provider.error!,
|
||||||
);
|
);
|
||||||
navigateAndReplace(context, Pages.login);
|
navigateAndReplace(context, Pages.login);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
if (provider.error == null && !provider.isReady && accountProvider.account != null) {
|
if (provider.error == null && !provider.isReady && accountProvider.account != null) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
provider.load();
|
provider.load();
|
||||||
});
|
});
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -14,6 +16,7 @@ import 'package:pweb/widgets/password/password.dart';
|
|||||||
import 'package:pweb/widgets/username.dart';
|
import 'package:pweb/widgets/username.dart';
|
||||||
import 'package:pweb/widgets/vspacer.dart';
|
import 'package:pweb/widgets/vspacer.dart';
|
||||||
import 'package:pweb/widgets/error/snackbar.dart';
|
import 'package:pweb/widgets/error/snackbar.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
@@ -43,6 +46,7 @@ class _LoginFormState extends State<LoginForm> {
|
|||||||
password: _passwordController.text,
|
password: _passwordController.text,
|
||||||
locale: context.read<LocaleProvider>().locale.languageCode,
|
locale: context.read<LocaleProvider>().locale.languageCode,
|
||||||
);
|
);
|
||||||
|
unawaited(PosthogService.login(pending: outcome.isPending));
|
||||||
if (outcome.isPending) {
|
if (outcome.isPending) {
|
||||||
// TODO: fix context usage
|
// TODO: fix context usage
|
||||||
navigateAndReplace(context, Pages.sfactor);
|
navigateAndReplace(context, Pages.sfactor);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:pweb/providers/payment_flow.dart';
|
|||||||
import 'package:pweb/pages/payment_methods/payment_page/body.dart';
|
import 'package:pweb/pages/payment_methods/payment_page/body.dart';
|
||||||
import 'package:pweb/providers/wallets.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentPage extends StatefulWidget {
|
class PaymentPage extends StatefulWidget {
|
||||||
@@ -109,7 +110,7 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
|
|
||||||
void _handleSendPayment() {
|
void _handleSendPayment() {
|
||||||
// TODO: Handle Payment logic
|
// TODO: Handle Payment logic
|
||||||
// AmplitudeService.paymentInitiated();
|
PosthogService.paymentInitiated(method: _flowProvider.selectedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import 'package:pshared/provider/locale.dart';
|
import 'package:pshared/provider/locale.dart';
|
||||||
|
|
||||||
// import 'package:pweb/services/amplitude.dart';
|
import 'package:pweb/services/posthog.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ class LocalePicker extends StatelessWidget {
|
|||||||
onChanged: (locale) {
|
onChanged: (locale) {
|
||||||
if (locale != null) {
|
if (locale != null) {
|
||||||
localeProvider.setLocale(locale);
|
localeProvider.setLocale(locale);
|
||||||
// AmplitudeService.localeChanged(locale);
|
PosthogService.localeChanged(locale);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
|
|||||||
136
frontend/pweb/lib/services/posthog.dart
Normal file
136
frontend/pweb/lib/services/posthog.dart
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
import 'package:posthog_flutter/posthog_flutter.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/config/constants.dart';
|
||||||
|
import 'package:pshared/models/account/account.dart';
|
||||||
|
import 'package:pshared/models/payment/type.dart';
|
||||||
|
import 'package:pshared/models/recipient/status.dart';
|
||||||
|
import 'package:pshared/models/recipient/type.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PosthogService {
|
||||||
|
static final _logger = Logger('service.posthog');
|
||||||
|
static String? _identifiedUserId;
|
||||||
|
static bool _initialized = false;
|
||||||
|
|
||||||
|
static bool get isEnabled => _initialized;
|
||||||
|
|
||||||
|
static Future<void> initialize() async {
|
||||||
|
final apiKey = Constants.posthogApiKey;
|
||||||
|
if (apiKey.isEmpty) {
|
||||||
|
_logger.warning('PostHog API key is not configured, analytics disabled.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final config = PostHogConfig(apiKey)
|
||||||
|
..host = Constants.posthogHost
|
||||||
|
..captureApplicationLifecycleEvents = true;
|
||||||
|
await Posthog().setup(config);
|
||||||
|
await Posthog().register('client_id', Constants.clientId);
|
||||||
|
_initialized = true;
|
||||||
|
_logger.info('PostHog initialized with host ${Constants.posthogHost}');
|
||||||
|
} catch (e, st) {
|
||||||
|
_initialized = false;
|
||||||
|
_logger.warning('Failed to initialize PostHog: $e', e, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> identify(Account account) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
if (_identifiedUserId == account.id) return;
|
||||||
|
|
||||||
|
await Posthog().identify(
|
||||||
|
userId: account.id,
|
||||||
|
userProperties: {
|
||||||
|
'email': account.login,
|
||||||
|
'name': account.name,
|
||||||
|
'locale': account.locale,
|
||||||
|
'created_at': account.createdAt.toIso8601String(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_identifiedUserId = account.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> reset() async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
_identifiedUserId = null;
|
||||||
|
await Posthog().reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> login({required bool pending}) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
await _capture(
|
||||||
|
'login',
|
||||||
|
properties: {
|
||||||
|
'result': pending ? 'pending' : 'success',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> pageOpened(PayoutDestination page, {String? path, String? uiSource}) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
return _capture(
|
||||||
|
'pageOpened',
|
||||||
|
properties: {
|
||||||
|
'page': page.name,
|
||||||
|
if (path != null) 'path': path,
|
||||||
|
if (uiSource != null) 'uiSource': uiSource,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> localeChanged(Locale locale) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
return _capture(
|
||||||
|
'localeChanged',
|
||||||
|
properties: {'locale': locale.toLanguageTag()},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> recipientAddCompleted(
|
||||||
|
RecipientType type,
|
||||||
|
RecipientStatus status,
|
||||||
|
Set<PaymentType> methods,
|
||||||
|
) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
return _capture(
|
||||||
|
'recipientAddCompleted',
|
||||||
|
properties: {
|
||||||
|
'methods': methods.map((m) => m.name).toList(),
|
||||||
|
'type': type.name,
|
||||||
|
'status': status.name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> paymentInitiated({PaymentType? method}) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
return _capture(
|
||||||
|
'paymentInitiated',
|
||||||
|
properties: {
|
||||||
|
if (method != null) 'method': method.name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> _capture(
|
||||||
|
String eventName, {
|
||||||
|
Map<String, Object?>? properties,
|
||||||
|
}) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
final filtered = <String, Object>{};
|
||||||
|
if (properties != null) {
|
||||||
|
for (final entry in properties.entries) {
|
||||||
|
final value = entry.value;
|
||||||
|
if (value != null) filtered[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Posthog().capture(eventName: eventName, properties: filtered.isEmpty ? null : filtered);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,10 +6,12 @@ import 'package:pshared/provider/account.dart';
|
|||||||
import 'package:pshared/provider/permissions.dart';
|
import 'package:pshared/provider/permissions.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
import 'package:pweb/app/router/pages.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
|
|
||||||
|
|
||||||
void logoutUtil(BuildContext context) {
|
void logoutUtil(BuildContext context) {
|
||||||
context.read<AccountProvider>().logout();
|
context.read<AccountProvider>().logout();
|
||||||
context.read<PermissionsProvider>().reset();
|
context.read<PermissionsProvider>().reset();
|
||||||
|
PosthogService.reset();
|
||||||
navigateAndReplace(context, Pages.login);
|
navigateAndReplace(context, Pages.login);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
// import 'package:pweb/services/amplitude.dart';
|
import 'package:pweb/services/posthog.dart';
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ class SideMenuColumn extends StatelessWidget {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onSelected(item);
|
onSelected(item);
|
||||||
// AmplitudeService.pageOpened(item, uiSource: 'sidebar');
|
PosthogService.pageOpened(item, uiSource: 'sidebar');
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
hoverColor: theme.colorScheme.primaryContainer,
|
hoverColor: theme.colorScheme.primaryContainer,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import amplitude_flutter
|
|||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
import flutter_timezone
|
import flutter_timezone
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
|
import posthog_flutter
|
||||||
import share_plus
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
@@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
|
FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
PosthogFlutterPlugin.register(with: registry.registrar(forPlugin: "PosthogFlutterPlugin"))
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
pshared:
|
pshared:
|
||||||
path: ../pshared
|
path: ../pshared
|
||||||
|
posthog_flutter: ^5.9.0
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
|||||||
Reference in New Issue
Block a user