Skip to content

Commit 234627b

Browse files
authored
refactor: extract transaction command parsing (#43)
1 parent d09f8f2 commit 234627b

2 files changed

Lines changed: 259 additions & 96 deletions

File tree

internal/commands/transactions/transactions.go

Lines changed: 123 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -138,62 +138,9 @@ func listTransactions(ctx context.Context, cmd *cli.Command) error {
138138
return err
139139
}
140140

141-
params := sumup.TransactionsListParams{}
142-
if cmd.IsSet("limit") {
143-
value := cmd.Int("limit")
144-
params.Limit = &value
145-
}
146-
if ts, err := parseRFC3339Flag(cmd, "changes-since"); err != nil {
147-
return err
148-
} else if ts != nil {
149-
params.ChangesSince = ts
150-
}
151-
if cmd.IsSet("newest-ref") {
152-
value := cmd.String("newest-ref")
153-
params.NewestRef = &value
154-
}
155-
if ts, err := parseRFC3339Flag(cmd, "newest-time"); err != nil {
156-
return err
157-
} else if ts != nil {
158-
params.NewestTime = ts
159-
}
160-
if cmd.IsSet("oldest-ref") {
161-
value := cmd.String("oldest-ref")
162-
params.OldestRef = &value
163-
}
164-
if ts, err := parseRFC3339Flag(cmd, "oldest-time"); err != nil {
141+
params, err := transactionsListParamsFromCommand(cmd)
142+
if err != nil {
165143
return err
166-
} else if ts != nil {
167-
params.OldestTime = ts
168-
}
169-
if cmd.IsSet("order") {
170-
value := cmd.String("order")
171-
params.Order = &value
172-
}
173-
if values := cmd.StringSlice("payment-type"); len(values) > 0 {
174-
types := make([]sumup.PaymentType, 0, len(values))
175-
for _, v := range values {
176-
if v == "" {
177-
continue
178-
}
179-
types = append(types, sumup.PaymentType(v))
180-
}
181-
if len(types) > 0 {
182-
params.PaymentTypes = types
183-
}
184-
}
185-
if values := cmd.StringSlice("status"); len(values) > 0 {
186-
params.Statuses = values
187-
}
188-
if cmd.IsSet("transaction-code") {
189-
value := cmd.String("transaction-code")
190-
params.TransactionCode = &value
191-
}
192-
if values := cmd.StringSlice("type"); len(values) > 0 {
193-
params.Types = values
194-
}
195-
if values := cmd.StringSlice("user"); len(values) > 0 {
196-
params.Users = values
197144
}
198145

199146
response, err := appCtx.Client.Transactions.List(ctx, merchantCode, params)
@@ -210,22 +157,10 @@ func listTransactions(ctx context.Context, cmd *cli.Command) error {
210157
return display.PrintJSON(items)
211158
}
212159

213-
rows := make([][]attribute.Value, 0, len(items))
214-
for _, tx := range items {
215-
rows = append(rows, []attribute.Value{
216-
attribute.OptionalStringValue(tx.ID),
217-
attribute.OptionalStringValue(tx.TransactionCode),
218-
attribute.ValueOf(currency.FormatPointers(tx.Amount, tx.Currency)),
219-
attribute.OptionalValue(tx.Status),
220-
attribute.OptionalValue(tx.PaymentType),
221-
attribute.ValueOf(util.TimeOrDash(appCtx, tx.Timestamp)),
222-
})
223-
}
224-
225160
display.RenderTable(
226161
"Transactions",
227162
[]string{"ID", "Code", "Amount", "Status", "Payment Type", "Created At"},
228-
rows,
163+
transactionRows(appCtx, items),
229164
)
230165
return nil
231166
}
@@ -241,34 +176,9 @@ func getTransaction(ctx context.Context, cmd *cli.Command) error {
241176
return err
242177
}
243178

244-
params := sumup.TransactionsGetParams{}
245-
lookupCount := 0
246-
if cmd.Args().Len() > 0 {
247-
transactionID := cmd.Args().Get(0)
248-
params.ID = &transactionID
249-
lookupCount++
250-
}
251-
if value := cmd.String("internal-id"); value != "" {
252-
params.InternalID = &value
253-
lookupCount++
254-
}
255-
if value := cmd.String("transaction-code"); value != "" {
256-
params.TransactionCode = &value
257-
lookupCount++
258-
}
259-
if value := cmd.String("foreign-transaction-id"); value != "" {
260-
params.ForeignTransactionID = &value
261-
lookupCount++
262-
}
263-
if value := cmd.String("client-transaction-id"); value != "" {
264-
params.ClientTransactionID = &value
265-
lookupCount++
266-
}
267-
if lookupCount == 0 {
268-
return fmt.Errorf("provide a transaction ID argument or one lookup flag")
269-
}
270-
if lookupCount > 1 {
271-
return fmt.Errorf("provide exactly one transaction lookup")
179+
params, err := transactionLookupParamsFromCommand(cmd)
180+
if err != nil {
181+
return err
272182
}
273183

274184
transaction, err := appCtx.Client.Transactions.Get(ctx, merchantCode, params)
@@ -354,6 +264,123 @@ func transactionCardLabel(card *sumup.CardResponse) string {
354264
return strings.Join(parts, " ")
355265
}
356266

267+
func transactionRows(appCtx *app.Context, items []sumup.TransactionHistory) [][]attribute.Value {
268+
rows := make([][]attribute.Value, 0, len(items))
269+
for _, tx := range items {
270+
rows = append(rows, []attribute.Value{
271+
attribute.OptionalStringValue(tx.ID),
272+
attribute.OptionalStringValue(tx.TransactionCode),
273+
attribute.ValueOf(currency.FormatPointers(tx.Amount, tx.Currency)),
274+
attribute.OptionalValue(tx.Status),
275+
attribute.OptionalValue(tx.PaymentType),
276+
attribute.ValueOf(util.TimeOrDash(appCtx, tx.Timestamp)),
277+
})
278+
}
279+
280+
return rows
281+
}
282+
283+
func transactionsListParamsFromCommand(cmd *cli.Command) (sumup.TransactionsListParams, error) {
284+
params := sumup.TransactionsListParams{}
285+
if cmd.IsSet("limit") {
286+
value := cmd.Int("limit")
287+
params.Limit = &value
288+
}
289+
if ts, err := parseRFC3339Flag(cmd, "changes-since"); err != nil {
290+
return sumup.TransactionsListParams{}, err
291+
} else if ts != nil {
292+
params.ChangesSince = ts
293+
}
294+
if cmd.IsSet("newest-ref") {
295+
value := cmd.String("newest-ref")
296+
params.NewestRef = &value
297+
}
298+
if ts, err := parseRFC3339Flag(cmd, "newest-time"); err != nil {
299+
return sumup.TransactionsListParams{}, err
300+
} else if ts != nil {
301+
params.NewestTime = ts
302+
}
303+
if cmd.IsSet("oldest-ref") {
304+
value := cmd.String("oldest-ref")
305+
params.OldestRef = &value
306+
}
307+
if ts, err := parseRFC3339Flag(cmd, "oldest-time"); err != nil {
308+
return sumup.TransactionsListParams{}, err
309+
} else if ts != nil {
310+
params.OldestTime = ts
311+
}
312+
if cmd.IsSet("order") {
313+
value := cmd.String("order")
314+
params.Order = &value
315+
}
316+
if values := cmd.StringSlice("payment-type"); len(values) > 0 {
317+
params.PaymentTypes = paymentTypesFromStrings(values)
318+
}
319+
if values := cmd.StringSlice("status"); len(values) > 0 {
320+
params.Statuses = values
321+
}
322+
if cmd.IsSet("transaction-code") {
323+
value := cmd.String("transaction-code")
324+
params.TransactionCode = &value
325+
}
326+
if values := cmd.StringSlice("type"); len(values) > 0 {
327+
params.Types = values
328+
}
329+
if values := cmd.StringSlice("user"); len(values) > 0 {
330+
params.Users = values
331+
}
332+
333+
return params, nil
334+
}
335+
336+
func transactionLookupParamsFromCommand(cmd *cli.Command) (sumup.TransactionsGetParams, error) {
337+
params := sumup.TransactionsGetParams{}
338+
lookupCount := 0
339+
340+
if cmd.Args().Len() > 0 {
341+
transactionID := cmd.Args().Get(0)
342+
params.ID = &transactionID
343+
lookupCount++
344+
}
345+
if value := cmd.String("internal-id"); value != "" {
346+
params.InternalID = &value
347+
lookupCount++
348+
}
349+
if value := cmd.String("transaction-code"); value != "" {
350+
params.TransactionCode = &value
351+
lookupCount++
352+
}
353+
if value := cmd.String("foreign-transaction-id"); value != "" {
354+
params.ForeignTransactionID = &value
355+
lookupCount++
356+
}
357+
if value := cmd.String("client-transaction-id"); value != "" {
358+
params.ClientTransactionID = &value
359+
lookupCount++
360+
}
361+
362+
switch lookupCount {
363+
case 0:
364+
return sumup.TransactionsGetParams{}, fmt.Errorf("provide a transaction ID argument or one lookup flag")
365+
case 1:
366+
return params, nil
367+
default:
368+
return sumup.TransactionsGetParams{}, fmt.Errorf("provide exactly one transaction lookup")
369+
}
370+
}
371+
372+
func paymentTypesFromStrings(values []string) []sumup.PaymentType {
373+
types := make([]sumup.PaymentType, 0, len(values))
374+
for _, v := range values {
375+
if v == "" {
376+
continue
377+
}
378+
types = append(types, sumup.PaymentType(v))
379+
}
380+
381+
return types
382+
}
383+
357384
func parseRFC3339Flag(cmd *cli.Command, name string) (*time.Time, error) {
358385
if !cmd.IsSet(name) {
359386
return nil, nil
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package transactions
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
sumup "github.com/sumup/sumup-go"
9+
"github.com/urfave/cli/v3"
10+
)
11+
12+
func TestTransactionsListParamsFromCommand(t *testing.T) {
13+
t.Run("maps CLI flags into request params", func(t *testing.T) {
14+
cmd := runCommandForTest(t, []string{
15+
"sumup",
16+
"--limit", "5",
17+
"--changes-since", "2026-03-26T12:00:00Z",
18+
"--payment-type", "CARD",
19+
"--payment-type", "",
20+
"--status", "SUCCESSFUL",
21+
"--user", "user@example.com",
22+
}, listTransactionsFlags())
23+
24+
params, err := transactionsListParamsFromCommand(cmd)
25+
if err != nil {
26+
t.Fatalf("transactionsListParamsFromCommand() error = %v", err)
27+
}
28+
29+
if params.Limit == nil || *params.Limit != 5 {
30+
t.Fatalf("Limit = %v, want 5", params.Limit)
31+
}
32+
if params.ChangesSince == nil || !params.ChangesSince.Equal(time.Date(2026, time.March, 26, 12, 0, 0, 0, time.UTC)) {
33+
t.Fatalf("ChangesSince = %v, want 2026-03-26T12:00:00Z", params.ChangesSince)
34+
}
35+
if len(params.PaymentTypes) != 1 || params.PaymentTypes[0] != sumup.PaymentType("CARD") {
36+
t.Fatalf("PaymentTypes = %v, want [CARD]", params.PaymentTypes)
37+
}
38+
if len(params.Statuses) != 1 || params.Statuses[0] != "SUCCESSFUL" {
39+
t.Fatalf("Statuses = %v, want [SUCCESSFUL]", params.Statuses)
40+
}
41+
if len(params.Users) != 1 || params.Users[0] != "user@example.com" {
42+
t.Fatalf("Users = %v, want [user@example.com]", params.Users)
43+
}
44+
})
45+
46+
t.Run("rejects invalid RFC3339 timestamps", func(t *testing.T) {
47+
cmd := runCommandForTest(t, []string{"sumup", "--changes-since", "not-a-time"}, listTransactionsFlags())
48+
49+
if _, err := transactionsListParamsFromCommand(cmd); err == nil {
50+
t.Fatal("transactionsListParamsFromCommand() error = nil, want non-nil")
51+
}
52+
})
53+
}
54+
55+
func TestTransactionLookupParamsFromCommand(t *testing.T) {
56+
t.Run("accepts exactly one lookup", func(t *testing.T) {
57+
cmd := runCommandForTest(t, []string{"sumup", "--transaction-code", "TX123"}, getTransactionFlags())
58+
59+
params, err := transactionLookupParamsFromCommand(cmd)
60+
if err != nil {
61+
t.Fatalf("transactionLookupParamsFromCommand() error = %v", err)
62+
}
63+
if params.TransactionCode == nil || *params.TransactionCode != "TX123" {
64+
t.Fatalf("TransactionCode = %v, want TX123", params.TransactionCode)
65+
}
66+
})
67+
68+
t.Run("rejects missing lookups", func(t *testing.T) {
69+
cmd := runCommandForTest(t, []string{"sumup"}, getTransactionFlags())
70+
71+
if _, err := transactionLookupParamsFromCommand(cmd); err == nil {
72+
t.Fatal("transactionLookupParamsFromCommand() error = nil, want non-nil")
73+
}
74+
})
75+
76+
t.Run("rejects multiple lookups", func(t *testing.T) {
77+
cmd := runCommandForTest(t, []string{"sumup", "txn-1", "--transaction-code", "TX123"}, getTransactionFlags())
78+
79+
if _, err := transactionLookupParamsFromCommand(cmd); err == nil {
80+
t.Fatal("transactionLookupParamsFromCommand() error = nil, want non-nil")
81+
}
82+
})
83+
}
84+
85+
func TestPaymentTypesFromStrings(t *testing.T) {
86+
got := paymentTypesFromStrings([]string{"CARD", "", "CASH"})
87+
if len(got) != 2 || got[0] != sumup.PaymentType("CARD") || got[1] != sumup.PaymentType("CASH") {
88+
t.Fatalf("paymentTypesFromStrings() = %v, want [CARD CASH]", got)
89+
}
90+
}
91+
92+
func listTransactionsFlags() []cli.Flag {
93+
return []cli.Flag{
94+
&cli.IntFlag{Name: "limit"},
95+
&cli.StringFlag{Name: "changes-since"},
96+
&cli.StringFlag{Name: "newest-ref"},
97+
&cli.StringFlag{Name: "newest-time"},
98+
&cli.StringFlag{Name: "oldest-ref"},
99+
&cli.StringFlag{Name: "oldest-time"},
100+
&cli.StringFlag{Name: "order"},
101+
&cli.StringSliceFlag{Name: "payment-type"},
102+
&cli.StringSliceFlag{Name: "status"},
103+
&cli.StringFlag{Name: "transaction-code"},
104+
&cli.StringSliceFlag{Name: "type"},
105+
&cli.StringSliceFlag{Name: "user"},
106+
}
107+
}
108+
109+
func getTransactionFlags() []cli.Flag {
110+
return []cli.Flag{
111+
&cli.StringFlag{Name: "internal-id"},
112+
&cli.StringFlag{Name: "transaction-code"},
113+
&cli.StringFlag{Name: "foreign-transaction-id"},
114+
&cli.StringFlag{Name: "client-transaction-id"},
115+
}
116+
}
117+
118+
func runCommandForTest(t *testing.T, args []string, flags []cli.Flag) *cli.Command {
119+
t.Helper()
120+
121+
var captured *cli.Command
122+
cmd := &cli.Command{
123+
Name: "sumup",
124+
Flags: flags,
125+
Action: func(_ context.Context, cmd *cli.Command) error {
126+
captured = cmd
127+
return nil
128+
},
129+
}
130+
131+
if err := cmd.Run(context.Background(), args); err != nil {
132+
t.Fatalf("Run() error = %v", err)
133+
}
134+
135+
return captured
136+
}

0 commit comments

Comments
 (0)