From d225b4516642e11f648c1b27ce99fad0d6a2214f Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Thu, 28 May 2026 02:01:47 -0700 Subject: [PATCH] Fix ArifPay verification when nonce is missing. Resolve payments by nonce or session_id in webhook/verify processing so status checks can complete and activate subscriptions even when ArifPay verify responses omit nonce. Co-authored-by: Cursor --- internal/services/arifpay/service.go | 39 +++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/internal/services/arifpay/service.go b/internal/services/arifpay/service.go index 65b0365..5799eec 100644 --- a/internal/services/arifpay/service.go +++ b/internal/services/arifpay/service.go @@ -186,11 +186,12 @@ func (s *ArifpayService) InitiateSubscriptionPayment(ctx context.Context, userID // ProcessPaymentWebhook handles the webhook callback from ArifPay func (s *ArifpayService) ProcessPaymentWebhook(ctx context.Context, req domain.WebhookRequest) error { - // Get payment by nonce - payment, err := s.paymentStore.GetPaymentByNonce(ctx, req.Nonce) + // ArifPay verify/webhook payloads are inconsistent: some responses include nonce, others only sessionId. + payment, err := s.resolvePaymentForWebhook(ctx, req) if err != nil { - return fmt.Errorf("payment not found for nonce %s: %w", req.Nonce, err) + return err } + nonce := payment.Nonce if payment.Status == string(domain.PaymentStatusSuccess) { return ErrPaymentAlreadyPaid @@ -216,12 +217,16 @@ func (s *ArifpayService) ProcessPaymentWebhook(ctx context.Context, req domain.W } // Update payment status + paymentMethod := req.PaymentMethod + if paymentMethod == "" && payment.PaymentMethod != nil { + paymentMethod = *payment.PaymentMethod + } if err := s.paymentStore.UpdatePaymentStatusByNonce( ctx, - req.Nonce, + nonce, newStatus, req.Transaction.TransactionID, - req.PaymentMethod, + paymentMethod, ); err != nil { return fmt.Errorf("failed to update payment status: %w", err) } @@ -237,8 +242,7 @@ func (s *ArifpayService) ProcessPaymentWebhook(ctx context.Context, req domain.W expiresAt := domain.CalculateExpiryDate(startsAt, plan.DurationValue, plan.DurationUnit) activeStatus := string(domain.SubscriptionStatusActive) autoRenew := false - paymentRef := payment.Nonce - paymentMethod := req.PaymentMethod + paymentRef := nonce subscription, err := s.subscriptionStore.CreateUserSubscription(ctx, domain.CreateUserSubscriptionInput{ UserID: payment.UserID, @@ -263,6 +267,23 @@ func (s *ArifpayService) ProcessPaymentWebhook(ctx context.Context, req domain.W return nil } +func (s *ArifpayService) resolvePaymentForWebhook(ctx context.Context, req domain.WebhookRequest) (*domain.Payment, error) { + if req.Nonce != "" { + payment, err := s.paymentStore.GetPaymentByNonce(ctx, req.Nonce) + if err == nil { + return payment, nil + } + } + if req.SessionID != "" { + payment, err := s.paymentStore.GetPaymentBySessionID(ctx, req.SessionID) + if err == nil { + return payment, nil + } + return nil, fmt.Errorf("payment not found for session %s: %w", req.SessionID, err) + } + return nil, fmt.Errorf("payment not found for nonce %s: %w", req.Nonce, ErrPaymentNotFound) +} + // VerifyPayment checks the status of a payment with ArifPay func (s *ArifpayService) VerifyPayment(ctx context.Context, sessionID string) (*domain.Payment, error) { // Get local payment record @@ -304,6 +325,10 @@ func (s *ArifpayService) VerifyPayment(ctx context.Context, sessionID string) (* if err := json.Unmarshal(respBytes, &result); err != nil { return nil, fmt.Errorf("failed to parse ArifPay response: %w", err) } + // Ensure fallback lookup key when ArifPay omits nonce in verify response. + if result.SessionID == "" { + result.SessionID = sessionID + } // Process the verification result same as webhook if err := s.ProcessPaymentWebhook(ctx, result); err != nil && err != ErrPaymentAlreadyPaid {