package service import ( "context" "crypto/rand" "encoding/hex" "fmt" "log/slog" "strconv" "time" ) type Config struct { PaymentGetToken string // آدرس دریافت توکن PaymentVerifyURL string // آدرس تأیید تراکنش PaymentReverseURL string // آدرس برگشت تراکنش CallbackURL string // آدرس بازگشت Credential string // TerminalId } type IPGTransaction struct { ID string `json:"id"` IPGType string `json:"ipg_type"` GatewayMeta map[string]string `json:"gateway_meta,omitempty"` Token string `json:"token"` Amount float64 `json:"amount"` CreatedAt time.Time `json:"created_at"` } type PaymentService struct { IPGTransaction *IPGTransaction Cfg Config } type InitPaymentResponse struct { Success bool `json:"success"` GatewayToken string `json:"gateway_token"` RedirectUrl string `json:"redirect_url"` IPGTransactionId string `json:"ipg_transaction_id"` Message string `json:"message"` Time time.Time `json:"time"` } // SEP Request/Response type SepTokenRequest struct { Action string `json:"action"` TerminalId string `json:"TerminalId"` Amount int64 `json:"Amount"` ResNum string `json:"ResNum"` RedirectUrl string `json:"RedirectUrl"` CellNumber string `json:"CellNumber"` TokenExpiryInMin int `json:"TokenExpiryInMin,omitempty"` ResNum1 string `json:"ResNum1,omitempty"` ResNum2 string `json:"ResNum2,omitempty"` ResNum3 string `json:"ResNum3,omitempty"` ResNum4 string `json:"ResNum4,omitempty"` } func NewPaymentService(cfg Config) PaymentService { return PaymentService{ Cfg: cfg, } } func (p *PaymentService) InitPayment(ctx context.Context, amount int64, phone string) (string, string, error) { res, err := p.startPayment(ctx, amount, phone) if err != nil { return "", "", err } return res.GatewayToken, res.RedirectUrl, nil } func (p *PaymentService) startPayment(ctx context.Context, amount int64, phone string) (*InitPaymentResponse, error) { if amount < 1000 { return nil, fmt.Errorf("حداقل مبلغ ۱۰۰۰ تومان است") } resNum := GenerateUniqueString() ipgTx := &IPGTransaction{ ID: resNum, IPGType: "sep", GatewayMeta: map[string]string{ "callback_url": p.Cfg.CallbackURL, }, Amount: float64(amount), CreatedAt: time.Now(), } p.IPGTransaction = ipgTx initRes, err := p.RealInitialize(ctx, p.Cfg.Credential, phone, resNum, amount) if err != nil { slog.Error("failed to init payment", "error", err) return nil, err } redirectUrl := fmt.Sprintf("https://sep.shaparak.ir/OnlinePG/SendToken?token=%s", initRes.Token) return &InitPaymentResponse{ Success: true, GatewayToken: initRes.Token, RedirectUrl: redirectUrl, IPGTransactionId: ipgTx.ID, Time: time.Now(), Message: "پرداخت ایجاد شد", }, nil } func (p *PaymentService) VerifyPayment(ctx context.Context, resNum, refNum, traceNo, amount string) (string, error) { if resNum == "" { return "", fmt.Errorf("شماره سفارش نامعتبر") } expectedAmount, err := strconv.ParseInt(amount, 10, 64) if err != nil { return "", fmt.Errorf("مبلغ نامعتبر: %v", err) } verifyResp, err := p.RealVerifyTransaction(ctx, refNum) if err != nil { return "", fmt.Errorf("خطا در تأیید تراکنش: %v", err) } if !verifyResp.Success { return "", fmt.Errorf("تأیید تراکنش ناموفق: %s", verifyResp.ResultDescription) } actualAmount := verifyResp.TransactionDetail.OrginalAmount if actualAmount != expectedAmount { slog.Error("amount mismatch", "expected", expectedAmount, "actual", actualAmount) return "", fmt.Errorf("مبلغ تراکنش مغایرت دارد") } trackingCode := verifyResp.TransactionDetail.StraceNo if trackingCode == "" { trackingCode = verifyResp.TransactionDetail.RRN } slog.Info("payment verified successfully", "refNum", refNum, "trackingCode", trackingCode, "amount", actualAmount) return trackingCode, nil } func GenerateUniqueString() string { timestamp := time.Now().UnixNano() randomBytes := make([]byte, 8) rand.Read(randomBytes) return fmt.Sprintf("%d-%s", timestamp, hex.EncodeToString(randomBytes)) }