mirror of
https://github.com/hanzo-fi/payments.git
synced 2026-10-30 20:31:20 +00:00
- go.mod module path + all self-imports: github.com/formancehq/payments -> github.com/hanzo-fi/payments (1590 .go files / 3152 refs; 20 modules: root, pkg/client, pkg/domain, 18 ce/plugins). External dep kept: formancehq/go-libs. - plugins: each ce/plugins/* consumed a *published* pkg/domain; added local 'replace .../pkg/domain => ../../../pkg/domain' (consistent with root) so every standalone plugin module builds in-monorepo. compile-plugin.sh MOD var rebranded too. - branding: README, LICENSE (dual MIT+EE; upstream copyright retained + Hanzo added), goreleaser (release images -> registry.hanzo.ai/hanzo-fi, OCI labels, homepage), proto go_package, renovate, speakeasy gen.yaml, connector-template *.gotpl. - CI: removed Numary/Formance workflows + composite action; added canonical hanzo.yml + cicd.yml (hanzoai/ci, gated HANZO_CI_ENABLED) + secretless ci.yml (go build/vet). - kept as data/compat keys: wire capability enums ALLOW_FORMANCE_*, generated SDK type client.Formance, domain field FormanceRedirectURL, OTEL_SERVICE_NAME, upstream base image. - verified: go build ./... (default + -tags ee) + all 20 nested modules green.
261 lines
8.2 KiB
Go
261 lines
8.2 KiB
Go
package client
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/hanzo-fi/payments/pkg/domain/metrics"
|
|
)
|
|
|
|
type WebhookEventType string
|
|
|
|
const (
|
|
AccountTransactionsModified WebhookEventType = "account-transactions:modified"
|
|
AccountTransactionsDeleted WebhookEventType = "account-transactions:deleted"
|
|
AccountBookedTransactionsModified WebhookEventType = "account-booked-transactions:modified"
|
|
AccountCreated WebhookEventType = "account:created"
|
|
AccountUpdated WebhookEventType = "account:updated"
|
|
RefreshFinished WebhookEventType = "refresh:finished"
|
|
)
|
|
|
|
type CreateWebhookRequest struct {
|
|
Description string `json:"description"`
|
|
EnabledEvents []string `json:"enabledEvents"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
type CreateWebhookResponse struct {
|
|
ID string `json:"id"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
Disabled bool `json:"disabled"`
|
|
EnabledEvents []string `json:"enabledEvents"`
|
|
URL string `json:"url"`
|
|
Secret string `json:"secret"`
|
|
}
|
|
|
|
func (p *client) CreateWebhook(ctx context.Context, eventType WebhookEventType, connectorID string, url string) (CreateWebhookResponse, error) {
|
|
ctx = context.WithValue(ctx, metrics.MetricOperationContextKey, "create_webhook")
|
|
|
|
body, err := json.Marshal(&CreateWebhookRequest{
|
|
Description: fmt.Sprintf("%s: %s", connectorID, string(eventType)),
|
|
EnabledEvents: []string{string(eventType)},
|
|
URL: url,
|
|
})
|
|
if err != nil {
|
|
return CreateWebhookResponse{}, err
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("%s/events/v2/webhook-endpoints", p.endpoint)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(body))
|
|
if err != nil {
|
|
return CreateWebhookResponse{}, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
var resp CreateWebhookResponse
|
|
_, err = p.httpClient.Do(ctx, req, &resp, nil)
|
|
if err != nil {
|
|
return CreateWebhookResponse{}, fmt.Errorf("failed to create webhook: %w", err)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (p *client) DeleteWebhook(ctx context.Context, webhookID string) error {
|
|
ctx = context.WithValue(ctx, metrics.MetricOperationContextKey, "delete_webhook")
|
|
|
|
endpoint := fmt.Sprintf("%s/events/v2/webhook-endpoints/%s", p.endpoint, webhookID)
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodDelete, endpoint, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
_, err = p.httpClient.Do(ctx, req, nil, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete webhook: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type WebhookContext struct {
|
|
UserID string `json:"userId"`
|
|
ExternalUserID string `json:"externalUserId"`
|
|
}
|
|
|
|
type WebhookTransactions struct {
|
|
EarliestModifiedBookedDate time.Time `json:"earliestModifiedBookedDate"`
|
|
LatestModifiedBookedDate time.Time `json:"latestModifiedBookedDate"`
|
|
Inserted int `json:"inserted"`
|
|
Updated int `json:"updated"`
|
|
Deleted int `json:"deleted"`
|
|
}
|
|
|
|
func (w *WebhookTransactions) UnmarshalJSON(data []byte) error {
|
|
type webhookTransactions struct {
|
|
EarliestModifiedBookedDate string `json:"earliestModifiedBookedDate"`
|
|
LatestModifiedBookedDate string `json:"latestModifiedBookedDate"`
|
|
Inserted int `json:"inserted"`
|
|
Updated int `json:"updated"`
|
|
Deleted int `json:"deleted"`
|
|
}
|
|
|
|
var tmp webhookTransactions
|
|
if err := json.Unmarshal(data, &tmp); err != nil {
|
|
return err
|
|
}
|
|
var earliestModifiedBookedDate time.Time
|
|
if tmp.EarliestModifiedBookedDate != "" {
|
|
parsed, err := time.Parse(time.DateOnly, tmp.EarliestModifiedBookedDate)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid earliestModifiedBookedDate %q: %w", tmp.EarliestModifiedBookedDate, err)
|
|
}
|
|
earliestModifiedBookedDate = parsed
|
|
}
|
|
|
|
var latestModifiedBookedDate time.Time
|
|
if tmp.LatestModifiedBookedDate != "" {
|
|
parsed, err := time.Parse(time.DateOnly, tmp.LatestModifiedBookedDate)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid latestModifiedBookedDate %q: %w", tmp.LatestModifiedBookedDate, err)
|
|
}
|
|
latestModifiedBookedDate = parsed
|
|
}
|
|
|
|
*w = WebhookTransactions{
|
|
EarliestModifiedBookedDate: earliestModifiedBookedDate,
|
|
LatestModifiedBookedDate: latestModifiedBookedDate,
|
|
Inserted: tmp.Inserted,
|
|
Updated: tmp.Updated,
|
|
Deleted: tmp.Deleted,
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (w WebhookTransactions) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(struct {
|
|
EarliestModifiedBookedDate string `json:"earliestModifiedBookedDate"`
|
|
LatestModifiedBookedDate string `json:"latestModifiedBookedDate"`
|
|
Inserted int `json:"inserted"`
|
|
Updated int `json:"updated"`
|
|
Deleted int `json:"deleted"`
|
|
}{
|
|
EarliestModifiedBookedDate: w.EarliestModifiedBookedDate.Format(time.DateOnly),
|
|
LatestModifiedBookedDate: w.LatestModifiedBookedDate.Format(time.DateOnly),
|
|
Inserted: w.Inserted,
|
|
Updated: w.Updated,
|
|
Deleted: w.Deleted,
|
|
})
|
|
}
|
|
|
|
type AccountTransactionsModifiedWebhook struct {
|
|
UserID string `json:"userId"`
|
|
ExternalUserID string `json:"externalUserId"`
|
|
Account struct {
|
|
ID string `json:"id"`
|
|
} `json:"account"`
|
|
Transactions WebhookTransactions `json:"transactions"`
|
|
}
|
|
|
|
func (c *client) GetAccountTransactionsModifiedWebhook(ctx context.Context, payload []byte) (AccountTransactionsModifiedWebhook, error) {
|
|
type baseWebhook struct {
|
|
Content AccountTransactionsModifiedWebhook `json:"content"`
|
|
}
|
|
|
|
var base baseWebhook
|
|
if err := json.Unmarshal(payload, &base); err != nil {
|
|
return AccountTransactionsModifiedWebhook{}, err
|
|
}
|
|
|
|
return base.Content, nil
|
|
}
|
|
|
|
type WebhookDeletedTransactions struct {
|
|
IDs []string `json:"ids"`
|
|
}
|
|
|
|
type AccountTransactionsDeletedWebhook struct {
|
|
UserID string `json:"userId"`
|
|
ExternalUserID string `json:"externalUserId"`
|
|
Account struct {
|
|
ID string `json:"id"`
|
|
} `json:"account"`
|
|
Transactions WebhookDeletedTransactions `json:"transactions"`
|
|
}
|
|
|
|
func (c *client) GetAccountTransactionsDeletedWebhook(ctx context.Context, payload []byte) (AccountTransactionsDeletedWebhook, error) {
|
|
type baseWebhook struct {
|
|
Content AccountTransactionsDeletedWebhook `json:"content"`
|
|
}
|
|
|
|
var base baseWebhook
|
|
if err := json.Unmarshal(payload, &base); err != nil {
|
|
return AccountTransactionsDeletedWebhook{}, err
|
|
}
|
|
|
|
return base.Content, nil
|
|
}
|
|
|
|
type AccountCreatedWebhook struct {
|
|
UserID string `json:"userId"`
|
|
ExternalUserID string `json:"externalUserId"`
|
|
ID string `json:"id"`
|
|
}
|
|
|
|
func (c *client) GetAccountCreatedWebhook(ctx context.Context, payload []byte) (AccountCreatedWebhook, error) {
|
|
type baseWebhook struct {
|
|
Context WebhookContext `json:"context"`
|
|
Content AccountCreatedWebhook `json:"content"`
|
|
}
|
|
|
|
var base baseWebhook
|
|
if err := json.Unmarshal(payload, &base); err != nil {
|
|
return AccountCreatedWebhook{}, err
|
|
}
|
|
|
|
base.Content.UserID = base.Context.UserID
|
|
base.Content.ExternalUserID = base.Context.ExternalUserID
|
|
|
|
return base.Content, nil
|
|
}
|
|
|
|
type RefreshFinishedWebhook struct {
|
|
UserID string `json:"userId"`
|
|
ExternalUserID string `json:"externalUserId"`
|
|
CredentialsID string `json:"credentialsId"`
|
|
CredentialsStatus string `json:"credentialsStatus"`
|
|
Finished int64 `json:"finished"`
|
|
DetailedError struct {
|
|
Type string `json:"type"`
|
|
DisplayMessage string `json:"displayMessage"`
|
|
Details struct {
|
|
Reason string `json:"reason"`
|
|
Retryable bool `json:"retryable"`
|
|
} `json:"details"`
|
|
} `json:"detailedError"`
|
|
}
|
|
|
|
func (c *client) GetRefreshFinishedWebhook(ctx context.Context, payload []byte) (RefreshFinishedWebhook, error) {
|
|
type baseWebhook struct {
|
|
Context WebhookContext `json:"context"`
|
|
Content RefreshFinishedWebhook `json:"content"`
|
|
}
|
|
|
|
var base baseWebhook
|
|
if err := json.Unmarshal(payload, &base); err != nil {
|
|
return RefreshFinishedWebhook{}, err
|
|
}
|
|
|
|
base.Content.UserID = base.Context.UserID
|
|
base.Content.ExternalUserID = base.Context.ExternalUserID
|
|
|
|
return base.Content, nil
|
|
}
|