package cash import ( "cmp" "database/sql" "errors" "fmt" "strconv" "strings" "time" _ "modernc.org/sqlite" "gopkg.awl.red/lx/internal/merchant/generic" ) type merchant struct { db *sql.DB } var ( ErrBusinessExists = errors.New("business exists") ErrNoSuchCustomer = errors.New("no such customer exists") ErrNoCustomerIds = errors.New("no such customer ids provided for search") ErrNoUniqueBusiness = errors.New("no unique business could be found given the inputs") ErrCouldNotCheckBusiness = errors.New("could not check for existing business") ErrBadCustomerId = errors.New("customer id invalid") ErrNoLatestPaymentOnPlan = errors.New("error no latest payment on plan") ) func (m *merchant) errIfBusinessExists(name string) error { checkConflictBusinessSql := `SELECT name FROM business WHERE name = $1;` rows, err := m.db.Query(checkConflictBusinessSql) if err != nil { return fmt.Errorf("%w: %w", ErrCouldNotCheckBusiness, err) } for rows.Next() { return ErrBusinessExists } if err := rows.Err(); err != nil { return err } return nil } func mkPl(start int) func() string { i := start return func() string { pl := "$" + strconv.Itoa(i) i += 1 return pl } } func (m *merchant) insertBusiness(signup generic.MercherSignup) (*generic.MercherCustomer, error) { pl := mkPl(1) cols := []string{ "name", "address_city", "address_country", "address_line1", "address_line2", "address_postal_code", "address_state", "business_tier_name", } pls := []string{ pl(), pl(), pl(), pl(), pl(), pl(), pl(), pl(), } vals := []any{ cmp.Or(signup.BusinessName, signup.Firstname+" "+signup.Lastname), signup.AddressCity, signup.AddressCountry, signup.AddressLine1, signup.AddressLine2, signup.AddressPostalCode, signup.AddressState, signup.BusinessTierString, } insertBusinessSql := fmt.Sprintf(`INSERT INTO business (%s) VALUES (%s) RETURNING CAST(id AS TEXT)`, strings.Join(cols, ", "), strings.Join(pls, ", ")) row := m.db.QueryRow(insertBusinessSql, vals...) var businessIdStr string err := row.Scan(&businessIdStr) if err != nil { return nil, err } return &generic.MercherCustomer{ PlanId: signup.BusinessTierString, MercherPlanId: signup.BusinessTierString, CustomerId: businessIdStr, }, nil } func (m *merchant) CreateBusiness(signup generic.MercherSignup) (*generic.MercherCustomer, error) { if err := m.errIfBusinessExists(signup.BusinessName); err != nil { return nil, err } customer, err := m.insertBusiness(signup) return customer, err } func (m *merchant) GetPlanExpires(ids generic.MercherCustomerIds) (time.Time, error) { if ids.CustomerId == "" && ids.BusinessName == "" && ids.Email == "" && ids.Phone == "" { return time.Time{}, ErrNoCustomerIds } var customerIdField string var customerId any if ids.CustomerId != "" { customerIdInt, err := strconv.Atoi(ids.CustomerId) if err != nil { return time.Time{}, ErrBadCustomerId } customerIdField = "id" customerId = customerIdInt } else if ids.Email != "" { customerIdField = "email" customerId = ids.Email } else if ids.Phone != "" { customerIdField = "phone" customerId = ids.Phone } else if ids.BusinessName != "" { customerIdField = "name" customerId = ids.BusinessName } // ;ustomerId = getPlanExpirySql := fmt.Sprintf(` SELECT b.business_tier_name, bp.business_tier_name, bp.payment_effective_until, FROM business LEFT JOIN LATERAL (SELECT achieved FROM business_payment bp WHERE bp.business_id = business.id ORDER BY bp.payment_effective_until DESC LIMIT 1) bp WHERE b.%s = $1 `, customerIdField) row := m.db.QueryRow(getPlanExpirySql, customerId) var bizTierBiz string var bizTierBizPayment string var effectiveUntil time.Time err := row.Scan(&bizTierBiz, &bizTierBizPayment, &effectiveUntil) if errors.Is(err, sql.ErrNoRows) { return time.Time{}, ErrNoSuchCustomer } if bizTierBiz != bizTierBizPayment { return time.Time{}, ErrNoLatestPaymentOnPlan } return effectiveUntil, nil } func NewMerchant(dsn string) (generic.Mercher, error) { merchant := &merchant{} sqlDb, err := sql.Open("sqlite", dsn) if err != nil { return nil, err } merchant.db = sqlDb return merchant, nil }