This commit is contained in:
2026-06-15 16:53:25 +03:30
commit 390ef96e7e
34 changed files with 4463 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
package economy
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)
// AdVerifier تأیید می‌کند که کاربر یک تبلیغ rewarded را کامل دیده است.
type AdVerifier interface {
Verify(ctx context.Context, userID int64, token string) (bool, error)
}
// IAPVerifier تأیید می‌کند که یک خرید درون‌برنامه‌ای معتبر و پرداخت‌شده است.
type IAPVerifier interface {
Verify(ctx context.Context, store, productID, purchaseToken string) (bool, error)
}
// --- استاب توسعه: هر توکن غیرخالی را معتبر می‌داند (فقط برای محیط dev/تست) ---
type DevAdVerifier struct{}
func (DevAdVerifier) Verify(_ context.Context, _ int64, token string) (bool, error) {
return token != "", nil
}
type DevIAPVerifier struct{}
func (DevIAPVerifier) Verify(_ context.Context, _ string, _ string, token string) (bool, error) {
return token != "", nil
}
// --- آداپتر تپسل (تأیید سمت‌سرور تبلیغ rewarded) ---
//
// تپسل پس از پایان کامل تبلیغ، یک توکن/شناسه می‌دهد که سرور باید آن را
// نزد تپسل اعتبارسنجی کند. آدرس و قالب دقیق بسته به پنل تپسل تنظیم می‌شود؛
// اینجا اسکلت فراخوانی با کلید قرار داده شده است.
type TapsellAdVerifier struct {
APIKey string
BaseURL string // پیش‌فرض اگر خالی باشد ست می‌شود
client *http.Client
}
func NewTapsellAdVerifier(apiKey string) *TapsellAdVerifier {
return &TapsellAdVerifier{
APIKey: apiKey,
BaseURL: "https://api.tapsell.ir",
client: &http.Client{Timeout: 5 * time.Second},
}
}
func (t *TapsellAdVerifier) Verify(ctx context.Context, userID int64, token string) (bool, error) {
if t.APIKey == "" || token == "" {
return false, fmt.Errorf("tapsell: missing api key or token")
}
// TODO: مسیر دقیق اعتبارسنجی تپسل را مطابق مستندات پنل تنظیم کنید.
url := fmt.Sprintf("%s/rewarded/verify?token=%s", t.BaseURL, token)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return false, err
}
req.Header.Set("Authorization", t.APIKey)
resp, err := t.client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
var out struct {
Valid bool `json:"valid"`
Completed bool `json:"completed"`
}
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return false, err
}
return out.Valid && out.Completed, nil
}
// --- آداپتر IAP کافه‌بازار / مایکت ---
//
// هر دو فروشگاه API شبیه Google Play دارند: با access_token (که از refresh_token
// گرفته می‌شود) وضعیت یک purchaseToken برای یک محصول را اعتبارسنجی می‌کنیم.
type StoreIAPVerifier struct {
// قالب آدرس اعتبارسنجی؛ %s ها به ترتیب: package, product, token
ValidateURLFmt string
PackageName string
AccessToken string // در عمل باید از refresh_token تازه‌سازی شود
client *http.Client
}
// NewBazaarVerifier آداپتر کافه‌بازار.
func NewBazaarVerifier(pkg, accessToken string) *StoreIAPVerifier {
return &StoreIAPVerifier{
ValidateURLFmt: "https://pardakht.cafebazaar.ir/devapi/v2/api/validate/%s/inapp/%s/purchases/%s/",
PackageName: pkg,
AccessToken: accessToken,
client: &http.Client{Timeout: 6 * time.Second},
}
}
// NewMyketVerifier آداپتر مایکت.
func NewMyketVerifier(pkg, accessToken string) *StoreIAPVerifier {
return &StoreIAPVerifier{
ValidateURLFmt: "https://developer.myket.ir/api/applications/%s/purchases/products/%s/tokens/%s/",
PackageName: pkg,
AccessToken: accessToken,
client: &http.Client{Timeout: 6 * time.Second},
}
}
func (v *StoreIAPVerifier) Verify(ctx context.Context, _ string, productID, purchaseToken string) (bool, error) {
if v.AccessToken == "" || purchaseToken == "" {
return false, fmt.Errorf("iap: missing access token or purchase token")
}
url := fmt.Sprintf(v.ValidateURLFmt, v.PackageName, productID, purchaseToken)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return false, err
}
req.Header.Set("Authorization", "Bearer "+v.AccessToken)
resp, err := v.client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, nil
}
var out struct {
// هر دو فروشگاه فیلدی شبیه purchaseState برمی‌گردانند (۰ = خریداری‌شده)
PurchaseState int `json:"purchaseState"`
ConsumptionState int `json:"consumptionState"`
}
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return false, err
}
return out.PurchaseState == 0, nil
}