Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,14 @@ sumup readers checkout \
--currency EUR \
--tip-rate 0.10 \
--tip-rate 0.15 \
--tip-rate 0.20 \
--description "In-person order #123"
```

When using affiliate attribution, pass all affiliate flags: `--affiliate-app-id`, `--affiliate-key`, and `--affiliate-foreign-transaction-id`.
For Pix payments on Brazilian merchants, add `--payment-type pix`.

When using affiliate attribution, pass both `--affiliate-app-id` and `--affiliate-key`.
Use `--affiliate-foreign-transaction-id` to include an optional transaction reference.

Trigger a checkout on a SumUp Go reader. `--client-transaction-id` is required and used as the idempotency key:

Expand All @@ -135,6 +139,27 @@ sumup readers status \
reader_42
```

Read or update Solo reader menu lock settings. Updates require the reader to be online;
offline reads return the last saved settings when available.

```bash
sumup readers settings get --merchant-code M123 reader_42
sumup readers settings update --merchant-code M123 --menu-lock-enabled=false reader_42
```

Set `SUMUP_READER_MENU_LOCK_PASSCODE` to a four-digit PIN to include it in an update.
When enabling menu lock on a reader without a PIN, provide the PIN in the same update.

## Create a Merchant Token

Create a short-lived merchant token for a SumUp SDK. The command requires
`--json` and returns the access token and its remaining lifetime. Without
`--json`, it exits before creating a token:

```bash
sumup --json tokens create --merchant-code M123 --action checkout
```

## OpenAPI Command Coverage

The CLI keeps its user-facing command implementations handwritten, but derives
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/mergestat/timediff v0.0.4
github.com/shopspring/decimal v1.5.0
github.com/stretchr/testify v1.12.1
github.com/sumup/sumup-go v0.19.0
github.com/sumup/sumup-go v0.21.0
github.com/urfave/cli-docs/v3 v3.1.0
github.com/urfave/cli/v3 v3.14.0
golang.org/x/term v0.46.0
Expand All @@ -33,7 +33,7 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/oauth2 v0.37.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.48.0 // indirect
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ github.com/shopspring/decimal v1.5.0 h1:HxSWk31RmJv0EyY4oLiGQB9u7WPludUwPBdvQZnY
github.com/shopspring/decimal v1.5.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/sumup/sumup-go v0.19.0 h1:/AT4t6R4geRDdAtXiEI/JgOK/PyfPd7va77KEjzYoqU=
github.com/sumup/sumup-go v0.19.0/go.mod h1:D++G2u9zvobimuUrgvSbah/tS3ON2Dmi6yb6ScA2Jpg=
github.com/sumup/sumup-go v0.21.0 h1:SSNgl80kST14mj/YsF5EdIa3O0lAUC/VPgzPMjZG1vE=
github.com/sumup/sumup-go v0.21.0/go.mod h1:LRmcEBSpL1rMvjam6OtD0YBK9cyONiMjNp0DnvSLW1o=
github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw=
github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to=
github.com/urfave/cli/v3 v3.14.0 h1:a8414NQlHJs0c/iBsulKLzlES0n/lEAskbL2LKpU4/s=
Expand All @@ -56,8 +56,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
Expand Down
146 changes: 92 additions & 54 deletions internal/apicommands/catalog.gen.go

Large diffs are not rendered by default.

18 changes: 15 additions & 3 deletions internal/codesamples/codesamples.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ func renderCommand(document *openAPIDocument, bound boundCommand, example operat

func buildInvocation(document *openAPIDocument, bound boundCommand, example operationExample) (commandInvocation, error) {
invocation := commandInvocation{path: strings.Fields(bound.path)}
if operationID, _ := apicommands.OperationID(bound.command); operationID == "CreateMerchantToken" {
invocation.flags = append(invocation.flags, sampleFlag{name: "json", boolean: true})
}
for _, match := range argumentPattern.FindAllStringSubmatch(bound.command.ArgsUsage, -1) {
value, ok := argumentValue(document, example, match[1])
if !ok {
Expand Down Expand Up @@ -216,7 +219,7 @@ func buildInvocation(document *openAPIDocument, bound boundCommand, example oper
if len(values) == 0 {
continue
}
invocation.flags = append(invocation.flags, sampleFlag{name: name, boolean: true})
invocation.flags = append(invocation.flags, sampleFlag{name: name, value: values[0], boolean: true})
continue
}
for _, value := range values {
Expand Down Expand Up @@ -325,6 +328,9 @@ func flagRequired(flag cli.Flag) bool {
}

func flagBoolean(flag cli.Flag) bool {
if _, ok := flag.(*cli.BoolFlag); ok {
return true
}
boolean, ok := flag.(interface{ IsBoolFlag() bool })
return ok && boolean.IsBoolFlag()
}
Expand All @@ -340,7 +346,9 @@ func (invocation commandInvocation) source() string {
source.WriteString(" \\\n ")
source.WriteString("--")
source.WriteString(flag.name)
if !flag.boolean {
if flag.boolean && flag.value == "false" {
source.WriteString("=false")
} else if !flag.boolean {
source.WriteByte(' ')
source.WriteString(shellQuote(flag.value))
}
Expand All @@ -353,7 +361,11 @@ func (invocation commandInvocation) args() []string {
args := append([]string{"sumup"}, invocation.path...)
args = append(args, invocation.arguments...)
for _, flag := range invocation.flags {
args = append(args, "--"+flag.name)
name := "--" + flag.name
if flag.boolean && flag.value == "false" {
name += "=false"
}
args = append(args, name)
if !flag.boolean {
args = append(args, flag.value)
}
Expand Down
54 changes: 47 additions & 7 deletions internal/codesamples/codesamples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func TestGenerate(t *testing.T) {
--merchant-code "MH4H92C7" \
--description "Purchase" \
--redirect-url "https://sumup.com" \
--valid-until "2020-02-29T10:56:56+00:00"
--valid-until "2030-12-31T23:59:59Z"
`, createCheckout.Source)
assert.Equal(t, `sumup merchants persons get "pers_5AKFHN2KSK8D3TS79DJE3P3A2Z" \
--merchant-code "MK10CL2A"
Expand All @@ -74,6 +74,9 @@ func TestGenerate(t *testing.T) {
assert.Contains(t, sampleByID(t, catalog.Samples, "CreateMerchantMember").Source, "sumup members create")
assert.NotContains(t, sampleByID(t, catalog.Samples, "CreateMerchantMember").Source, "members invite")
assert.Equal(t, "# not supported", sampleByID(t, catalog.Samples, "ProcessCheckout").Source)
assert.Contains(t, sampleByID(t, catalog.Samples, "CreateMerchantToken").Source, `--action "checkout"`)
assert.Contains(t, sampleByID(t, catalog.Samples, "CreateMerchantToken").Source, "--json \\\n")
assert.Contains(t, sampleByID(t, catalog.Samples, "UpdateReaderSettings").Source, "--menu-lock-enabled \\\n")

encodedSample, err := json.Marshal(sampleByID(t, catalog.Samples, "CreateCheckout"))
require.NoError(t, err)
Expand Down Expand Up @@ -104,6 +107,39 @@ func TestGenerateRequiresVersion(t *testing.T) {
require.EqualError(t, err, "cli version is required")
}

func TestBooleanInvocations(t *testing.T) {
t.Parallel()

for _, enabled := range []bool{true, false} {
name := "enabled"
wantFlag := "--menu-lock-enabled"
if !enabled {
name = "disabled"
wantFlag += "=false"
}
t.Run(name, func(t *testing.T) {
t.Parallel()

bound := boundCommand{
path: "readers settings update",
command: &cli.Command{Flags: []cli.Flag{
&cli.BoolFlag{Name: "menu-lock-enabled"},
}},
}
example := operationExample{
body: map[string]any{"menu_lock": map[string]any{"enabled": enabled}},
bodyProvided: true,
}

invocation, err := buildInvocation(&openAPIDocument{}, bound, example)

require.NoError(t, err)
assert.Equal(t, []string{"sumup", "readers", "settings", "update", wantFlag}, invocation.args())
assert.Equal(t, "sumup readers settings update \\\n "+wantFlag+"\n", invocation.source())
})
}
}

func TestGeneratedInvocationsReachAPITransport(t *testing.T) {
for _, operation := range apicommands.Operations {
t.Run(operation.ID, func(t *testing.T) {
Expand All @@ -125,16 +161,20 @@ func TestGeneratedInvocationsReachAPITransport(t *testing.T) {
called = true
return nil, transportError
})}
appCtx := &app.Context{
Client: sumup.NewClient(sumupclient.WithClient(httpClient), sumupclient.WithBaseURL("https://api.test")),
MerchantCode: "M123",
Output: io.Discard,
StatusOutput: io.Discard,
}
root := &cli.Command{
Name: "sumup",
Commands: resourceCommands,
Flags: []cli.Flag{
&cli.BoolFlag{Name: "json", Destination: &appCtx.JSONOutput},
},
Metadata: map[string]any{
app.ContextKey: &app.Context{
Client: sumup.NewClient(sumupclient.WithClient(httpClient), sumupclient.WithBaseURL("https://api.test")),
MerchantCode: "M123",
Output: io.Discard,
StatusOutput: io.Discard,
},
app.ContextKey: appCtx,
},
}

Expand Down
9 changes: 3 additions & 6 deletions internal/codesamples/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,10 @@ func formatExampleValue(value any) []string {
case nil:
return nil
case bool:
if !typed {
return nil
}
return []string{""}
return []string{strconv.FormatBool(typed)}
case map[string]any:
if enabled, ok := typed["enabled"].(bool); ok && enabled {
return []string{""}
if enabled, ok := typed["enabled"].(bool); ok {
return []string{strconv.FormatBool(enabled)}
}
return nil
case []any:
Expand Down
2 changes: 2 additions & 0 deletions internal/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/sumup/sumup-cli/internal/commands/readers"
"github.com/sumup/sumup-cli/internal/commands/receipts"
"github.com/sumup/sumup-cli/internal/commands/roles"
"github.com/sumup/sumup-cli/internal/commands/tokens"
"github.com/sumup/sumup-cli/internal/commands/transactions"
"github.com/sumup/sumup-cli/internal/commands/version"
)
Expand All @@ -32,6 +33,7 @@ func All() []*cli.Command {
readers.NewCommand(),
receipts.NewCommand(),
roles.NewCommand(),
tokens.NewCommand(),
transactions.NewCommand(),
version.NewCommand(),
}
Expand Down
7 changes: 7 additions & 0 deletions internal/commands/memberships/memberships.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ func NewCommand() *cli.Command {
Name: "resource-type",
Usage: "Filter memberships by the resource type.",
},
&cli.StringFlag{
Name: "resource-id",
Usage: "Filter memberships by resource ID.",
},
&cli.StringFlag{
Name: "resource-name",
Usage: "Filter memberships by resource name.",
Expand Down Expand Up @@ -92,6 +96,9 @@ func listMemberships(ctx context.Context, cmd *cli.Command) error {
if value := cmd.String("resource-name"); value != "" {
params.ResourceName = &value
}
if value := cmd.String("resource-id"); value != "" {
params.ResourceID = new(value)
}
if cmd.Bool("sandbox") {
value := true
params.ResourceAttributesSandbox = &value
Expand Down
43 changes: 3 additions & 40 deletions internal/commands/merchants/merchants.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ func NewCommand() *cli.Command {
Action: getMerchant,
Flags: []cli.Flag{
merchantCodeFlag("Merchant code to retrieve information for. Falls back to context."),
versionFlag(),
},
}),
{
Expand All @@ -42,7 +41,6 @@ func NewCommand() *cli.Command {
Action: listPersons,
Flags: []cli.Flag{
merchantCodeFlag("Merchant code whose people should be listed. Falls back to context."),
versionFlag(),
},
}),
apicommands.Bind("GetPerson", &cli.Command{
Expand All @@ -52,7 +50,6 @@ func NewCommand() *cli.Command {
Action: getPerson,
Flags: []cli.Flag{
merchantCodeFlag("Merchant code associated with the person. Falls back to context."),
versionFlag(),
},
}),
},
Expand All @@ -69,13 +66,6 @@ func merchantCodeFlag(usage string) cli.Flag {
}
}

func versionFlag() cli.Flag {
return &cli.StringFlag{
Name: "version",
Usage: "Resource version to retrieve. Currently only latest is supported.",
}
}

func getMerchant(ctx context.Context, cmd *cli.Command) error {
appCtx, err := app.GetAppContext(cmd)
if err != nil {
Expand All @@ -87,12 +77,7 @@ func getMerchant(ctx context.Context, cmd *cli.Command) error {
return err
}

version, err := resourceVersion(cmd)
if err != nil {
return err
}

merchant, err := appCtx.Client.Merchants.Get(ctx, merchantCode, sumup.MerchantsGetParams{Version: version})
merchant, err := appCtx.Client.Merchants.Get(ctx, merchantCode)
if err != nil {
return fmt.Errorf("get merchant: %w", err)
}
Expand All @@ -113,12 +98,7 @@ func listPersons(ctx context.Context, cmd *cli.Command) error {
if err != nil {
return err
}
version, err := resourceVersion(cmd)
if err != nil {
return err
}

response, err := appCtx.Client.Merchants.ListPersons(ctx, merchantCode, sumup.MerchantsListPersonsParams{Version: version})
response, err := appCtx.Client.Merchants.ListPersons(ctx, merchantCode)
if err != nil {
return fmt.Errorf("list persons: %w", err)
}
Expand Down Expand Up @@ -160,12 +140,7 @@ func getPerson(ctx context.Context, cmd *cli.Command) error {
if err != nil {
return err
}
version, err := resourceVersion(cmd)
if err != nil {
return err
}

person, err := appCtx.Client.Merchants.GetPerson(ctx, merchantCode, personID, sumup.MerchantsGetPersonParams{Version: version})
person, err := appCtx.Client.Merchants.GetPerson(ctx, merchantCode, personID)
if err != nil {
return fmt.Errorf("get person: %w", err)
}
Expand All @@ -175,18 +150,6 @@ func getPerson(ctx context.Context, cmd *cli.Command) error {
return renderPerson(appCtx.Output, person)
}

func resourceVersion(cmd *cli.Command) (*string, error) {
if !cmd.IsSet("version") {
return nil, nil
}

version := strings.TrimSpace(cmd.String("version"))
if version != "latest" {
return nil, fmt.Errorf("version must be latest")
}
return &version, nil
}

func renderMerchant(appCtx *app.Context, merchant *sumup.Merchant) error {
if merchant == nil {
return nil
Expand Down
Loading
Loading