ce511a50d9
Диалог AdWalletTopupDialog теперь предлагает два способа: «Оплатить картой» (POST /api/billing/topup, credit_target=advertising — редирект на confirmation_url при включённом шлюзе, мгновенный успех при заглушке) и прежнее «Получить счёт» (регресс не тронут). Убран устаревший текст про «оплата картой скоро появится». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
202 lines
8.8 KiB
TypeScript
202 lines
8.8 KiB
TypeScript
import { apiClient, ensureCsrfCookie } from './client';
|
||
|
||
/**
|
||
* API-модуль рекламного модуля «Яндекс Аудитория» (Часть B2, Task 1).
|
||
*
|
||
* Эндпоинты под [auth:sanctum, tenant], префикс /api/advertising (см. Часть B1 —
|
||
* docs/superpowers/plans/2026-07-24-yandex-kanal-chast-B1-backend.md Task 12,
|
||
* routes/web.php). GET'ы не требуют CSRF-cookie, мутации (POST/PATCH) — требуют.
|
||
*/
|
||
|
||
/** Статус кампании Директа (см. App\Models\AdCampaign константы STATUS_*). */
|
||
export type CampaignStatus = 'draft' | 'pending_moderation' | 'running' | 'paused' | 'rejected' | 'stopped_no_funds';
|
||
|
||
/** Ответ GET /api/advertising/wallet — статус рекламного кошелька тенанта. */
|
||
export interface AdWalletStatus {
|
||
solvent: boolean;
|
||
balance_rub: string;
|
||
frozen_rub: string;
|
||
free_rub: string;
|
||
}
|
||
|
||
/** Кампания Директа (строка из GET /api/advertising/campaigns, а также POST/PATCH-ответ). */
|
||
export interface Campaign {
|
||
id: number;
|
||
name: string;
|
||
status: CampaignStatus;
|
||
weekly_budget_rub: string;
|
||
audience_days: number;
|
||
launched_at: string | null;
|
||
/**
|
||
* Приходят и в POST/PATCH-ответе, и в GET .../{id} (detail, controller::show
|
||
* отдаёт модель целиком) — но НЕ в списке GET /campaigns (index явно
|
||
* выбирает узкий набор колонок), поэтому опциональны.
|
||
*/
|
||
use_uploaded_list?: boolean;
|
||
daily_budget_rub?: string | null;
|
||
}
|
||
|
||
/** Ответ GET /api/advertising/campaigns/{id}/audience-size. */
|
||
export interface AudienceSize {
|
||
size: number;
|
||
min: number;
|
||
enough: boolean;
|
||
hint: string | null;
|
||
}
|
||
|
||
/** Тело POST /api/advertising/campaigns (создание черновика кампании). */
|
||
export interface CampaignCreate {
|
||
name: string;
|
||
audience_days: number;
|
||
use_uploaded_list: boolean;
|
||
weekly_budget_rub: string;
|
||
daily_budget_rub?: string | null;
|
||
click_bid_rub?: string | null;
|
||
}
|
||
|
||
/** Объявление кампании (ответ POST /api/advertising/campaigns/{id}/ads). */
|
||
export interface AdCreative {
|
||
id: number;
|
||
campaign_id: number;
|
||
title: string;
|
||
text: string;
|
||
href: string;
|
||
title2: string | null;
|
||
moderation_status: string;
|
||
/** Причина отказа модерации Яндекса — приходит вместе с moderation_status='rejected'. */
|
||
moderation_reason?: string | null;
|
||
image_normal_hash?: string | null;
|
||
}
|
||
|
||
/** Ответ GET /api/advertising/campaigns/{id} — детали кампании + объявления + расход. */
|
||
export interface CampaignDetail {
|
||
campaign: Campaign;
|
||
ads: AdCreative[];
|
||
spent_rub: string;
|
||
}
|
||
|
||
/** GET /api/advertising/wallet — статус рекламного кошелька (баланс/заморожено/свободно). */
|
||
export async function fetchWallet(): Promise<AdWalletStatus> {
|
||
const { data } = await apiClient.get<AdWalletStatus>('/api/advertising/wallet');
|
||
return data;
|
||
}
|
||
|
||
/** GET /api/advertising/campaigns — список кампаний тенанта. */
|
||
export async function fetchCampaigns(): Promise<Campaign[]> {
|
||
const { data } = await apiClient.get<{ data: Campaign[] }>('/api/advertising/campaigns');
|
||
return data.data ?? [];
|
||
}
|
||
|
||
/** GET /api/advertising/campaigns/{id} — детали кампании (объявления + расход). */
|
||
export async function fetchCampaign(id: number): Promise<CampaignDetail> {
|
||
const { data } = await apiClient.get<CampaignDetail>(`/api/advertising/campaigns/${id}`);
|
||
return data;
|
||
}
|
||
|
||
/** POST /api/advertising/campaigns — создать черновик кампании. */
|
||
export async function createCampaign(payload: CampaignCreate): Promise<Campaign> {
|
||
await ensureCsrfCookie();
|
||
const { data } = await apiClient.post<Campaign>('/api/advertising/campaigns', payload);
|
||
return data;
|
||
}
|
||
|
||
/** PATCH /api/advertising/campaigns/{id} — частичная правка кампании. */
|
||
export async function patchCampaign(id: number, payload: Partial<CampaignCreate>): Promise<Campaign> {
|
||
await ensureCsrfCookie();
|
||
const { data } = await apiClient.patch<Campaign>(`/api/advertising/campaigns/${id}`, payload);
|
||
return data;
|
||
}
|
||
|
||
/** GET /api/advertising/campaigns/{id}/audience-size — живой счётчик аудитории. */
|
||
export async function fetchAudienceSize(id: number, days?: number): Promise<AudienceSize> {
|
||
const { data } = await apiClient.get<AudienceSize>(`/api/advertising/campaigns/${id}/audience-size`, {
|
||
params: days ? { days } : {},
|
||
});
|
||
return data;
|
||
}
|
||
|
||
/** POST /api/advertising/campaigns/{id}/launch — запустить кампанию (модерация Яндекса). */
|
||
export async function launchCampaign(id: number): Promise<{ status: CampaignStatus }> {
|
||
await ensureCsrfCookie();
|
||
const { data } = await apiClient.post<{ status: CampaignStatus }>(`/api/advertising/campaigns/${id}/launch`);
|
||
return data;
|
||
}
|
||
|
||
/** POST /api/advertising/campaigns/{id}/pause — поставить кампанию на паузу. */
|
||
export async function pauseCampaign(id: number): Promise<{ status: CampaignStatus }> {
|
||
await ensureCsrfCookie();
|
||
const { data } = await apiClient.post<{ status: CampaignStatus }>(`/api/advertising/campaigns/${id}/pause`);
|
||
return data;
|
||
}
|
||
|
||
/** POST /api/advertising/campaigns/{id}/resume — возобновить кампанию с паузы. */
|
||
export async function resumeCampaign(id: number): Promise<{ status: CampaignStatus }> {
|
||
await ensureCsrfCookie();
|
||
const { data } = await apiClient.post<{ status: CampaignStatus }>(`/api/advertising/campaigns/${id}/resume`);
|
||
return data;
|
||
}
|
||
|
||
/** POST /api/advertising/campaigns/{id}/ads — добавить объявление (текстовый креатив). */
|
||
export async function addCreative(
|
||
id: number,
|
||
payload: { title: string; text: string; href: string; title2?: string },
|
||
): Promise<AdCreative> {
|
||
await ensureCsrfCookie();
|
||
const { data } = await apiClient.post<AdCreative>(`/api/advertising/campaigns/${id}/ads`, payload);
|
||
return data;
|
||
}
|
||
|
||
/** POST /api/advertising/campaigns/{id}/ads/{adId}/image — загрузить картинку объявления. */
|
||
export async function uploadCreativeImage(id: number, adId: number, file: File): Promise<{ hash: string }> {
|
||
await ensureCsrfCookie();
|
||
const form = new FormData();
|
||
form.append('file', file);
|
||
const { data } = await apiClient.post<{ hash: string }>(`/api/advertising/campaigns/${id}/ads/${adId}/image`, form);
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* Счёт на пополнение РЕКЛАМНОГО кошелька (POST /api/billing/invoices, credit_target=advertising).
|
||
* Тот же эндпоинт, что и обычный счёт за лиды (api/billing.ts::createInvoice) —
|
||
* InvoicePaymentService зачисляет ad_wallets вместо tenants.balance_rub (см.
|
||
* app/Http/Controllers/Api/InvoiceController.php::store, min:100/max:1000000).
|
||
*/
|
||
export interface AdvertisingInvoice {
|
||
id: number;
|
||
invoice_number: string;
|
||
amount_total: string;
|
||
pdf_url: string;
|
||
}
|
||
|
||
/** POST /api/billing/invoices с credit_target='advertising' — счёт по реквизитам для пополнения рекламного кошелька. */
|
||
export async function createAdvertisingInvoice(amountRub: number): Promise<AdvertisingInvoice> {
|
||
await ensureCsrfCookie();
|
||
const { data } = await apiClient.post<{ invoice: AdvertisingInvoice }>('/api/billing/invoices', {
|
||
amount_rub: amountRub,
|
||
credit_target: 'advertising',
|
||
});
|
||
return data.invoice;
|
||
}
|
||
|
||
/**
|
||
* Результат POST /api/billing/topup с credit_target='advertising' — две формы (зеркалит
|
||
* api/billing.ts::TopupResult / BillingController::topup):
|
||
* • реальный шлюз (флаг billing_yookassa_enabled ВКЛ): confirmation_url — редирект на оплату
|
||
* ЮKassa, кошелёк зачислится позже по webhook;
|
||
* • заглушка (флаг ВЫКЛ): ok:true — AdWalletService зачисляет рекламный кошелёк мгновенно.
|
||
*/
|
||
export interface AdvertisingCardTopupResult {
|
||
confirmation_url?: string;
|
||
ok?: boolean;
|
||
}
|
||
|
||
/** POST /api/billing/topup с credit_target='advertising' — оплата картой рекламного кошелька. */
|
||
export async function topupAdvertisingByCard(amountRub: string | number): Promise<AdvertisingCardTopupResult> {
|
||
await ensureCsrfCookie();
|
||
const { data } = await apiClient.post<AdvertisingCardTopupResult>('/api/billing/topup', {
|
||
amount_rub: amountRub,
|
||
credit_target: 'advertising',
|
||
});
|
||
return data;
|
||
}
|