Skip to content

Commit

Permalink
payments: unsubscribe (#7)
Browse files Browse the repository at this point in the history
What
Adds a new POST /payments/unsubscribe endpoint.

Why
So that a Proprietary wallet backend can call to unregister its user accounts for payments tracking.
  • Loading branch information
daniel-burghardt authored May 8, 2024
1 parent f62ad63 commit c93a897
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 2 deletions.
10 changes: 10 additions & 0 deletions internal/data/payments.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@ func (m *PaymentModel) SubscribeAddress(ctx context.Context, address string) err

return nil
}

func (m *PaymentModel) UnsubscribeAddress(ctx context.Context, address string) error {
const query = `DELETE FROM accounts WHERE stellar_address = $1`
_, err := m.db.ExecContext(ctx, query, address)
if err != nil {
return errors.Wrapf(err, "unsubscribing address %s to payments tracking", address)
}

return nil
}
18 changes: 18 additions & 0 deletions internal/serve/httphandler/payments_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,21 @@ func (h PaymentsHandler) SubscribeAddress(w http.ResponseWriter, r *http.Request
return
}
}

func (h PaymentsHandler) UnsubscribeAddress(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

var reqBody PaymentsSubscribeRequest
err := httpdecode.DecodeJSON(r, &reqBody)
if err != nil {
httperror.BadRequest("Invalid request body").Render(w)
return
}

err = h.PaymentModel.UnsubscribeAddress(ctx, reqBody.Address)
if err != nil {
httperror.InternalServerError.Render(w)
// TODO: track in Sentry
return
}
}
70 changes: 68 additions & 2 deletions internal/serve/httphandler/payments_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ func TestSubscribeAddress(t *testing.T) {
require.NoError(t, err)
}

t.Run("success", func(t *testing.T) {
t.Run("success_happy_path", func(t *testing.T) {
// Prepare request
address := "GANZZQ5WFFDAUVI4RDMRJN2QAEWO7WCLXBVIME6ZXAWO5ZEQA4ZPFWFL"
address := keypair.MustRandom().Address()
payload := fmt.Sprintf(`{ "address": "%s" }`, address)
req, err := http.NewRequest(http.MethodPost, "/payments/subscribe", strings.NewReader(payload))
require.NoError(t, err)
Expand Down Expand Up @@ -99,3 +99,69 @@ func TestSubscribeAddress(t *testing.T) {
clearAccounts(ctx)
})
}

func TestUnsubscribeAddress(t *testing.T) {
dbtest := dbtest.Open(t)
defer dbtest.Close()

dbConnectionPool, err := db.OpenDBConnectionPool(dbtest.DSN)
require.NoError(t, err)
defer dbConnectionPool.Close()

models, err := data.NewModels(dbConnectionPool)
require.NoError(t, err)
handler := &PaymentsHandler{
PaymentModel: models.Payments,
}

// Setup router
r := chi.NewRouter()
r.Post("/payments/unsubscribe", handler.UnsubscribeAddress)

t.Run("successHappyPath", func(t *testing.T) {
address := keypair.MustRandom().Address()
ctx := context.Background()

// Insert address in DB
_, err = dbConnectionPool.ExecContext(ctx, "INSERT INTO accounts (stellar_address) VALUES ($1)", address)
require.NoError(t, err)

// Prepare request
payload := fmt.Sprintf(`{ "address": "%s" }`, address)
req, err := http.NewRequest(http.MethodPost, "/payments/unsubscribe", strings.NewReader(payload))
require.NoError(t, err)

// Serve request
rr := httptest.NewRecorder()
r.ServeHTTP(rr, req)

// Assert 200 response
assert.Equal(t, http.StatusOK, rr.Code)

// Assert no address no longer in DB
var dbAddress sql.NullString
err = dbConnectionPool.GetContext(ctx, &dbAddress, "SELECT stellar_address FROM accounts")
assert.ErrorIs(t, err, sql.ErrNoRows)
})

t.Run("idempotency", func(t *testing.T) {
address := keypair.MustRandom().Address()
ctx := context.Background()

// Make sure DB is empty
_, err = dbConnectionPool.ExecContext(ctx, "DELETE FROM accounts")
require.NoError(t, err)

// Prepare request
payload := fmt.Sprintf(`{ "address": "%s" }`, address)
req, err := http.NewRequest(http.MethodPost, "/payments/unsubscribe", strings.NewReader(payload))
require.NoError(t, err)

// Serve request
rr := httptest.NewRecorder()
r.ServeHTTP(rr, req)

// Assert 200 response
assert.Equal(t, http.StatusOK, rr.Code)
})
}
1 change: 1 addition & 0 deletions internal/serve/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ func handler(deps handlerDeps) http.Handler {
}

r.Post("/subscribe", handler.SubscribeAddress)
r.Post("/unsubscribe", handler.UnsubscribeAddress)
})
})

Expand Down

0 comments on commit c93a897

Please sign in to comment.