telebirr-ussd direct payment fix

This commit is contained in:
Yared Yemane 2026-05-07 09:08:43 -07:00
parent f906862676
commit 21ce61b910

View File

@ -596,10 +596,62 @@ func (s *ArifpayService) InitiateDirectPayment(ctx context.Context, userID int64
return nil, fmt.Errorf("failed to create payment record: %w", err) return nil, fmt.Errorf("failed to create payment record: %w", err)
} }
// Create ArifPay checkout session with specific payment method phone := formatPhone(req.Phone)
checkoutReq := domain.CheckoutSessionRequest{ checkoutReq := s.buildDirectCheckoutRequest(req, phone, nonce, expiresAt, plan)
var (
sessionID string
directResp string
)
if req.PaymentMethod == domain.DirectPaymentTelebirrUSSD {
// TELEBIRR_USSD uses a direct proxy endpoint with full checkout payload.
sessionID, directResp, err = s.initiateTelebirrUSSDDirect(ctx, checkoutReq)
} else {
sessionID, err = s.createCheckoutSessionForDirect(ctx, checkoutReq)
if err == nil {
directResp, err = s.initiateDirectTransfer(ctx, sessionID, phone, req.PaymentMethod)
}
}
if err != nil {
// Update payment status to failed
s.paymentStore.UpdatePaymentStatus(ctx, payment.ID, string(domain.PaymentStatusFailed))
return nil, fmt.Errorf("failed to initiate direct transfer: %w", err)
}
// Update payment with session info
if err := s.paymentStore.UpdatePaymentSessionID(ctx, payment.ID, sessionID, ""); err != nil {
return nil, fmt.Errorf("failed to update payment session: %w", err)
}
requiresOTP := s.paymentMethodRequiresOTP(req.PaymentMethod)
message := "Payment initiated"
if requiresOTP {
message = "OTP sent to your phone. Please verify to complete payment."
} else {
message = directResp
}
return &domain.InitiateDirectPaymentResponse{
PaymentID: payment.ID,
SessionID: sessionID,
RequiresOTP: requiresOTP,
Message: message,
Amount: plan.Price,
Currency: plan.Currency,
}, nil
}
func (s *ArifpayService) buildDirectCheckoutRequest(
req domain.InitiateDirectPaymentRequest,
phone string,
nonce string,
expiresAt time.Time,
plan *domain.SubscriptionPlan,
) domain.CheckoutSessionRequest {
return domain.CheckoutSessionRequest{
CancelURL: s.cfg.ARIFPAY.CancelUrl, CancelURL: s.cfg.ARIFPAY.CancelUrl,
Phone: formatPhone(req.Phone), Phone: phone,
Email: req.Email, Email: req.Email,
Nonce: nonce, Nonce: nonce,
SuccessURL: s.cfg.ARIFPAY.SuccessUrl, SuccessURL: s.cfg.ARIFPAY.SuccessUrl,
@ -633,77 +685,101 @@ func (s *ArifpayService) InitiateDirectPayment(ctx context.Context, userID int64
}, },
Lang: s.cfg.ARIFPAY.Lang, Lang: s.cfg.ARIFPAY.Lang,
} }
}
// Create session func (s *ArifpayService) createCheckoutSessionForDirect(ctx context.Context, checkoutReq domain.CheckoutSessionRequest) (string, error) {
payload, err := json.Marshal(checkoutReq) payload, err := json.Marshal(checkoutReq)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to marshal checkout request: %w", err) return "", fmt.Errorf("failed to marshal checkout request: %w", err)
} }
url := fmt.Sprintf("%s/api/checkout/session", s.cfg.ARIFPAY.BaseURL) url := fmt.Sprintf("%s/api/checkout/session", s.cfg.ARIFPAY.BaseURL)
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(payload)) httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(payload))
if err != nil { if err != nil {
return nil, err return "", err
} }
httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey) httpReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
resp, err := s.httpClient.Do(httpReq) resp, err := s.httpClient.Do(httpReq)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to call ArifPay API: %w", err) return "", fmt.Errorf("failed to call ArifPay API: %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return "", err
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("ArifPay API error: %s", string(body)) return "", fmt.Errorf("ArifPay API error: %s", string(body))
} }
var result map[string]interface{} var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil { if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("invalid response from ArifPay: %w", err) return "", fmt.Errorf("invalid response from ArifPay: %w", err)
} }
data, ok := result["data"].(map[string]interface{}) data, ok := result["data"].(map[string]interface{})
if !ok { if !ok {
return nil, errors.New("invalid response structure from ArifPay") return "", errors.New("invalid response structure from ArifPay")
} }
sessionID := fmt.Sprintf("%v", data["sessionId"]) return fmt.Sprintf("%v", data["sessionId"]), nil
}
// Update payment with session info func (s *ArifpayService) initiateTelebirrUSSDDirect(ctx context.Context, checkoutReq domain.CheckoutSessionRequest) (string, string, error) {
if err := s.paymentStore.UpdatePaymentSessionID(ctx, payment.ID, sessionID, ""); err != nil { payload, err := json.Marshal(checkoutReq)
return nil, fmt.Errorf("failed to update payment session: %w", err)
}
// Now initiate direct transfer based on payment method
directResp, err := s.initiateDirectTransfer(ctx, sessionID, formatPhone(req.Phone), req.PaymentMethod)
if err != nil { if err != nil {
// Update payment status to failed return "", "", fmt.Errorf("failed to marshal telebirr ussd request: %w", err)
s.paymentStore.UpdatePaymentStatus(ctx, payment.ID, string(domain.PaymentStatusFailed))
return nil, fmt.Errorf("failed to initiate direct transfer: %w", err)
} }
requiresOTP := s.paymentMethodRequiresOTP(req.PaymentMethod) endpoint := fmt.Sprintf("%s/api/checkout/telebirr-ussd/transfer/direct", s.cfg.ARIFPAY.BaseURL)
message := "Payment initiated" httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(payload))
if requiresOTP { if err != nil {
message = "OTP sent to your phone. Please verify to complete payment." return "", "", err
} else { }
message = directResp httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("x-arifpay-key", s.cfg.ARIFPAY.APIKey)
resp, err := s.httpClient.Do(httpReq)
if err != nil {
return "", "", fmt.Errorf("failed to call telebirr ussd direct API: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", "", err
}
if resp.StatusCode != http.StatusOK {
return "", "", fmt.Errorf("telebirr ussd direct failed: %s", string(body))
} }
return &domain.InitiateDirectPaymentResponse{ var result struct {
PaymentID: payment.ID, Msg string `json:"msg"`
SessionID: sessionID, Data struct {
RequiresOTP: requiresOTP, SessionID interface{} `json:"sessionId"`
Message: message, URL string `json:"url"`
Amount: plan.Price, } `json:"data"`
Currency: plan.Currency, }
}, nil if err := json.Unmarshal(body, &result); err != nil {
return "", "", fmt.Errorf("invalid telebirr ussd response: %w", err)
}
sessionID := fmt.Sprintf("%v", result.Data.SessionID)
if sessionID == "" || sessionID == "<nil>" {
sessionID = checkoutReq.Nonce
}
message := result.Msg
if message == "" {
message = "USSD transfer initiated"
}
if result.Data.URL != "" {
message = result.Data.URL
}
return sessionID, message, nil
} }
// initiateDirectTransfer calls the appropriate direct transfer endpoint // initiateDirectTransfer calls the appropriate direct transfer endpoint