Merge pull request 'payment recipient data' (#199) from mntx-198 into main
Some checks failed
ci/woodpecker/push/chain_gateway Pipeline is pending
ci/woodpecker/push/fx_ingestor Pipeline is pending
ci/woodpecker/push/notification Pipeline is pending
ci/woodpecker/push/payments_orchestrator Pipeline is pending
ci/woodpecker/push/db Pipeline is pending
ci/woodpecker/push/frontend Pipeline is pending
ci/woodpecker/push/fx_oracle Pipeline is pending
ci/woodpecker/push/ledger Pipeline is pending
ci/woodpecker/push/mntx_gateway Pipeline is pending
ci/woodpecker/push/nats Pipeline is pending
ci/woodpecker/push/billing_fees Pipeline failed
ci/woodpecker/push/bff Pipeline failed
Some checks failed
ci/woodpecker/push/chain_gateway Pipeline is pending
ci/woodpecker/push/fx_ingestor Pipeline is pending
ci/woodpecker/push/notification Pipeline is pending
ci/woodpecker/push/payments_orchestrator Pipeline is pending
ci/woodpecker/push/db Pipeline is pending
ci/woodpecker/push/frontend Pipeline is pending
ci/woodpecker/push/fx_oracle Pipeline is pending
ci/woodpecker/push/ledger Pipeline is pending
ci/woodpecker/push/mntx_gateway Pipeline is pending
ci/woodpecker/push/nats Pipeline is pending
ci/woodpecker/push/billing_fees Pipeline failed
ci/woodpecker/push/bff Pipeline failed
Reviewed-on: #199
This commit was merged in pull request #199.
This commit is contained in:
@@ -313,6 +313,47 @@ func (s *Service) submitCardPayout(ctx context.Context, payment *model.Payment)
|
|||||||
currency := strings.TrimSpace(amount.GetCurrency())
|
currency := strings.TrimSpace(amount.GetCurrency())
|
||||||
holder := strings.TrimSpace(card.Cardholder)
|
holder := strings.TrimSpace(card.Cardholder)
|
||||||
meta := cloneMetadata(payment.Metadata)
|
meta := cloneMetadata(payment.Metadata)
|
||||||
|
customer := intent.Customer
|
||||||
|
customerID := ""
|
||||||
|
customerFirstName := ""
|
||||||
|
customerMiddleName := ""
|
||||||
|
customerLastName := ""
|
||||||
|
customerIP := ""
|
||||||
|
customerZip := ""
|
||||||
|
customerCountry := ""
|
||||||
|
customerState := ""
|
||||||
|
customerCity := ""
|
||||||
|
customerAddress := ""
|
||||||
|
if customer != nil {
|
||||||
|
customerID = strings.TrimSpace(customer.ID)
|
||||||
|
customerFirstName = strings.TrimSpace(customer.FirstName)
|
||||||
|
customerMiddleName = strings.TrimSpace(customer.MiddleName)
|
||||||
|
customerLastName = strings.TrimSpace(customer.LastName)
|
||||||
|
customerIP = strings.TrimSpace(customer.IP)
|
||||||
|
customerZip = strings.TrimSpace(customer.Zip)
|
||||||
|
customerCountry = strings.TrimSpace(customer.Country)
|
||||||
|
customerState = strings.TrimSpace(customer.State)
|
||||||
|
customerCity = strings.TrimSpace(customer.City)
|
||||||
|
customerAddress = strings.TrimSpace(customer.Address)
|
||||||
|
}
|
||||||
|
if customerFirstName == "" {
|
||||||
|
customerFirstName = strings.TrimSpace(card.Cardholder)
|
||||||
|
}
|
||||||
|
if customerLastName == "" {
|
||||||
|
customerLastName = strings.TrimSpace(card.CardholderSurname)
|
||||||
|
}
|
||||||
|
if customerID == "" {
|
||||||
|
return merrors.InvalidArgument("card payout: customer id is required")
|
||||||
|
}
|
||||||
|
if customerFirstName == "" {
|
||||||
|
return merrors.InvalidArgument("card payout: customer first name is required")
|
||||||
|
}
|
||||||
|
if customerLastName == "" {
|
||||||
|
return merrors.InvalidArgument("card payout: customer last name is required")
|
||||||
|
}
|
||||||
|
if customerIP == "" {
|
||||||
|
return merrors.InvalidArgument("card payout: customer ip is required")
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
state *mntxv1.CardPayoutState
|
state *mntxv1.CardPayoutState
|
||||||
@@ -320,13 +361,23 @@ func (s *Service) submitCardPayout(ctx context.Context, payment *model.Payment)
|
|||||||
|
|
||||||
if token := strings.TrimSpace(card.Token); token != "" {
|
if token := strings.TrimSpace(card.Token); token != "" {
|
||||||
req := &mntxv1.CardTokenPayoutRequest{
|
req := &mntxv1.CardTokenPayoutRequest{
|
||||||
PayoutId: payoutID,
|
PayoutId: payoutID,
|
||||||
AmountMinor: minor,
|
CustomerId: customerID,
|
||||||
Currency: currency,
|
CustomerFirstName: customerFirstName,
|
||||||
CardToken: token,
|
CustomerMiddleName: customerMiddleName,
|
||||||
CardHolder: holder,
|
CustomerLastName: customerLastName,
|
||||||
MaskedPan: strings.TrimSpace(card.MaskedPan),
|
CustomerIp: customerIP,
|
||||||
Metadata: meta,
|
CustomerZip: customerZip,
|
||||||
|
CustomerCountry: customerCountry,
|
||||||
|
CustomerState: customerState,
|
||||||
|
CustomerCity: customerCity,
|
||||||
|
CustomerAddress: customerAddress,
|
||||||
|
AmountMinor: minor,
|
||||||
|
Currency: currency,
|
||||||
|
CardToken: token,
|
||||||
|
CardHolder: holder,
|
||||||
|
MaskedPan: strings.TrimSpace(card.MaskedPan),
|
||||||
|
Metadata: meta,
|
||||||
}
|
}
|
||||||
resp, err := s.deps.mntx.client.CreateCardTokenPayout(ctx, req)
|
resp, err := s.deps.mntx.client.CreateCardTokenPayout(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -336,14 +387,24 @@ func (s *Service) submitCardPayout(ctx context.Context, payment *model.Payment)
|
|||||||
state = resp.GetPayout()
|
state = resp.GetPayout()
|
||||||
} else if pan := strings.TrimSpace(card.Pan); pan != "" {
|
} else if pan := strings.TrimSpace(card.Pan); pan != "" {
|
||||||
req := &mntxv1.CardPayoutRequest{
|
req := &mntxv1.CardPayoutRequest{
|
||||||
PayoutId: payoutID,
|
PayoutId: payoutID,
|
||||||
AmountMinor: minor,
|
CustomerId: customerID,
|
||||||
Currency: currency,
|
CustomerFirstName: customerFirstName,
|
||||||
CardPan: pan,
|
CustomerMiddleName: customerMiddleName,
|
||||||
CardExpYear: card.ExpYear,
|
CustomerLastName: customerLastName,
|
||||||
CardExpMonth: card.ExpMonth,
|
CustomerIp: customerIP,
|
||||||
CardHolder: holder,
|
CustomerZip: customerZip,
|
||||||
Metadata: meta,
|
CustomerCountry: customerCountry,
|
||||||
|
CustomerState: customerState,
|
||||||
|
CustomerCity: customerCity,
|
||||||
|
CustomerAddress: customerAddress,
|
||||||
|
AmountMinor: minor,
|
||||||
|
Currency: currency,
|
||||||
|
CardPan: pan,
|
||||||
|
CardExpYear: card.ExpYear,
|
||||||
|
CardExpMonth: card.ExpMonth,
|
||||||
|
CardHolder: holder,
|
||||||
|
Metadata: meta,
|
||||||
}
|
}
|
||||||
resp, err := s.deps.mntx.client.CreateCardPayout(ctx, req)
|
resp, err := s.deps.mntx.client.CreateCardPayout(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -266,6 +266,12 @@ func TestSubmitCardPayout_UsesSettlementAmountAndTransfersFee(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Amount: &moneyv1.Money{Currency: "USDT", Amount: "5"},
|
Amount: &moneyv1.Money{Currency: "USDT", Amount: "5"},
|
||||||
|
Customer: &model.Customer{
|
||||||
|
ID: "recipient-1",
|
||||||
|
FirstName: "Stephan",
|
||||||
|
LastName: "Tester",
|
||||||
|
IP: "198.51.100.10",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
LastQuote: &model.PaymentQuoteSnapshot{
|
LastQuote: &model.PaymentQuoteSnapshot{
|
||||||
ExpectedSettlementAmount: &moneyv1.Money{Currency: "RUB", Amount: "392.30"},
|
ExpectedSettlementAmount: &moneyv1.Money{Currency: "RUB", Amount: "392.30"},
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ func intentFromProto(src *orchestratorv1.PaymentIntent) model.PaymentIntent {
|
|||||||
FeePolicy: src.GetFeePolicy(),
|
FeePolicy: src.GetFeePolicy(),
|
||||||
SettlementMode: src.GetSettlementMode(),
|
SettlementMode: src.GetSettlementMode(),
|
||||||
Attributes: cloneMetadata(src.GetAttributes()),
|
Attributes: cloneMetadata(src.GetAttributes()),
|
||||||
|
Customer: customerFromProto(src.GetCustomer()),
|
||||||
}
|
}
|
||||||
if src.GetFx() != nil {
|
if src.GetFx() != nil {
|
||||||
intent.FX = fxIntentFromProto(src.GetFx())
|
intent.FX = fxIntentFromProto(src.GetFx())
|
||||||
@@ -69,13 +70,14 @@ func endpointFromProto(src *orchestratorv1.PaymentEndpoint) model.PaymentEndpoin
|
|||||||
if card := src.GetCard(); card != nil {
|
if card := src.GetCard(); card != nil {
|
||||||
result.Type = model.EndpointTypeCard
|
result.Type = model.EndpointTypeCard
|
||||||
result.Card = &model.CardEndpoint{
|
result.Card = &model.CardEndpoint{
|
||||||
Pan: strings.TrimSpace(card.GetPan()),
|
Pan: strings.TrimSpace(card.GetPan()),
|
||||||
Token: strings.TrimSpace(card.GetToken()),
|
Token: strings.TrimSpace(card.GetToken()),
|
||||||
Cardholder: strings.TrimSpace(card.GetCardholderName()),
|
Cardholder: strings.TrimSpace(card.GetCardholderName()),
|
||||||
ExpMonth: card.GetExpMonth(),
|
CardholderSurname: strings.TrimSpace(card.GetCardholderSurname()),
|
||||||
ExpYear: card.GetExpYear(),
|
ExpMonth: card.GetExpMonth(),
|
||||||
Country: strings.TrimSpace(card.GetCountry()),
|
ExpYear: card.GetExpYear(),
|
||||||
MaskedPan: strings.TrimSpace(card.GetMaskedPan()),
|
Country: strings.TrimSpace(card.GetCountry()),
|
||||||
|
MaskedPan: strings.TrimSpace(card.GetMaskedPan()),
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -161,6 +163,7 @@ func protoIntentFromModel(src model.PaymentIntent) *orchestratorv1.PaymentIntent
|
|||||||
FeePolicy: src.FeePolicy,
|
FeePolicy: src.FeePolicy,
|
||||||
SettlementMode: src.SettlementMode,
|
SettlementMode: src.SettlementMode,
|
||||||
Attributes: cloneMetadata(src.Attributes),
|
Attributes: cloneMetadata(src.Attributes),
|
||||||
|
Customer: protoCustomerFromModel(src.Customer),
|
||||||
}
|
}
|
||||||
if src.FX != nil {
|
if src.FX != nil {
|
||||||
intent.Fx = protoFXIntentFromModel(src.FX)
|
intent.Fx = protoFXIntentFromModel(src.FX)
|
||||||
@@ -168,6 +171,42 @@ func protoIntentFromModel(src model.PaymentIntent) *orchestratorv1.PaymentIntent
|
|||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func customerFromProto(src *orchestratorv1.Customer) *model.Customer {
|
||||||
|
if src == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &model.Customer{
|
||||||
|
ID: strings.TrimSpace(src.GetId()),
|
||||||
|
FirstName: strings.TrimSpace(src.GetFirstName()),
|
||||||
|
MiddleName: strings.TrimSpace(src.GetMiddleName()),
|
||||||
|
LastName: strings.TrimSpace(src.GetLastName()),
|
||||||
|
IP: strings.TrimSpace(src.GetIp()),
|
||||||
|
Zip: strings.TrimSpace(src.GetZip()),
|
||||||
|
Country: strings.TrimSpace(src.GetCountry()),
|
||||||
|
State: strings.TrimSpace(src.GetState()),
|
||||||
|
City: strings.TrimSpace(src.GetCity()),
|
||||||
|
Address: strings.TrimSpace(src.GetAddress()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func protoCustomerFromModel(src *model.Customer) *orchestratorv1.Customer {
|
||||||
|
if src == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &orchestratorv1.Customer{
|
||||||
|
Id: strings.TrimSpace(src.ID),
|
||||||
|
FirstName: strings.TrimSpace(src.FirstName),
|
||||||
|
MiddleName: strings.TrimSpace(src.MiddleName),
|
||||||
|
LastName: strings.TrimSpace(src.LastName),
|
||||||
|
Ip: strings.TrimSpace(src.IP),
|
||||||
|
Zip: strings.TrimSpace(src.Zip),
|
||||||
|
Country: strings.TrimSpace(src.Country),
|
||||||
|
State: strings.TrimSpace(src.State),
|
||||||
|
City: strings.TrimSpace(src.City),
|
||||||
|
Address: strings.TrimSpace(src.Address),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func protoEndpointFromModel(src model.PaymentEndpoint) *orchestratorv1.PaymentEndpoint {
|
func protoEndpointFromModel(src model.PaymentEndpoint) *orchestratorv1.PaymentEndpoint {
|
||||||
endpoint := &orchestratorv1.PaymentEndpoint{
|
endpoint := &orchestratorv1.PaymentEndpoint{
|
||||||
Metadata: cloneMetadata(src.Metadata),
|
Metadata: cloneMetadata(src.Metadata),
|
||||||
@@ -204,11 +243,12 @@ func protoEndpointFromModel(src model.PaymentEndpoint) *orchestratorv1.PaymentEn
|
|||||||
case model.EndpointTypeCard:
|
case model.EndpointTypeCard:
|
||||||
if src.Card != nil {
|
if src.Card != nil {
|
||||||
card := &orchestratorv1.CardEndpoint{
|
card := &orchestratorv1.CardEndpoint{
|
||||||
CardholderName: src.Card.Cardholder,
|
CardholderName: src.Card.Cardholder,
|
||||||
ExpMonth: src.Card.ExpMonth,
|
CardholderSurname: src.Card.CardholderSurname,
|
||||||
ExpYear: src.Card.ExpYear,
|
ExpMonth: src.Card.ExpMonth,
|
||||||
Country: src.Card.Country,
|
ExpYear: src.Card.ExpYear,
|
||||||
MaskedPan: src.Card.MaskedPan,
|
Country: src.Card.Country,
|
||||||
|
MaskedPan: src.Card.MaskedPan,
|
||||||
}
|
}
|
||||||
if pan := strings.TrimSpace(src.Card.Pan); pan != "" {
|
if pan := strings.TrimSpace(src.Card.Pan); pan != "" {
|
||||||
card.Card = &orchestratorv1.CardEndpoint_Pan{Pan: pan}
|
card.Card = &orchestratorv1.CardEndpoint_Pan{Pan: pan}
|
||||||
|
|||||||
@@ -11,12 +11,13 @@ func TestEndpointFromProtoCard(t *testing.T) {
|
|||||||
protoEndpoint := &orchestratorv1.PaymentEndpoint{
|
protoEndpoint := &orchestratorv1.PaymentEndpoint{
|
||||||
Endpoint: &orchestratorv1.PaymentEndpoint_Card{
|
Endpoint: &orchestratorv1.PaymentEndpoint_Card{
|
||||||
Card: &orchestratorv1.CardEndpoint{
|
Card: &orchestratorv1.CardEndpoint{
|
||||||
Card: &orchestratorv1.CardEndpoint_Pan{Pan: " 411111 "},
|
Card: &orchestratorv1.CardEndpoint_Pan{Pan: " 411111 "},
|
||||||
CardholderName: " Jane Doe ",
|
CardholderName: " Jane ",
|
||||||
ExpMonth: 12,
|
CardholderSurname: " Doe ",
|
||||||
ExpYear: 2030,
|
ExpMonth: 12,
|
||||||
Country: " US ",
|
ExpYear: 2030,
|
||||||
MaskedPan: " ****1111 ",
|
Country: " US ",
|
||||||
|
MaskedPan: " ****1111 ",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Metadata: map[string]string{"k": "v"},
|
Metadata: map[string]string{"k": "v"},
|
||||||
@@ -29,7 +30,7 @@ func TestEndpointFromProtoCard(t *testing.T) {
|
|||||||
if modelEndpoint.Card == nil {
|
if modelEndpoint.Card == nil {
|
||||||
t.Fatalf("card payload missing")
|
t.Fatalf("card payload missing")
|
||||||
}
|
}
|
||||||
if modelEndpoint.Card.Pan != "411111" || modelEndpoint.Card.Cardholder != "Jane Doe" || modelEndpoint.Card.Country != "US" || modelEndpoint.Card.MaskedPan != "****1111" {
|
if modelEndpoint.Card.Pan != "411111" || modelEndpoint.Card.Cardholder != "Jane" || modelEndpoint.Card.CardholderSurname != "Doe" || modelEndpoint.Card.Country != "US" || modelEndpoint.Card.MaskedPan != "****1111" {
|
||||||
t.Fatalf("card payload not trimmed as expected: %#v", modelEndpoint.Card)
|
t.Fatalf("card payload not trimmed as expected: %#v", modelEndpoint.Card)
|
||||||
}
|
}
|
||||||
if modelEndpoint.Metadata["k"] != "v" {
|
if modelEndpoint.Metadata["k"] != "v" {
|
||||||
@@ -41,12 +42,13 @@ func TestProtoEndpointFromModelCard(t *testing.T) {
|
|||||||
modelEndpoint := model.PaymentEndpoint{
|
modelEndpoint := model.PaymentEndpoint{
|
||||||
Type: model.EndpointTypeCard,
|
Type: model.EndpointTypeCard,
|
||||||
Card: &model.CardEndpoint{
|
Card: &model.CardEndpoint{
|
||||||
Token: "tok_123",
|
Token: "tok_123",
|
||||||
Cardholder: "Jane",
|
Cardholder: "Jane",
|
||||||
ExpMonth: 1,
|
CardholderSurname: "Doe",
|
||||||
ExpYear: 2028,
|
ExpMonth: 1,
|
||||||
Country: "GB",
|
ExpYear: 2028,
|
||||||
MaskedPan: "****1234",
|
Country: "GB",
|
||||||
|
MaskedPan: "****1234",
|
||||||
},
|
},
|
||||||
Metadata: map[string]string{"k": "v"},
|
Metadata: map[string]string{"k": "v"},
|
||||||
}
|
}
|
||||||
@@ -60,7 +62,7 @@ func TestProtoEndpointFromModelCard(t *testing.T) {
|
|||||||
if !ok || token.Token != "tok_123" {
|
if !ok || token.Token != "tok_123" {
|
||||||
t.Fatalf("expected token payload, got %T %#v", card.Card, card.Card)
|
t.Fatalf("expected token payload, got %T %#v", card.Card, card.Card)
|
||||||
}
|
}
|
||||||
if card.GetCardholderName() != "Jane" || card.GetCountry() != "GB" || card.GetMaskedPan() != "****1234" {
|
if card.GetCardholderName() != "Jane" || card.GetCardholderSurname() != "Doe" || card.GetCountry() != "GB" || card.GetMaskedPan() != "****1234" {
|
||||||
t.Fatalf("card details mismatch: %#v", card)
|
t.Fatalf("card details mismatch: %#v", card)
|
||||||
}
|
}
|
||||||
if protoEndpoint.GetMetadata()["k"] != "v" {
|
if protoEndpoint.GetMetadata()["k"] != "v" {
|
||||||
|
|||||||
@@ -82,13 +82,14 @@ type ExternalChainEndpoint struct {
|
|||||||
|
|
||||||
// CardEndpoint describes a card payout destination.
|
// CardEndpoint describes a card payout destination.
|
||||||
type CardEndpoint struct {
|
type CardEndpoint struct {
|
||||||
Pan string `bson:"pan,omitempty" json:"pan,omitempty"`
|
Pan string `bson:"pan,omitempty" json:"pan,omitempty"`
|
||||||
Token string `bson:"token,omitempty" json:"token,omitempty"`
|
Token string `bson:"token,omitempty" json:"token,omitempty"`
|
||||||
Cardholder string `bson:"cardholder,omitempty" json:"cardholder,omitempty"`
|
Cardholder string `bson:"cardholder,omitempty" json:"cardholder,omitempty"`
|
||||||
ExpMonth uint32 `bson:"expMonth,omitempty" json:"expMonth,omitempty"`
|
CardholderSurname string `bson:"cardholderSurname,omitempty" json:"cardholderSurname,omitempty"`
|
||||||
ExpYear uint32 `bson:"expYear,omitempty" json:"expYear,omitempty"`
|
ExpMonth uint32 `bson:"expMonth,omitempty" json:"expMonth,omitempty"`
|
||||||
Country string `bson:"country,omitempty" json:"country,omitempty"`
|
ExpYear uint32 `bson:"expYear,omitempty" json:"expYear,omitempty"`
|
||||||
MaskedPan string `bson:"maskedPan,omitempty" json:"maskedPan,omitempty"`
|
Country string `bson:"country,omitempty" json:"country,omitempty"`
|
||||||
|
MaskedPan string `bson:"maskedPan,omitempty" json:"maskedPan,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CardPayout stores gateway payout tracking info.
|
// CardPayout stores gateway payout tracking info.
|
||||||
@@ -134,6 +135,21 @@ type PaymentIntent struct {
|
|||||||
FeePolicy *feesv1.PolicyOverrides `bson:"feePolicy,omitempty" json:"feePolicy,omitempty"`
|
FeePolicy *feesv1.PolicyOverrides `bson:"feePolicy,omitempty" json:"feePolicy,omitempty"`
|
||||||
SettlementMode orchestratorv1.SettlementMode `bson:"settlementMode,omitempty" json:"settlementMode,omitempty"`
|
SettlementMode orchestratorv1.SettlementMode `bson:"settlementMode,omitempty" json:"settlementMode,omitempty"`
|
||||||
Attributes map[string]string `bson:"attributes,omitempty" json:"attributes,omitempty"`
|
Attributes map[string]string `bson:"attributes,omitempty" json:"attributes,omitempty"`
|
||||||
|
Customer *Customer `bson:"customer,omitempty" json:"customer,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Customer captures payer/recipient identity details for downstream processing.
|
||||||
|
type Customer struct {
|
||||||
|
ID string `bson:"id,omitempty" json:"id,omitempty"`
|
||||||
|
FirstName string `bson:"firstName,omitempty" json:"firstName,omitempty"`
|
||||||
|
MiddleName string `bson:"middleName,omitempty" json:"middleName,omitempty"`
|
||||||
|
LastName string `bson:"lastName,omitempty" json:"lastName,omitempty"`
|
||||||
|
IP string `bson:"ip,omitempty" json:"ip,omitempty"`
|
||||||
|
Zip string `bson:"zip,omitempty" json:"zip,omitempty"`
|
||||||
|
Country string `bson:"country,omitempty" json:"country,omitempty"`
|
||||||
|
State string `bson:"state,omitempty" json:"state,omitempty"`
|
||||||
|
City string `bson:"city,omitempty" json:"city,omitempty"`
|
||||||
|
Address string `bson:"address,omitempty" json:"address,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PaymentQuoteSnapshot stores the latest quote info.
|
// PaymentQuoteSnapshot stores the latest quote info.
|
||||||
@@ -172,8 +188,8 @@ type ExecutionStep struct {
|
|||||||
|
|
||||||
// ExecutionPlan captures the ordered list of steps to execute a payment.
|
// ExecutionPlan captures the ordered list of steps to execute a payment.
|
||||||
type ExecutionPlan struct {
|
type ExecutionPlan struct {
|
||||||
Steps []*ExecutionStep `bson:"steps,omitempty" json:"steps,omitempty"`
|
Steps []*ExecutionStep `bson:"steps,omitempty" json:"steps,omitempty"`
|
||||||
TotalNetworkFee *moneyv1.Money `bson:"totalNetworkFee,omitempty" json:"totalNetworkFee,omitempty"`
|
TotalNetworkFee *moneyv1.Money `bson:"totalNetworkFee,omitempty" json:"totalNetworkFee,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Payment persists orchestrated payment lifecycle.
|
// Payment persists orchestrated payment lifecycle.
|
||||||
@@ -231,6 +247,18 @@ func (p *Payment) Normalize() {
|
|||||||
p.Intent.Attributes[k] = strings.TrimSpace(v)
|
p.Intent.Attributes[k] = strings.TrimSpace(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if p.Intent.Customer != nil {
|
||||||
|
p.Intent.Customer.ID = strings.TrimSpace(p.Intent.Customer.ID)
|
||||||
|
p.Intent.Customer.FirstName = strings.TrimSpace(p.Intent.Customer.FirstName)
|
||||||
|
p.Intent.Customer.MiddleName = strings.TrimSpace(p.Intent.Customer.MiddleName)
|
||||||
|
p.Intent.Customer.LastName = strings.TrimSpace(p.Intent.Customer.LastName)
|
||||||
|
p.Intent.Customer.IP = strings.TrimSpace(p.Intent.Customer.IP)
|
||||||
|
p.Intent.Customer.Zip = strings.TrimSpace(p.Intent.Customer.Zip)
|
||||||
|
p.Intent.Customer.Country = strings.TrimSpace(p.Intent.Customer.Country)
|
||||||
|
p.Intent.Customer.State = strings.TrimSpace(p.Intent.Customer.State)
|
||||||
|
p.Intent.Customer.City = strings.TrimSpace(p.Intent.Customer.City)
|
||||||
|
p.Intent.Customer.Address = strings.TrimSpace(p.Intent.Customer.Address)
|
||||||
|
}
|
||||||
if p.Execution != nil {
|
if p.Execution != nil {
|
||||||
p.Execution.DebitEntryRef = strings.TrimSpace(p.Execution.DebitEntryRef)
|
p.Execution.DebitEntryRef = strings.TrimSpace(p.Execution.DebitEntryRef)
|
||||||
p.Execution.CreditEntryRef = strings.TrimSpace(p.Execution.CreditEntryRef)
|
p.Execution.CreditEntryRef = strings.TrimSpace(p.Execution.CreditEntryRef)
|
||||||
@@ -293,6 +321,7 @@ func normalizeEndpoint(ep *PaymentEndpoint) {
|
|||||||
ep.Card.Pan = strings.TrimSpace(ep.Card.Pan)
|
ep.Card.Pan = strings.TrimSpace(ep.Card.Pan)
|
||||||
ep.Card.Token = strings.TrimSpace(ep.Card.Token)
|
ep.Card.Token = strings.TrimSpace(ep.Card.Token)
|
||||||
ep.Card.Cardholder = strings.TrimSpace(ep.Card.Cardholder)
|
ep.Card.Cardholder = strings.TrimSpace(ep.Card.Cardholder)
|
||||||
|
ep.Card.CardholderSurname = strings.TrimSpace(ep.Card.CardholderSurname)
|
||||||
ep.Card.Country = strings.TrimSpace(ep.Card.Country)
|
ep.Card.Country = strings.TrimSpace(ep.Card.Country)
|
||||||
ep.Card.MaskedPan = strings.TrimSpace(ep.Card.MaskedPan)
|
ep.Card.MaskedPan = strings.TrimSpace(ep.Card.MaskedPan)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,20 @@ message PaymentIntent {
|
|||||||
fees.v1.PolicyOverrides fee_policy = 7;
|
fees.v1.PolicyOverrides fee_policy = 7;
|
||||||
map<string, string> attributes = 8;
|
map<string, string> attributes = 8;
|
||||||
SettlementMode settlement_mode = 9;
|
SettlementMode settlement_mode = 9;
|
||||||
|
Customer customer = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Customer {
|
||||||
|
string id = 1;
|
||||||
|
string first_name = 2;
|
||||||
|
string middle_name = 3;
|
||||||
|
string last_name = 4;
|
||||||
|
string ip = 5;
|
||||||
|
string zip = 6;
|
||||||
|
string country = 7;
|
||||||
|
string state = 8;
|
||||||
|
string city = 9;
|
||||||
|
string address = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PaymentQuote {
|
message PaymentQuote {
|
||||||
|
|||||||
15
api/server/interface/api/srequest/customer.go
Normal file
15
api/server/interface/api/srequest/customer.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package srequest
|
||||||
|
|
||||||
|
// Customer captures payer/recipient identity details for downstream processing.
|
||||||
|
type Customer struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
FirstName string `json:"first_name,omitempty"`
|
||||||
|
MiddleName string `json:"middle_name,omitempty"`
|
||||||
|
LastName string `json:"last_name,omitempty"`
|
||||||
|
IP string `json:"ip,omitempty"`
|
||||||
|
Zip string `json:"zip,omitempty"`
|
||||||
|
Country string `json:"country,omitempty"`
|
||||||
|
State string `json:"state,omitempty"`
|
||||||
|
City string `json:"city,omitempty"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ type PaymentIntent struct {
|
|||||||
FX *FXIntent `json:"fx,omitempty"`
|
FX *FXIntent `json:"fx,omitempty"`
|
||||||
SettlementMode SettlementMode `json:"settlement_mode,omitempty"`
|
SettlementMode SettlementMode `json:"settlement_mode,omitempty"`
|
||||||
Attributes map[string]string `json:"attributes,omitempty"`
|
Attributes map[string]string `json:"attributes,omitempty"`
|
||||||
|
Customer *Customer `json:"customer,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AssetResolverStub struct{}
|
type AssetResolverStub struct{}
|
||||||
|
|||||||
25
api/server/internal/server/paymentapiimp/customer.go
Normal file
25
api/server/internal/server/paymentapiimp/customer.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package paymentapiimp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tech/sendico/server/interface/api/srequest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func applyCustomerIP(intent *srequest.PaymentIntent, remoteAddr string) {
|
||||||
|
if intent == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ip := strings.TrimSpace(remoteAddr)
|
||||||
|
if ip == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if host, _, err := net.SplitHostPort(ip); err == nil && host != "" {
|
||||||
|
ip = host
|
||||||
|
}
|
||||||
|
if intent.Customer == nil {
|
||||||
|
intent.Customer = &srequest.Customer{}
|
||||||
|
}
|
||||||
|
intent.Customer.IP = strings.TrimSpace(ip)
|
||||||
|
}
|
||||||
@@ -50,6 +50,7 @@ func mapPaymentIntent(intent *srequest.PaymentIntent) (*orchestratorv1.PaymentIn
|
|||||||
Fx: fx,
|
Fx: fx,
|
||||||
SettlementMode: settlementMode,
|
SettlementMode: settlementMode,
|
||||||
Attributes: copyStringMap(intent.Attributes),
|
Attributes: copyStringMap(intent.Attributes),
|
||||||
|
Customer: mapCustomer(intent.Customer),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,6 +201,24 @@ func mapFXIntent(fx *srequest.FXIntent) (*orchestratorv1.FXIntent, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mapCustomer(customer *srequest.Customer) *orchestratorv1.Customer {
|
||||||
|
if customer == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &orchestratorv1.Customer{
|
||||||
|
Id: strings.TrimSpace(customer.ID),
|
||||||
|
FirstName: strings.TrimSpace(customer.FirstName),
|
||||||
|
MiddleName: strings.TrimSpace(customer.MiddleName),
|
||||||
|
LastName: strings.TrimSpace(customer.LastName),
|
||||||
|
Ip: strings.TrimSpace(customer.IP),
|
||||||
|
Zip: strings.TrimSpace(customer.Zip),
|
||||||
|
Country: strings.TrimSpace(customer.Country),
|
||||||
|
State: strings.TrimSpace(customer.State),
|
||||||
|
City: strings.TrimSpace(customer.City),
|
||||||
|
Address: strings.TrimSpace(customer.Address),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mapCurrencyPair(pair *srequest.CurrencyPair) *fxv1.CurrencyPair {
|
func mapCurrencyPair(pair *srequest.CurrencyPair) *fxv1.CurrencyPair {
|
||||||
if pair == nil {
|
if pair == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ func (a *PaymentAPI) initiatePayment(r *http.Request, account *model.Account, to
|
|||||||
|
|
||||||
var intent *orchestratorv1.PaymentIntent
|
var intent *orchestratorv1.PaymentIntent
|
||||||
if payload.Intent != nil {
|
if payload.Intent != nil {
|
||||||
|
applyCustomerIP(payload.Intent, r.RemoteAddr)
|
||||||
intent, err = mapPaymentIntent(payload.Intent)
|
intent, err = mapPaymentIntent(payload.Intent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.BadPayload(a.logger, a.Name(), err)
|
return response.BadPayload(a.logger, a.Name(), err)
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ func (a *PaymentAPI) quotePayment(r *http.Request, account *model.Account, token
|
|||||||
return response.Auto(a.logger, a.Name(), err)
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyCustomerIP(&payload.Intent, r.RemoteAddr)
|
||||||
intent, err := mapPaymentIntent(&payload.Intent)
|
intent, err := mapPaymentIntent(&payload.Intent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Debug("Failed to map payment intent", zap.Error(err), mutil.PLog(a.oph, r))
|
a.logger.Debug("Failed to map payment intent", zap.Error(err), mutil.PLog(a.oph, r))
|
||||||
@@ -97,6 +98,7 @@ func (a *PaymentAPI) quotePayments(r *http.Request, account *model.Account, toke
|
|||||||
|
|
||||||
intents := make([]*orchestratorv1.PaymentIntent, 0, len(payload.Intents))
|
intents := make([]*orchestratorv1.PaymentIntent, 0, len(payload.Intents))
|
||||||
for i := range payload.Intents {
|
for i := range payload.Intents {
|
||||||
|
applyCustomerIP(&payload.Intents[i], r.RemoteAddr)
|
||||||
intent, err := mapPaymentIntent(&payload.Intents[i])
|
intent, err := mapPaymentIntent(&payload.Intents[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Debug("Failed to map payment intent", zap.Error(err), mutil.PLog(a.oph, r))
|
a.logger.Debug("Failed to map payment intent", zap.Error(err), mutil.PLog(a.oph, r))
|
||||||
|
|||||||
41
frontend/pshared/lib/data/dto/payment/intent/customer.dart
Normal file
41
frontend/pshared/lib/data/dto/payment/intent/customer.dart
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'customer.g.dart';
|
||||||
|
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class CustomerDTO {
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
@JsonKey(name: 'first_name')
|
||||||
|
final String? firstName;
|
||||||
|
|
||||||
|
@JsonKey(name: 'middle_name')
|
||||||
|
final String? middleName;
|
||||||
|
|
||||||
|
@JsonKey(name: 'last_name')
|
||||||
|
final String? lastName;
|
||||||
|
|
||||||
|
final String? ip;
|
||||||
|
final String? zip;
|
||||||
|
final String? country;
|
||||||
|
final String? state;
|
||||||
|
final String? city;
|
||||||
|
final String? address;
|
||||||
|
|
||||||
|
const CustomerDTO({
|
||||||
|
required this.id,
|
||||||
|
this.firstName,
|
||||||
|
this.middleName,
|
||||||
|
this.lastName,
|
||||||
|
this.ip,
|
||||||
|
this.zip,
|
||||||
|
this.country,
|
||||||
|
this.state,
|
||||||
|
this.city,
|
||||||
|
this.address,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory CustomerDTO.fromJson(Map<String, dynamic> json) => _$CustomerDTOFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$CustomerDTOToJson(this);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
import 'package:pshared/data/dto/payment/endpoint.dart';
|
import 'package:pshared/data/dto/payment/endpoint.dart';
|
||||||
|
import 'package:pshared/data/dto/payment/intent/customer.dart';
|
||||||
import 'package:pshared/data/dto/payment/intent/fx.dart';
|
import 'package:pshared/data/dto/payment/intent/fx.dart';
|
||||||
import 'package:pshared/data/dto/payment/money.dart';
|
import 'package:pshared/data/dto/payment/money.dart';
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ class PaymentIntentDTO {
|
|||||||
final String? settlementMode;
|
final String? settlementMode;
|
||||||
|
|
||||||
final Map<String, String>? attributes;
|
final Map<String, String>? attributes;
|
||||||
|
final CustomerDTO? customer;
|
||||||
|
|
||||||
const PaymentIntentDTO({
|
const PaymentIntentDTO({
|
||||||
this.kind,
|
this.kind,
|
||||||
@@ -29,6 +31,7 @@ class PaymentIntentDTO {
|
|||||||
this.fx,
|
this.fx,
|
||||||
this.settlementMode,
|
this.settlementMode,
|
||||||
this.attributes,
|
this.attributes,
|
||||||
|
this.customer,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PaymentIntentDTO.fromJson(Map<String, dynamic> json) => _$PaymentIntentDTOFromJson(json);
|
factory PaymentIntentDTO.fromJson(Map<String, dynamic> json) => _$PaymentIntentDTOFromJson(json);
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import 'package:pshared/data/dto/payment/intent/customer.dart';
|
||||||
|
import 'package:pshared/models/payment/customer.dart';
|
||||||
|
|
||||||
|
|
||||||
|
extension CustomerMapper on Customer {
|
||||||
|
CustomerDTO toDTO() => CustomerDTO(
|
||||||
|
id: id,
|
||||||
|
firstName: firstName,
|
||||||
|
middleName: middleName,
|
||||||
|
lastName: lastName,
|
||||||
|
ip: ip,
|
||||||
|
zip: zip,
|
||||||
|
country: country,
|
||||||
|
state: state,
|
||||||
|
city: city,
|
||||||
|
address: address,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CustomerDTOMapper on CustomerDTO {
|
||||||
|
Customer toDomain() => Customer(
|
||||||
|
id: id,
|
||||||
|
firstName: firstName,
|
||||||
|
middleName: middleName,
|
||||||
|
lastName: lastName,
|
||||||
|
ip: ip,
|
||||||
|
zip: zip,
|
||||||
|
country: country,
|
||||||
|
state: state,
|
||||||
|
city: city,
|
||||||
|
address: address,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,30 +1,34 @@
|
|||||||
import 'package:pshared/data/dto/payment/intent/payment.dart';
|
import 'package:pshared/data/dto/payment/intent/payment.dart';
|
||||||
import 'package:pshared/data/mapper/payment/payment.dart';
|
import 'package:pshared/data/mapper/payment/payment.dart';
|
||||||
import 'package:pshared/data/mapper/payment/enums.dart';
|
import 'package:pshared/data/mapper/payment/enums.dart';
|
||||||
|
import 'package:pshared/data/mapper/payment/intent/customer.dart';
|
||||||
import 'package:pshared/data/mapper/payment/intent/fx.dart';
|
import 'package:pshared/data/mapper/payment/intent/fx.dart';
|
||||||
import 'package:pshared/data/mapper/payment/money.dart';
|
import 'package:pshared/data/mapper/payment/money.dart';
|
||||||
import 'package:pshared/models/payment/intent.dart';
|
import 'package:pshared/models/payment/intent.dart';
|
||||||
|
|
||||||
|
|
||||||
extension PaymentIntentMapper on PaymentIntent {
|
extension PaymentIntentMapper on PaymentIntent {
|
||||||
PaymentIntentDTO toDTO() => PaymentIntentDTO(
|
PaymentIntentDTO toDTO() => PaymentIntentDTO(
|
||||||
kind: paymentKindToValue(kind),
|
kind: paymentKindToValue(kind),
|
||||||
source: source?.toDTO(),
|
source: source?.toDTO(),
|
||||||
destination: destination?.toDTO(),
|
destination: destination?.toDTO(),
|
||||||
amount: amount?.toDTO(),
|
amount: amount?.toDTO(),
|
||||||
fx: fx?.toDTO(),
|
fx: fx?.toDTO(),
|
||||||
settlementMode: settlementModeToValue(settlementMode),
|
settlementMode: settlementModeToValue(settlementMode),
|
||||||
attributes: attributes,
|
attributes: attributes,
|
||||||
);
|
customer: customer?.toDTO(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PaymentIntentDTOMapper on PaymentIntentDTO {
|
extension PaymentIntentDTOMapper on PaymentIntentDTO {
|
||||||
PaymentIntent toDomain() => PaymentIntent(
|
PaymentIntent toDomain() => PaymentIntent(
|
||||||
kind: paymentKindFromValue(kind),
|
kind: paymentKindFromValue(kind),
|
||||||
source: source?.toDomain(),
|
source: source?.toDomain(),
|
||||||
destination: destination?.toDomain(),
|
destination: destination?.toDomain(),
|
||||||
amount: amount?.toDomain(),
|
amount: amount?.toDomain(),
|
||||||
fx: fx?.toDomain(),
|
fx: fx?.toDomain(),
|
||||||
settlementMode: settlementModeFromValue(settlementMode),
|
settlementMode: settlementModeFromValue(settlementMode),
|
||||||
attributes: attributes,
|
attributes: attributes,
|
||||||
);
|
customer: customer?.toDomain(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
25
frontend/pshared/lib/models/payment/customer.dart
Normal file
25
frontend/pshared/lib/models/payment/customer.dart
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
class Customer {
|
||||||
|
final String id;
|
||||||
|
final String? firstName;
|
||||||
|
final String? middleName;
|
||||||
|
final String? lastName;
|
||||||
|
final String? ip;
|
||||||
|
final String? zip;
|
||||||
|
final String? country;
|
||||||
|
final String? state;
|
||||||
|
final String? city;
|
||||||
|
final String? address;
|
||||||
|
|
||||||
|
const Customer({
|
||||||
|
required this.id,
|
||||||
|
this.firstName,
|
||||||
|
this.middleName,
|
||||||
|
this.lastName,
|
||||||
|
this.ip,
|
||||||
|
this.zip,
|
||||||
|
this.country,
|
||||||
|
this.state,
|
||||||
|
this.city,
|
||||||
|
this.address,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:pshared/models/payment/fx/intent.dart';
|
import 'package:pshared/models/payment/fx/intent.dart';
|
||||||
import 'package:pshared/models/payment/kind.dart';
|
import 'package:pshared/models/payment/kind.dart';
|
||||||
|
import 'package:pshared/models/payment/customer.dart';
|
||||||
import 'package:pshared/models/payment/methods/data.dart';
|
import 'package:pshared/models/payment/methods/data.dart';
|
||||||
import 'package:pshared/models/payment/money.dart';
|
import 'package:pshared/models/payment/money.dart';
|
||||||
import 'package:pshared/models/payment/settlement_mode.dart';
|
import 'package:pshared/models/payment/settlement_mode.dart';
|
||||||
@@ -13,6 +14,7 @@ class PaymentIntent {
|
|||||||
final FxIntent? fx;
|
final FxIntent? fx;
|
||||||
final SettlementMode settlementMode;
|
final SettlementMode settlementMode;
|
||||||
final Map<String, String>? attributes;
|
final Map<String, String>? attributes;
|
||||||
|
final Customer? customer;
|
||||||
|
|
||||||
const PaymentIntent({
|
const PaymentIntent({
|
||||||
this.kind = PaymentKind.unspecified,
|
this.kind = PaymentKind.unspecified,
|
||||||
@@ -22,5 +24,6 @@ class PaymentIntent {
|
|||||||
this.fx,
|
this.fx,
|
||||||
this.settlementMode = SettlementMode.unspecified,
|
this.settlementMode = SettlementMode.unspecified,
|
||||||
this.attributes,
|
this.attributes,
|
||||||
|
this.customer,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,22 @@ import 'package:pshared/api/requests/payment/quote.dart';
|
|||||||
import 'package:pshared/data/mapper/payment/intent/payment.dart';
|
import 'package:pshared/data/mapper/payment/intent/payment.dart';
|
||||||
import 'package:pshared/models/asset.dart';
|
import 'package:pshared/models/asset.dart';
|
||||||
import 'package:pshared/models/payment/currency_pair.dart';
|
import 'package:pshared/models/payment/currency_pair.dart';
|
||||||
|
import 'package:pshared/models/payment/customer.dart';
|
||||||
import 'package:pshared/models/payment/fx/intent.dart';
|
import 'package:pshared/models/payment/fx/intent.dart';
|
||||||
import 'package:pshared/models/payment/fx/side.dart';
|
import 'package:pshared/models/payment/fx/side.dart';
|
||||||
import 'package:pshared/models/payment/kind.dart';
|
import 'package:pshared/models/payment/kind.dart';
|
||||||
import 'package:pshared/models/payment/methods/managed_wallet.dart';
|
import 'package:pshared/models/payment/methods/managed_wallet.dart';
|
||||||
|
import 'package:pshared/models/payment/methods/type.dart';
|
||||||
import 'package:pshared/models/payment/money.dart';
|
import 'package:pshared/models/payment/money.dart';
|
||||||
import 'package:pshared/models/payment/settlement_mode.dart';
|
import 'package:pshared/models/payment/settlement_mode.dart';
|
||||||
import 'package:pshared/models/payment/intent.dart';
|
import 'package:pshared/models/payment/intent.dart';
|
||||||
import 'package:pshared/models/payment/quote.dart';
|
import 'package:pshared/models/payment/quote.dart';
|
||||||
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
import 'package:pshared/provider/payment/amount.dart';
|
import 'package:pshared/provider/payment/amount.dart';
|
||||||
import 'package:pshared/provider/payment/flow.dart';
|
import 'package:pshared/provider/payment/flow.dart';
|
||||||
import 'package:pshared/provider/payment/wallets.dart';
|
import 'package:pshared/provider/payment/wallets.dart';
|
||||||
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||||
import 'package:pshared/provider/resource.dart';
|
import 'package:pshared/provider/resource.dart';
|
||||||
import 'package:pshared/service/payment/quotation.dart';
|
import 'package:pshared/service/payment/quotation.dart';
|
||||||
@@ -36,12 +40,17 @@ class QuotationProvider extends ChangeNotifier {
|
|||||||
PaymentAmountProvider payment,
|
PaymentAmountProvider payment,
|
||||||
WalletsProvider wallets,
|
WalletsProvider wallets,
|
||||||
PaymentFlowProvider flow,
|
PaymentFlowProvider flow,
|
||||||
|
RecipientsProvider recipients,
|
||||||
PaymentMethodsProvider methods,
|
PaymentMethodsProvider methods,
|
||||||
) {
|
) {
|
||||||
_organizations = venue;
|
_organizations = venue;
|
||||||
final t = flow.selectedType;
|
final t = flow.selectedType;
|
||||||
final method = methods.methods.firstWhereOrNull((m) => m.type == t);
|
final method = methods.methods.firstWhereOrNull((m) => m.type == t);
|
||||||
if ((wallets.selectedWallet != null) && (method != null)) {
|
if ((wallets.selectedWallet != null) && (method != null)) {
|
||||||
|
final customer = _buildCustomer(
|
||||||
|
recipient: recipients.currentObject,
|
||||||
|
method: method,
|
||||||
|
);
|
||||||
getQuotation(PaymentIntent(
|
getQuotation(PaymentIntent(
|
||||||
kind: PaymentKind.payout,
|
kind: PaymentKind.payout,
|
||||||
amount: Money(
|
amount: Money(
|
||||||
@@ -61,6 +70,7 @@ class QuotationProvider extends ChangeNotifier {
|
|||||||
side: FxSide.sellBaseBuyQuote,
|
side: FxSide.sellBaseBuyQuote,
|
||||||
),
|
),
|
||||||
settlementMode: payment.payerCoversFee ? SettlementMode.fixReceived : SettlementMode.fixSource,
|
settlementMode: payment.payerCoversFee ? SettlementMode.fixReceived : SettlementMode.fixSource,
|
||||||
|
customer: customer,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,3 +118,42 @@ class QuotationProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Customer? _buildCustomer({
|
||||||
|
required Recipient? recipient,
|
||||||
|
required PaymentMethod method,
|
||||||
|
}) {
|
||||||
|
final recipientId = (recipient?.id ?? method.recipientRef).trim();
|
||||||
|
if (recipientId.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstName = '';
|
||||||
|
var lastName = '';
|
||||||
|
final cardData = method.cardData;
|
||||||
|
if (cardData != null) {
|
||||||
|
firstName = cardData.firstName.trim();
|
||||||
|
lastName = cardData.lastName.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((firstName.isEmpty || lastName.isEmpty) && recipient != null) {
|
||||||
|
final parts = recipient.name.trim().split(RegExp(r'\s+'));
|
||||||
|
if (parts.isNotEmpty && firstName.isEmpty) {
|
||||||
|
firstName = parts.first;
|
||||||
|
}
|
||||||
|
if (parts.length > 1 && lastName.isEmpty) {
|
||||||
|
lastName = parts.sublist(1).join(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastName.isEmpty) {
|
||||||
|
lastName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Customer(
|
||||||
|
id: recipientId,
|
||||||
|
firstName: firstName.isEmpty ? null : firstName,
|
||||||
|
lastName: lastName.isEmpty ? null : lastName,
|
||||||
|
country: cardData?.country,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -128,9 +128,10 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => PaymentAmountProvider(),
|
create: (_) => PaymentAmountProvider(),
|
||||||
),
|
),
|
||||||
ChangeNotifierProxyProvider5<OrganizationsProvider, PaymentAmountProvider, WalletsProvider, PaymentFlowProvider, PaymentMethodsProvider, QuotationProvider>(
|
ChangeNotifierProxyProvider6<OrganizationsProvider, PaymentAmountProvider, WalletsProvider, PaymentFlowProvider, RecipientsProvider, PaymentMethodsProvider, QuotationProvider>(
|
||||||
create: (_) => QuotationProvider(),
|
create: (_) => QuotationProvider(),
|
||||||
update: (_, organization, payment, wallet, flow, methods, provider) => provider!..update(organization, payment, wallet, flow, methods),
|
update: (_, organization, payment, wallet, flow, recipients, methods, provider) =>
|
||||||
|
provider!..update(organization, payment, wallet, flow, recipients, methods),
|
||||||
),
|
),
|
||||||
ChangeNotifierProxyProvider2<OrganizationsProvider, QuotationProvider, PaymentProvider>(
|
ChangeNotifierProxyProvider2<OrganizationsProvider, QuotationProvider, PaymentProvider>(
|
||||||
create: (_) => PaymentProvider(),
|
create: (_) => PaymentProvider(),
|
||||||
|
|||||||
Reference in New Issue
Block a user