From 302ce29e4a8aeb47a66c9d72df97145a85862866 Mon Sep 17 00:00:00 2001 From: jordi fita mas Date: Thu, 19 Oct 2023 21:37:34 +0200 Subject: [PATCH] Add the first draft of the booking and payment forms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The form is based on the one in the current website, but in a single page instead of split into many pages; possibly each
should be in a separate page/view. The idea is for Oriol to check the design and decide how it would be presented to the user, so i needed something to show him first. I hardcoded the **test** data for the customer’s Redsys account. Is this bad? I hope not, but i am not really, really sure. The data sent to Redsys is just a placeholder because there are booking details that i do not know, like what i have to do with the “teenagers” field or the area preferences, thus i can not yet have a booking relation. Nevertheless, had to generate a random order number up to 12-chars in length or Redsys would refuse the payment, claiming that the order is duplicated. The Redsys package is based on the PHP code provided by Redsys themselves, plus some hints at the implementations from various Go packages that did not know why they were so complicated. Had to grant select on table country to guest in order to show the select with the country options. I have changed the “Postal code” input in taxDetails for “Postcode” because this is the spell that it is used in the current web, i did not see a reason to change it—it is an accepted form—, and i did not want to have inconsistencies between forms. --- deploy/country.sql | 1 + deploy/country_i18n.sql | 1 + pkg/app/public.go | 5 + pkg/booking/payment.go | 99 +++++ pkg/booking/public.go | 345 +++++++++++++++ pkg/form/validator.go | 11 + pkg/redsys/client.go | 174 ++++++++ po/ca.po | 448 +++++++++++++++----- po/es.po | 447 ++++++++++++++----- test/country.sql | 2 +- test/country_i18n.sql | 2 +- web/templates/admin/taxDetails.gohtml | 2 +- web/templates/public/booking.gohtml | 179 ++++++++ web/templates/public/payment/failure.gohtml | 12 + web/templates/public/payment/request.gohtml | 20 + web/templates/public/payment/success.gohtml | 12 + 16 files changed, 1545 insertions(+), 215 deletions(-) create mode 100644 pkg/booking/payment.go create mode 100644 pkg/booking/public.go create mode 100644 pkg/redsys/client.go create mode 100644 web/templates/public/booking.gohtml create mode 100644 web/templates/public/payment/failure.gohtml create mode 100644 web/templates/public/payment/request.gohtml create mode 100644 web/templates/public/payment/success.gohtml diff --git a/deploy/country.sql b/deploy/country.sql index 420d9d1..dccc95b 100644 --- a/deploy/country.sql +++ b/deploy/country.sql @@ -13,6 +13,7 @@ create table country ( postal_code_regex text not null ); +grant select on table country to guest; grant select on table country to employee; grant select on table country to admin; diff --git a/deploy/country_i18n.sql b/deploy/country_i18n.sql index 590e148..82dc9c6 100644 --- a/deploy/country_i18n.sql +++ b/deploy/country_i18n.sql @@ -16,6 +16,7 @@ create table country_i18n ( primary key (country_code, lang_tag) ); +grant select on table country_i18n to guest; grant select on table country_i18n to employee; grant select on table country_i18n to admin; diff --git a/pkg/app/public.go b/pkg/app/public.go index 0f688f5..e2a59db 100644 --- a/pkg/app/public.go +++ b/pkg/app/public.go @@ -9,6 +9,7 @@ import ( "net/http" "dev.tandem.ws/tandem/camper/pkg/auth" + "dev.tandem.ws/tandem/camper/pkg/booking" "dev.tandem.ws/tandem/camper/pkg/campsite" "dev.tandem.ws/tandem/camper/pkg/database" "dev.tandem.ws/tandem/camper/pkg/home" @@ -19,6 +20,7 @@ import ( type publicHandler struct { home *home.PublicHandler + booking *booking.PublicHandler campsite *campsite.PublicHandler services *services.PublicHandler } @@ -26,6 +28,7 @@ type publicHandler struct { func newPublicHandler() *publicHandler { return &publicHandler{ home: home.NewPublicHandler(), + booking: booking.NewPublicHandler(), campsite: campsite.NewPublicHandler(), services: services.NewPublicHandler(), } @@ -38,6 +41,8 @@ func (h *publicHandler) Handler(user *auth.User, company *auth.Company, conn *da switch head { case "": h.home.Handler(user, company, conn).ServeHTTP(w, r) + case "booking": + h.booking.Handler(user, company, conn).ServeHTTP(w, r) case "campground": campgroundHandler(user, company, conn).ServeHTTP(w, r) case "campsites": diff --git a/pkg/booking/payment.go b/pkg/booking/payment.go new file mode 100644 index 0000000..ca72b15 --- /dev/null +++ b/pkg/booking/payment.go @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2023 jordi fita mas + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package booking + +import ( + "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 + Request *redsys.SignedRequest +} + +func newPaymentPage(request *redsys.SignedRequest) *paymentPage { + return &paymentPage{ + PublicPage: template.NewPublicPage(), + Request: request, + } +} + +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) + 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, http.MethodPost) + } + 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, http.MethodPost) + } + 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) +} diff --git a/pkg/booking/public.go b/pkg/booking/public.go new file mode 100644 index 0000000..d127a24 --- /dev/null +++ b/pkg/booking/public.go @@ -0,0 +1,345 @@ +/* + * SPDX-FileCopyrightText: 2023 jordi fita mas + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package booking + +import ( + "context" + "crypto/rand" + "encoding/hex" + "fmt" + "net/http" + + "dev.tandem.ws/tandem/camper/pkg/auth" + "dev.tandem.ws/tandem/camper/pkg/database" + "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" +) + +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 head string + head, r.URL.Path = httplib.ShiftPath(r.URL.Path) + + switch head { + case "": + switch r.Method { + case http.MethodGet: + page := newPublicPage(r.Context(), company, conn, user.Locale) + page.MustRender(w, r, user, company, conn) + case http.MethodPost: + makeReservation(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 := newBookingForm(r.Context(), company, conn, user.Locale) + if err := f.Parse(r); 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 + } + + client, err := redsys.New("361716962", "001", "sq7HjrUOBfKmC576ILgskD5srU870gJ7") + if err != nil { + panic(err) + } + schema := httplib.Protocol(r) + authority := httplib.Host(r) + request := &redsys.Request{ + TransactionType: redsys.TransactionTypeCharge, + Amount: 1234, + Currency: redsys.CurrencyEUR, + Order: randomOrderNumber(), + Product: "Test Booking", + SuccessURL: fmt.Sprintf("%s://%s/%s/booking/success", schema, authority, user.Locale.Language), + FailureURL: fmt.Sprintf("%s://%s/%s/booking/failure", schema, authority, user.Locale.Language), + ConsumerLanguage: redsys.LanguageCatalan, + } + signed, err := client.SignRequest(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 +} + +func newPublicPage(ctx context.Context, company *auth.Company, conn *database.Conn, l *locale.Locale) *publicPage { + return newPublicPageWithForm(newBookingForm(ctx, company, conn, l)) +} + +func newPublicPageWithForm(form *bookingForm) *publicPage { + return &publicPage{ + PublicPage: template.NewPublicPage(), + Form: form, + } +} + +func (p *publicPage) 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, "booking.gohtml", p) +} + +type bookingForm struct { + FullName *form.Input + Address *form.Input + PostalCode *form.Input + City *form.Input + Country *form.Select + Email *form.Input + Phone *form.Input + Adults *form.Input + Teenagers *form.Input + Children *form.Input + Dogs *form.Input + CampsiteType *form.Select + CampsiteTypeOptions map[string][]*campsiteTypeOption + ArrivalDate *form.Input + DepartureDate *form.Input + AreaPreferences *form.Input + ACSICard *form.Checkbox + Agreement *form.Checkbox +} + +type campsiteTypeOption struct { + Label string + Min int + Max int + Input *form.Input +} + +func newBookingForm(ctx context.Context, company *auth.Company, conn *database.Conn, l *locale.Locale) *bookingForm { + f := &bookingForm{ + FullName: &form.Input{ + Name: "full_name", + }, + Address: &form.Input{ + Name: "address", + }, + PostalCode: &form.Input{ + Name: "postal_code", + }, + City: &form.Input{ + Name: "city", + }, + Country: &form.Select{ + Name: "country", + Options: form.MustGetOptions(ctx, conn, "select country.country_code, coalesce(i18n.name, country.name) as l10n_name from country left join country_i18n as i18n on country.country_code = i18n.country_code and i18n.lang_tag = $1 order by l10n_name", l.Language), + }, + Email: &form.Input{ + Name: "email", + }, + Phone: &form.Input{ + Name: "phone", + }, + Adults: &form.Input{ + Name: "adults", + Val: "2", + }, + Teenagers: &form.Input{ + Name: "teenagers", + Val: "0", + }, + Children: &form.Input{ + Name: "children", + Val: "0", + }, + Dogs: &form.Input{ + Name: "dogs", + Val: "0", + }, + CampsiteType: &form.Select{ + Name: "campsite_type", + Options: form.MustGetOptions(ctx, conn, "select type.slug, coalesce(i18n.name, type.name) as l10n_name from campsite_type as type left join campsite_type_i18n as i18n on type.campsite_type_id = i18n.campsite_type_id and i18n.lang_tag = $1 where company_id = $2 order by l10n_name", l.Language, company.ID), + }, + CampsiteTypeOptions: make(map[string][]*campsiteTypeOption), + ArrivalDate: &form.Input{ + Name: "arrival_date", + }, + DepartureDate: &form.Input{ + Name: "departure_date", + }, + AreaPreferences: &form.Input{ + Name: "area_preferences", + }, + ACSICard: &form.Checkbox{ + Name: "acsi_card", + }, + Agreement: &form.Checkbox{ + Name: "agreement", + }, + } + rows, err := conn.Query(ctx, ` + select 'campsite_type_option_' || option.campsite_type_option_id + , slug + , coalesce(option.name, i18n.name) as l10_name + , lower(range)::text + , lower(range) + , upper(range) + from campsite_type_option as option + join campsite_type using (campsite_type_id) + left join campsite_type_option_i18n as i18n on i18n.campsite_type_option_id = option.campsite_type_id and i18n.lang_tag = $1 + where company_id = $2 +`, l.Language, company.ID) + if err != nil { + panic(err) + } + for rows.Next() { + var slug string + option := &campsiteTypeOption{ + Input: &form.Input{}, + } + if err := rows.Scan(&option.Input.Name, &slug, &option.Label, &option.Input.Val, &option.Min, &option.Max); err != nil { + panic(err) + } + f.CampsiteTypeOptions[slug] = append(f.CampsiteTypeOptions[slug], option) + } + if rows.Err() != nil { + panic(rows.Err()) + } + return f +} +func (f *bookingForm) Parse(r *http.Request) error { + if err := r.ParseForm(); err != nil { + return err + } + f.FullName.FillValue(r) + f.Address.FillValue(r) + f.PostalCode.FillValue(r) + f.City.FillValue(r) + f.Country.FillValue(r) + f.Email.FillValue(r) + f.Phone.FillValue(r) + f.Adults.FillValue(r) + f.Teenagers.FillValue(r) + f.Children.FillValue(r) + f.Dogs.FillValue(r) + f.CampsiteType.FillValue(r) + f.ArrivalDate.FillValue(r) + f.DepartureDate.FillValue(r) + f.AreaPreferences.FillValue(r) + f.ACSICard.FillValue(r) + f.Agreement.FillValue(r) + for _, options := range f.CampsiteTypeOptions { + for _, option := range options { + option.Input.FillValue(r) + } + } + return nil +} + +func (f *bookingForm) Valid(ctx context.Context, conn *database.Conn, l *locale.Locale) (bool, error) { + v := form.NewValidator(l) + + var country string + if v.CheckSelectedOptions(f.Country, l.GettextNoop("Selected country is not valid.")) { + country = f.Country.Selected[0] + } + + if v.CheckRequired(f.FullName, l.GettextNoop("Full name can not be empty.")) { + v.CheckMinLength(f.FullName, 1, l.GettextNoop("Full name must have at least one letter.")) + } + + if f.PostalCode.Val != "" { + if _, err := v.CheckValidPostalCode(ctx, conn, f.PostalCode, country, l.GettextNoop("This postal code is not valid.")); err != nil { + return false, err + } + } + if v.CheckRequired(f.Email, l.GettextNoop("Email can not be empty.")) { + v.CheckValidEmail(f.Email, l.GettextNoop("This email is not valid. It should be like name@domain.com.")) + } + if v.CheckRequired(f.Phone, l.GettextNoop("Phone can not be empty.")) { + if _, err := v.CheckValidPhone(ctx, conn, f.Phone, country, l.GettextNoop("This phone number is not valid.")); err != nil { + return false, err + } + } + if v.CheckRequired(f.Adults, l.GettextNoop("Number of adults can not be empty")) { + if v.CheckValidInteger(f.Adults, l.GettextNoop("Number of adults must be an integer.")) { + v.CheckMinInteger(f.Adults, 1, l.GettextNoop("Number of adults must be one or greater.")) + } + } + if v.CheckRequired(f.Teenagers, l.GettextNoop("Number of teenagers can not be empty")) { + if v.CheckValidInteger(f.Teenagers, l.GettextNoop("Number of teenagers must be an integer.")) { + v.CheckMinInteger(f.Teenagers, 0, l.GettextNoop("Number of teenagers must be zero or greater.")) + } + } + if v.CheckRequired(f.Children, l.GettextNoop("Number of children can not be empty")) { + if v.CheckValidInteger(f.Children, l.GettextNoop("Number of children must be an integer.")) { + v.CheckMinInteger(f.Children, 0, l.GettextNoop("Number of children must be zero or greater.")) + } + } + if v.CheckRequired(f.Dogs, l.GettextNoop("Number of dogs can not be empty")) { + if v.CheckValidInteger(f.Dogs, l.GettextNoop("Number of dogs must be an integer.")) { + v.CheckMinInteger(f.Dogs, 0, l.GettextNoop("Number of dogs must be zero or greater.")) + } + } + var validBefore bool + v.CheckSelectedOptions(f.CampsiteType, l.GettextNoop("Selected campsite type is not valid.")) + if v.CheckRequired(f.ArrivalDate, l.GettextNoop("Arrival date can not be empty")) { + if v.CheckValidDate(f.ArrivalDate, l.GettextNoop("Arrival date must be a valid date.")) { + validBefore = true + } + } + if v.CheckRequired(f.DepartureDate, l.GettextNoop("Departure date can not be empty")) { + if v.CheckValidDate(f.DepartureDate, l.GettextNoop("Departure date must be a valid date.")) && validBefore { + v.CheckDateAfter(f.DepartureDate, f.ArrivalDate, l.GettextNoop("The departure date must be after the arrival date.")) + } + } + v.Check(f.Agreement, f.Agreement.Checked, l.GettextNoop("It is mandatory to agree to the reservation conditions.")) + for _, options := range f.CampsiteTypeOptions { + for _, option := range options { + if v.CheckRequired(option.Input, fmt.Sprintf(l.Gettext("%s can not be empty"), option.Label)) { + if v.CheckValidInteger(option.Input, fmt.Sprintf(l.Gettext("%s must be an integer."), option.Label)) { + if v.CheckMinInteger(option.Input, option.Min, fmt.Sprintf(l.Gettext("%s must be %d or greater."), option.Label, option.Min)) { + v.CheckMaxInteger(option.Input, option.Max, fmt.Sprintf(l.Gettext("%s must be at most %d."), option.Label, option.Max)) + } + } + } + } + } + return v.AllOK, nil +} diff --git a/pkg/form/validator.go b/pkg/form/validator.go index 95074d5..87673e9 100644 --- a/pkg/form/validator.go +++ b/pkg/form/validator.go @@ -43,6 +43,11 @@ func (v *Validator) CheckMinInteger(input *Input, min int, message string) bool return v.Check(input, i >= min, message) } +func (v *Validator) CheckMaxInteger(input *Input, max int, message string) bool { + i, _ := strconv.Atoi(input.Val) + return v.Check(input, i <= max, message) +} + func (v *Validator) CheckMinDecimal(input *Input, min float64, message string) bool { f, _ := strconv.ParseFloat(input.Val, 64) return v.Check(input, f >= min, message) @@ -109,6 +114,12 @@ func (v *Validator) CheckValidDate(field *Input, message string) bool { return v.Check(field, err == nil, message) } +func (v *Validator) CheckDateAfter(field *Input, beforeField *Input, message string) bool { + date, _ := time.Parse("2006-01-02", field.Val) + before, _ := time.Parse("2006-01-02", beforeField.Val) + return v.Check(field, date.After(before), message) +} + func (v *Validator) CheckPasswordConfirmation(password *Input, confirm *Input, message string) bool { return v.Check(confirm, password.Val == confirm.Val, message) } diff --git a/pkg/redsys/client.go b/pkg/redsys/client.go new file mode 100644 index 0000000..d5d69a5 --- /dev/null +++ b/pkg/redsys/client.go @@ -0,0 +1,174 @@ +/* + * SPDX-FileCopyrightText: 2023 jordi fita mas + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package redsys + +import ( + "bytes" + "crypto/cipher" + "crypto/des" + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "strings" +) + +type Language int + +const ( + LanguageDefault Language = 0 + LanguageSpanish Language = 1 + LanguageEnglish Language = 2 + LanguageCatalan Language = 3 + LanguageFrench Language = 4 +) + +type TransactionType int + +const ( + TransactionTypeCharge TransactionType = 0 + TransactionTypeRefund TransactionType = 3 + TransactionTypePreauth TransactionType = 1 + TransactionTypePreauthConfirm TransactionType = 2 + TransactionTypePreauthVoid TransactionType = 9 + TransactionTypeSplitAuth TransactionType = 7 + TransactionTypeSplitConfirm TransactionType = 8 +) + +type Currency int + +// Source: https://sis-d.redsys.es/pagosonline/codigos-autorizacion.html#monedas +// The codes seem to be the same as ISO 4217 https://en.wikipedia.org/wiki/ISO_4217 +const ( + CurrencyEUR Currency = 978 +) + +type Request struct { + TransactionType TransactionType + Amount int64 + Currency Currency + Order string + Product string + CardHolder string + SuccessURL string + FailureURL string + ConsumerLanguage Language +} + +type merchantParameters struct { + MerchantCode string `json:"Ds_Merchant_MerchantCode"` + Terminal string `json:"Ds_Merchant_Terminal"` + TransactionType TransactionType `json:"Ds_Merchant_TransactionType,string"` + Amount int64 `json:"Ds_Merchant_Amount,string"` + Currency Currency `json:"Ds_Merchant_Currency,string"` + Order string `json:"Ds_Merchant_Order"` + MerchantURL string `json:"Ds_Merchant_MerchantURL,omitempty"` + Product string `json:"Ds_Merchant_ProductDescription,omitempty"` + CardHolder string `json:"Ds_Merchant_Titular,omitempty"` + SuccessURL string `json:"Ds_Merchant_UrlOK,omitempty"` + FailureURL string `json:"Ds_Merchant_UrlKO,omitempty"` + MerchantName string `json:"Ds_Merchant_MerchantName,omitempty"` + ConsumerLanguage Language `json:"Ds_Merchant_ConsumerLanguage,string,omitempty"` +} + +type Client struct { + merchantCode string + terminalCode string + crypto cipher.Block +} + +func New(merchantCode string, terminalCode string, key string) (*Client, error) { + b, err := base64.StdEncoding.DecodeString(key) + if err != nil { + return nil, fmt.Errorf("key is invalid base64: %v", err) + } + + crypto, err := des.NewTripleDESCipher(b) + if err != nil { + return nil, err + } + + return &Client{ + merchantCode: merchantCode, + terminalCode: terminalCode, + crypto: crypto, + }, nil +} + +func (p *Client) SignRequest(req *Request) (*SignedRequest, error) { + signed := &SignedRequest{ + SignatureVersion: "HMAC_SHA256_V1", + } + params := &merchantParameters{ + MerchantCode: p.merchantCode, + Terminal: p.terminalCode, + TransactionType: req.TransactionType, + Amount: req.Amount, + Currency: req.Currency, + Order: req.Order, + MerchantURL: "http://localhost/ca/", + Product: req.Product, + CardHolder: req.CardHolder, + SuccessURL: req.SuccessURL, + FailureURL: req.FailureURL, + MerchantName: "Merchant Here", + ConsumerLanguage: req.ConsumerLanguage, + } + var err error + signed.MerchantParameters, err = encodeMerchantParameters(params) + if err != nil { + return nil, err + } + signed.Signature = p.sign(params.Order, signed.MerchantParameters) + return signed, nil +} + +func encodeMerchantParameters(params *merchantParameters) (string, error) { + marshalled, err := json.Marshal(params) + if err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(marshalled), err +} + +func (p *Client) sign(id string, data string) string { + key := encrypt3DES(id, p.crypto) + return mac256(data, key) +} + +var iv = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + +func encrypt3DES(str string, crypto cipher.Block) string { + cbc := cipher.NewCBCEncrypter(crypto, iv) + + decrypted := []byte(str) + decryptedPadded, _ := zeroPad(decrypted, crypto.BlockSize()) + cbc.CryptBlocks(decryptedPadded, decryptedPadded) + + return base64.StdEncoding.EncodeToString(decryptedPadded) +} + +func zeroPad(data []byte, blockLen int) ([]byte, error) { + padLen := (blockLen - (len(data) % blockLen)) % blockLen + pad := bytes.Repeat([]byte{0x00}, padLen) + + return append(data, pad...), nil +} + +func mac256(data string, key string) string { + decodedKey, _ := base64.StdEncoding.DecodeString(key) + res := hmac.New(sha256.New, decodedKey) + res.Write([]byte(strings.TrimSpace(data))) + result := res.Sum(nil) + return base64.StdEncoding.EncodeToString(result) +} + +type SignedRequest struct { + MerchantParameters string + Signature string + SignatureVersion string +} diff --git a/po/ca.po b/po/ca.po index e071d1d..a2a0b7f 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: 2023-10-14 21:54+0200\n" +"POT-Creation-Date: 2023-10-19 21:23+0200\n" "PO-Revision-Date: 2023-07-22 23:45+0200\n" "Last-Translator: jordi fita mas \n" "Language-Team: Catalan \n" @@ -18,9 +18,32 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#: 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/request.gohtml:17 +msgctxt "action" +msgid "Pay" +msgstr "Paga" + +#: 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/services.gohtml:6 #: web/templates/public/services.gohtml:15 -#: web/templates/public/layout.gohtml:43 web/templates/public/layout.gohtml:70 +#: web/templates/public/layout.gohtml:44 web/templates/public/layout.gohtml:71 #: web/templates/admin/services/index.gohtml:53 msgctxt "title" msgid "Services" @@ -30,8 +53,8 @@ msgstr "Serveis" msgid "The campsite offers many different services." msgstr "El càmping disposa de diversos serveis." -#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:29 -#: web/templates/public/layout.gohtml:68 +#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:30 +#: web/templates/public/layout.gohtml:69 msgctxt "title" msgid "Home" msgstr "Inici" @@ -52,7 +75,7 @@ msgstr "Els nostres serveis" #: web/templates/public/home.gohtml:34 #: web/templates/public/surroundings.gohtml:6 #: web/templates/public/surroundings.gohtml:10 -#: web/templates/public/layout.gohtml:44 web/templates/public/layout.gohtml:71 +#: web/templates/public/layout.gohtml:45 web/templates/public/layout.gohtml:72 msgctxt "title" msgid "Surroundings" msgstr "L’entorn" @@ -77,60 +100,109 @@ msgstr "Descobreix l’entorn" msgid "Come and enjoy!" msgstr "Vine a gaudir!" -#: web/templates/public/campsite/type.gohtml:40 +#: web/templates/public/campsite/type.gohtml:41 msgctxt "input" msgid "Check-in Date" msgstr "Data d’entrada" -#: web/templates/public/campsite/type.gohtml:46 +#: web/templates/public/campsite/type.gohtml:47 msgctxt "input" msgid "Check-out Date" msgstr "Data de sortida" -#: web/templates/public/campsite/type.gohtml:53 +#: web/templates/public/campsite/type.gohtml:54 +#: web/templates/public/booking.gohtml:175 msgctxt "action" msgid "Book" msgstr "Reserva" -#: web/templates/public/campsite/type.gohtml:59 +#: web/templates/public/campsite/type.gohtml:60 #: web/templates/admin/campsite/option/form.gohtml:58 #: web/templates/admin/campsite/type/form.gohtml:73 msgctxt "title" msgid "Prices" msgstr "Preus" -#: web/templates/public/campsite/type.gohtml:71 +#: web/templates/public/campsite/type.gohtml:72 msgid "Starting from %s €/night" msgstr "A partir de %s €/nit" -#: web/templates/public/campsite/type.gohtml:73 +#: web/templates/public/campsite/type.gohtml:74 msgid "%s €/night" msgstr "%s €/nit" -#: web/templates/public/campsite/type.gohtml:76 +#: web/templates/public/campsite/type.gohtml:77 msgid "*Minimum %d nights per stay" msgstr "*Mínim %d nits per estada" -#: web/templates/public/campsite/type.gohtml:90 +#: web/templates/public/campsite/type.gohtml:89 +#: web/templates/admin/season/index.gohtml:48 +msgctxt "title" +msgid "Calendar" +msgstr "Calendari" + +#: web/templates/public/campsite/type.gohtml:100 msgctxt "title" msgid "Features" msgstr "Característiques" -#: web/templates/public/campsite/type.gohtml:101 +#: web/templates/public/campsite/type.gohtml:111 msgctxt "title" msgid "Info" msgstr "Informació" -#: web/templates/public/campsite/type.gohtml:105 +#: web/templates/public/campsite/type.gohtml:115 msgctxt "title" msgid "Facilities" msgstr "Equipaments" -#: web/templates/public/campsite/type.gohtml:109 +#: web/templates/public/campsite/type.gohtml:119 msgctxt "title" msgid "Description" msgstr "Descripció" +#: web/templates/public/campsite/calendar.gohtml:18 +#: web/templates/admin/season/calendar.gohtml:16 +msgctxt "day" +msgid "Mon" +msgstr "dl" + +#: web/templates/public/campsite/calendar.gohtml:19 +#: web/templates/admin/season/calendar.gohtml:17 +msgctxt "day" +msgid "Tue" +msgstr "dt" + +#: web/templates/public/campsite/calendar.gohtml:20 +#: web/templates/admin/season/calendar.gohtml:18 +msgctxt "day" +msgid "Wed" +msgstr "dc" + +#: web/templates/public/campsite/calendar.gohtml:21 +#: web/templates/admin/season/calendar.gohtml:19 +msgctxt "day" +msgid "Thu" +msgstr "dj" + +#: web/templates/public/campsite/calendar.gohtml:22 +#: web/templates/admin/season/calendar.gohtml:20 +msgctxt "day" +msgid "Fri" +msgstr "dv" + +#: web/templates/public/campsite/calendar.gohtml:23 +#: web/templates/admin/season/calendar.gohtml:21 +msgctxt "day" +msgid "Sat" +msgstr "ds" + +#: web/templates/public/campsite/calendar.gohtml:24 +#: web/templates/admin/season/calendar.gohtml:22 +msgctxt "day" +msgid "Sun" +msgstr "dg" + #: web/templates/public/surroundings.gohtml:13 msgctxt "title" msgid "What to Do Outside the Campsite?" @@ -191,27 +263,140 @@ msgstr "Hi ha diversos punts on poder anar amb caiac, des de trams del riu Ter c #: web/templates/public/campground.gohtml:6 #: web/templates/public/campground.gohtml:11 -#: web/templates/public/layout.gohtml:30 web/templates/public/layout.gohtml:69 +#: web/templates/public/layout.gohtml:31 web/templates/public/layout.gohtml:70 msgctxt "title" msgid "Campground" msgstr "El càmping" #: web/templates/public/contact.gohtml:6 web/templates/public/contact.gohtml:15 -#: web/templates/public/layout.gohtml:45 web/templates/public/layout.gohtml:72 +#: web/templates/public/layout.gohtml:46 web/templates/public/layout.gohtml:73 msgctxt "title" msgid "Contact" msgstr "Contacte" -#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:24 -#: web/templates/public/layout.gohtml:95 +#: web/templates/public/booking.gohtml:6 web/templates/public/booking.gohtml:11 +msgctxt "title" +msgid "Booking" +msgstr "Reserva" + +#: web/templates/public/booking.gohtml:15 +msgctxt "title" +msgid "Customer Details" +msgstr "Detalls del client" + +#: web/templates/public/booking.gohtml:18 +msgctxt "input" +msgid "Full name" +msgstr "Nom i cognoms" + +#: web/templates/public/booking.gohtml:26 +msgctxt "input" +msgid "Address (optional)" +msgstr "Adreça (opcional)" + +#: web/templates/public/booking.gohtml:34 +msgctxt "input" +msgid "Postcode (optional)" +msgstr "Codi postal (opcional)" + +#: web/templates/public/booking.gohtml:42 +msgctxt "input" +msgid "Town or village (optional)" +msgstr "Població (opcional)" + +#: web/templates/public/booking.gohtml:50 +#: web/templates/admin/taxDetails.gohtml:98 +msgctxt "input" +msgid "Country" +msgstr "País" + +#: web/templates/public/booking.gohtml:53 +msgid "Choose a country" +msgstr "Esculli un país" + +#: web/templates/public/booking.gohtml:60 web/templates/admin/login.gohtml:22 +#: web/templates/admin/profile.gohtml:35 +#: web/templates/admin/taxDetails.gohtml:50 +msgctxt "input" +msgid "Email" +msgstr "Correu-e" + +#: web/templates/public/booking.gohtml:68 +#: web/templates/admin/taxDetails.gohtml:42 +msgctxt "input" +msgid "Phone" +msgstr "Telèfon" + +#: web/templates/public/booking.gohtml:76 +msgctxt "title" +msgid "Party Details" +msgstr "Dades dels visitants" + +#: web/templates/public/booking.gohtml:79 +msgctxt "input" +msgid "Adults" +msgstr "Adults" + +#: web/templates/public/booking.gohtml:87 +msgctxt "input" +msgid "Teenagers (from 11 to 16 years old)" +msgstr "Adolescents (entre 11 i 16 anys)" + +#: web/templates/public/booking.gohtml:95 +msgctxt "input" +msgid "Children (up to 10 years old)" +msgstr "Nens (fins a 10 anys)" + +#: web/templates/public/booking.gohtml:103 +msgctxt "input" +msgid "Dogs" +msgstr "Gossos" + +#: web/templates/public/booking.gohtml:111 +msgctxt "title" +msgid "Accomodation" +msgstr "Acomodacions" + +#: web/templates/public/booking.gohtml:136 +msgctxt "title" +msgid "Booking Period" +msgstr "Període de reserva" + +#: web/templates/public/booking.gohtml:139 +msgctxt "input" +msgid "Arrival date" +msgstr "Data d’arribada" + +#: web/templates/public/booking.gohtml:147 +msgctxt "input" +msgid "Departure date" +msgstr "Data de sortida" + +#: web/templates/public/booking.gohtml:155 +msgctxt "input" +msgid "Area preferences (optional)" +msgstr "Preferències d’àrea (opcional)" + +#: web/templates/public/booking.gohtml:165 +msgctxt "input" +msgid "ACSI card? (optional)" +msgstr "Targeta ACSI? (opcional)" + +#: web/templates/public/booking.gohtml:171 +msgctxt "input" +msgid "I have read and I accept the reservation conditions" +msgstr "He llegit i accepto les condicions de reserves" + +#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:25 +#: web/templates/public/layout.gohtml:96 msgid "Campsite Montagut" msgstr "Càmping Montagut" -#: web/templates/public/layout.gohtml:22 web/templates/admin/layout.gohtml:18 +#: web/templates/public/layout.gohtml:23 web/templates/admin/layout.gohtml:18 msgid "Skip to main content" msgstr "Salta al contingut principal" -#: web/templates/public/layout.gohtml:34 web/templates/public/layout.gohtml:79 +#: web/templates/public/layout.gohtml:35 web/templates/public/layout.gohtml:80 #: web/templates/admin/campsite/index.gohtml:6 #: web/templates/admin/campsite/index.gohtml:12 #: web/templates/admin/layout.gohtml:40 web/templates/admin/layout.gohtml:71 @@ -219,12 +404,12 @@ msgctxt "title" msgid "Campsites" msgstr "Allotjaments" -#: web/templates/public/layout.gohtml:66 +#: web/templates/public/layout.gohtml:67 msgctxt "title" msgid "Sections" msgstr "Apartats" -#: web/templates/public/layout.gohtml:92 +#: web/templates/public/layout.gohtml:93 msgid "RTC #%s" msgstr "Núm. RTC %s" @@ -735,52 +920,12 @@ msgstr "Color" msgid "No seasons added yet." msgstr "No s’ha afegit cap temporada encara." -#: web/templates/admin/season/index.gohtml:48 -msgctxt "title" -msgid "Calendar" -msgstr "Calendari" - #: web/templates/admin/season/l10n.gohtml:7 #: web/templates/admin/season/l10n.gohtml:14 msgctxt "title" msgid "Translate Season to %s" msgstr "Traducció de la temporada a %s" -#: web/templates/admin/season/calendar.gohtml:16 -msgctxt "day" -msgid "Mon" -msgstr "dl" - -#: web/templates/admin/season/calendar.gohtml:17 -msgctxt "day" -msgid "Tue" -msgstr "dt" - -#: web/templates/admin/season/calendar.gohtml:18 -msgctxt "day" -msgid "Wed" -msgstr "dc" - -#: web/templates/admin/season/calendar.gohtml:19 -msgctxt "day" -msgid "Thu" -msgstr "dj" - -#: web/templates/admin/season/calendar.gohtml:20 -msgctxt "day" -msgid "Fri" -msgstr "dv" - -#: web/templates/admin/season/calendar.gohtml:21 -msgctxt "day" -msgid "Sat" -msgstr "ds" - -#: web/templates/admin/season/calendar.gohtml:22 -msgctxt "day" -msgid "Sun" -msgstr "dg" - #: web/templates/admin/season/calendar.gohtml:49 #: web/templates/admin/media/picker.gohtml:61 msgctxt "action" @@ -798,12 +943,6 @@ msgctxt "title" msgid "Login" msgstr "Entrada" -#: web/templates/admin/login.gohtml:22 web/templates/admin/profile.gohtml:35 -#: web/templates/admin/taxDetails.gohtml:50 -msgctxt "input" -msgid "Email" -msgstr "Correu-e" - #: web/templates/admin/login.gohtml:31 web/templates/admin/profile.gohtml:46 msgctxt "input" msgid "Password" @@ -916,11 +1055,6 @@ msgctxt "input" msgid "Trade Name" msgstr "Nom comercial" -#: web/templates/admin/taxDetails.gohtml:42 -msgctxt "input" -msgid "Phone" -msgstr "Telèfon" - #: web/templates/admin/taxDetails.gohtml:66 msgctxt "input" msgid "Address" @@ -938,14 +1072,9 @@ msgstr "Província" #: web/templates/admin/taxDetails.gohtml:90 msgctxt "input" -msgid "Postal Code" +msgid "Postcode" msgstr "Codi postal" -#: web/templates/admin/taxDetails.gohtml:98 -msgctxt "input" -msgid "Country" -msgstr "País" - #: web/templates/admin/taxDetails.gohtml:108 msgctxt "input" msgid "RTC number" @@ -1082,10 +1211,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:210 +#: pkg/booking/public.go:292 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:211 +#: pkg/booking/public.go:293 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." @@ -1106,7 +1237,7 @@ msgstr "Automàtic" #: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268 #: pkg/campsite/types/option.go:340 pkg/campsite/types/feature.go:243 #: pkg/campsite/types/admin.go:415 pkg/season/l10n.go:69 -#: pkg/season/admin.go:382 pkg/services/l10n.go:73 pkg/services/admin.go:266 +#: pkg/season/admin.go:394 pkg/services/l10n.go:73 pkg/services/admin.go:266 msgid "Name can not be empty." msgstr "No podeu deixar el nom en blanc." @@ -1213,7 +1344,12 @@ msgstr "El número mínim de nits ha de ser enter." msgid "Minimum number of nights must be one or greater." msgstr "El número mínim de nits no pot ser zero." -#: pkg/campsite/admin.go:226 +#: pkg/campsite/types/public.go:157 +msgctxt "season" +msgid "Closed" +msgstr "Tancat" + +#: pkg/campsite/admin.go:226 pkg/booking/public.go:321 msgid "Selected campsite type is not valid." msgstr "El tipus d’allotjament escollit no és vàlid." @@ -1221,96 +1357,96 @@ msgstr "El tipus d’allotjament escollit no és vàlid." msgid "Label can not be empty." msgstr "No podeu deixar l’etiqueta en blanc." -#: pkg/season/admin.go:202 +#: pkg/season/admin.go:212 msgctxt "month" msgid "January" msgstr "gener" -#: pkg/season/admin.go:203 +#: pkg/season/admin.go:213 msgctxt "month" msgid "February" msgstr "febrer" -#: pkg/season/admin.go:204 +#: pkg/season/admin.go:214 msgctxt "month" msgid "March" msgstr "març" -#: pkg/season/admin.go:205 +#: pkg/season/admin.go:215 msgctxt "month" msgid "April" msgstr "abril" -#: pkg/season/admin.go:206 +#: pkg/season/admin.go:216 msgctxt "month" msgid "May" msgstr "maig" -#: pkg/season/admin.go:207 +#: pkg/season/admin.go:217 msgctxt "month" msgid "June" msgstr "juny" -#: pkg/season/admin.go:208 +#: pkg/season/admin.go:218 msgctxt "month" msgid "July" msgstr "juliol" -#: pkg/season/admin.go:209 +#: pkg/season/admin.go:219 msgctxt "month" msgid "August" msgstr "agost" -#: pkg/season/admin.go:210 +#: pkg/season/admin.go:220 msgctxt "month" msgid "September" msgstr "setembre" -#: pkg/season/admin.go:211 +#: pkg/season/admin.go:221 msgctxt "month" msgid "October" msgstr "octubre" -#: pkg/season/admin.go:212 +#: pkg/season/admin.go:222 msgctxt "month" msgid "November" msgstr "novembre" -#: pkg/season/admin.go:213 +#: pkg/season/admin.go:223 msgctxt "month" msgid "December" msgstr "desembre" -#: pkg/season/admin.go:383 +#: pkg/season/admin.go:395 msgid "Color can not be empty." msgstr "No podeu deixar el color en blanc." -#: pkg/season/admin.go:384 +#: pkg/season/admin.go:396 msgid "This color is not valid. It must be like #123abc." msgstr "Aquest color no és vàlid. Hauria de ser similar a #123abc." -#: pkg/season/admin.go:460 +#: pkg/season/admin.go:472 msgctxt "action" msgid "Unset" msgstr "Desassigna" -#: pkg/season/admin.go:491 +#: pkg/season/admin.go:503 msgid "Start date can not be empty." msgstr "No podeu deixar la data d’inici en blanc." -#: pkg/season/admin.go:492 +#: pkg/season/admin.go:504 msgid "Start date must be a valid date." msgstr "La data d’inici ha de ser una data vàlida." -#: pkg/season/admin.go:494 +#: pkg/season/admin.go:506 msgid "End date can not be empty." msgstr "No podeu deixar la data de fi en blanc." -#: pkg/season/admin.go:495 +#: pkg/season/admin.go:507 msgid "End date must be a valid date." msgstr "La data de fi ha de ser una data vàlida." -#: pkg/company/admin.go:193 +#: pkg/company/admin.go:193 pkg/booking/public.go:279 msgid "Selected country is not valid." msgstr "El país escollit no és vàlid." @@ -1330,11 +1466,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:205 +#: pkg/company/admin.go:205 pkg/booking/public.go:295 msgid "Phone can not be empty." msgstr "No podeu deixar el telèfon en blanc." -#: pkg/company/admin.go:206 +#: pkg/company/admin.go:206 pkg/booking/public.go:296 msgid "This phone number is not valid." msgstr "Aquest número de telèfon no és vàlid." @@ -1358,7 +1494,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:220 +#: pkg/company/admin.go:220 pkg/booking/public.go:288 msgid "This postal code is not valid." msgstr "Aquest codi postal no és vàlid." @@ -1386,6 +1522,106 @@ msgstr "No podeu deixar el fitxer del mèdia en blanc." msgid "Filename can not be empty." msgstr "No podeu deixar el nom del fitxer en blanc." +#: pkg/booking/public.go:283 +msgid "Full name can not be empty." +msgstr "No podeu deixar el nom i els cognoms en blanc." + +#: pkg/booking/public.go:284 +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:300 +msgid "Number of adults can not be empty" +msgstr "No podeu deixar el número d’adults en blanc." + +#: pkg/booking/public.go:301 +msgid "Number of adults must be an integer." +msgstr "El número d’adults ha de ser enter." + +#: pkg/booking/public.go:302 +msgid "Number of adults must be one or greater." +msgstr "El número d’adults no pot ser zero." + +#: pkg/booking/public.go:305 +msgid "Number of teenagers can not be empty" +msgstr "No podeu deixar el número d’adolescents en blanc." + +#: pkg/booking/public.go:306 +msgid "Number of teenagers must be an integer." +msgstr "El número d’adolescents ha de ser enter." + +#: pkg/booking/public.go:307 +msgid "Number of teenagers must be zero or greater." +msgstr "El número d’adolescents ha de ser com a mínim zero." + +#: pkg/booking/public.go:310 +msgid "Number of children can not be empty" +msgstr "No podeu deixar el número de nens en blanc." + +#: pkg/booking/public.go:311 +msgid "Number of children must be an integer." +msgstr "El número de nens ha de ser enter." + +#: pkg/booking/public.go:312 +msgid "Number of children must be zero or greater." +msgstr "El número de nens ha de ser com a mínim zero." + +#: pkg/booking/public.go:315 +msgid "Number of dogs can not be empty" +msgstr "No podeu deixar el número de gossos en blanc." + +#: pkg/booking/public.go:316 +msgid "Number of dogs must be an integer." +msgstr "El número de gossos ha de ser enter." + +#: pkg/booking/public.go:317 +msgid "Number of dogs must be zero or greater." +msgstr "El número de gossos ha de ser com a mínim zero." + +#: pkg/booking/public.go:322 +msgid "Arrival date can not be empty" +msgstr "No podeu deixar la data d’arribada en blanc." + +#: pkg/booking/public.go:323 +msgid "Arrival date must be a valid date." +msgstr "La data d’arribada ha de ser una data vàlida." + +#: pkg/booking/public.go:327 +msgid "Departure date can not be empty" +msgstr "No podeu deixar la data de sortida en blanc." + +#: pkg/booking/public.go:328 +msgid "Departure date must be a valid date." +msgstr "La data de sortida ha de ser una data vàlida." + +#: pkg/booking/public.go:329 +msgid "The departure date must be after the arrival date." +msgstr "La data de sortida ha de ser posterior a la d’arribada." + +#: pkg/booking/public.go:332 +msgid "It is mandatory to agree to the reservation conditions." +msgstr "És obligatori acceptar les condicions de reserves." + +#: pkg/booking/public.go:335 +#, c-format +msgid "%s can not be empty" +msgstr "No podeu deixar %s en blanc." + +#: pkg/booking/public.go:336 +#, c-format +msgid "%s must be an integer." +msgstr "%s ha de ser un número enter." + +#: pkg/booking/public.go:337 +#, 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:338 +#, c-format +msgid "%s must be at most %d." +msgstr "El valor de %s ha de ser com a màxim %d." + #~ msgid "%s: %s €/night" #~ msgstr "%s: %s €/nit" diff --git a/po/es.po b/po/es.po index ba8e5d0..089ceb6 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: 2023-10-14 21:54+0200\n" +"POT-Creation-Date: 2023-10-19 21:23+0200\n" "PO-Revision-Date: 2023-07-22 23:46+0200\n" "Last-Translator: jordi fita mas \n" "Language-Team: Spanish \n" @@ -18,9 +18,32 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#: 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/request.gohtml:17 +msgctxt "action" +msgid "Pay" +msgstr "Pagar" + +#: 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/services.gohtml:6 #: web/templates/public/services.gohtml:15 -#: web/templates/public/layout.gohtml:43 web/templates/public/layout.gohtml:70 +#: web/templates/public/layout.gohtml:44 web/templates/public/layout.gohtml:71 #: web/templates/admin/services/index.gohtml:53 msgctxt "title" msgid "Services" @@ -30,8 +53,8 @@ msgstr "Servicios" msgid "The campsite offers many different services." msgstr "El camping dispone de varios servicios." -#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:29 -#: web/templates/public/layout.gohtml:68 +#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:30 +#: web/templates/public/layout.gohtml:69 msgctxt "title" msgid "Home" msgstr "Inicio" @@ -52,7 +75,7 @@ msgstr "Nuestros servicios" #: web/templates/public/home.gohtml:34 #: web/templates/public/surroundings.gohtml:6 #: web/templates/public/surroundings.gohtml:10 -#: web/templates/public/layout.gohtml:44 web/templates/public/layout.gohtml:71 +#: web/templates/public/layout.gohtml:45 web/templates/public/layout.gohtml:72 msgctxt "title" msgid "Surroundings" msgstr "El entorno" @@ -77,60 +100,109 @@ msgstr "Descubre el entorno" msgid "Come and enjoy!" msgstr "¡Ven a disfrutar!" -#: web/templates/public/campsite/type.gohtml:40 +#: web/templates/public/campsite/type.gohtml:41 msgctxt "input" msgid "Check-in Date" msgstr "Fecha de entrada" -#: web/templates/public/campsite/type.gohtml:46 +#: web/templates/public/campsite/type.gohtml:47 msgctxt "input" msgid "Check-out Date" msgstr "Fecha de salida" -#: web/templates/public/campsite/type.gohtml:53 +#: web/templates/public/campsite/type.gohtml:54 +#: web/templates/public/booking.gohtml:175 msgctxt "action" msgid "Book" msgstr "Reservar" -#: web/templates/public/campsite/type.gohtml:59 +#: web/templates/public/campsite/type.gohtml:60 #: web/templates/admin/campsite/option/form.gohtml:58 #: web/templates/admin/campsite/type/form.gohtml:73 msgctxt "title" msgid "Prices" msgstr "Precios" -#: web/templates/public/campsite/type.gohtml:71 +#: web/templates/public/campsite/type.gohtml:72 msgid "Starting from %s €/night" msgstr "A partir de %s €/noche" -#: web/templates/public/campsite/type.gohtml:73 +#: web/templates/public/campsite/type.gohtml:74 msgid "%s €/night" msgstr "%s €/noche" -#: web/templates/public/campsite/type.gohtml:76 +#: web/templates/public/campsite/type.gohtml:77 msgid "*Minimum %d nights per stay" msgstr "*Mínimo %d noches por estancia" -#: web/templates/public/campsite/type.gohtml:90 +#: web/templates/public/campsite/type.gohtml:89 +#: web/templates/admin/season/index.gohtml:48 +msgctxt "title" +msgid "Calendar" +msgstr "Calendario" + +#: web/templates/public/campsite/type.gohtml:100 msgctxt "title" msgid "Features" msgstr "Características" -#: web/templates/public/campsite/type.gohtml:101 +#: web/templates/public/campsite/type.gohtml:111 msgctxt "title" msgid "Info" msgstr "Información" -#: web/templates/public/campsite/type.gohtml:105 +#: web/templates/public/campsite/type.gohtml:115 msgctxt "title" msgid "Facilities" msgstr "Equipamento" -#: web/templates/public/campsite/type.gohtml:109 +#: web/templates/public/campsite/type.gohtml:119 msgctxt "title" msgid "Description" msgstr "Descripción" +#: web/templates/public/campsite/calendar.gohtml:18 +#: web/templates/admin/season/calendar.gohtml:16 +msgctxt "day" +msgid "Mon" +msgstr "lu" + +#: web/templates/public/campsite/calendar.gohtml:19 +#: web/templates/admin/season/calendar.gohtml:17 +msgctxt "day" +msgid "Tue" +msgstr "ma" + +#: web/templates/public/campsite/calendar.gohtml:20 +#: web/templates/admin/season/calendar.gohtml:18 +msgctxt "day" +msgid "Wed" +msgstr "mi" + +#: web/templates/public/campsite/calendar.gohtml:21 +#: web/templates/admin/season/calendar.gohtml:19 +msgctxt "day" +msgid "Thu" +msgstr "ju" + +#: web/templates/public/campsite/calendar.gohtml:22 +#: web/templates/admin/season/calendar.gohtml:20 +msgctxt "day" +msgid "Fri" +msgstr "vi" + +#: web/templates/public/campsite/calendar.gohtml:23 +#: web/templates/admin/season/calendar.gohtml:21 +msgctxt "day" +msgid "Sat" +msgstr "sá" + +#: web/templates/public/campsite/calendar.gohtml:24 +#: web/templates/admin/season/calendar.gohtml:22 +msgctxt "day" +msgid "Sun" +msgstr "do" + #: web/templates/public/surroundings.gohtml:13 msgctxt "title" msgid "What to Do Outside the Campsite?" @@ -191,27 +263,140 @@ msgstr "Hay diversos puntos dónde podéis ir en kayak, desde tramos del río Te #: web/templates/public/campground.gohtml:6 #: web/templates/public/campground.gohtml:11 -#: web/templates/public/layout.gohtml:30 web/templates/public/layout.gohtml:69 +#: web/templates/public/layout.gohtml:31 web/templates/public/layout.gohtml:70 msgctxt "title" msgid "Campground" msgstr "El camping" #: web/templates/public/contact.gohtml:6 web/templates/public/contact.gohtml:15 -#: web/templates/public/layout.gohtml:45 web/templates/public/layout.gohtml:72 +#: web/templates/public/layout.gohtml:46 web/templates/public/layout.gohtml:73 msgctxt "title" msgid "Contact" msgstr "Contacto" -#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:24 -#: web/templates/public/layout.gohtml:95 +#: web/templates/public/booking.gohtml:6 web/templates/public/booking.gohtml:11 +msgctxt "title" +msgid "Booking" +msgstr "Reserva" + +#: web/templates/public/booking.gohtml:15 +msgctxt "title" +msgid "Customer Details" +msgstr "Detalles del cliente" + +#: web/templates/public/booking.gohtml:18 +msgctxt "input" +msgid "Full name" +msgstr "Nombre y apellidos" + +#: web/templates/public/booking.gohtml:26 +msgctxt "input" +msgid "Address (optional)" +msgstr "Dirección (opcional)" + +#: web/templates/public/booking.gohtml:34 +msgctxt "input" +msgid "Postcode (optional)" +msgstr "Código postal (opcional)" + +#: web/templates/public/booking.gohtml:42 +msgctxt "input" +msgid "Town or village (optional)" +msgstr "Población (opcional)" + +#: web/templates/public/booking.gohtml:50 +#: web/templates/admin/taxDetails.gohtml:98 +msgctxt "input" +msgid "Country" +msgstr "País" + +#: web/templates/public/booking.gohtml:53 +msgid "Choose a country" +msgstr "Escoja un país" + +#: web/templates/public/booking.gohtml:60 web/templates/admin/login.gohtml:22 +#: web/templates/admin/profile.gohtml:35 +#: web/templates/admin/taxDetails.gohtml:50 +msgctxt "input" +msgid "Email" +msgstr "Correo-e" + +#: web/templates/public/booking.gohtml:68 +#: web/templates/admin/taxDetails.gohtml:42 +msgctxt "input" +msgid "Phone" +msgstr "Teléfono" + +#: web/templates/public/booking.gohtml:76 +msgctxt "title" +msgid "Party Details" +msgstr "Datos de los visitantes" + +#: web/templates/public/booking.gohtml:79 +msgctxt "input" +msgid "Adults" +msgstr "Adultos" + +#: web/templates/public/booking.gohtml:87 +msgctxt "input" +msgid "Teenagers (from 11 to 16 years old)" +msgstr "Adolescentes (de 11 a 16 años)" + +#: web/templates/public/booking.gohtml:95 +msgctxt "input" +msgid "Children (up to 10 years old)" +msgstr "Niños (hasta 10 años)" + +#: web/templates/public/booking.gohtml:103 +msgctxt "input" +msgid "Dogs" +msgstr "Perros" + +#: web/templates/public/booking.gohtml:111 +msgctxt "title" +msgid "Accomodation" +msgstr "Acomodaciones" + +#: web/templates/public/booking.gohtml:136 +msgctxt "title" +msgid "Booking Period" +msgstr "Periodo de reserva" + +#: web/templates/public/booking.gohtml:139 +msgctxt "input" +msgid "Arrival date" +msgstr "Fecha de llegada" + +#: web/templates/public/booking.gohtml:147 +msgctxt "input" +msgid "Departure date" +msgstr "Fecha de salida" + +#: web/templates/public/booking.gohtml:155 +msgctxt "input" +msgid "Area preferences (optional)" +msgstr "Preferencias de área (opcional)" + +#: web/templates/public/booking.gohtml:165 +msgctxt "input" +msgid "ACSI card? (optional)" +msgstr "¿Tarjeta ACSI? (opcional)" + +#: web/templates/public/booking.gohtml:171 +msgctxt "input" +msgid "I have read and I accept the reservation conditions" +msgstr "He leído y acepto las condiciones de reserva" + +#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:25 +#: web/templates/public/layout.gohtml:96 msgid "Campsite Montagut" msgstr "Camping Montagut" -#: web/templates/public/layout.gohtml:22 web/templates/admin/layout.gohtml:18 +#: web/templates/public/layout.gohtml:23 web/templates/admin/layout.gohtml:18 msgid "Skip to main content" msgstr "Saltar al contenido principal" -#: web/templates/public/layout.gohtml:34 web/templates/public/layout.gohtml:79 +#: web/templates/public/layout.gohtml:35 web/templates/public/layout.gohtml:80 #: web/templates/admin/campsite/index.gohtml:6 #: web/templates/admin/campsite/index.gohtml:12 #: web/templates/admin/layout.gohtml:40 web/templates/admin/layout.gohtml:71 @@ -219,12 +404,12 @@ msgctxt "title" msgid "Campsites" msgstr "Alojamientos" -#: web/templates/public/layout.gohtml:66 +#: web/templates/public/layout.gohtml:67 msgctxt "title" msgid "Sections" msgstr "Apartados" -#: web/templates/public/layout.gohtml:92 +#: web/templates/public/layout.gohtml:93 msgid "RTC #%s" msgstr " RTC %s" @@ -735,52 +920,12 @@ msgstr "Color" msgid "No seasons added yet." msgstr "No se ha añadido ninguna temporada todavía." -#: web/templates/admin/season/index.gohtml:48 -msgctxt "title" -msgid "Calendar" -msgstr "Calendario" - #: web/templates/admin/season/l10n.gohtml:7 #: web/templates/admin/season/l10n.gohtml:14 msgctxt "title" msgid "Translate Season to %s" msgstr "Traducción de la temporada a %s" -#: web/templates/admin/season/calendar.gohtml:16 -msgctxt "day" -msgid "Mon" -msgstr "lu" - -#: web/templates/admin/season/calendar.gohtml:17 -msgctxt "day" -msgid "Tue" -msgstr "ma" - -#: web/templates/admin/season/calendar.gohtml:18 -msgctxt "day" -msgid "Wed" -msgstr "mi" - -#: web/templates/admin/season/calendar.gohtml:19 -msgctxt "day" -msgid "Thu" -msgstr "ju" - -#: web/templates/admin/season/calendar.gohtml:20 -msgctxt "day" -msgid "Fri" -msgstr "vi" - -#: web/templates/admin/season/calendar.gohtml:21 -msgctxt "day" -msgid "Sat" -msgstr "sá" - -#: web/templates/admin/season/calendar.gohtml:22 -msgctxt "day" -msgid "Sun" -msgstr "do" - #: web/templates/admin/season/calendar.gohtml:49 #: web/templates/admin/media/picker.gohtml:61 msgctxt "action" @@ -798,12 +943,6 @@ msgctxt "title" msgid "Login" msgstr "Entrada" -#: web/templates/admin/login.gohtml:22 web/templates/admin/profile.gohtml:35 -#: web/templates/admin/taxDetails.gohtml:50 -msgctxt "input" -msgid "Email" -msgstr "Correo-e" - #: web/templates/admin/login.gohtml:31 web/templates/admin/profile.gohtml:46 msgctxt "input" msgid "Password" @@ -916,11 +1055,6 @@ msgctxt "input" msgid "Trade Name" msgstr "Nombre comercial" -#: web/templates/admin/taxDetails.gohtml:42 -msgctxt "input" -msgid "Phone" -msgstr "Teléfono" - #: web/templates/admin/taxDetails.gohtml:66 msgctxt "input" msgid "Address" @@ -938,14 +1072,9 @@ msgstr "Provincia" #: web/templates/admin/taxDetails.gohtml:90 msgctxt "input" -msgid "Postal Code" +msgid "Postcode" msgstr "Código postal" -#: web/templates/admin/taxDetails.gohtml:98 -msgctxt "input" -msgid "Country" -msgstr "País" - #: web/templates/admin/taxDetails.gohtml:108 msgctxt "input" msgid "RTC number" @@ -1082,10 +1211,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:210 +#: pkg/booking/public.go:292 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:211 +#: pkg/booking/public.go:293 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." @@ -1106,7 +1237,7 @@ msgstr "Automático" #: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268 #: pkg/campsite/types/option.go:340 pkg/campsite/types/feature.go:243 #: pkg/campsite/types/admin.go:415 pkg/season/l10n.go:69 -#: pkg/season/admin.go:382 pkg/services/l10n.go:73 pkg/services/admin.go:266 +#: pkg/season/admin.go:394 pkg/services/l10n.go:73 pkg/services/admin.go:266 msgid "Name can not be empty." msgstr "No podéis dejar el nombre en blanco." @@ -1213,7 +1344,12 @@ msgstr "El número mínimo de noches tiene que ser entero." msgid "Minimum number of nights must be one or greater." msgstr "El número mínimo de noches no puede ser cero." -#: pkg/campsite/admin.go:226 +#: pkg/campsite/types/public.go:157 +msgctxt "season" +msgid "Closed" +msgstr "Cerrado" + +#: pkg/campsite/admin.go:226 pkg/booking/public.go:321 msgid "Selected campsite type is not valid." msgstr "El tipo de alojamiento escogido no es válido." @@ -1221,96 +1357,96 @@ msgstr "El tipo de alojamiento escogido no es válido." msgid "Label can not be empty." msgstr "No podéis dejar la etiqueta en blanco." -#: pkg/season/admin.go:202 +#: pkg/season/admin.go:212 msgctxt "month" msgid "January" msgstr "enero" -#: pkg/season/admin.go:203 +#: pkg/season/admin.go:213 msgctxt "month" msgid "February" msgstr "febrero" -#: pkg/season/admin.go:204 +#: pkg/season/admin.go:214 msgctxt "month" msgid "March" msgstr "marzo" -#: pkg/season/admin.go:205 +#: pkg/season/admin.go:215 msgctxt "month" msgid "April" msgstr "abril" -#: pkg/season/admin.go:206 +#: pkg/season/admin.go:216 msgctxt "month" msgid "May" msgstr "mayo" -#: pkg/season/admin.go:207 +#: pkg/season/admin.go:217 msgctxt "month" msgid "June" msgstr "junio" -#: pkg/season/admin.go:208 +#: pkg/season/admin.go:218 msgctxt "month" msgid "July" msgstr "julio" -#: pkg/season/admin.go:209 +#: pkg/season/admin.go:219 msgctxt "month" msgid "August" msgstr "agosto" -#: pkg/season/admin.go:210 +#: pkg/season/admin.go:220 msgctxt "month" msgid "September" msgstr "septiembre" -#: pkg/season/admin.go:211 +#: pkg/season/admin.go:221 msgctxt "month" msgid "October" msgstr "octubre" -#: pkg/season/admin.go:212 +#: pkg/season/admin.go:222 msgctxt "month" msgid "November" msgstr "noviembre" -#: pkg/season/admin.go:213 +#: pkg/season/admin.go:223 msgctxt "month" msgid "December" msgstr "diciembre" -#: pkg/season/admin.go:383 +#: pkg/season/admin.go:395 msgid "Color can not be empty." msgstr "No podéis dejar el color en blanco." -#: pkg/season/admin.go:384 +#: pkg/season/admin.go:396 msgid "This color is not valid. It must be like #123abc." msgstr "Este color no es válido. Tiene que ser parecido a #123abc." -#: pkg/season/admin.go:460 +#: pkg/season/admin.go:472 msgctxt "action" msgid "Unset" msgstr "Desasignar" -#: pkg/season/admin.go:491 +#: pkg/season/admin.go:503 msgid "Start date can not be empty." msgstr "No podéis dejar la fecha de inicio en blanco." -#: pkg/season/admin.go:492 +#: pkg/season/admin.go:504 msgid "Start date must be a valid date." msgstr "La fecha de inicio tiene que ser una fecha válida." -#: pkg/season/admin.go:494 +#: pkg/season/admin.go:506 msgid "End date can not be empty." msgstr "No podéis dejar la fecha final en blanco." -#: pkg/season/admin.go:495 +#: pkg/season/admin.go:507 msgid "End date must be a valid date." msgstr "La fecha final tiene que ser una fecha válida." -#: pkg/company/admin.go:193 +#: pkg/company/admin.go:193 pkg/booking/public.go:279 msgid "Selected country is not valid." msgstr "El país escogido no es válido." @@ -1330,11 +1466,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:205 +#: pkg/company/admin.go:205 pkg/booking/public.go:295 msgid "Phone can not be empty." msgstr "No podéis dejar el teléfono en blanco." -#: pkg/company/admin.go:206 +#: pkg/company/admin.go:206 pkg/booking/public.go:296 msgid "This phone number is not valid." msgstr "Este teléfono no es válido." @@ -1358,7 +1494,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:220 +#: pkg/company/admin.go:220 pkg/booking/public.go:288 msgid "This postal code is not valid." msgstr "Este código postal no es válido." @@ -1386,6 +1522,105 @@ msgstr "No podéis dejar el archivo del medio en blanco." msgid "Filename can not be empty." msgstr "No podéis dejar el nombre del archivo en blanco." +#: pkg/booking/public.go:283 +msgid "Full name can not be empty." +msgstr "No podéis dejar el nombre y los apellidos en blanco." + +#: pkg/booking/public.go:284 +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:300 +msgid "Number of adults can not be empty" +msgstr "No podéis dejar el número de adultos blanco." + +#: pkg/booking/public.go:301 +msgid "Number of adults must be an integer." +msgstr "El número de adultos tiene que ser entero." + +#: pkg/booking/public.go:302 +msgid "Number of adults must be one or greater." +msgstr "El número de adultos no puede ser cero." + +#: pkg/booking/public.go:305 +msgid "Number of teenagers can not be empty" +msgstr "No podéis dejar el número de adolescentes en blanco." + +#: pkg/booking/public.go:306 +msgid "Number of teenagers must be an integer." +msgstr "El número de adolescentes tiene que ser entero." + +#: pkg/booking/public.go:307 +msgid "Number of teenagers must be zero or greater." +msgstr "El número de adolescentes tiene que ser como mínimo cero." + +#: pkg/booking/public.go:310 +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:311 +msgid "Number of children must be an integer." +msgstr "El número de niños tiene que ser entero." + +#: pkg/booking/public.go:312 +msgid "Number of children must be zero or greater." +msgstr "El número de niños tiene que ser como mínimo cero." + +#: pkg/booking/public.go:315 +msgid "Number of dogs can not be empty" +msgstr "No podéis dejar el número de perros en blanco." + +#: pkg/booking/public.go:316 +msgid "Number of dogs must be an integer." +msgstr "El número de perros tiene que ser entero." + +#: pkg/booking/public.go:317 +msgid "Number of dogs must be zero or greater." +msgstr "El número de perros tiene que ser como mínimo cero." + +#: pkg/booking/public.go:322 +msgid "Arrival date can not be empty" +msgstr "No podéis dejar la fecha de llegada en blanco." + +#: pkg/booking/public.go:323 +msgid "Arrival date must be a valid date." +msgstr "La fecha de llegada tiene que ser una fecha válida." + +#: pkg/booking/public.go:327 +msgid "Departure date can not be empty" +msgstr "No podéis dejar la fecha de partida en blanco." + +#: pkg/booking/public.go:328 +msgid "Departure date must be a valid date." +msgstr "La fecha de partida tiene que ser una fecha válida." + +#: pkg/booking/public.go:329 +msgid "The departure date must be after the arrival date." +msgstr "La fecha de partida tiene que ser posterior a la de llegada." + +#: pkg/booking/public.go:332 +msgid "It is mandatory to agree to the reservation conditions." +msgstr "Es obligatorio aceptar las condiciones de reserva." + +#: pkg/booking/public.go:335 +msgid "%s can not be empty" +msgstr "No podéis dejar %s en blanco." + +#: pkg/booking/public.go:336 +#, c-format +msgid "%s must be an integer." +msgstr "%s tiene que ser un número entero." + +#: pkg/booking/public.go:337 +#, c-format +msgid "%s must be %d or greater." +msgstr "%s tiene que ser como mínimo %d." + +#: pkg/booking/public.go:338 +#, c-format +msgid "%s must be at most %d." +msgstr "%s tiene que ser como máximo %d" + #~ msgid "%s: %s €/night" #~ msgstr "%s: %s €/noche" diff --git a/test/country.sql b/test/country.sql index 7e32160..d47b16c 100644 --- a/test/country.sql +++ b/test/country.sql @@ -11,7 +11,7 @@ set search_path to camper, public; select has_table('country'); select has_pk('country' ); -select table_privs_are('country', 'guest', array []::text[]); +select table_privs_are('country', 'guest', array ['SELECT']); select table_privs_are('country', 'employee', array ['SELECT']); select table_privs_are('country', 'admin', array ['SELECT']); select table_privs_are('country', 'authenticator', array []::text[]); diff --git a/test/country_i18n.sql b/test/country_i18n.sql index d4bba51..fe27be4 100644 --- a/test/country_i18n.sql +++ b/test/country_i18n.sql @@ -12,7 +12,7 @@ set search_path to camper, public; select has_table('country_i18n'); select has_pk('country_i18n' ); select col_is_pk('country_i18n', array['country_code', 'lang_tag']); -select table_privs_are('country_i18n', 'guest', array []::text[]); +select table_privs_are('country_i18n', 'guest', array ['SELECT']); select table_privs_are('country_i18n', 'employee', array ['SELECT']); select table_privs_are('country_i18n', 'admin', array ['SELECT']); select table_privs_are('country_i18n', 'authenticator', array []::text[]); diff --git a/web/templates/admin/taxDetails.gohtml b/web/templates/admin/taxDetails.gohtml index cd9587f..39c0c57 100644 --- a/web/templates/admin/taxDetails.gohtml +++ b/web/templates/admin/taxDetails.gohtml @@ -87,7 +87,7 @@ {{- end }} {{ with .PostalCode -}} diff --git a/web/templates/public/booking.gohtml b/web/templates/public/booking.gohtml new file mode 100644 index 0000000..48e3ec8 --- /dev/null +++ b/web/templates/public/booking.gohtml @@ -0,0 +1,179 @@ + +{{ define "title" -}} + {{( pgettext "Booking" "title" )}} +{{- end }} + +{{ define "content" -}} + {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.publicPage*/ -}} +

{{( pgettext "Booking" "title" )}}

+ {{ with .Form -}} +
+
+ {{( pgettext "Customer Details" "title" )}} + {{ with .FullName -}} + + {{- end }} + {{ with .Address -}} + + {{- end }} + {{ with .PostalCode -}} + + {{- end }} + {{ with .City -}} + + {{- end }} + {{ with .Country -}} + + {{- end }} + {{ with .Email -}} + + {{- end }} + {{ with .Phone -}} + + {{- end }} +
+
+ {{( pgettext "Party Details" "title" )}} + {{ with .Adults -}} + + {{- end }} + {{ with .Teenagers -}} + + {{- end }} + {{ with .Children -}} + + {{- end }} + {{ with .Dogs -}} + + {{- end }} +
+
+ {{( pgettext "Accomodation" "title" )}} + {{ range .CampsiteType.Options -}} +
+ {{- end }} +
+ {{ range .CampsiteType.Options -}} + {{ $options := index $.Form.CampsiteTypeOptions .Value }} + {{ if $options -}} +
+ {{ .Label }} + {{ range $options -}} + + {{- end }} +
+ {{- end }} + {{- end }} +
+ {{( pgettext "Booking Period" "title" )}} + {{ with .ArrivalDate -}} + + {{- end }} + {{ with .DepartureDate -}} + + {{- end }} + {{ with .AreaPreferences -}} + + {{- end }} + {{ with .ACSICard -}} +
+ {{- end }} + {{ with .Agreement -}} +
+ {{- end }} +
+
+ +
+
+ {{- end }} +{{- end }} diff --git a/web/templates/public/payment/failure.gohtml b/web/templates/public/payment/failure.gohtml new file mode 100644 index 0000000..1c42b9c --- /dev/null +++ b/web/templates/public/payment/failure.gohtml @@ -0,0 +1,12 @@ + +{{ define "title" -}} + {{( pgettext "Payment Failed" "title" )}} +{{- end }} + +{{ define "content" -}} + {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.failedPaymentPage*/ -}} +

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

+{{- end }} diff --git a/web/templates/public/payment/request.gohtml b/web/templates/public/payment/request.gohtml new file mode 100644 index 0000000..38bfd5c --- /dev/null +++ b/web/templates/public/payment/request.gohtml @@ -0,0 +1,20 @@ + +{{ define "title" -}} + {{( pgettext "Payment" "title" )}} +{{- end }} + +{{ define "content" -}} + {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.paymentPage*/ -}} +

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

+ {{ with .Request -}} +
+ + + + +
+ {{- end }} +{{- end }} diff --git a/web/templates/public/payment/success.gohtml b/web/templates/public/payment/success.gohtml new file mode 100644 index 0000000..753b889 --- /dev/null +++ b/web/templates/public/payment/success.gohtml @@ -0,0 +1,12 @@ + +{{ define "title" -}} + {{( pgettext "Payment Successful" "title" )}} +{{- end }} + +{{ define "content" -}} + {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.failedPaymentPage*/ -}} +

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

+{{- end }}