telebirr-ussd direct payment fix
This commit is contained in:
parent
f906862676
commit
21ce61b910
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user