@@ -3,88 +3,330 @@ package customers
33import (
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
1721func 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