diff --git a/deploy/payment_customer.sql b/deploy/payment_customer.sql index 7437663..8ffd044 100644 --- a/deploy/payment_customer.sql +++ b/deploy/payment_customer.sql @@ -19,10 +19,11 @@ create table payment_customer ( country_code country_code not null references country, email email not null, phone packed_phone_number not null, - acsi_card boolean not null + acsi_card boolean not null, + lang_tag text not null references language ); -grant select, insert on table payment_customer to guest; +grant select, insert, update on table payment_customer to guest; grant select, insert, update on table payment_customer to employee; grant select, insert, update, delete on table payment_customer to admin; diff --git a/deploy/ready_payment.sql b/deploy/ready_payment.sql new file mode 100644 index 0000000..ad25355 --- /dev/null +++ b/deploy/ready_payment.sql @@ -0,0 +1,56 @@ +-- Deploy camper:ready_payment to pg +-- requires: roles +-- requires: schema_camper +-- requires: payment +-- requires: payment_customer +-- requires: country_code +-- requires: email +-- requires: extension_pg_libphonenumber + +begin; + +set search_path to camper, public; + +create or replace function ready_payment(payment_slug uuid, customer_name text, customer_address text, customer_post_code text, customer_city text, customer_country_code country_code, customer_email email, customer_phone text, customer_lang_tag text, customer_acsi_card boolean) returns integer as +$$ +declare + pid integer; +begin + update payment + set payment_status = 'pending' + , updated_at = current_timestamp + where slug = payment_slug + and payment_status = 'draft' + returning payment_id into pid + ; + + if pid is null then + raise check_violation using message = 'insert or update on table "payment" violates check constraint "payment_is_draft"'; + end if; + + insert into payment_customer (payment_id, full_name, address, postal_code, city, country_code, email, phone, acsi_card, lang_tag) + values (pid, customer_name, customer_address, customer_post_code, customer_city, customer_country_code, customer_email, parse_packed_phone_number(customer_phone, customer_country_code), customer_acsi_card, customer_lang_tag) + on conflict (payment_id) do update + set full_name = excluded.full_name + , address = excluded.address + , postal_code = excluded.postal_code + , city = excluded.city + , country_code = excluded.country_code + , email = excluded.email + , phone = excluded.phone + , acsi_card = excluded.acsi_card + , lang_tag = excluded.lang_tag + ; + + return pid; +end; +$$ + language plpgsql +; + +revoke execute on function ready_payment(uuid, text, text, text, text, country_code, email, text, text, boolean) from public; +grant execute on function ready_payment(uuid, text, text, text, text, country_code, email, text, text, boolean) to guest; +grant execute on function ready_payment(uuid, text, text, text, text, country_code, email, text, text, boolean) to employee; +grant execute on function ready_payment(uuid, text, text, text, text, country_code, email, text, text, boolean) to admin; + +commit; diff --git a/pkg/app/public.go b/pkg/app/public.go index 04dc973..1dbf14b 100644 --- a/pkg/app/public.go +++ b/pkg/app/public.go @@ -17,6 +17,7 @@ import ( httplib "dev.tandem.ws/tandem/camper/pkg/http" "dev.tandem.ws/tandem/camper/pkg/legal" "dev.tandem.ws/tandem/camper/pkg/location" + "dev.tandem.ws/tandem/camper/pkg/payment" "dev.tandem.ws/tandem/camper/pkg/services" "dev.tandem.ws/tandem/camper/pkg/surroundings" "dev.tandem.ws/tandem/camper/pkg/template" @@ -29,6 +30,7 @@ type publicHandler struct { campsite *campsite.PublicHandler legal *legal.PublicHandler location *location.PublicHandler + payment *payment.PublicHandler services *services.PublicHandler surroundings *surroundings.PublicHandler } @@ -41,6 +43,7 @@ func newPublicHandler() *publicHandler { campsite: campsite.NewPublicHandler(), legal: legal.NewPublicHandler(), location: location.NewPublicHandler(), + payment: payment.NewPublicHandler(), services: services.NewPublicHandler(), surroundings: surroundings.NewPublicHandler(), } @@ -65,6 +68,8 @@ func (h *publicHandler) Handler(user *auth.User, company *auth.Company, conn *da h.legal.Handler(user, company, conn).ServeHTTP(w, r) case "location": h.location.Handler(user, company, conn).ServeHTTP(w, r) + case "payments": + h.payment.Handler(user, company, conn).ServeHTTP(w, r) case "services": h.services.Handler(user, company, conn).ServeHTTP(w, r) case "surroundings": diff --git a/pkg/booking/payment.go b/pkg/booking/payment.go index 2ef11dd..8b7523f 100644 --- a/pkg/booking/payment.go +++ b/pkg/booking/payment.go @@ -6,98 +6,46 @@ package booking import ( + "fmt" "net/http" "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" ) -type paymentPage struct { - *template.PublicPage - Environment string - Request *redsys.SignedRequest -} - -func newPaymentPage(request *redsys.SignedRequest) *paymentPage { - return &paymentPage{ - PublicPage: template.NewPublicPage(), - Request: request, +func requestPayment(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { + f, err := newBookingForm(r, company, conn, user.Locale) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if ok, err := f.Valid(r.Context(), conn, user.Locale); err != nil { + panic(err) + } else if !ok { + if !httplib.IsHTMxRequest(r) { + w.WriteHeader(http.StatusUnprocessableEntity) + } + page := newPublicPageWithForm(f) + page.MustRender(w, r, user, company, conn) + return } -} -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) - if err := conn.QueryRow(r.Context(), "select environment from redsys where company_id = $1", company.ID).Scan(&p.Environment); err != nil && !database.ErrorIsNotFound(err) { + _, err = conn.ReadyPayment( + r.Context(), + f.PaymentSlug.Val, + f.Customer.FullName.Val, + f.Customer.Address.Val, + f.Customer.PostalCode.Val, + f.Customer.City.Val, + f.Customer.Country.String(), + f.Customer.Email.Val, + f.Customer.Phone.Val, + user.Locale.Language, + f.Customer.ACSICard.Checked, + ) + if err != nil { 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) { - var head string - head, r.URL.Path = httplib.ShiftPath(r.URL.Path) - - switch head { - case "": - switch r.Method { - case http.MethodGet: - page := newSuccessfulPaymentPage() - page.MustRender(w, r, user, company, conn) - default: - httplib.MethodNotAllowed(w, r, http.MethodGet) - } - default: - http.NotFound(w, r) - } -} - -type successfulPaymentPage struct { - *template.PublicPage -} - -func newSuccessfulPaymentPage() *successfulPaymentPage { - return &successfulPaymentPage{ - PublicPage: template.NewPublicPage(), - } -} - -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.MustRenderPublic(w, r, user, company, "payment/success.gohtml", p) -} - -func handleFailedPayment(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { - var head string - head, r.URL.Path = httplib.ShiftPath(r.URL.Path) - - switch head { - case "": - switch r.Method { - case http.MethodGet: - page := newFailedPaymentPage() - page.MustRender(w, r, user, company, conn) - default: - httplib.MethodNotAllowed(w, r, http.MethodGet) - } - default: - http.NotFound(w, r) - } -} - -type failedPaymentPage struct { - *template.PublicPage -} - -func newFailedPaymentPage() *failedPaymentPage { - return &failedPaymentPage{ - PublicPage: template.NewPublicPage(), - } -} - -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.MustRenderPublic(w, r, user, company, "payment/failure.gohtml", p) + httplib.Redirect(w, r, fmt.Sprintf("/%s/payments/%s", user.Locale.Language, f.PaymentSlug.Val), http.StatusSeeOther) } diff --git a/pkg/booking/public.go b/pkg/booking/public.go index ce0abb6..b7eb805 100644 --- a/pkg/booking/public.go +++ b/pkg/booking/public.go @@ -7,8 +7,6 @@ package booking import ( "context" - "crypto/rand" - "encoding/hex" "errors" "fmt" "net/http" @@ -20,7 +18,6 @@ import ( "dev.tandem.ws/tandem/camper/pkg/form" httplib "dev.tandem.ws/tandem/camper/pkg/http" "dev.tandem.ws/tandem/camper/pkg/locale" - "dev.tandem.ws/tandem/camper/pkg/redsys" "dev.tandem.ws/tandem/camper/pkg/template" ) @@ -47,66 +44,16 @@ func (h *PublicHandler) Handler(user *auth.User, company *auth.Company, conn *da } page.MustRender(w, r, user, company, conn) case http.MethodPost: - makeReservation(w, r, user, company, conn) + requestPayment(w, r, user, company, conn) default: httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost) } - case "success": - handleSuccessfulPayment(w, r, user, company, conn) - case "failure": - handleFailedPayment(w, r, user, company, conn) default: http.NotFound(w, r) } }) } -func makeReservation(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) { - f, err := newBookingForm(r, company, conn, user.Locale) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if ok, err := f.Valid(r.Context(), conn, user.Locale); err != nil { - panic(err) - } else if !ok { - if !httplib.IsHTMxRequest(r) { - w.WriteHeader(http.StatusUnprocessableEntity) - } - page := newPublicPageWithForm(f) - page.MustRender(w, r, user, company, conn) - return - } - - schema := httplib.Protocol(r) - authority := httplib.Host(r) - baseURL := fmt.Sprintf("%s://%s/%s/booking", schema, authority, user.Locale.Language) - request := &redsys.Request{ - TransactionType: redsys.TransactionTypeCharge, - Amount: f.Cart.Total, - OrderNumber: randomOrderNumber(), - Product: "Test Booking", - SuccessURL: fmt.Sprintf("%s/success", baseURL), - FailureURL: fmt.Sprintf("%s/failure", baseURL), - NotificationURL: fmt.Sprintf("%s/notification", baseURL), - ConsumerLanguage: user.Locale.Language, - } - signed, err := redsys.SignRequest(r.Context(), conn, company, request) - if err != nil { - panic(err) - } - page := newPaymentPage(signed) - page.MustRender(w, r, user, company, conn) -} - -func randomOrderNumber() string { - bytes := make([]byte, 6) - if _, err := rand.Read(bytes); err != nil { - panic(err) - } - return hex.EncodeToString(bytes) -} - type publicPage struct { *template.PublicPage Form *bookingForm @@ -272,6 +219,9 @@ func (f *bookingForm) Valid(ctx context.Context, conn *database.Conn, l *locale. if f.Customer == nil { return false, errors.New("no customer fields") } + if f.Cart == nil { + return false, errors.New("no booking cart") + } v.CheckSelectedOptions(f.CampsiteType, l.GettextNoop("Selected campsite type is not valid.")) f.Dates.Valid(v, l) diff --git a/pkg/database/funcs.go b/pkg/database/funcs.go index 0b8097b..3859915 100644 --- a/pkg/database/funcs.go +++ b/pkg/database/funcs.go @@ -357,3 +357,7 @@ func (c *Conn) DraftPayment(ctx context.Context, paymentSlug string, arrivalDate } return c.GetText(ctx, "select draft_payment($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", paymentSlugParam, arrivalDate, departureDate, campsiteTypeSlug, numAdults, numTeenagers, numChildren, numDogs, zonePreferences, options) } + +func (c *Conn) ReadyPayment(ctx context.Context, paymentSlug string, customerName string, customerAddress string, customerPostCode string, customerCity string, customerCountryCode string, customerEmail string, customerPhone string, customerLangTag language.Tag, acsiCard bool) (int, error) { + return c.GetInt(ctx, "select ready_payment($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", paymentSlug, customerName, customerAddress, customerPostCode, customerCity, customerCountryCode, customerEmail, customerPhone, customerLangTag, acsiCard) +} diff --git a/pkg/payment/public.go b/pkg/payment/public.go new file mode 100644 index 0000000..ae38931 --- /dev/null +++ b/pkg/payment/public.go @@ -0,0 +1,204 @@ +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") +} diff --git a/po/ca.po b/po/ca.po index 1b3c3c9..fa3c638 100644 --- a/po/ca.po +++ b/po/ca.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: camper\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n" -"POT-Creation-Date: 2024-02-12 05:10+0100\n" +"POT-Creation-Date: 2024-02-12 17:49+0100\n" "PO-Revision-Date: 2024-02-06 10:04+0100\n" "Last-Translator: jordi fita mas \n" "Language-Team: Catalan \n" @@ -60,28 +60,56 @@ msgid "Zone 1" msgstr "Zona 1" #: web/templates/public/payment/success.gohtml:6 -#: web/templates/public/payment/success.gohtml:11 msgctxt "title" msgid "Payment Successful" msgstr "Pagament amb èxit" -#: web/templates/public/payment/request.gohtml:6 -#: web/templates/public/payment/request.gohtml:11 -msgctxt "title" -msgid "Payment" -msgstr "Pagament" +#: web/templates/public/payment/success.gohtml:12 +msgid "We have received the payment. Thank you." +msgstr "Hem rebut el vostre pagament. Gràcies." -#: web/templates/public/payment/request.gohtml:23 +#: web/templates/public/payment/request.gohtml:6 +msgctxt "title" +msgid "Booking Payment" +msgstr "Pagament de la reserva" + +#: web/templates/public/payment/request.gohtml:18 +msgid "Thank you for your booking. Please, click the button below to pay with a credit card via Servired/Redsys." +msgstr "Gràcies per la vostra reserva. Feu clic al botó de sota per pagar amb targeta de crèdit via Servired/Redsys." + +#: web/templates/public/payment/request.gohtml:27 msgctxt "action" -msgid "Pay" -msgstr "Paga" +msgid "Pay with credit card" +msgstr "Paga amb targeta de crèdit" + +#: web/templates/public/payment/request.gohtml:30 +msgid "Please, wait until we redirect you to the payment page." +msgstr "Espereu mentre us enviem a la pàgina de pagament." #: web/templates/public/payment/failure.gohtml:6 -#: web/templates/public/payment/failure.gohtml:11 msgctxt "title" msgid "Payment Failed" msgstr "Ha fallat el pagament" +#: web/templates/public/payment/failure.gohtml:12 +msgid "We could not process the payment. Please, contact us for support." +msgstr "No hem pogut processar el pagament. Poseu-vos en contacte amb nosaltres per solucionar el problema." + +#: web/templates/public/payment/details.gohtml:4 +msgctxt "title" +msgid "Order Number" +msgstr "Número de comanda" + +#: web/templates/public/payment/details.gohtml:8 +msgctxt "title" +msgid "Date" +msgstr "Data" + +#: web/templates/public/payment/details.gohtml:12 +msgctxt "title" +msgid "Total" +msgstr "Total" + #: web/templates/public/services.gohtml:7 #: web/templates/public/services.gohtml:16 #: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95 @@ -1951,6 +1979,11 @@ msgstr "Estat" msgid "No booking found." msgstr "No s’ha trobat cap reserva." +#: pkg/payment/public.go:97 +msgctxt "order product name" +msgid "Campsite Booking" +msgstr "Reserva de càmping" + #: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:365 #: pkg/campsite/types/feature.go:272 pkg/campsite/types/admin.go:577 #: pkg/campsite/feature.go:269 pkg/season/admin.go:412 @@ -1990,12 +2023,12 @@ msgid "Slide image must be an image media type." msgstr "La imatge de la diapositiva ha de ser un mèdia de tipus imatge." #: pkg/app/login.go:56 pkg/app/user.go:246 pkg/company/admin.go:217 -#: pkg/booking/public.go:583 +#: pkg/booking/public.go:533 msgid "Email can not be empty." msgstr "No podeu deixar el correu-e en blanc." #: pkg/app/login.go:57 pkg/app/user.go:247 pkg/company/admin.go:218 -#: pkg/booking/public.go:584 +#: pkg/booking/public.go:534 msgid "This email is not valid. It should be like name@domain.com." msgstr "Aquest correu-e no és vàlid. Hauria de ser similar a nom@domini.com." @@ -2206,8 +2239,8 @@ msgctxt "header" msgid "Children (aged 2 to 10)" msgstr "Mainada (entre 2 i 10 anys)" -#: pkg/campsite/admin.go:275 pkg/booking/public.go:224 -#: pkg/booking/public.go:276 +#: pkg/campsite/admin.go:275 pkg/booking/public.go:171 +#: pkg/booking/public.go:226 msgid "Selected campsite type is not valid." msgstr "El tipus d’allotjament escollit no és vàlid." @@ -2363,7 +2396,7 @@ msgstr "No podeu deixar l’adreça de l’enllaç en blanc." msgid "This web address is not valid. It should be like https://domain.com/." msgstr "Aquesta adreça web no és vàlida. Hauria de ser similar a https://domini.com/." -#: pkg/company/admin.go:200 pkg/booking/public.go:570 +#: pkg/company/admin.go:200 pkg/booking/public.go:520 msgid "Selected country is not valid." msgstr "El país escollit no és vàlid." @@ -2383,11 +2416,11 @@ msgstr "No podeu deixar el NIF en blanc." msgid "This VAT number is not valid." msgstr "Aquest NIF no és vàlid." -#: pkg/company/admin.go:212 pkg/booking/public.go:586 +#: pkg/company/admin.go:212 pkg/booking/public.go:536 msgid "Phone can not be empty." msgstr "No podeu deixar el telèfon en blanc." -#: pkg/company/admin.go:213 pkg/booking/public.go:587 +#: pkg/company/admin.go:213 pkg/booking/public.go:537 msgid "This phone number is not valid." msgstr "Aquest número de telèfon no és vàlid." @@ -2407,7 +2440,7 @@ msgstr "No podeu deixar la província en blanc." msgid "Postal code can not be empty." msgstr "No podeu deixar el codi postal en blanc." -#: pkg/company/admin.go:227 pkg/booking/public.go:579 +#: pkg/company/admin.go:227 pkg/booking/public.go:529 msgid "This postal code is not valid." msgstr "Aquest codi postal no és vàlid." @@ -2534,127 +2567,135 @@ msgstr "La integració escollida no és vàlida." msgid "The merchant key is not valid." msgstr "Aquesta clau del comerç no és vàlid." -#: pkg/booking/public.go:325 pkg/booking/public.go:354 +#: pkg/booking/public.go:275 pkg/booking/public.go:304 msgid "Arrival date must be a valid date." msgstr "La data d’arribada ha de ser una data vàlida." -#: pkg/booking/public.go:339 pkg/booking/public.go:361 +#: pkg/booking/public.go:289 pkg/booking/public.go:311 msgid "Departure date must be a valid date." msgstr "La data de sortida ha de ser una data vàlida." -#: pkg/booking/public.go:353 +#: pkg/booking/public.go:303 msgid "Arrival date can not be empty" msgstr "No podeu deixar la data d’arribada en blanc." -#: pkg/booking/public.go:355 +#: pkg/booking/public.go:305 #, c-format msgid "Arrival date must be %s or after." msgstr "La data d’arribada ha de ser igual o posterior a %s." -#: pkg/booking/public.go:356 +#: pkg/booking/public.go:306 #, c-format msgid "Arrival date must be %s or before." msgstr "La data d’arribada ha de ser anterior o igual a %s." -#: pkg/booking/public.go:360 +#: pkg/booking/public.go:310 msgid "Departure date can not be empty" msgstr "No podeu deixar la data de sortida en blanc." -#: pkg/booking/public.go:362 +#: pkg/booking/public.go:312 #, c-format msgid "Departure date must be %s or after." msgstr "La data de sortida ha de ser igual o posterior a %s." -#: pkg/booking/public.go:363 +#: pkg/booking/public.go:313 #, c-format msgid "Departure date must be %s or before." msgstr "La data de sortida ha de ser anterior o igual a %s." -#: pkg/booking/public.go:405 +#: pkg/booking/public.go:355 #, c-format msgid "There can be at most %d guests in this accommodation." msgstr "Hi poden haver com a màxim %d convidats a aquest allotjament." -#: pkg/booking/public.go:425 +#: pkg/booking/public.go:375 msgid "Number of adults can not be empty" msgstr "No podeu deixar el número d’adults en blanc." -#: pkg/booking/public.go:426 +#: pkg/booking/public.go:376 msgid "Number of adults must be an integer." msgstr "El número d’adults ha de ser enter." -#: pkg/booking/public.go:427 +#: pkg/booking/public.go:377 msgid "There must be at least one adult." msgstr "Hi ha d’haver com a mínim un adult." -#: pkg/booking/public.go:430 +#: pkg/booking/public.go:380 msgid "Number of teenagers can not be empty" msgstr "No podeu deixar el número d’adolescents en blanc." -#: pkg/booking/public.go:431 +#: pkg/booking/public.go:381 msgid "Number of teenagers must be an integer." msgstr "El número d’adolescents ha de ser enter." -#: pkg/booking/public.go:432 +#: pkg/booking/public.go:382 msgid "Number of teenagers can not be negative." msgstr "El número d’adolescents no pot ser negatiu." -#: pkg/booking/public.go:435 +#: pkg/booking/public.go:385 msgid "Number of children can not be empty" msgstr "No podeu deixar el número de nens en blanc." -#: pkg/booking/public.go:436 +#: pkg/booking/public.go:386 msgid "Number of children must be an integer." msgstr "El número de nens ha de ser enter." -#: pkg/booking/public.go:437 +#: pkg/booking/public.go:387 msgid "Number of children can not be negative." msgstr "El número de nens no pot ser negatiu." -#: pkg/booking/public.go:440 +#: pkg/booking/public.go:390 msgid "Number of dogs can not be empty" msgstr "No podeu deixar el número de gossos en blanc." -#: pkg/booking/public.go:441 +#: pkg/booking/public.go:391 msgid "Number of dogs must be an integer." msgstr "El número de gossos ha de ser enter." -#: pkg/booking/public.go:442 +#: pkg/booking/public.go:392 msgid "Number of dogs can not be negative." msgstr "El número de gossos no pot ser negatiu." -#: pkg/booking/public.go:513 +#: pkg/booking/public.go:463 #, c-format msgid "%s can not be empty" msgstr "No podeu deixar %s en blanc." -#: pkg/booking/public.go:514 +#: pkg/booking/public.go:464 #, c-format msgid "%s must be an integer." msgstr "%s ha de ser un número enter." -#: pkg/booking/public.go:515 +#: pkg/booking/public.go:465 #, c-format msgid "%s must be %d or greater." msgstr "El valor de %s ha de ser com a mínim %d." -#: pkg/booking/public.go:516 +#: pkg/booking/public.go:466 #, c-format msgid "%s must be at most %d." msgstr "El valor de %s ha de ser com a màxim %d." -#: pkg/booking/public.go:574 +#: pkg/booking/public.go:524 msgid "Full name can not be empty." msgstr "No podeu deixar el nom i els cognoms en blanc." -#: pkg/booking/public.go:575 +#: pkg/booking/public.go:525 msgid "Full name must have at least one letter." msgstr "El nom i els cognoms han de tenir com a mínim una lletra." -#: pkg/booking/public.go:592 +#: pkg/booking/public.go:542 msgid "It is mandatory to agree to the reservation conditions." msgstr "És obligatori acceptar les condicions de reserves." +#~ msgctxt "title" +#~ msgid "Payment" +#~ msgstr "Pagament" + +#~ msgctxt "action" +#~ msgid "Pay" +#~ msgstr "Paga" + #~ msgctxt "input" #~ msgid "Check-in Date" #~ msgstr "Data d’entrada" diff --git a/po/es.po b/po/es.po index 996b9aa..1aab691 100644 --- a/po/es.po +++ b/po/es.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: camper\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n" -"POT-Creation-Date: 2024-02-12 05:10+0100\n" +"POT-Creation-Date: 2024-02-12 17:49+0100\n" "PO-Revision-Date: 2024-02-06 10:04+0100\n" "Last-Translator: jordi fita mas \n" "Language-Team: Spanish \n" @@ -60,28 +60,56 @@ msgid "Zone 1" msgstr "Zona 1" #: web/templates/public/payment/success.gohtml:6 -#: web/templates/public/payment/success.gohtml:11 msgctxt "title" msgid "Payment Successful" msgstr "Pago con éxito" -#: web/templates/public/payment/request.gohtml:6 -#: web/templates/public/payment/request.gohtml:11 -msgctxt "title" -msgid "Payment" -msgstr "Pago" +#: web/templates/public/payment/success.gohtml:12 +msgid "We have received the payment. Thank you." +msgstr "Hemos recibido su pago. Gracias." -#: web/templates/public/payment/request.gohtml:23 +#: web/templates/public/payment/request.gohtml:6 +msgctxt "title" +msgid "Booking Payment" +msgstr "Pago de la reserva" + +#: web/templates/public/payment/request.gohtml:18 +msgid "Thank you for your booking. Please, click the button below to pay with a credit card via Servired/Redsys." +msgstr "Gracias por vuestro pedido. Haced clic al botón de abajo para pagar con tarjeta de crédito vía Servired/Redsys." + +#: web/templates/public/payment/request.gohtml:27 msgctxt "action" -msgid "Pay" -msgstr "Pagar" +msgid "Pay with credit card" +msgstr "Pagar con tarjeta de crédito" + +#: web/templates/public/payment/request.gohtml:30 +msgid "Please, wait until we redirect you to the payment page." +msgstr "Esperad mientras se os redirige a la página de pago." #: web/templates/public/payment/failure.gohtml:6 -#: web/templates/public/payment/failure.gohtml:11 msgctxt "title" msgid "Payment Failed" msgstr "Pago fallido" +#: web/templates/public/payment/failure.gohtml:12 +msgid "We could not process the payment. Please, contact us for support." +msgstr "No hemos podido procesar su pago. Póngase en contacto con nosotros para solucionar el problema." + +#: web/templates/public/payment/details.gohtml:4 +msgctxt "title" +msgid "Order Number" +msgstr "Número de pedido" + +#: web/templates/public/payment/details.gohtml:8 +msgctxt "title" +msgid "Date" +msgstr "Fecha" + +#: web/templates/public/payment/details.gohtml:12 +msgctxt "title" +msgid "Total" +msgstr "Total" + #: web/templates/public/services.gohtml:7 #: web/templates/public/services.gohtml:16 #: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95 @@ -1951,6 +1979,11 @@ msgstr "Estado" msgid "No booking found." msgstr "No se ha encontrado ninguna reserva." +#: pkg/payment/public.go:97 +msgctxt "order product name" +msgid "Campsite Booking" +msgstr "Reserva de camping" + #: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:365 #: pkg/campsite/types/feature.go:272 pkg/campsite/types/admin.go:577 #: pkg/campsite/feature.go:269 pkg/season/admin.go:412 @@ -1990,12 +2023,12 @@ msgid "Slide image must be an image media type." msgstr "La imagen de la diapositiva tiene que ser un medio de tipo imagen." #: pkg/app/login.go:56 pkg/app/user.go:246 pkg/company/admin.go:217 -#: pkg/booking/public.go:583 +#: pkg/booking/public.go:533 msgid "Email can not be empty." msgstr "No podéis dejar el correo-e en blanco." #: pkg/app/login.go:57 pkg/app/user.go:247 pkg/company/admin.go:218 -#: pkg/booking/public.go:584 +#: pkg/booking/public.go:534 msgid "This email is not valid. It should be like name@domain.com." msgstr "Este correo-e no es válido. Tiene que ser parecido a nombre@dominio.com." @@ -2206,8 +2239,8 @@ msgctxt "header" msgid "Children (aged 2 to 10)" msgstr "Niños (de 2 a 10 años)" -#: pkg/campsite/admin.go:275 pkg/booking/public.go:224 -#: pkg/booking/public.go:276 +#: pkg/campsite/admin.go:275 pkg/booking/public.go:171 +#: pkg/booking/public.go:226 msgid "Selected campsite type is not valid." msgstr "El tipo de alojamiento escogido no es válido." @@ -2363,7 +2396,7 @@ msgstr "No podéis dejar la dirección del enlace en blanco." msgid "This web address is not valid. It should be like https://domain.com/." msgstr "Esta dirección web no es válida. Tiene que ser parecido a https://dominio.com/." -#: pkg/company/admin.go:200 pkg/booking/public.go:570 +#: pkg/company/admin.go:200 pkg/booking/public.go:520 msgid "Selected country is not valid." msgstr "El país escogido no es válido." @@ -2383,11 +2416,11 @@ msgstr "No podéis dejar el NIF en blanco." msgid "This VAT number is not valid." msgstr "Este NIF no es válido." -#: pkg/company/admin.go:212 pkg/booking/public.go:586 +#: pkg/company/admin.go:212 pkg/booking/public.go:536 msgid "Phone can not be empty." msgstr "No podéis dejar el teléfono en blanco." -#: pkg/company/admin.go:213 pkg/booking/public.go:587 +#: pkg/company/admin.go:213 pkg/booking/public.go:537 msgid "This phone number is not valid." msgstr "Este teléfono no es válido." @@ -2407,7 +2440,7 @@ msgstr "No podéis dejar la provincia en blanco." msgid "Postal code can not be empty." msgstr "No podéis dejar el código postal en blanco." -#: pkg/company/admin.go:227 pkg/booking/public.go:579 +#: pkg/company/admin.go:227 pkg/booking/public.go:529 msgid "This postal code is not valid." msgstr "Este código postal no es válido." @@ -2534,127 +2567,135 @@ msgstr "La integración escogida no es válida." msgid "The merchant key is not valid." msgstr "Esta clave del comercio no es válida." -#: pkg/booking/public.go:325 pkg/booking/public.go:354 +#: pkg/booking/public.go:275 pkg/booking/public.go:304 msgid "Arrival date must be a valid date." msgstr "La fecha de llegada tiene que ser una fecha válida." -#: pkg/booking/public.go:339 pkg/booking/public.go:361 +#: pkg/booking/public.go:289 pkg/booking/public.go:311 msgid "Departure date must be a valid date." msgstr "La fecha de partida tiene que ser una fecha válida." -#: pkg/booking/public.go:353 +#: pkg/booking/public.go:303 msgid "Arrival date can not be empty" msgstr "No podéis dejar la fecha de llegada en blanco." -#: pkg/booking/public.go:355 +#: pkg/booking/public.go:305 #, c-format msgid "Arrival date must be %s or after." msgstr "La fecha de llegada tiene que ser igual o posterior a %s." -#: pkg/booking/public.go:356 +#: pkg/booking/public.go:306 #, c-format msgid "Arrival date must be %s or before." msgstr "La fecha de llegada tiene que ser anterior o igual a %s." -#: pkg/booking/public.go:360 +#: pkg/booking/public.go:310 msgid "Departure date can not be empty" msgstr "No podéis dejar la fecha de partida en blanco." -#: pkg/booking/public.go:362 +#: pkg/booking/public.go:312 #, c-format msgid "Departure date must be %s or after." msgstr "La fecha de partida tiene que igual o posterior a %s." -#: pkg/booking/public.go:363 +#: pkg/booking/public.go:313 #, c-format msgid "Departure date must be %s or before." msgstr "La fecha de partida tiene que ser anterior o igual a %s." -#: pkg/booking/public.go:405 +#: pkg/booking/public.go:355 #, c-format msgid "There can be at most %d guests in this accommodation." msgstr "Solo puede haber como máximo %d invitados en este alojamiento." -#: pkg/booking/public.go:425 +#: pkg/booking/public.go:375 msgid "Number of adults can not be empty" msgstr "No podéis dejar el número de adultos blanco." -#: pkg/booking/public.go:426 +#: pkg/booking/public.go:376 msgid "Number of adults must be an integer." msgstr "El número de adultos tiene que ser entero." -#: pkg/booking/public.go:427 +#: pkg/booking/public.go:377 msgid "There must be at least one adult." msgstr "Tiene que haber como mínimo un adulto." -#: pkg/booking/public.go:430 +#: pkg/booking/public.go:380 msgid "Number of teenagers can not be empty" msgstr "No podéis dejar el número de adolescentes en blanco." -#: pkg/booking/public.go:431 +#: pkg/booking/public.go:381 msgid "Number of teenagers must be an integer." msgstr "El número de adolescentes tiene que ser entero." -#: pkg/booking/public.go:432 +#: pkg/booking/public.go:382 msgid "Number of teenagers can not be negative." msgstr "El número de adolescentes no puede ser negativo." -#: pkg/booking/public.go:435 +#: pkg/booking/public.go:385 msgid "Number of children can not be empty" msgstr "No podéis dejar el número de niños en blanco." -#: pkg/booking/public.go:436 +#: pkg/booking/public.go:386 msgid "Number of children must be an integer." msgstr "El número de niños tiene que ser entero." -#: pkg/booking/public.go:437 +#: pkg/booking/public.go:387 msgid "Number of children can not be negative." msgstr "El número de niños no puede ser negativo." -#: pkg/booking/public.go:440 +#: pkg/booking/public.go:390 msgid "Number of dogs can not be empty" msgstr "No podéis dejar el número de perros en blanco." -#: pkg/booking/public.go:441 +#: pkg/booking/public.go:391 msgid "Number of dogs must be an integer." msgstr "El número de perros tiene que ser entero." -#: pkg/booking/public.go:442 +#: pkg/booking/public.go:392 msgid "Number of dogs can not be negative." msgstr "El número de perros no puede ser negativo." -#: pkg/booking/public.go:513 +#: pkg/booking/public.go:463 #, c-format msgid "%s can not be empty" msgstr "No podéis dejar %s en blanco." -#: pkg/booking/public.go:514 +#: pkg/booking/public.go:464 #, c-format msgid "%s must be an integer." msgstr "%s tiene que ser un número entero." -#: pkg/booking/public.go:515 +#: pkg/booking/public.go:465 #, c-format msgid "%s must be %d or greater." msgstr "%s tiene que ser como mínimo %d." -#: pkg/booking/public.go:516 +#: pkg/booking/public.go:466 #, c-format msgid "%s must be at most %d." msgstr "%s tiene que ser como máximo %d" -#: pkg/booking/public.go:574 +#: pkg/booking/public.go:524 msgid "Full name can not be empty." msgstr "No podéis dejar el nombre y los apellidos en blanco." -#: pkg/booking/public.go:575 +#: pkg/booking/public.go:525 msgid "Full name must have at least one letter." msgstr "El nombre y los apellidos tienen que tener como mínimo una letra." -#: pkg/booking/public.go:592 +#: pkg/booking/public.go:542 msgid "It is mandatory to agree to the reservation conditions." msgstr "Es obligatorio aceptar las condiciones de reserva." +#~ msgctxt "title" +#~ msgid "Payment" +#~ msgstr "Pago" + +#~ msgctxt "action" +#~ msgid "Pay" +#~ msgstr "Pagar" + #~ msgctxt "input" #~ msgid "Check-in Date" #~ msgstr "Fecha de entrada" diff --git a/po/fr.po b/po/fr.po index 89495e0..dad9918 100644 --- a/po/fr.po +++ b/po/fr.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: camper\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n" -"POT-Creation-Date: 2024-02-12 05:10+0100\n" +"POT-Creation-Date: 2024-02-12 17:49+0100\n" "PO-Revision-Date: 2024-02-06 10:05+0100\n" "Last-Translator: Oriol Carbonell \n" "Language-Team: French \n" @@ -60,28 +60,56 @@ msgid "Zone 1" msgstr "Zone 1" #: web/templates/public/payment/success.gohtml:6 -#: web/templates/public/payment/success.gohtml:11 msgctxt "title" msgid "Payment Successful" msgstr "Paiement réussi" -#: web/templates/public/payment/request.gohtml:6 -#: web/templates/public/payment/request.gohtml:11 -msgctxt "title" -msgid "Payment" -msgstr "Paiement" +#: web/templates/public/payment/success.gohtml:12 +msgid "We have received the payment. Thank you." +msgstr "Nous avons reçu le paiement. Merci." -#: web/templates/public/payment/request.gohtml:23 +#: web/templates/public/payment/request.gohtml:6 +msgctxt "title" +msgid "Booking Payment" +msgstr "Paiement de la réservation" + +#: web/templates/public/payment/request.gohtml:18 +msgid "Thank you for your booking. Please, click the button below to pay with a credit card via Servired/Redsys." +msgstr "Merci pour votre réservation. Veuillez cliquer sur le bouton ci-dessous pour payer avec une carte de crédit via Servired/Redsys." + +#: web/templates/public/payment/request.gohtml:27 msgctxt "action" -msgid "Pay" -msgstr "Payer" +msgid "Pay with credit card" +msgstr "Payez avec une carte de crédit" + +#: web/templates/public/payment/request.gohtml:30 +msgid "Please, wait until we redirect you to the payment page." +msgstr "Veuillez attendre que nous vous redirigeons vers la page de paiement." #: web/templates/public/payment/failure.gohtml:6 -#: web/templates/public/payment/failure.gohtml:11 msgctxt "title" msgid "Payment Failed" msgstr "Le paiement a échoué" +#: web/templates/public/payment/failure.gohtml:12 +msgid "We could not process the payment. Please, contact us for support." +msgstr "Nous n’avons pas pu traiter le paiement. S’il vous plaît, contactez-nous pour obtenir de l’aide." + +#: web/templates/public/payment/details.gohtml:4 +msgctxt "title" +msgid "Order Number" +msgstr "Numéro de commande" + +#: web/templates/public/payment/details.gohtml:8 +msgctxt "title" +msgid "Date" +msgstr "Date" + +#: web/templates/public/payment/details.gohtml:12 +msgctxt "title" +msgid "Total" +msgstr "Totale" + #: web/templates/public/services.gohtml:7 #: web/templates/public/services.gohtml:16 #: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95 @@ -1951,6 +1979,11 @@ msgstr "Statut" msgid "No booking found." msgstr "Aucune réservation trouvée." +#: pkg/payment/public.go:97 +msgctxt "order product name" +msgid "Campsite Booking" +msgstr "Réservation camping" + #: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:365 #: pkg/campsite/types/feature.go:272 pkg/campsite/types/admin.go:577 #: pkg/campsite/feature.go:269 pkg/season/admin.go:412 @@ -1990,12 +2023,12 @@ msgid "Slide image must be an image media type." msgstr "L’image de la diapositive doit être de type média d’image." #: pkg/app/login.go:56 pkg/app/user.go:246 pkg/company/admin.go:217 -#: pkg/booking/public.go:583 +#: pkg/booking/public.go:533 msgid "Email can not be empty." msgstr "L’e-mail ne peut pas être vide." #: pkg/app/login.go:57 pkg/app/user.go:247 pkg/company/admin.go:218 -#: pkg/booking/public.go:584 +#: pkg/booking/public.go:534 msgid "This email is not valid. It should be like name@domain.com." msgstr "Cette adresse e-mail n’est pas valide. Il devrait en être name@domain.com." @@ -2206,8 +2239,8 @@ msgctxt "header" msgid "Children (aged 2 to 10)" msgstr "Enfants (de 2 à 10 anys)" -#: pkg/campsite/admin.go:275 pkg/booking/public.go:224 -#: pkg/booking/public.go:276 +#: pkg/campsite/admin.go:275 pkg/booking/public.go:171 +#: pkg/booking/public.go:226 msgid "Selected campsite type is not valid." msgstr "Le type d’emplacement sélectionné n’est pas valide." @@ -2363,7 +2396,7 @@ msgstr "L’addresse du lien ne peut pas être vide." msgid "This web address is not valid. It should be like https://domain.com/." msgstr "Cette adresse web n’est pas valide. Il devrait en être https://domain.com/." -#: pkg/company/admin.go:200 pkg/booking/public.go:570 +#: pkg/company/admin.go:200 pkg/booking/public.go:520 msgid "Selected country is not valid." msgstr "Le pays sélectionné n’est pas valide." @@ -2383,11 +2416,11 @@ msgstr "Le numéro de TVA ne peut pas être vide." msgid "This VAT number is not valid." msgstr "Ce numéro de TVA n’est pas valide." -#: pkg/company/admin.go:212 pkg/booking/public.go:586 +#: pkg/company/admin.go:212 pkg/booking/public.go:536 msgid "Phone can not be empty." msgstr "Le téléphone ne peut pas être vide." -#: pkg/company/admin.go:213 pkg/booking/public.go:587 +#: pkg/company/admin.go:213 pkg/booking/public.go:537 msgid "This phone number is not valid." msgstr "Ce numéro de téléphone n’est pas valide." @@ -2407,7 +2440,7 @@ msgstr "La province ne peut pas être vide." msgid "Postal code can not be empty." msgstr "Le code postal ne peut pas être vide." -#: pkg/company/admin.go:227 pkg/booking/public.go:579 +#: pkg/company/admin.go:227 pkg/booking/public.go:529 msgid "This postal code is not valid." msgstr "Ce code postal n’est pas valide." @@ -2534,127 +2567,135 @@ msgstr "L’intégration sélectionnée n’est pas valide." msgid "The merchant key is not valid." msgstr "La clé marchand n’est pas valide." -#: pkg/booking/public.go:325 pkg/booking/public.go:354 +#: pkg/booking/public.go:275 pkg/booking/public.go:304 msgid "Arrival date must be a valid date." msgstr "La date d’arrivée doit être une date valide." -#: pkg/booking/public.go:339 pkg/booking/public.go:361 +#: pkg/booking/public.go:289 pkg/booking/public.go:311 msgid "Departure date must be a valid date." msgstr "La date de départ doit être une date valide." -#: pkg/booking/public.go:353 +#: pkg/booking/public.go:303 msgid "Arrival date can not be empty" msgstr "La date d’arrivée ne peut pas être vide" -#: pkg/booking/public.go:355 +#: pkg/booking/public.go:305 #, c-format msgid "Arrival date must be %s or after." msgstr "La date d’arrivée doit être égale ou postérieure à %s." -#: pkg/booking/public.go:356 +#: pkg/booking/public.go:306 #, c-format msgid "Arrival date must be %s or before." msgstr "La date d’arrivée doit être antérieure ou égale à %s." -#: pkg/booking/public.go:360 +#: pkg/booking/public.go:310 msgid "Departure date can not be empty" msgstr "La date de départ ne peut pas être vide" -#: pkg/booking/public.go:362 +#: pkg/booking/public.go:312 #, c-format msgid "Departure date must be %s or after." msgstr "La date de départ doit être égale ou postérieure à %s." -#: pkg/booking/public.go:363 +#: pkg/booking/public.go:313 #, c-format msgid "Departure date must be %s or before." msgstr "La date de départ doit être antérieure ou égale à %s." -#: pkg/booking/public.go:405 +#: pkg/booking/public.go:355 #, c-format msgid "There can be at most %d guests in this accommodation." msgstr "Il peut y avoir au plus %d invités dans cet hébergement." -#: pkg/booking/public.go:425 +#: pkg/booking/public.go:375 msgid "Number of adults can not be empty" msgstr "Le nombre d’adultes ne peut pas être vide." -#: pkg/booking/public.go:426 +#: pkg/booking/public.go:376 msgid "Number of adults must be an integer." msgstr "Le nombre d’adultes doit être un entier." -#: pkg/booking/public.go:427 +#: pkg/booking/public.go:377 msgid "There must be at least one adult." msgstr "Il doit y avoir au moins un adulte." -#: pkg/booking/public.go:430 +#: pkg/booking/public.go:380 msgid "Number of teenagers can not be empty" msgstr "Le nombre d’adolescents ne peut pas être vide." -#: pkg/booking/public.go:431 +#: pkg/booking/public.go:381 msgid "Number of teenagers must be an integer." msgstr "Le nombre d’adolescents doit être un entier." -#: pkg/booking/public.go:432 +#: pkg/booking/public.go:382 msgid "Number of teenagers can not be negative." msgstr "Le nombre d’adolescents ne peut pas être négatif." -#: pkg/booking/public.go:435 +#: pkg/booking/public.go:385 msgid "Number of children can not be empty" msgstr "Le nombre d’enfants ne peut pas être vide." -#: pkg/booking/public.go:436 +#: pkg/booking/public.go:386 msgid "Number of children must be an integer." msgstr "Le nombre d’enfants doit être un entier." -#: pkg/booking/public.go:437 +#: pkg/booking/public.go:387 msgid "Number of children can not be negative." msgstr "Le nombre d’enfants ne peut pas être négatif." -#: pkg/booking/public.go:440 +#: pkg/booking/public.go:390 msgid "Number of dogs can not be empty" msgstr "Le nombre de chiens ne peut pas être vide." -#: pkg/booking/public.go:441 +#: pkg/booking/public.go:391 msgid "Number of dogs must be an integer." msgstr "Le nombre de chiens nuits être un entier." -#: pkg/booking/public.go:442 +#: pkg/booking/public.go:392 msgid "Number of dogs can not be negative." msgstr "Le nombre de chiens ne peut pas être négatif." -#: pkg/booking/public.go:513 +#: pkg/booking/public.go:463 #, c-format msgid "%s can not be empty" msgstr "%s ne peut pas être vide" -#: pkg/booking/public.go:514 +#: pkg/booking/public.go:464 #, c-format msgid "%s must be an integer." msgstr "%s doit être un entier." -#: pkg/booking/public.go:515 +#: pkg/booking/public.go:465 #, c-format msgid "%s must be %d or greater." msgstr "%s doit être %d ou plus." -#: pkg/booking/public.go:516 +#: pkg/booking/public.go:466 #, c-format msgid "%s must be at most %d." msgstr "%s doit être tout au plus %d." -#: pkg/booking/public.go:574 +#: pkg/booking/public.go:524 msgid "Full name can not be empty." msgstr "Le nom complet ne peut pas être vide." -#: pkg/booking/public.go:575 +#: pkg/booking/public.go:525 msgid "Full name must have at least one letter." msgstr "Le nom complet doit comporter au moins une lettre." -#: pkg/booking/public.go:592 +#: pkg/booking/public.go:542 msgid "It is mandatory to agree to the reservation conditions." msgstr "Il est obligatoire d’accepter les conditions de réservation." +#~ msgctxt "title" +#~ msgid "Payment" +#~ msgstr "Paiement" + +#~ msgctxt "action" +#~ msgid "Pay" +#~ msgstr "Payer" + #~ msgctxt "input" #~ msgid "Check-in Date" #~ msgstr "Date d'arrivée" diff --git a/revert/ready_payment.sql b/revert/ready_payment.sql new file mode 100644 index 0000000..4656a04 --- /dev/null +++ b/revert/ready_payment.sql @@ -0,0 +1,7 @@ +-- Revert camper:ready_payment from pg + +begin; + +drop function if exists camper.ready_payment(uuid, text, text, text, text, camper.country_code, camper.email, text, text, boolean); + +commit; diff --git a/sqitch.plan b/sqitch.plan index 5de135f..ffd20f1 100644 --- a/sqitch.plan +++ b/sqitch.plan @@ -247,3 +247,4 @@ payment [roles schema_camper company campsite_type payment_status] 2024-02-11T21 payment_customer [roles schema_camper payment country country_code extension_pg_libphonenumber] 2024-02-12T00:10:20Z jordi fita mas # Add relation of payment customer payment_option [roles schema_camper payment campsite_type_option] 2024-02-12T00:58:07Z jordi fita mas # Add relation of payment for campsite type options draft_payment [roles schema_camper season_calendar season campsite_type campsite_type_pet_cost campsite_type_cost campsite_type_option_cost campsite_type_option payment payment_option] 2024-02-12T01:31:52Z jordi fita mas # Add function to create a payment draft +ready_payment [roles schema_camper payment payment_customer country_code email extension_pg_libphonenumber] 2024-02-12T12:57:24Z jordi fita mas # Add function to ready a draft payment diff --git a/test/draft_payment.sql b/test/draft_payment.sql index c193b09..0baa028 100644 --- a/test/draft_payment.sql +++ b/test/draft_payment.sql @@ -115,9 +115,9 @@ select lives_ok( ); select bag_eq( - $$ select company_id, campsite_type_id, arrival_date::text, departure_date::text, subtotal_nights, number_adults, subtotal_adults, number_teenagers, subtotal_teenagers, number_children, subtotal_children, number_dogs, subtotal_dogs, subtotal_tourist_tax, total, zone_preferences, created_at, updated_at from payment $$, - $$ values (2, 12, '2024-08-28', '2024-09-04', 3200, 2, 10420, 4, 20840, 6, 25080, 3, 2450, 4900, 79160, 'pref I before E', '2024-01-01 01:01:01', current_timestamp) - , (2, 14, '2024-08-29', '2024-09-03', 71000, 1, 0, 2, 0, 3, 0, 0, 0, 1750, 72750, '', current_timestamp, current_timestamp) + $$ select company_id, campsite_type_id, arrival_date::text, departure_date::text, subtotal_nights, number_adults, subtotal_adults, number_teenagers, subtotal_teenagers, number_children, subtotal_children, number_dogs, subtotal_dogs, subtotal_tourist_tax, total, zone_preferences, payment_status, created_at, updated_at from payment $$, + $$ values (2, 12, '2024-08-28', '2024-09-04', 3200, 2, 10420, 4, 20840, 6, 25080, 3, 2450, 4900, 79160, 'pref I before E', 'draft', '2024-01-01 01:01:01', current_timestamp) + , (2, 14, '2024-08-29', '2024-09-03', 71000, 1, 0, 2, 0, 3, 0, 0, 0, 1750, 72750, '', 'draft', current_timestamp, current_timestamp) $$, 'Should have added and updated payments' ); diff --git a/test/payment_customer.sql b/test/payment_customer.sql index e6dcc8b..d722344 100644 --- a/test/payment_customer.sql +++ b/test/payment_customer.sql @@ -5,13 +5,13 @@ reset client_min_messages; begin; -select plan(47); +select plan(53); set search_path to camper, public; select has_table('payment_customer'); select has_pk('payment_customer'); -select table_privs_are('payment_customer', 'guest', array['SELECT', 'INSERT']); +select table_privs_are('payment_customer', 'guest', array['SELECT', 'INSERT', 'UPDATE']); select table_privs_are('payment_customer', 'employee', array['SELECT', 'INSERT', 'UPDATE']); select table_privs_are('payment_customer', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']); select table_privs_are('payment_customer', 'authenticator', array[]::text[]); @@ -66,6 +66,13 @@ select col_type_is('payment_customer', 'acsi_card', 'boolean'); select col_not_null('payment_customer', 'acsi_card'); select col_hasnt_default('payment_customer', 'acsi_card'); +select has_column('payment_customer', 'lang_tag'); +select col_is_fk('payment_customer', 'lang_tag'); +select fk_ok('payment_customer', 'lang_tag', 'language', 'lang_tag'); +select col_type_is('payment_customer', 'lang_tag', 'text'); +select col_not_null('payment_customer', 'lang_tag'); +select col_hasnt_default('payment_customer', 'lang_tag'); + select * from finish(); diff --git a/test/ready_payment.sql b/test/ready_payment.sql new file mode 100644 index 0000000..5b58d03 --- /dev/null +++ b/test/ready_payment.sql @@ -0,0 +1,101 @@ +-- Test ready_payment +set client_min_messages to warning; +create extension if not exists pgtap; +reset client_min_messages; + +begin; + +select plan(15); + +set search_path to camper, public; + +select has_function('camper', 'ready_payment', array['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean']); +select function_lang_is('camper', 'ready_payment', array['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'plpgsql'); +select function_returns('camper', 'ready_payment', array['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'integer'); +select isnt_definer('camper', 'ready_payment', array['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean']); +select volatility_is('camper', 'ready_payment', array['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'volatile'); +select function_privs_are('camper', 'ready_payment', array ['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'guest', array['EXECUTE']); +select function_privs_are('camper', 'ready_payment', array ['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'employee', array['EXECUTE']); +select function_privs_are('camper', 'ready_payment', array ['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'admin', array['EXECUTE']); +select function_privs_are('camper', 'ready_payment', array ['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'authenticator', array[]::text[]); + + +set client_min_messages to warning; +truncate payment_customer cascade; +truncate payment cascade; +truncate campsite_type cascade; +truncate media cascade; +truncate media_content cascade; +truncate company cascade; +reset client_min_messages; + +insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, rtc_number, tourist_tax, country_code, currency_code, default_lang_tag) +values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '', 350, 'ES', 'EUR', 'ca') +; + +insert into media_content (media_type, bytes) +values ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};') +; + +insert into media (media_id, company_id, original_filename, content_hash) +values (10, 2, 'cover2.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};')) +; + +insert into campsite_type (campsite_type_id, slug, company_id, name, media_id, max_campers, bookable_nights, overflow_allowed) +values (12, 'c1b6f4fc-32c1-4cd5-b796-0c5059152a52', 2, 'Plots', 10, 6, '[1, 7]', true) +; + +insert into payment (payment_id, slug, company_id, campsite_type_id, arrival_date, departure_date, subtotal_nights, number_adults, subtotal_adults, number_teenagers, subtotal_teenagers, number_children, subtotal_children, number_dogs, subtotal_dogs, subtotal_tourist_tax, total, zone_preferences, payment_status, created_at, updated_at) +values (22, '4ef35e2f-ef98-42d6-a724-913bd761ca8c', 2, 12, '2024-08-28', '2024-09-04', 3200, 2, 10420, 4, 20840, 6, 25080, 3, 2450, 4900, 79160, 'pref I before E', 'draft', '2024-01-01 01:01:01', '2024-01-01') + , (24, '6d1b8e4c-c3c6-4fe4-92c1-2cbf94526693', 2, 12, '2024-08-29', '2024-09-03', 71000, 1, 0, 2, 0, 3, 0, 0, 0, 1750, 72750, '', 'draft', current_timestamp, current_timestamp) +; + +insert into payment_customer (payment_id, full_name, address, postal_code, city, country_code, email, phone, acsi_card, lang_tag) +values (24, '', '', '', '', 'FR', 'a@a.com', '555-555-555', true, 'es') +; + +select is( + ready_payment('4ef35e2f-ef98-42d6-a724-913bd761ca8c', 'Juli Verd', 'C/ Fals, 123', '17486', 'Castelló d’Empúries', 'ES', 'juli@verd.cat', '972486 160', 'ca', true), + 22, + 'Should be able to ready the first draft payment' +); + +select is( + ready_payment('6d1b8e4c-c3c6-4fe4-92c1-2cbf94526693', 'Pere Gil', 'Gutenbergstr. 9-13', '82178', 'Puchheim', 'DE', 'pere@gil.de', '8980902-0', 'en', false), + 24, + 'Should be able to ready the second draft payment, and update customer details' +); + +select throws_ok( + $$ select ready_payment('7ba7f0a5-d73d-4d6e-a9c4-3b88f2b2a357', 'Juli Verd', 'C/ Fals, 123', '17486', 'Castelló d’Empúries', 'ES', 'juli@verd.cat', '972 486 160', 'ca', true) $$, + '23514', 'insert or update on table "payment" violates check constraint "payment_is_draft"', + 'Should not be able to ready an inexistent payment' +); + +select throws_ok( + $$ select ready_payment('4ef35e2f-ef98-42d6-a724-913bd761ca8c', 'Juli Verd', 'C/ Fals, 123', '17486', 'Castelló d’Empúries', 'ES', 'juli@verd.cat', '972 486 160', 'ca', true) $$, + '23514', 'insert or update on table "payment" violates check constraint "payment_is_draft"', + 'Should not be able to ready an already processed payment' +); + +select bag_eq( + $$ select payment_id, slug::text, company_id, campsite_type_id, arrival_date::text, departure_date::text, subtotal_nights, number_adults, subtotal_adults, number_teenagers, subtotal_teenagers, number_children, subtotal_children, number_dogs, subtotal_dogs, subtotal_tourist_tax, total, zone_preferences, payment_status, created_at, updated_at from payment $$, + $$ values (22, '4ef35e2f-ef98-42d6-a724-913bd761ca8c', 2, 12, '2024-08-28', '2024-09-04', 3200, 2, 10420, 4, 20840, 6, 25080, 3, 2450, 4900, 79160, 'pref I before E', 'pending', '2024-01-01 01:01:01', current_timestamp) + , (24, '6d1b8e4c-c3c6-4fe4-92c1-2cbf94526693', 2, 12, '2024-08-29', '2024-09-03', 71000, 1, 0, 2, 0, 3, 0, 0, 0, 1750, 72750, '', 'pending', current_timestamp, current_timestamp) + $$, + 'Should have updated payments' +); + +select bag_eq( + $$ select payment_id, full_name, address, postal_code, city, country_code::text, email::text, phone::text, acsi_card, lang_tag from payment_customer $$, + $$ values (22, 'Juli Verd', 'C/ Fals, 123', '17486', 'Castelló d’Empúries', 'ES', 'juli@verd.cat', '+34 972 48 61 60', true, 'ca') + , (24, 'Pere Gil', 'Gutenbergstr. 9-13', '82178', 'Puchheim', 'DE', 'pere@gil.de', '+49 89 809020', false, 'en') + $$, + 'Should have added and updated customer details' +); + + +select * +from finish(); + +rollback; diff --git a/verify/payment_customer.sql b/verify/payment_customer.sql index 5b2b805..06f3fee 100644 --- a/verify/payment_customer.sql +++ b/verify/payment_customer.sql @@ -11,6 +11,7 @@ select payment_id , email , phone , acsi_card + , lang_tag from camper.payment_customer where false; diff --git a/verify/ready_payment.sql b/verify/ready_payment.sql new file mode 100644 index 0000000..dc23475 --- /dev/null +++ b/verify/ready_payment.sql @@ -0,0 +1,7 @@ +-- Verify camper:ready_payment on pg + +begin; + +select has_function_privilege('camper.ready_payment(uuid, text, text, text, text, camper.country_code, camper.email, text, text, boolean)', 'execute'); + +rollback; diff --git a/web/static/public.css b/web/static/public.css index 5867016..5ef9d55 100644 --- a/web/static/public.css +++ b/web/static/public.css @@ -140,6 +140,10 @@ p + p, dl + p { margin-top: 1.5em; } +dialog:modal { + margin: auto; +} + h2 { font-size: 4.2rem; font-weight: 400; @@ -1526,6 +1530,16 @@ input[type="checkbox"]:focus { width: 4.8rem; } +dl.payment-details div { + flex-basis: unset; + min-height: unset; + padding: unset; +} + +dl.payment-details dt { + padding: unset; +} + body > footer { display: flex; flex-direction: column; @@ -1812,7 +1826,6 @@ dt { z-index: 3; } - .services_icon::before { content: ''; background: var(--accent-2); @@ -1825,17 +1838,41 @@ dt { z-index: 1; } -/* -.services_icon:hover::before { - background: var(--accent-2); -} -*/ - .services_icon a { color: var(--accent); text-decoration: var(--accent) wavy underline; } +/**/ + +dialog.redirect:modal { + display: flex; + gap: 1rem; +} + +dialog.redirect::before { + content: ""; + display: inline-block; + width: 2.4rem; + aspect-ratio: 1; + border: 3px solid var(--contrast); + border-bottom-color: transparent; + border-radius: 50%; + animation: rotation linear 1s infinite; +} + +@keyframes rotation { + from { + transform: rotate(0); + } + + to { + transform: rotate(360deg); + } +} + +/**/ + [x-cloak] { display: none !important; } diff --git a/web/templates/public/payment/details.gohtml b/web/templates/public/payment/details.gohtml new file mode 100644 index 0000000..0d8120e --- /dev/null +++ b/web/templates/public/payment/details.gohtml @@ -0,0 +1,15 @@ +{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/payment.Payment*/ -}} +
+
+
{{( pgettext "Order Number" "title" )}}
+
{{ .OrderNumber }}
+
+
+
{{( pgettext "Date" "title" )}}
+
{{ .CreateTime | formatDate }}
+
+
+
{{( pgettext "Total" "title" )}}
+
{{ .Total | formatPrice }}
+
+
diff --git a/web/templates/public/payment/failure.gohtml b/web/templates/public/payment/failure.gohtml index 1c42b9c..decf654 100644 --- a/web/templates/public/payment/failure.gohtml +++ b/web/templates/public/payment/failure.gohtml @@ -7,6 +7,8 @@ {{- end }} {{ define "content" -}} - {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.failedPaymentPage*/ -}} -

{{( pgettext "Payment Failed" "title" )}}

+ {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/payment.failedPaymentPage*/ -}} +

{{ template "title" . }}

+

{{( gettext "We could not process the payment. Please, contact us for support." )}}

+ {{ template "details.gohtml" .Payment }} {{- end }} diff --git a/web/templates/public/payment/request.gohtml b/web/templates/public/payment/request.gohtml index 57e5e95..085e458 100644 --- a/web/templates/public/payment/request.gohtml +++ b/web/templates/public/payment/request.gohtml @@ -3,24 +3,31 @@ SPDX-License-Identifier: AGPL-3.0-only --> {{ define "title" -}} - {{( pgettext "Payment" "title" )}} + {{( pgettext "Booking Payment" "title" )}} +{{- end }} + +{{ define "head" -}} + {{ template "alpineScript" }} {{- end }} {{ define "content" -}} - {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.paymentPage*/ -}} -

{{( pgettext "Payment" "title" )}}

+ {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/payment.paymentPage*/ -}} +

{{ template "title" . }}

+ {{ template "details.gohtml" .Payment }} {{ with .Request -}} -
+

{{( gettext "Thank you for your booking. Please, click the button below to pay with a credit card via Servired/Redsys.")}}

+ - +
+ +

{{( gettext "Please, wait until we redirect you to the payment page.")}}

+
{{- end }} {{- end }} diff --git a/web/templates/public/payment/success.gohtml b/web/templates/public/payment/success.gohtml index 753b889..e145392 100644 --- a/web/templates/public/payment/success.gohtml +++ b/web/templates/public/payment/success.gohtml @@ -7,6 +7,8 @@ {{- end }} {{ define "content" -}} - {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.failedPaymentPage*/ -}} -

{{( pgettext "Payment Successful" "title" )}}

+ {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/payment.successfulPaymentPage*/ -}} +

{{ template "title" . }}

+

{{( gettext "We have received the payment. Thank you." )}}

+ {{ template "details.gohtml" .Payment }} {{- end }}