Batch payment execution + got rid of intent references
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
package paymentapiimp
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -18,11 +16,6 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
fanoutIdempotencyHashLen = 16
|
||||
maxExecuteIdempotencyKey = 256
|
||||
)
|
||||
|
||||
func (a *PaymentAPI) initiatePaymentsByQuote(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||
orgRef, err := a.oph.GetRef(r)
|
||||
if err != nil {
|
||||
@@ -68,18 +61,30 @@ func (a *PaymentAPI) initiatePaymentsByQuote(r *http.Request, account *model.Acc
|
||||
return resp.GetPayment(), nil
|
||||
}
|
||||
|
||||
executeBatch := func(idempotencyKey string) ([]*orchestrationv2.Payment, error) {
|
||||
req := &orchestrationv2.ExecuteBatchPaymentRequest{
|
||||
Meta: requestMeta(orgRef.Hex(), idempotencyKey),
|
||||
QuotationRef: quotationRef,
|
||||
ClientPaymentRef: clientPaymentRef,
|
||||
}
|
||||
resp, executeErr := a.execution.ExecuteBatchPayment(ctx, req)
|
||||
if executeErr != nil {
|
||||
return nil, executeErr
|
||||
}
|
||||
if resp == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return resp.GetPayments(), nil
|
||||
}
|
||||
|
||||
payments := make([]*orchestrationv2.Payment, 0, max(1, len(intentSelectors)))
|
||||
if len(payload.IntentRefs) > 0 {
|
||||
for _, intentRef := range payload.IntentRefs {
|
||||
payment, executeErr := executeOne(deriveFanoutIdempotencyKey(baseIdempotencyKey, intentRef), intentRef)
|
||||
if executeErr != nil {
|
||||
a.logger.Warn("Failed to initiate batch payments", zap.Error(executeErr), zap.String("organization_ref", orgRef.Hex()))
|
||||
return grpcErrorResponse(a.logger, a.Name(), executeErr)
|
||||
}
|
||||
if payment != nil {
|
||||
payments = append(payments, payment)
|
||||
}
|
||||
executed, executeErr := executeBatch(baseIdempotencyKey)
|
||||
if executeErr != nil {
|
||||
a.logger.Warn("Failed to initiate batch payments", zap.Error(executeErr), zap.String("organization_ref", orgRef.Hex()))
|
||||
return grpcErrorResponse(a.logger, a.Name(), executeErr)
|
||||
}
|
||||
payments = append(payments, executed...)
|
||||
return sresponse.PaymentsResponse(a.logger, payments, token)
|
||||
}
|
||||
|
||||
@@ -118,28 +123,6 @@ func resolveExecutionIntentSelectors(payload *srequest.InitiatePayments, allowLe
|
||||
return nil, merrors.InvalidArgument("metadata.intent_ref is no longer supported; use intentRef or intentRefs", "metadata.intent_ref")
|
||||
}
|
||||
|
||||
func deriveFanoutIdempotencyKey(baseIdempotencyKey, intentRef string) string {
|
||||
baseIdempotencyKey = strings.TrimSpace(baseIdempotencyKey)
|
||||
intentRef = strings.TrimSpace(intentRef)
|
||||
if baseIdempotencyKey == "" || intentRef == "" {
|
||||
return baseIdempotencyKey
|
||||
}
|
||||
sum := sha256.Sum256([]byte(intentRef))
|
||||
hash := hex.EncodeToString(sum[:])
|
||||
if len(hash) > fanoutIdempotencyHashLen {
|
||||
hash = hash[:fanoutIdempotencyHashLen]
|
||||
}
|
||||
suffix := ":i:" + hash
|
||||
if len(baseIdempotencyKey)+len(suffix) <= maxExecuteIdempotencyKey {
|
||||
return baseIdempotencyKey + suffix
|
||||
}
|
||||
if len(suffix) >= maxExecuteIdempotencyKey {
|
||||
return suffix[:maxExecuteIdempotencyKey]
|
||||
}
|
||||
prefixLen := maxExecuteIdempotencyKey - len(suffix)
|
||||
return baseIdempotencyKey[:prefixLen] + suffix
|
||||
}
|
||||
|
||||
func decodeInitiatePaymentsPayload(r *http.Request) (*srequest.InitiatePayments, error) {
|
||||
defer r.Body.Close()
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -24,7 +23,7 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestInitiatePaymentsByQuote_FansOutByIntentRefs(t *testing.T) {
|
||||
func TestInitiatePaymentsByQuote_PassesIntentRefsInSingleExecuteCall(t *testing.T) {
|
||||
orgRef := bson.NewObjectID()
|
||||
exec := &fakeExecutionClientForBatch{}
|
||||
api := newBatchAPI(exec)
|
||||
@@ -35,20 +34,17 @@ func TestInitiatePaymentsByQuote_FansOutByIntentRefs(t *testing.T) {
|
||||
t.Fatalf("status mismatch: got=%d want=%d body=%s", got, want, rr.Body.String())
|
||||
}
|
||||
|
||||
if got, want := len(exec.executeReqs), 2; got != want {
|
||||
t.Fatalf("execute calls mismatch: got=%d want=%d", got, want)
|
||||
if got, want := len(exec.executeBatchReqs), 1; got != want {
|
||||
t.Fatalf("execute batch calls mismatch: got=%d want=%d", got, want)
|
||||
}
|
||||
if got, want := exec.executeReqs[0].GetIntentRef(), "intent-a"; got != want {
|
||||
t.Fatalf("intent_ref[0] mismatch: got=%q want=%q", got, want)
|
||||
if got := len(exec.executeReqs); got != 0 {
|
||||
t.Fatalf("expected no execute calls, got=%d", got)
|
||||
}
|
||||
if got, want := exec.executeReqs[1].GetIntentRef(), "intent-b"; got != want {
|
||||
t.Fatalf("intent_ref[1] mismatch: got=%q want=%q", got, want)
|
||||
if got, want := exec.executeBatchReqs[0].GetQuotationRef(), "quote-1"; got != want {
|
||||
t.Fatalf("quotation_ref mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := exec.executeReqs[0].GetMeta().GetTrace().GetIdempotencyKey(), deriveFanoutIdempotencyKey("idem-batch", "intent-a"); got != want {
|
||||
t.Fatalf("idempotency[0] mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := exec.executeReqs[1].GetMeta().GetTrace().GetIdempotencyKey(), deriveFanoutIdempotencyKey("idem-batch", "intent-b"); got != want {
|
||||
t.Fatalf("idempotency[1] mismatch: got=%q want=%q", got, want)
|
||||
if got, want := exec.executeBatchReqs[0].GetMeta().GetTrace().GetIdempotencyKey(), "idem-batch"; got != want {
|
||||
t.Fatalf("idempotency mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,28 +121,6 @@ func TestInitiatePaymentsByQuote_RejectsLegacyMetadataIntentRefWhenDateGateExpir
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveFanoutIdempotencyKey_IsDeterministicAndBounded(t *testing.T) {
|
||||
a := deriveFanoutIdempotencyKey("idem-1", "intent-a")
|
||||
b := deriveFanoutIdempotencyKey("idem-1", "intent-a")
|
||||
if got, want := a, b; got != want {
|
||||
t.Fatalf("determinism mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if a == "idem-1" {
|
||||
t.Fatalf("expected derived key to differ from base")
|
||||
}
|
||||
|
||||
c := deriveFanoutIdempotencyKey("idem-1", "intent-b")
|
||||
if c == a {
|
||||
t.Fatalf("expected different derived keys for different intents")
|
||||
}
|
||||
|
||||
longBase := strings.Repeat("a", 400)
|
||||
long := deriveFanoutIdempotencyKey(longBase, "intent-a")
|
||||
if got, want := len(long), maxExecuteIdempotencyKey; got != want {
|
||||
t.Fatalf("length mismatch: got=%d want=%d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveExecutionIntentSelectors_PrefersExplicitSelectors(t *testing.T) {
|
||||
payload := &srequest.InitiatePayments{
|
||||
IntentRefs: []string{"intent-a", "intent-b"},
|
||||
@@ -229,7 +203,8 @@ func invokeInitiatePaymentsByQuote(t *testing.T, api *PaymentAPI, orgRef bson.Ob
|
||||
}
|
||||
|
||||
type fakeExecutionClientForBatch struct {
|
||||
executeReqs []*orchestrationv2.ExecutePaymentRequest
|
||||
executeReqs []*orchestrationv2.ExecutePaymentRequest
|
||||
executeBatchReqs []*orchestrationv2.ExecuteBatchPaymentRequest
|
||||
}
|
||||
|
||||
func (f *fakeExecutionClientForBatch) ExecutePayment(_ context.Context, req *orchestrationv2.ExecutePaymentRequest) (*orchestrationv2.ExecutePaymentResponse, error) {
|
||||
@@ -239,6 +214,13 @@ func (f *fakeExecutionClientForBatch) ExecutePayment(_ context.Context, req *orc
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *fakeExecutionClientForBatch) ExecuteBatchPayment(_ context.Context, req *orchestrationv2.ExecuteBatchPaymentRequest) (*orchestrationv2.ExecuteBatchPaymentResponse, error) {
|
||||
f.executeBatchReqs = append(f.executeBatchReqs, req)
|
||||
return &orchestrationv2.ExecuteBatchPaymentResponse{
|
||||
Payments: []*orchestrationv2.Payment{{PaymentRef: bson.NewObjectID().Hex()}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (*fakeExecutionClientForBatch) ListPayments(context.Context, *orchestrationv2.ListPaymentsRequest) (*orchestrationv2.ListPaymentsResponse, error) {
|
||||
return &orchestrationv2.ListPaymentsResponse{}, nil
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ const (
|
||||
|
||||
type executionClient interface {
|
||||
ExecutePayment(ctx context.Context, req *orchestrationv2.ExecutePaymentRequest) (*orchestrationv2.ExecutePaymentResponse, error)
|
||||
ExecuteBatchPayment(ctx context.Context, req *orchestrationv2.ExecuteBatchPaymentRequest) (*orchestrationv2.ExecuteBatchPaymentResponse, error)
|
||||
ListPayments(ctx context.Context, req *orchestrationv2.ListPaymentsRequest) (*orchestrationv2.ListPaymentsResponse, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user