diff --git a/.gitrepo b/.gitrepo index 09dd1d76..cb7951fb 100644 --- a/.gitrepo +++ b/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = payments branch = main - commit = ae1fa5ec4508bc3a7603bd15953228b6a6152761 - parent = e11fe64274a7326f57cf409cd542ce7a08b05233 + commit = e4c0f219fda7416c2a3d30fe7ec4184870134999 + parent = b2827a506b6324812752e3689b2a4845480a397e method = merge cmdver = 0.4.5 diff --git a/internal/app/api/metadata.go b/internal/app/api/metadata.go new file mode 100644 index 00000000..c65b0735 --- /dev/null +++ b/internal/app/api/metadata.go @@ -0,0 +1,54 @@ +package api + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/pkg/errors" + + "github.com/google/uuid" + + "github.com/gorilla/mux" +) + +type updateMetadataRepository interface { + UpdatePaymentMetadata(ctx context.Context, paymentID uuid.UUID, metadata map[string]string) error +} + +type updateMetadataRequest map[string]string + +func updateMetadataHandler(repo updateMetadataRepository) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + paymentID, err := uuid.Parse(mux.Vars(r)["paymentID"]) + if err != nil { + handleErrorBadRequest(w, r, err) + + return + } + + var metadata updateMetadataRequest + + if r.ContentLength == 0 { + handleErrorBadRequest(w, r, errors.New("body is required")) + + return + } + + err = json.NewDecoder(r.Body).Decode(&metadata) + if err != nil { + handleError(w, r, err) + + return + } + + err = repo.UpdatePaymentMetadata(r.Context(), paymentID, metadata) + if err != nil { + handleError(w, r, err) + + return + } + + w.WriteHeader(http.StatusNoContent) + } +} diff --git a/internal/app/api/payments.go b/internal/app/api/payments.go index abbf2e7e..80dfe1ed 100644 --- a/internal/app/api/payments.go +++ b/internal/app/api/payments.go @@ -34,18 +34,7 @@ type paymentResponse struct { CreatedAt time.Time `json:"createdAt"` Raw interface{} `json:"raw"` Adjustments []paymentAdjustment `json:"adjustments"` - Metadata []paymentMetadata `json:"metadata"` -} - -type paymentMetadata struct { - Key string `json:"key"` - Value string `json:"value"` - Changelog []paymentMetadataChangelog `json:"changelog"` -} - -type paymentMetadataChangelog struct { - Timestamp string `json:"timestamp"` - Value string `json:"value"` + Metadata map[string]string `json:"metadata"` } type paymentAdjustment struct { @@ -139,19 +128,11 @@ func listPaymentsHandler(repo listPaymentsRepository) http.HandlerFunc { } } - for metadataIDx := range ret[i].Metadata { - data[i].Metadata = append(data[i].Metadata, - paymentMetadata{ - Key: ret[i].Metadata[metadataIDx].Key, - Value: ret[i].Metadata[metadataIDx].Value, - }) - - for changelogIdx := range ret[i].Metadata[metadataIDx].Changelog { - data[i].Metadata[metadataIDx].Changelog = append(data[i].Metadata[metadataIDx].Changelog, - paymentMetadataChangelog{ - Timestamp: ret[i].Metadata[metadataIDx].Changelog[changelogIdx].CreatedAt.Format(time.RFC3339), - Value: ret[i].Metadata[metadataIDx].Changelog[changelogIdx].Value, - }) + if ret[i].Metadata != nil { + data[i].Metadata = make(map[string]string) + + for metadataIDx := range ret[i].Metadata { + data[i].Metadata[ret[i].Metadata[metadataIDx].Key] = ret[i].Metadata[metadataIDx].Value } } } @@ -202,7 +183,6 @@ func readPaymentHandler(repo readPaymentRepository) http.HandlerFunc { CreatedAt: payment.CreatedAt, Raw: payment.RawData, Adjustments: make([]paymentAdjustment, len(payment.Adjustments)), - Metadata: make([]paymentMetadata, len(payment.Metadata)), } if payment.AccountID != uuid.Nil { @@ -219,19 +199,11 @@ func readPaymentHandler(repo readPaymentRepository) http.HandlerFunc { } } - for metadataIDx := range payment.Metadata { - data.Metadata = append(data.Metadata, - paymentMetadata{ - Key: payment.Metadata[metadataIDx].Key, - Value: payment.Metadata[metadataIDx].Value, - }) - - for changelogIdx := range payment.Metadata[metadataIDx].Changelog { - data.Metadata[metadataIDx].Changelog = append(data.Metadata[metadataIDx].Changelog, - paymentMetadataChangelog{ - Timestamp: payment.Metadata[metadataIDx].Changelog[changelogIdx].CreatedAt.Format(time.RFC3339), - Value: payment.Metadata[metadataIDx].Changelog[changelogIdx].Value, - }) + if payment.Metadata != nil { + data.Metadata = make(map[string]string) + + for metadataIDx := range payment.Metadata { + data.Metadata[payment.Metadata[metadataIDx].Key] = payment.Metadata[metadataIDx].Value } } diff --git a/internal/app/api/router.go b/internal/app/api/router.go index a1e68c9b..0c807539 100644 --- a/internal/app/api/router.go +++ b/internal/app/api/router.go @@ -36,6 +36,7 @@ func httpRouter(store *storage.Storage, serviceInfo api.ServiceInfo, connectorHa authGroup.Path("/payments").Methods(http.MethodGet).Handler(listPaymentsHandler(store)) authGroup.Path("/payments/{paymentID}").Methods(http.MethodGet).Handler(readPaymentHandler(store)) + authGroup.Path("/payments/{paymentID}/metadata").Methods(http.MethodPatch).Handler(updateMetadataHandler(store)) authGroup.Path("/accounts").Methods(http.MethodGet).Handler(listAccountsHandler(store)) diff --git a/internal/app/models/metadata.go b/internal/app/models/metadata.go index fdfbb41b..730aa19c 100644 --- a/internal/app/models/metadata.go +++ b/internal/app/models/metadata.go @@ -16,11 +16,11 @@ type Metadata struct { Key string `bun:",pk,nullzero"` Value string - Changelog []MetadataChangelog `bun:",pk,nullzero"` + Changelog []MetadataChangelog `bun:",nullzero"` Payment *Payment `bun:"rel:has-one,join:payment_id=id"` } type MetadataChangelog struct { - CreatedAt time.Time - Value string + CreatedAt time.Time `json:"createdAt"` + Value string `json:"value"` } diff --git a/internal/app/storage/metadata.go b/internal/app/storage/metadata.go new file mode 100644 index 00000000..853229f8 --- /dev/null +++ b/internal/app/storage/metadata.go @@ -0,0 +1,39 @@ +package storage + +import ( + "context" + "time" + + "github.com/formancehq/payments/internal/app/models" + "github.com/google/uuid" +) + +func (s *Storage) UpdatePaymentMetadata(ctx context.Context, paymentID uuid.UUID, metadata map[string]string) error { + var metadataToInsert []models.Metadata // nolint:prealloc // it's against a map + + for key, value := range metadata { + metadataToInsert = append(metadataToInsert, models.Metadata{ + PaymentID: paymentID, + Key: key, + Value: value, + Changelog: []models.MetadataChangelog{ + { + CreatedAt: time.Now(), + Value: value, + }, + }, + }) + } + + _, err := s.db.NewInsert(). + Model(&metadataToInsert). + On("CONFLICT (payment_id, key) DO UPDATE"). + Set("value = EXCLUDED.value"). + Set("changelog = metadata.changelog || EXCLUDED.changelog"). + Exec(ctx) + if err != nil { + return e("failed to update payment metadata", err) + } + + return nil +} diff --git a/internal/app/storage/payments.go b/internal/app/storage/payments.go index 320eb488..10f09662 100644 --- a/internal/app/storage/payments.go +++ b/internal/app/storage/payments.go @@ -156,7 +156,7 @@ func (s *Storage) UpsertPayments(ctx context.Context, provider models.ConnectorP Model(&metadata). On("CONFLICT (payment_id, key) DO UPDATE"). Set("value = EXCLUDED.value"). - Set("changelog = changelog || EXCLUDED.changelog"). + Set("changelog = metadata.changelog || EXCLUDED.changelog"). Exec(ctx) if err != nil { return e("failed to create metadata", err) diff --git a/openapi.yaml b/openapi.yaml index 6eeca419..78247737 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -36,6 +36,19 @@ paths: responses: '200': $ref: '#/components/responses/Payment' + /payments/{paymentId}/metadata: + patch: + summary: Update metadata + tags: + - Payments + operationId: updateMetadata + parameters: + - $ref: '#/components/parameters/PaymentId' + requestBody: + $ref: '#/components/requestBodies/UpdateMetadata' + responses: + '204': + $ref: '#/components/responses/NoContent' /accounts: get: summary: List accounts @@ -147,7 +160,7 @@ paths: responses: '200': $ref: '#/components/responses/Task' - /connectors/stripe/transfer: + /connectors/stripe/transfers: post: summary: Transfer funds between Stripe accounts tags: @@ -298,6 +311,12 @@ components: application/json: schema: $ref: '#/components/schemas/StripeTransferRequest' + UpdateMetadata: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/PaymentMetadata' # ---------------------- SCHEMAS ---------------------- schemas: @@ -644,9 +663,7 @@ components: items: $ref: '#/components/schemas/PaymentAdjustment' metadata: - type: array - items: - $ref: '#/components/schemas/PaymentMetadata' + $ref: '#/components/schemas/PaymentMetadata' PaymentAdjustment: type: object required: @@ -672,27 +689,9 @@ components: type: boolean PaymentMetadata: type: object - required: - - key - - value properties: key: type: string - value: - type: string - changelog: - $ref: '#/components/schemas/PaymentMetadataChangelog' - PaymentMetadataChangelog: - type: object - required: - - timestamp - - value - properties: - timestamp: - type: string - format: date-time - value: - type: string Account: type: object required: @@ -870,6 +869,7 @@ components: - ACTIVE - TERMINATED - FAILED + - SUCCEEDED ServerInfo: type: object required: