package payment import ( "context" "fmt" "net/http" "time" "dev.tandem.ws/tandem/camper/pkg/auth" "dev.tandem.ws/tandem/camper/pkg/database" httplib "dev.tandem.ws/tandem/camper/pkg/http" "dev.tandem.ws/tandem/camper/pkg/redsys" "dev.tandem.ws/tandem/camper/pkg/template" "dev.tandem.ws/tandem/camper/pkg/uuid" ) type PublicHandler struct { } func NewPublicHandler() *PublicHandler { return &PublicHandler{} } func (h *PublicHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var paymentSlug string paymentSlug, r.URL.Path = httplib.ShiftPath(r.URL.Path) if !uuid.Valid(paymentSlug) { http.NotFound(w, r) return } payment, err := fetchPayment(r.Context(), conn, paymentSlug) if err != nil { if database.ErrorIsNotFound(err) { http.NotFound(w, r) return } panic(err) } var head string head, r.URL.Path = httplib.ShiftPath(r.URL.Path) switch head { case "": switch r.Method { case http.MethodGet: page := newPaymentPage(payment) page.MustRender(w, r, user, company, conn) default: httplib.MethodNotAllowed(w, r, http.MethodGet) } case "success": handleSuccessfulPayment(w, r, user, company, conn, payment) case "failure": handleFailedPayment(w, r, user, company, conn, payment) default: http.NotFound(w, r) } }) } func fetchPayment(ctx context.Context, conn *database.Conn, paymentSlug string) (*Payment, error) { row := conn.QueryRow(ctx, ` select payment_id , payment.slug::text , payment.created_at , to_price(total, decimal_digits) from payment join company using (company_id) join currency using (currency_code) where payment.slug = $1 and payment_status <> 'draft' `, paymentSlug) payment := &Payment{} if err := row.Scan(&payment.ID, &payment.Slug, &payment.CreateTime, &payment.Total); err != nil { return nil, err } return payment, nil } type Payment struct { ID int Slug string Total string CreateTime time.Time } func (payment *Payment) createRequest(r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) (*redsys.SignedRequest, error) { schema := httplib.Protocol(r) authority := httplib.Host(r) baseURL := fmt.Sprintf("%s://%s/%s/payments/%s", schema, authority, user.Locale.Language, payment.Slug) request := &redsys.Request{ TransactionType: redsys.TransactionTypeCharge, Amount: payment.Total, OrderNumber: payment.OrderNumber(), Product: user.Locale.Pgettext("Campsite Booking", "order product name"), SuccessURL: fmt.Sprintf("%s/success", baseURL), FailureURL: fmt.Sprintf("%s/failure", baseURL), NotificationURL: fmt.Sprintf("%s/notification", baseURL), ConsumerLanguage: user.Locale.Language, } return redsys.SignRequest(r.Context(), conn, company, request) } func (payment *Payment) OrderNumber() string { return fmt.Sprintf("%08d%s", payment.ID, payment.Slug[:4]) } type paymentPage struct { *template.PublicPage Environment string Payment *Payment Request *redsys.SignedRequest } func newPaymentPage(payment *Payment) *paymentPage { return &paymentPage{ PublicPage: template.NewPublicPage(), Payment: payment, } } func (p *paymentPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { p.Setup(r, user, company, conn) request, err := p.Payment.createRequest(r, user, company, conn) if err != nil { panic(err) } p.Request = request if err := conn.QueryRow(r.Context(), "select environment from redsys where company_id = $1", company.ID).Scan(&p.Environment); err != nil && !database.ErrorIsNotFound(err) { panic(err) } template.MustRenderPublic(w, r, user, company, "payment/request.gohtml", p) } func handleSuccessfulPayment(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, payment *Payment) { var head string head, r.URL.Path = httplib.ShiftPath(r.URL.Path) switch head { case "": switch r.Method { case http.MethodGet: page := newSuccessfulPaymentPage(payment) page.MustRender(w, r, user, company, conn) default: httplib.MethodNotAllowed(w, r, http.MethodGet) } default: http.NotFound(w, r) } } type successfulPaymentPage struct { *template.PublicPage Payment *Payment } func newSuccessfulPaymentPage(payment *Payment) *successfulPaymentPage { return &successfulPaymentPage{ PublicPage: template.NewPublicPage(), Payment: payment, } } func (p *successfulPaymentPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { p.Setup(r, user, company, conn) template.MustRenderPublicFiles(w, r, user, company, p, "payment/success.gohtml", "payment/details.gohtml") } func handleFailedPayment(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, payment *Payment) { var head string head, r.URL.Path = httplib.ShiftPath(r.URL.Path) switch head { case "": switch r.Method { case http.MethodGet: page := newFailedPaymentPage(payment) page.MustRender(w, r, user, company, conn) default: httplib.MethodNotAllowed(w, r, http.MethodGet) } default: http.NotFound(w, r) } } type failedPaymentPage struct { *template.PublicPage Payment *Payment } func newFailedPaymentPage(payment *Payment) *failedPaymentPage { return &failedPaymentPage{ PublicPage: template.NewPublicPage(), Payment: payment, } } func (p *failedPaymentPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { p.Setup(r, user, company, conn) template.MustRenderPublicFiles(w, r, user, company, p, "payment/failure.gohtml", "payment/details.gohtml") }