155 lines
4.3 KiB
Go
155 lines
4.3 KiB
Go
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))
|
||
}
|