+source currency pick fix +fx side propagation
This commit is contained in:
@@ -13,19 +13,18 @@ type StatusInput struct {
|
||||
BlockReason quotationv2.QuoteBlockReason
|
||||
}
|
||||
|
||||
type PersistItemInput struct {
|
||||
Intent *model.PaymentIntent
|
||||
Quote *model.PaymentQuoteSnapshot
|
||||
Status *StatusInput
|
||||
}
|
||||
|
||||
type PersistInput struct {
|
||||
OrganizationID bson.ObjectID
|
||||
QuoteRef string
|
||||
IdempotencyKey string
|
||||
Hash string
|
||||
ExpiresAt time.Time
|
||||
|
||||
Intent *model.PaymentIntent
|
||||
Intents []model.PaymentIntent
|
||||
|
||||
Quote *model.PaymentQuoteSnapshot
|
||||
Quotes []*model.PaymentQuoteSnapshot
|
||||
|
||||
Status *StatusInput
|
||||
Statuses []*StatusInput
|
||||
RequestShape model.QuoteRequestShape
|
||||
Items []PersistItemInput
|
||||
}
|
||||
|
||||
@@ -52,53 +52,46 @@ func (s *QuotePersistenceService) BuildRecord(in PersistInput) (*model.PaymentQu
|
||||
if in.ExpiresAt.IsZero() {
|
||||
return nil, merrors.InvalidArgument("expires_at is required")
|
||||
}
|
||||
|
||||
isSingle := in.Quote != nil
|
||||
isBatch := len(in.Quotes) > 0
|
||||
|
||||
if isSingle == isBatch {
|
||||
return nil, merrors.InvalidArgument("exactly one quote shape is required")
|
||||
switch in.RequestShape {
|
||||
case model.QuoteRequestShapeSingle:
|
||||
if len(in.Items) != 1 {
|
||||
return nil, merrors.InvalidArgument("single shape requires exactly one item")
|
||||
}
|
||||
case model.QuoteRequestShapeBatch:
|
||||
if len(in.Items) == 0 {
|
||||
return nil, merrors.InvalidArgument("batch shape requires at least one item")
|
||||
}
|
||||
default:
|
||||
return nil, merrors.InvalidArgument("request_shape is required")
|
||||
}
|
||||
|
||||
record := &model.PaymentQuoteRecord{
|
||||
QuoteRef: strings.TrimSpace(in.QuoteRef),
|
||||
IdempotencyKey: strings.TrimSpace(in.IdempotencyKey),
|
||||
RequestShape: in.RequestShape,
|
||||
Hash: strings.TrimSpace(in.Hash),
|
||||
ExpiresAt: in.ExpiresAt,
|
||||
Items: make([]*model.PaymentQuoteItemV2, 0, len(in.Items)),
|
||||
}
|
||||
record.SetID(bson.NewObjectID())
|
||||
record.SetOrganizationRef(in.OrganizationID)
|
||||
|
||||
if isSingle {
|
||||
if in.Intent == nil {
|
||||
return nil, merrors.InvalidArgument("intent is required")
|
||||
for idx, item := range in.Items {
|
||||
if item.Intent == nil {
|
||||
return nil, merrors.InvalidArgument("items[" + itoa(idx) + "].intent is required")
|
||||
}
|
||||
status, err := mapStatusInput(in.Status)
|
||||
if item.Quote == nil {
|
||||
return nil, merrors.InvalidArgument("items[" + itoa(idx) + "].quote is required")
|
||||
}
|
||||
status, err := mapStatusInput(item.Status)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, merrors.InvalidArgument("items[" + itoa(idx) + "]." + err.Error())
|
||||
}
|
||||
record.Intent = *in.Intent
|
||||
record.Quote = in.Quote
|
||||
record.StatusV2 = status
|
||||
return record, nil
|
||||
record.Items = append(record.Items, &model.PaymentQuoteItemV2{
|
||||
Intent: item.Intent,
|
||||
Quote: item.Quote,
|
||||
Status: status,
|
||||
})
|
||||
}
|
||||
|
||||
if len(in.Intents) == 0 {
|
||||
return nil, merrors.InvalidArgument("intents are required")
|
||||
}
|
||||
if len(in.Intents) != len(in.Quotes) {
|
||||
return nil, merrors.InvalidArgument("intents and quotes count mismatch")
|
||||
}
|
||||
statuses, err := mapStatusInputs(in.Statuses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(statuses) != len(in.Quotes) {
|
||||
return nil, merrors.InvalidArgument("statuses and quotes count mismatch")
|
||||
}
|
||||
|
||||
record.Intents = in.Intents
|
||||
record.Quotes = in.Quotes
|
||||
record.StatusesV2 = statuses
|
||||
return record, nil
|
||||
}
|
||||
|
||||
@@ -24,12 +24,17 @@ func TestPersistSingle(t *testing.T) {
|
||||
IdempotencyKey: "idem-1",
|
||||
Hash: "hash-1",
|
||||
ExpiresAt: time.Now().Add(time.Minute),
|
||||
Intent: &model.PaymentIntent{Ref: "intent-1"},
|
||||
Quote: &model.PaymentQuoteSnapshot{
|
||||
QuoteRef: "quote-1",
|
||||
},
|
||||
Status: &StatusInput{
|
||||
State: quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE,
|
||||
RequestShape: model.QuoteRequestShapeSingle,
|
||||
Items: []PersistItemInput{
|
||||
{
|
||||
Intent: &model.PaymentIntent{Ref: "intent-1"},
|
||||
Quote: &model.PaymentQuoteSnapshot{
|
||||
QuoteRef: "quote-1",
|
||||
},
|
||||
Status: &StatusInput{
|
||||
State: quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -41,17 +46,20 @@ func TestPersistSingle(t *testing.T) {
|
||||
if store.created == nil {
|
||||
t.Fatalf("expected record to be created")
|
||||
}
|
||||
if store.created.ExecutionNote != "" {
|
||||
t.Fatalf("expected no legacy execution note, got %q", store.created.ExecutionNote)
|
||||
if store.created.RequestShape != model.QuoteRequestShapeSingle {
|
||||
t.Fatalf("unexpected request shape: %q", store.created.RequestShape)
|
||||
}
|
||||
if store.created.StatusV2 == nil {
|
||||
t.Fatalf("expected v2 status metadata")
|
||||
if len(store.created.Items) != 1 || store.created.Items[0] == nil {
|
||||
t.Fatalf("expected single persisted item")
|
||||
}
|
||||
if store.created.StatusV2.State != model.QuoteStateExecutable {
|
||||
t.Fatalf("unexpected state: %q", store.created.StatusV2.State)
|
||||
if store.created.Items[0].Status == nil {
|
||||
t.Fatalf("expected item status metadata")
|
||||
}
|
||||
if store.created.StatusV2.BlockReason != model.QuoteBlockReasonUnspecified {
|
||||
t.Fatalf("unexpected block_reason: %q", store.created.StatusV2.BlockReason)
|
||||
if store.created.Items[0].Status.State != model.QuoteStateExecutable {
|
||||
t.Fatalf("unexpected state: %q", store.created.Items[0].Status.State)
|
||||
}
|
||||
if store.created.Items[0].Status.BlockReason != model.QuoteBlockReasonUnspecified {
|
||||
t.Fatalf("unexpected block_reason: %q", store.created.Items[0].Status.BlockReason)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,21 +74,22 @@ func TestPersistBatch(t *testing.T) {
|
||||
IdempotencyKey: "idem-batch-1",
|
||||
Hash: "hash-batch-1",
|
||||
ExpiresAt: time.Now().Add(time.Minute),
|
||||
Intents: []model.PaymentIntent{
|
||||
{Ref: "i1"},
|
||||
{Ref: "i2"},
|
||||
},
|
||||
Quotes: []*model.PaymentQuoteSnapshot{
|
||||
{QuoteRef: "q1"},
|
||||
{QuoteRef: "q2"},
|
||||
},
|
||||
Statuses: []*StatusInput{
|
||||
RequestShape: model.QuoteRequestShapeBatch,
|
||||
Items: []PersistItemInput{
|
||||
{
|
||||
State: quotationv2.QuoteState_QUOTE_STATE_BLOCKED,
|
||||
BlockReason: quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_ROUTE_UNAVAILABLE,
|
||||
Intent: &model.PaymentIntent{Ref: "i1"},
|
||||
Quote: &model.PaymentQuoteSnapshot{QuoteRef: "q1"},
|
||||
Status: &StatusInput{
|
||||
State: quotationv2.QuoteState_QUOTE_STATE_BLOCKED,
|
||||
BlockReason: quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_ROUTE_UNAVAILABLE,
|
||||
},
|
||||
},
|
||||
{
|
||||
State: quotationv2.QuoteState_QUOTE_STATE_INDICATIVE,
|
||||
Intent: &model.PaymentIntent{Ref: "i2"},
|
||||
Quote: &model.PaymentQuoteSnapshot{QuoteRef: "q2"},
|
||||
Status: &StatusInput{
|
||||
State: quotationv2.QuoteState_QUOTE_STATE_INDICATIVE,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -90,11 +99,14 @@ func TestPersistBatch(t *testing.T) {
|
||||
if record == nil {
|
||||
t.Fatalf("expected record")
|
||||
}
|
||||
if len(record.StatusesV2) != 2 {
|
||||
t.Fatalf("expected 2 statuses, got %d", len(record.StatusesV2))
|
||||
if record.RequestShape != model.QuoteRequestShapeBatch {
|
||||
t.Fatalf("unexpected request shape: %q", record.RequestShape)
|
||||
}
|
||||
if record.StatusesV2[0].BlockReason != model.QuoteBlockReasonRouteUnavailable {
|
||||
t.Fatalf("unexpected first status block reason: %q", record.StatusesV2[0].BlockReason)
|
||||
if len(record.Items) != 2 {
|
||||
t.Fatalf("expected 2 items, got %d", len(record.Items))
|
||||
}
|
||||
if record.Items[0].Status == nil || record.Items[0].Status.BlockReason != model.QuoteBlockReasonRouteUnavailable {
|
||||
t.Fatalf("unexpected first status block reason")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,11 +126,16 @@ func TestPersistValidation(t *testing.T) {
|
||||
IdempotencyKey: "i",
|
||||
Hash: "h",
|
||||
ExpiresAt: time.Now().Add(time.Minute),
|
||||
Intent: &model.PaymentIntent{Ref: "intent"},
|
||||
Quote: &model.PaymentQuoteSnapshot{QuoteRef: "q"},
|
||||
Status: &StatusInput{
|
||||
State: quotationv2.QuoteState_QUOTE_STATE_BLOCKED,
|
||||
BlockReason: quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED,
|
||||
RequestShape: model.QuoteRequestShapeSingle,
|
||||
Items: []PersistItemInput{
|
||||
{
|
||||
Intent: &model.PaymentIntent{Ref: "intent"},
|
||||
Quote: &model.PaymentQuoteSnapshot{QuoteRef: "q"},
|
||||
Status: &StatusInput{
|
||||
State: quotationv2.QuoteState_QUOTE_STATE_BLOCKED,
|
||||
BlockReason: quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, merrors.ErrInvalidArg) {
|
||||
@@ -131,16 +148,22 @@ func TestPersistValidation(t *testing.T) {
|
||||
IdempotencyKey: "i",
|
||||
Hash: "h",
|
||||
ExpiresAt: time.Now().Add(time.Minute),
|
||||
Intents: []model.PaymentIntent{
|
||||
{Ref: "i1"},
|
||||
RequestShape: model.QuoteRequestShapeSingle,
|
||||
Items: []PersistItemInput{
|
||||
{
|
||||
Intent: &model.PaymentIntent{Ref: "i1"},
|
||||
Quote: &model.PaymentQuoteSnapshot{QuoteRef: "q1"},
|
||||
Status: &StatusInput{State: quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE},
|
||||
},
|
||||
{
|
||||
Intent: &model.PaymentIntent{Ref: "i2"},
|
||||
Quote: &model.PaymentQuoteSnapshot{QuoteRef: "q2"},
|
||||
Status: &StatusInput{State: quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE},
|
||||
},
|
||||
},
|
||||
Quotes: []*model.PaymentQuoteSnapshot{
|
||||
{QuoteRef: "q1"},
|
||||
},
|
||||
Statuses: []*StatusInput{},
|
||||
})
|
||||
if !errors.Is(err, merrors.ErrInvalidArg) {
|
||||
t.Fatalf("expected invalid argument for statuses mismatch, got %v", err)
|
||||
t.Fatalf("expected invalid argument for single shape with multiple items, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,22 +28,6 @@ func mapStatusInput(input *StatusInput) (*model.QuoteStatusV2, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapStatusInputs(inputs []*StatusInput) ([]*model.QuoteStatusV2, error) {
|
||||
if len(inputs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
result := make([]*model.QuoteStatusV2, 0, len(inputs))
|
||||
for i, item := range inputs {
|
||||
mapped, err := mapStatusInput(item)
|
||||
if err != nil {
|
||||
return nil, merrors.InvalidArgument("statuses[" + itoa(i) + "]: " + err.Error())
|
||||
}
|
||||
result = append(result, mapped)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func mapQuoteState(state quotationv2.QuoteState) model.QuoteState {
|
||||
switch state {
|
||||
case quotationv2.QuoteState_QUOTE_STATE_INDICATIVE:
|
||||
|
||||
Reference in New Issue
Block a user