From 6423bb261e414363449c2ef2f1b177345d801c38 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Fri, 29 May 2026 04:38:55 -0700 Subject: [PATCH] Sanitize Chapa checkout customization text for initialize API. Strip disallowed characters from customization title and description so subscription payments pass Chapa validation. Co-authored-by: Cursor --- internal/services/chapa/customization_test.go | 28 ++++++++++++++++++ internal/services/chapa/service.go | 29 +++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 internal/services/chapa/customization_test.go diff --git a/internal/services/chapa/customization_test.go b/internal/services/chapa/customization_test.go new file mode 100644 index 0000000..1a39760 --- /dev/null +++ b/internal/services/chapa/customization_test.go @@ -0,0 +1,28 @@ +package chapa + +import "testing" + +func TestSanitizeChapaCustomization(t *testing.T) { + tests := []struct { + in string + want string + }{ + {"Subscription: Premium", "Subscription Premium"}, + {"New Test Monthly Premium", "New Test Monthly Premium"}, + {"IELTS (Prep) / Monthly", "IELTS Prep Monthly"}, + {"", "Yimaru subscription"}, + } + for _, tc := range tests { + if got := sanitizeChapaCustomization(tc.in); got != tc.want { + t.Fatalf("sanitizeChapaCustomization(%q) = %q, want %q", tc.in, got, tc.want) + } + } +} + +func TestChapaSubscriptionDescription(t *testing.T) { + got := chapaSubscriptionDescription("New Test Monthly Premium") + want := "Subscription New Test Monthly Premium" + if got != want { + t.Fatalf("chapaSubscriptionDescription() = %q, want %q", got, want) + } +} diff --git a/internal/services/chapa/service.go b/internal/services/chapa/service.go index cdddf3f..6d983c9 100644 --- a/internal/services/chapa/service.go +++ b/internal/services/chapa/service.go @@ -141,8 +141,8 @@ func (s *Service) InitiateSubscriptionPayment(ctx context.Context, userID int64, CallbackURL: s.cfg.CHAPA_CALLBACK_URL, ReturnURL: s.cfg.CHAPA_RETURN_URL, } - initReq.Customization.Title = "Yimaru LMS" - initReq.Customization.Description = fmt.Sprintf("Subscription: %s", plan.Name) + initReq.Customization.Title = sanitizeChapaCustomization("Yimaru LMS") + initReq.Customization.Description = chapaSubscriptionDescription(plan.Name) checkoutURL, err := s.initializeTransaction(ctx, initReq) if err != nil { @@ -453,6 +453,31 @@ func formatAmount(amount float64) string { return strconv.FormatFloat(math.Round(amount*100)/100, 'f', 2, 64) } +// sanitizeChapaCustomization keeps only characters allowed by Chapa customization fields. +func sanitizeChapaCustomization(value string) string { + var b strings.Builder + b.Grow(len(value)) + for _, r := range value { + switch { + case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9': + b.WriteRune(r) + case r == '-', r == '_', r == ' ', r == '.': + b.WriteRune(r) + default: + b.WriteRune(' ') + } + } + cleaned := strings.Join(strings.Fields(b.String()), " ") + if cleaned == "" { + return "Yimaru subscription" + } + return cleaned +} + +func chapaSubscriptionDescription(planName string) string { + return sanitizeChapaCustomization("Subscription " + strings.TrimSpace(planName)) +} + func normalizeCurrency(currency string) string { c := strings.TrimSpace(strings.ToUpper(currency)) if c == "" {