140 lines
4.8 KiB
Go
140 lines
4.8 KiB
Go
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
|
|
}
|