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
}