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)
}
// Create ArifPay checkout session with specific payment method
checkoutReq := domain.CheckoutSessionRequest{
phone := formatPhone(req.Phone)
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,
Phone: formatPhone(req.Phone),
Phone: phone,
Email: req.Email,
Nonce: nonce,
SuccessURL: s.cfg.ARIFPAY.SuccessUrl,
@ -633,77 +685,101 @@ func (s *ArifpayService) InitiateDirectPayment(ctx context.Context, userID int64
},
Lang: s.cfg.ARIFPAY.Lang,
}
}
// Create session
func (s *ArifpayService) createCheckoutSessionForDirect(ctx context.Context, checkoutReq domain.CheckoutSessionRequest) (string, error) {
payload, err := json.Marshal(checkoutReq)
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)
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(payload))
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(payload))
if err != nil {
return nil, err
return "", err
}
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 nil, fmt.Errorf("failed to call ArifPay API: %w", err)
return "", fmt.Errorf("failed to call ArifPay API: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
return "", err
}
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{}
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{})
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
if err := s.paymentStore.UpdatePaymentSessionID(ctx, payment.ID, sessionID, ""); err != nil {
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)
func (s *ArifpayService) initiateTelebirrUSSDDirect(ctx context.Context, checkoutReq domain.CheckoutSessionRequest) (string, string, error) {
payload, err := json.Marshal(checkoutReq)
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)
return "", "", fmt.Errorf("failed to marshal telebirr ussd request: %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
endpoint := fmt.Sprintf("%s/api/checkout/telebirr-ussd/transfer/direct", s.cfg.ARIFPAY.BaseURL)
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(payload))
if err != nil {
return "", "", err
}
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{
PaymentID: payment.ID,
SessionID: sessionID,
RequiresOTP: requiresOTP,
Message: message,
Amount: plan.Price,
Currency: plan.Currency,
}, nil
var result struct {
Msg string `json:"msg"`
Data struct {
SessionID interface{} `json:"sessionId"`
URL string `json:"url"`
} `json:"data"`
}
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