package main
import (
        "fmt"
        "github.com/spf13/cobra"
)
//nolint:errcheck
func completionCmd(cmd *cobra.Command) *cobra.Command {
        return &cobra.Command{
                Use:   "completion [bash|zsh|fish|powershell]",
                Short: "Generate completion script",
                Long: fmt.Sprintf(`To load completions:
Bash:
  $ source <(%[1]s completion bash)
  # To load completions for each session, execute once:
  # Linux:
  $ %[1]s completion bash > /etc/bash_completion.d/%[1]s
  # macOS:
  $ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s
Zsh:
  # If shell completion is not already enabled in your environment,
  # you will need to enable it.  You can execute the following once:
  $ echo "autoload -U compinit; compinit" >> ~/.zshrc
  # To load completions for each session, execute once:
  $ %[1]s completion zsh > "${fpath[1]}/_%[1]s"
  # You will need to start a new shell for this setup to take effect.
fish:
  $ %[1]s completion fish | source
  # To load completions for each session, execute once:
  $ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish
PowerShell:
  PS> %[1]s completion powershell | Out-String | Invoke-Expression
  # To load completions for every new session, run:
  PS> %[1]s completion powershell > %[1]s.ps1
  # and source this file from your PowerShell profile.
`, cmd.Root().Name()),
                DisableFlagsInUseLine: true,
                ValidArgs:             []string{"bash", "zsh", "fish", "powershell"},
                Args:                  cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
                Run: func(cmd *cobra.Command, args []string) {
                        switch args[0] {
                        case "bash":
                                cmd.Root().GenBashCompletion(cmd.OutOrStdout())
                        case "zsh":
                                cmd.Root().GenZshCompletion(cmd.OutOrStdout())
                        case "fish":
                                cmd.Root().GenFishCompletion(cmd.OutOrStdout(), true)
                        case "powershell":
                                cmd.Root().GenPowerShellCompletionWithDesc(cmd.OutOrStdout())
                        }
                },
        }
}
		
		package main
import (
        "fmt"
        "log"
        "os"
        "github.com/jovandeginste/payme/payment"
        "github.com/spf13/cobra"
        "github.com/spf13/viper"
)
// CLI to generate SEPA payment QR codes, either as ASCII or PNG
const qrSize = 300
var (
        // gitRef     = "0.0.0-dev"
        // gitRefType = "local"
        gitRefName = "local"
        gitCommit  = "local"
        buildTime  = "manually"
)
type qrParams struct {
        Payment    *payment.Payment
        OutputType string
        OutputFile string
        Debug      bool
}
func main() {
        q := qrParams{
                Payment: payment.New(),
        }
        cmdRoot, err := newCommand(&q)
        if err != nil {
                log.Fatal(err)
        }
        if err := cmdRoot.Execute(); err != nil {
                log.Fatal(err)
        }
}
func newCommand(q *qrParams) (*cobra.Command, error) {
        cmdRoot := &cobra.Command{
                Use:     "payme",
                Version: fmt.Sprintf("%s (%s), built %s\n", gitRefName, gitCommit, buildTime),
                Short:   "Generate SEPA payment QR code",
                Args:    cobra.NoArgs,
                Run: func(_ *cobra.Command, _ []string) {
                        q.generate()
                },
        }
        cmdRoot.AddCommand(completionCmd(cmdRoot))
        if err := q.init(cmdRoot); err != nil {
                return nil, err
        }
        return cmdRoot, nil
}
func (q *qrParams) init(cmdRoot *cobra.Command) error {
        viper.SetEnvPrefix("PAYME")
        for _, e := range []string{"name", "bic", "iban"} {
                if err := viper.BindEnv(e); err != nil {
                        return err
                }
        }
        cmdRoot.Flags().StringVar(&q.OutputType, "output", "stdout", "output type: png or stdout")
        cmdRoot.Flags().StringVar(&q.OutputFile, "file", "", "write code to file, leave empty for stdout")
        cmdRoot.Flags().BoolVar(&q.Debug, "debug", false, "print debug output")
        cmdRoot.Flags().IntVar(&q.Payment.CharacterSet, "character-set", 2, "QR code character set")
        cmdRoot.Flags().IntVar(&q.Payment.Version, "qr-version", 2, "QR code version")
        cmdRoot.Flags().StringVar(&q.Payment.NameBeneficiary, "name", viper.GetString("name"), "Name of the beneficiary")
        cmdRoot.Flags().StringVar(&q.Payment.BICBeneficiary, "bic", viper.GetString("bic"), "BIC of the beneficiary")
        cmdRoot.Flags().StringVar(&q.Payment.IBANBeneficiary, "iban", viper.GetString("iban"), "IBAN of the beneficiary")
        cmdRoot.Flags().Float64Var(&q.Payment.EuroAmount, "amount", 0, "Amount of the transaction")
        cmdRoot.Flags().StringVar(&q.Payment.Remittance, "remittance", "", "Remittance (message)")
        cmdRoot.Flags().StringVar(&q.Payment.Purpose, "purpose", "", "Purpose of the transaction")
        cmdRoot.Flags().BoolVar(&q.Payment.RemittanceIsStructured, "structured", false, "Make the remittance (message) structured")
        return nil
}
func (q *qrParams) generate() {
        var (
                qr  []byte
                err error
        )
        if q.Debug {
                log.Printf("%#v\n", q)
        }
        switch q.OutputType {
        case "png":
                qr, err = q.generateQRPNG()
        case "stdout":
                qr, err = q.generateQRStdout()
        }
        if err != nil {
                log.Fatal(err)
        }
        if q.OutputFile == "" {
                fmt.Fprintf(os.Stdout, "%s", qr)
                return
        }
        err = os.WriteFile(q.OutputFile, qr, 0o600)
        if err != nil {
                log.Fatal(err)
        }
}
func (q *qrParams) generateQRStdout() ([]byte, error) {
        p := q.Payment
        if q.Debug {
                s, err := p.ToString()
                if err != nil {
                        return nil, err
                }
                log.Print("Data: ", s)
        }
        return p.ToQRBytes()
}
func (q *qrParams) generateQRPNG() ([]byte, error) {
        p := q.Payment
        if q.Debug {
                s, err := p.ToString()
                if err != nil {
                        return nil, err
                }
                log.Print("Data: ", s)
        }
        return p.ToQRPNG(qrSize)
}
		
		package payment
import (
        "bytes"
        "image/png"
        "strings"
        "github.com/boombuler/barcode"
        "github.com/boombuler/barcode/qr"
        "github.com/mdp/qrterminal/v3"
)
// ToString returns the content of the QR code as string
// Use this to then generate the QR code in the form you need
func (p *Payment) ToString() (string, error) {
        if err := p.IsValid(); err != nil {
                return "", err
        }
        fields := []string{
                p.ServiceTag,
                p.VersionString(),
                p.CharacterSetString(),
                p.IdentificationCode,
                p.BICBeneficiaryString(),
                p.NameBeneficiary,
                p.IBANBeneficiaryString(),
                p.EuroAmountString(),
                p.PurposeString(),
                p.RemittanceStructured(),
                p.RemittanceText(),
                p.B2OInformation,
        }
        return strings.Join(fields, "\n"), nil
}
// ToQRBytes returns an ASCII representation of the QR code
// You can print this to the console, save to a file, etc.
func (p *Payment) ToQRBytes() ([]byte, error) {
        var result bytes.Buffer
        t, err := p.ToString()
        if err != nil {
                return nil, err
        }
        qrterminal.GenerateHalfBlock(t, qrterminal.M, &result)
        return result.Bytes(), nil
}
// ToQRPNG returns an PNG representation of the QR code
// You should save this to a file, or pass it to an image processing library
func (p *Payment) ToQRPNG(qrSize int) ([]byte, error) {
        t, err := p.ToString()
        if err != nil {
                return nil, err
        }
        // Create the barcode
        qrCode, err := qr.Encode(t, qr.M, qr.Auto)
        if err != nil {
                return nil, err
        }
        // Scale the barcode to qrSize x qrSize pixels
        qrCode, err = barcode.Scale(qrCode, qrSize, qrSize)
        if err != nil {
                return nil, err
        }
        var b bytes.Buffer
        // encode the barcode as png
        err = png.Encode(&b, qrCode)
        return b.Bytes(), err
}
		
		package payment
import (
        "fmt"
        "regexp"
        "strconv"
        "strings"
        "github.com/almerlucke/go-iban/iban"
)
// See: https://www.europeanpaymentscouncil.eu/document-library/guidance-documents/quick-response-code-guidelines-enable-data-capture-initiation
// commonSeparatorChars is a regular expression that matches common separator characters for IBAN
// These characters are removed before the IBAN is validated
var commonSeparatorChars = regexp.MustCompile("[ _-]")
// Payment encapsulates all fields needed to generate the QR code
type Payment struct {
        // ServiceTag should always be BCD
        ServiceTag string
        // Version should be v1 or v2
        Version int
        /*
                1: UTF-8 5: ISO 8859-5
                2: ISO 8859-1 6: ISO 8859-7
                3: ISO 8859-2 7: ISO 8859-10
                4: ISO 8859-4 8: ISO 8859-15
        */
        CharacterSet int
        // IdentificationCode should always be SCT (SEPA Credit Transfer)
        IdentificationCode string
        // AT-23 BIC of the Beneficiary Bank [optional in Version 2]
        // The BIC will continue to be mandatory for SEPA payment transactions involving non-EEA countries.
        BICBeneficiary string
        // AT-21 Name of the Beneficiary
        NameBeneficiary string
        // AT-20 Account number of the Beneficiary
        // Only IBAN is allowed.
        IBANBeneficiary string
        // AT-04 Amount of the Credit Transfer in Euro [optional]
        // Amount must be 0.01 or more and 999999999.99 or less
        EuroAmount float64
        // AT-44 Purpose of the Credit Transfer [optional]
        Purpose string
        // AT-05 Remittance Information (Structured) [optional]
        // Creditor Reference (ISO 11649 RFCreditor Reference may be used
        // *or*
        // AT-05 Remittance Information (Unstructured) [optional]
        Remittance string
        // Beneficiary to originator information [optional]
        B2OInformation string
        // Defines whether the Remittance Information is Structured or Unstructured
        RemittanceIsStructured bool
}
// NewStructured returns a default Payment with the Structured flag enabled
func NewStructured() *Payment {
        p := New()
        p.RemittanceIsStructured = true
        return p
}
// New returns a new Payment struct with default values for version 2
func New() *Payment {
        return &Payment{
                ServiceTag:             "BCD",
                Version:                2,
                CharacterSet:           2,
                IdentificationCode:     "SCT",
                RemittanceIsStructured: false,
        }
}
// IBANBeneficiaryString returns the IBAN of the beneficiary in a standardized form
func (p *Payment) IBANBeneficiaryString() string {
        i, err := p.IBAN()
        if err != nil {
                return ""
        }
        return i.PrintCode
}
// IBAN returns the parsed, sanitized IBAN of the beneficiary
func (p *Payment) IBAN() (*iban.IBAN, error) {
        s := commonSeparatorChars.ReplaceAllString(p.IBANBeneficiary, "")
        return iban.NewIBAN(s)
}
// PurposeString returns the parsed purpose
func (p *Payment) PurposeString() string {
        return strings.ReplaceAll(p.Purpose, " ", "")
}
// VersionString returns the version converted to a 3-digit number with leading zeros
func (p *Payment) VersionString() string {
        return fmt.Sprintf("%03d", p.Version)
}
// CharacterSetString returns the character set converted to string
func (p *Payment) CharacterSetString() string {
        return strconv.Itoa(p.CharacterSet)
}
// EuroAmountString returns the set amount in financial format (eg. EUR12.34)
// or an empty string if the amount is 0
func (p *Payment) EuroAmountString() string {
        return fmt.Sprintf("EUR%.2f", p.EuroAmount)
}
// RemittanceStructured returns the value for the structured remittance line
func (p *Payment) RemittanceStructured() string {
        return p.RemittanceString(true)
}
// RemittanceText returns the value for the unstructured (freeform) remittance line
func (p *Payment) RemittanceText() string {
        return p.RemittanceString(false)
}
// RemittanceString returns the value for the remittance field, independing on being structured
func (p *Payment) RemittanceString(structured bool) string {
        if p.RemittanceIsStructured != structured {
                return ""
        }
        return p.Remittance
}
// BICBeneficiaryString returns the BIC of the beneficiary, depending on the version of the QR code
func (p *Payment) BICBeneficiaryString() string {
        if p.Version != 1 {
                return ""
        }
        return p.BICBeneficiary
}
		
		package payment
import (
        "errors"
        "regexp"
)
const (
        specialChars = `@&+()"':?.,-/`
)
var (
        stringValidator = regexp.MustCompile(`^[\p{L}\d ` + specialChars + `]+$`)
        // ErrValidationServiceTag is returned when ServiceTag is not the correct value
        ErrValidationServiceTag = errors.New("field 'ServiceTag' should be BCD")
        // ErrValidationCharacterSet is returned when CharacterSet is not in the allowed range
        ErrValidationCharacterSet = errors.New("field 'CharacterSet' should be 1..8")
        // ErrValidationVersion is returned when Version is not 1 or 2
        ErrValidationVersion = errors.New("field 'Version' should be 1 or 2")
        // ErrValidationIdentificationCode is returned when IdentificationCode is not the correct value
        ErrValidationIdentificationCode = errors.New("field 'IdentificationCode' should be SCT")
        // ErrValidationBICBeneficiary is returned when BICBeneficiary is not set
        ErrValidationBICBeneficiary = errors.New("field 'BICBeneficiary' is required when version is 1")
        // ErrValidationEuroAmount is returned when EuroAmount is not a valid amount
        ErrValidationEuroAmount = errors.New("field 'EuroAmount' must be 0.01 or more and 999999999.99 or less")
        // ErrValidationPurpose is returned when Purpose is not within bounds
        ErrValidationPurpose = errors.New("field 'Purpose' should not exceed 4 characters")
        // ErrValidationRemittanceRequired is returned when Remittance is empty
        ErrValidationRemittanceRequired = errors.New("field 'Remittance' is required")
        // ErrValidationRemittanceStructuredTooLong is returned when Remittance is not within bounds for structured field
        ErrValidationRemittanceStructuredTooLong = errors.New("structured 'Remittance' should not exceed 35 characters")
        // ErrValidationRemittanceUnstructuredTooLong is returned when Remittance is not within bounds for unstructured field
        ErrValidationRemittanceUnstructuredTooLong = errors.New("unstructured 'Remittance' should not exceed 140 characters")
        // ErrValidationRemittanceUnstructuredCharacters is returned when Remittance contains invalid characters
        ErrValidationRemittanceUnstructuredCharacters = errors.New("unstructured 'Remittance' should only contain alpha-numerics, spaces and/or " + specialChars)
        // ErrValidationNameBeneficiaryRequired is returned when NameBeneficiary is empty
        ErrValidationNameBeneficiaryRequired = errors.New("field 'NameBeneficiary' is required")
        // ErrValidationNameBeneficiaryTooLong is returned when NameBeneficiary is not within bounds
        ErrValidationNameBeneficiaryTooLong = errors.New("field 'NameBeneficiary' should not exceed 70 characers")
        // ErrValidationNameBeneficiaryCharacters is returned when NameBeneficiary contains invalid characters
        ErrValidationNameBeneficiaryCharacters = errors.New("field 'NameBeneficiary' should not only contain alpha-numerics, spaces and/or " + specialChars)
)
// IsValid checks if all fields in the payment are consistent and meet the requirements.
// It returns the first error it encounters, or nil if all is well.
func (p *Payment) IsValid() error {
        return p.validateFields()
}
func (p *Payment) validateFields() error {
        if err := p.validateHeader(); err != nil {
                return err
        }
        if err := p.validateBeneficiary(); err != nil {
                return err
        }
        if p.EuroAmount < 0.01 || p.EuroAmount > 999999999.99 {
                return ErrValidationEuroAmount
        }
        if len(p.PurposeString()) > 4 {
                return ErrValidationPurpose
        }
        return p.validateRemittance()
}
func (p *Payment) validateHeader() error {
        if p.ServiceTag != "BCD" {
                return ErrValidationServiceTag
        }
        if p.CharacterSet < 1 || p.CharacterSet > 8 {
                return ErrValidationCharacterSet
        }
        if p.Version != 1 && p.Version != 2 {
                return ErrValidationVersion
        }
        if p.IdentificationCode != "SCT" {
                return ErrValidationIdentificationCode
        }
        if p.Version == 1 && p.BICBeneficiary == "" {
                return ErrValidationBICBeneficiary
        }
        return nil
}
func (p *Payment) validateRemittance() error {
        if p.Remittance == "" {
                return ErrValidationRemittanceRequired
        }
        if p.RemittanceIsStructured && len(p.Remittance) > 35 {
                return ErrValidationRemittanceStructuredTooLong
        }
        if !p.RemittanceIsStructured {
                if len(p.Remittance) > 140 {
                        return ErrValidationRemittanceUnstructuredTooLong
                }
                if !stringValidator.MatchString(p.Remittance) {
                        return ErrValidationRemittanceUnstructuredCharacters
                }
        }
        return nil
}
func (p *Payment) validateBeneficiary() error {
        if p.NameBeneficiary == "" {
                return ErrValidationNameBeneficiaryRequired
        }
        if len(p.NameBeneficiary) > 70 {
                return ErrValidationNameBeneficiaryTooLong
        }
        if !stringValidator.MatchString(p.NameBeneficiary) {
                return ErrValidationNameBeneficiaryCharacters
        }
        return p.validateIBAN()
}
func (p *Payment) validateIBAN() error {
        _, err := p.IBAN()
        return err
}