Skip to content

Commit 05c7a77

Browse files
authored
feat: support customers (#26)
1 parent b61b2cb commit 05c7a77

File tree

1 file changed

+279
-37
lines changed

1 file changed

+279
-37
lines changed

internal/commands/customers/customers.go

Lines changed: 279 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,88 +3,330 @@ package customers
33
import (
44
"context"
55
"fmt"
6+
"strings"
7+
"time"
68

79
"github.com/urfave/cli/v3"
810

911
sumup "github.com/sumup/sumup-go"
12+
"github.com/sumup/sumup-go/datetime"
1013

1114
"github.com/sumup/sumup-cli/internal/app"
1215
"github.com/sumup/sumup-cli/internal/commands/util"
1316
"github.com/sumup/sumup-cli/internal/display"
1417
"github.com/sumup/sumup-cli/internal/display/attribute"
18+
"github.com/sumup/sumup-cli/internal/display/message"
1519
)
1620

1721
func NewCommand() *cli.Command {
1822
return &cli.Command{
1923
Name: "customers",
20-
Usage: "Commands for managing sumup.",
24+
Usage: "Commands for managing customers.",
2125
Commands: []*cli.Command{
2226
{
23-
Name: "list",
24-
Usage: "List saved payment instruments for a customer.",
25-
Action: listPaymentInstruments,
27+
Name: "create",
28+
Usage: "Create a customer.",
29+
Action: createCustomer,
30+
Flags: customerDetailsFlags(),
31+
},
32+
{
33+
Name: "get",
34+
Usage: "Get a customer by ID.",
35+
Action: getCustomer,
2636
ArgsUsage: "<customer-id>",
2737
},
38+
{
39+
Name: "update",
40+
Usage: "Update customer details.",
41+
Action: updateCustomer,
42+
ArgsUsage: "<customer-id>",
43+
Flags: customerDetailsFlags(),
44+
},
45+
},
46+
}
47+
}
48+
49+
func customerDetailsFlags() []cli.Flag {
50+
return []cli.Flag{
51+
&cli.StringFlag{
52+
Name: "first-name",
53+
Usage: "Customer first name.",
54+
},
55+
&cli.StringFlag{
56+
Name: "last-name",
57+
Usage: "Customer last name.",
58+
},
59+
&cli.StringFlag{
60+
Name: "email",
61+
Usage: "Customer email address.",
62+
},
63+
&cli.StringFlag{
64+
Name: "phone",
65+
Usage: "Customer phone number.",
66+
},
67+
&cli.StringFlag{
68+
Name: "tax-id",
69+
Usage: "Customer tax identifier.",
70+
},
71+
&cli.StringFlag{
72+
Name: "birth-date",
73+
Usage: "Customer birth date in YYYY-MM-DD format.",
74+
},
75+
&cli.StringFlag{
76+
Name: "address-line-1",
77+
Usage: "Address line 1.",
78+
},
79+
&cli.StringFlag{
80+
Name: "address-line-2",
81+
Usage: "Address line 2.",
82+
},
83+
&cli.StringFlag{
84+
Name: "address-city",
85+
Usage: "Address city.",
86+
},
87+
&cli.StringFlag{
88+
Name: "address-postal-code",
89+
Usage: "Address postal code.",
90+
},
91+
&cli.StringFlag{
92+
Name: "address-state",
93+
Usage: "Address state.",
94+
},
95+
&cli.StringFlag{
96+
Name: "address-country",
97+
Usage: "Address country code (ISO 3166-1 alpha-2).",
2898
},
2999
}
30100
}
31101

32-
func listPaymentInstruments(ctx context.Context, cmd *cli.Command) error {
102+
func createCustomer(ctx context.Context, cmd *cli.Command) error {
33103
appCtx, err := app.GetAppContext(cmd)
34104
if err != nil {
35105
return err
36106
}
107+
108+
personalDetails, _, err := customerDetailsFromFlags(cmd)
109+
if err != nil {
110+
return err
111+
}
112+
113+
body := sumup.CustomersCreateParams{
114+
PersonalDetails: personalDetails,
115+
}
116+
customer, err := appCtx.Client.Customers.Create(ctx, body)
117+
if err != nil {
118+
return fmt.Errorf("create customer: %w", err)
119+
}
120+
121+
if appCtx.JSONOutput {
122+
return display.PrintJSON(customer)
123+
}
124+
125+
message.Success("Customer created")
126+
renderCustomer(customer)
127+
return nil
128+
}
129+
130+
func getCustomer(ctx context.Context, cmd *cli.Command) error {
131+
appCtx, err := app.GetAppContext(cmd)
132+
if err != nil {
133+
return err
134+
}
135+
37136
customerID, err := util.RequireSingleArg(cmd, "customer ID")
38137
if err != nil {
39138
return err
40139
}
41-
instruments, err := appCtx.Client.Customers.ListPaymentInstruments(ctx, customerID)
140+
141+
customer, err := appCtx.Client.Customers.Get(ctx, customerID)
42142
if err != nil {
43-
return fmt.Errorf("list customer payment instruments: %w", err)
143+
return fmt.Errorf("get customer: %w", err)
44144
}
45145

46146
if appCtx.JSONOutput {
47-
return display.PrintJSON(instruments)
147+
return display.PrintJSON(customer)
48148
}
49149

50-
rows := make([][]attribute.Value, 0, len(*instruments))
51-
for _, instrument := range *instruments {
52-
rows = append(rows, []attribute.Value{
53-
attribute.OptionalStringValue(instrument.Token),
54-
attribute.ValueOf(paymentInstrumentType(&instrument)),
55-
attribute.ValueOf(lastFour(&instrument)),
56-
attribute.ValueOf(util.BoolLabel(instrument.Active)),
57-
attribute.ValueOf(util.TimeOrDash(appCtx, instrument.CreatedAt)),
58-
})
150+
renderCustomer(customer)
151+
return nil
152+
}
153+
154+
func updateCustomer(ctx context.Context, cmd *cli.Command) error {
155+
appCtx, err := app.GetAppContext(cmd)
156+
if err != nil {
157+
return err
59158
}
60159

61-
display.RenderTable(
62-
"Payment Instruments",
63-
[]string{"Token", "Type", "Last 4", "Active", "Created At"},
64-
rows,
65-
)
160+
customerID, err := util.RequireSingleArg(cmd, "customer ID")
161+
if err != nil {
162+
return err
163+
}
164+
165+
personalDetails, changedCount, err := customerDetailsFromFlags(cmd)
166+
if err != nil {
167+
return err
168+
}
169+
if changedCount == 0 {
170+
return fmt.Errorf("no update fields provided")
171+
}
172+
173+
body := sumup.CustomersUpdateParams{
174+
PersonalDetails: personalDetails,
175+
}
176+
customer, err := appCtx.Client.Customers.Update(ctx, customerID, body)
177+
if err != nil {
178+
return fmt.Errorf("update customer: %w", err)
179+
}
180+
181+
if appCtx.JSONOutput {
182+
return display.PrintJSON(customer)
183+
}
184+
185+
message.Success("Customer updated")
186+
renderCustomer(customer)
66187
return nil
67188
}
68189

69-
func paymentInstrumentType(instrument *sumup.PaymentInstrumentResponse) string {
70-
if instrument.Type != nil {
71-
value := string(*instrument.Type)
72-
if value != "" {
73-
return value
74-
}
190+
func customerDetailsFromFlags(cmd *cli.Command) (*sumup.PersonalDetails, int, error) {
191+
details := &sumup.PersonalDetails{}
192+
changedCount := 0
193+
194+
if value := cmd.String("first-name"); value != "" {
195+
details.FirstName = &value
196+
changedCount++
197+
}
198+
if value := cmd.String("last-name"); value != "" {
199+
details.LastName = &value
200+
changedCount++
201+
}
202+
if value := cmd.String("email"); value != "" {
203+
details.Email = &value
204+
changedCount++
205+
}
206+
if value := cmd.String("phone"); value != "" {
207+
details.Phone = &value
208+
changedCount++
209+
}
210+
if value := cmd.String("tax-id"); value != "" {
211+
details.TaxID = &value
212+
changedCount++
75213
}
76-
if instrument.Card != nil && instrument.Card.Type != nil {
77-
value := string(*instrument.Card.Type)
78-
if value != "" {
79-
return value
214+
if value := cmd.String("birth-date"); value != "" {
215+
parsedDate, err := parseDate(value)
216+
if err != nil {
217+
return nil, 0, err
80218
}
219+
details.BirthDate = parsedDate
220+
changedCount++
221+
}
222+
223+
var address sumup.AddressLegacy
224+
addressChanged := false
225+
if value := cmd.String("address-line-1"); value != "" {
226+
address.Line1 = &value
227+
addressChanged = true
228+
changedCount++
229+
}
230+
if value := cmd.String("address-line-2"); value != "" {
231+
address.Line2 = &value
232+
addressChanged = true
233+
changedCount++
234+
}
235+
if value := cmd.String("address-city"); value != "" {
236+
address.City = &value
237+
addressChanged = true
238+
changedCount++
239+
}
240+
if value := cmd.String("address-postal-code"); value != "" {
241+
address.PostalCode = &value
242+
addressChanged = true
243+
changedCount++
244+
}
245+
if value := cmd.String("address-state"); value != "" {
246+
address.State = &value
247+
addressChanged = true
248+
changedCount++
81249
}
82-
return "-"
250+
if value := cmd.String("address-country"); value != "" {
251+
address.Country = &value
252+
addressChanged = true
253+
changedCount++
254+
}
255+
256+
if addressChanged {
257+
details.Address = &address
258+
}
259+
if changedCount == 0 {
260+
return nil, 0, nil
261+
}
262+
263+
return details, changedCount, nil
83264
}
84265

85-
func lastFour(instrument *sumup.PaymentInstrumentResponse) string {
86-
if instrument.Card != nil && instrument.Card.Last4Digits != nil && *instrument.Card.Last4Digits != "" {
87-
return *instrument.Card.Last4Digits
266+
func parseDate(value string) (*datetime.Date, error) {
267+
parsed, err := time.Parse(time.DateOnly, value)
268+
if err != nil {
269+
return nil, fmt.Errorf("invalid date %q: %w", value, err)
270+
}
271+
date := datetime.Date{Time: parsed}
272+
return &date, nil
273+
}
274+
275+
func renderCustomer(customer *sumup.Customer) {
276+
if customer == nil {
277+
return
278+
}
279+
280+
details := []attribute.KeyValue{
281+
attribute.Attribute("Customer ID", attribute.Styled(customer.CustomerID)),
282+
}
283+
if customer.PersonalDetails == nil {
284+
display.DataList(details)
285+
return
286+
}
287+
288+
personal := customer.PersonalDetails
289+
details = append(details, attribute.OptionalString("First Name", personal.FirstName))
290+
details = append(details, attribute.OptionalString("Last Name", personal.LastName))
291+
details = append(details, attribute.OptionalString("Email", personal.Email))
292+
details = append(details, attribute.OptionalString("Phone", personal.Phone))
293+
details = append(details, attribute.OptionalString("Tax ID", personal.TaxID))
294+
if personal.BirthDate != nil {
295+
birthDate := personal.BirthDate.Format(time.DateOnly)
296+
details = append(details, attribute.Attribute("Birth Date", attribute.Styled(birthDate)))
297+
} else {
298+
details = append(details, attribute.Attribute("Birth Date", attribute.Styled("-")))
299+
}
300+
details = append(details, attribute.Attribute("Address", attribute.Styled(formatAddress(personal.Address))))
301+
display.DataList(details)
302+
}
303+
304+
func formatAddress(address *sumup.AddressLegacy) string {
305+
if address == nil {
306+
return "-"
307+
}
308+
309+
parts := make([]string, 0, 6)
310+
if address.Line1 != nil && *address.Line1 != "" {
311+
parts = append(parts, *address.Line1)
312+
}
313+
if address.Line2 != nil && *address.Line2 != "" {
314+
parts = append(parts, *address.Line2)
315+
}
316+
if address.City != nil && *address.City != "" {
317+
parts = append(parts, *address.City)
318+
}
319+
if address.PostalCode != nil && *address.PostalCode != "" {
320+
parts = append(parts, *address.PostalCode)
321+
}
322+
if address.State != nil && *address.State != "" {
323+
parts = append(parts, *address.State)
324+
}
325+
if address.Country != nil && *address.Country != "" {
326+
parts = append(parts, *address.Country)
327+
}
328+
if len(parts) == 0 {
329+
return "-"
88330
}
89-
return "-"
331+
return strings.Join(parts, ", ")
90332
}

0 commit comments

Comments
 (0)