Files
portal/app/resources/js/api/advertising.ts
T
Дмитрий ce511a50d9 feat(реклама-фронт): оплата рекламного кошелька картой (ЮKassa) в диалоге пополнения
Диалог AdWalletTopupDialog теперь предлагает два способа: «Оплатить картой»
(POST /api/billing/topup, credit_target=advertising — редирект на confirmation_url
при включённом шлюзе, мгновенный успех при заглушке) и прежнее «Получить счёт»
(регресс не тронут). Убран устаревший текст про «оплата картой скоро появится».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-25 11:55:04 +03:00

202 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}