@revclamp/google-ads-transparency-mcp-actor 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.actor/actor.json +63 -2
- package/.actor/input_schema.json +4 -1
- package/actor_config.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/apify_actor.mjs +338 -79
package/.actor/actor.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"actorSpecification": 1,
|
|
3
3
|
"name": "google-ads-transparency-mcp-actor",
|
|
4
|
-
"title": "RevClamp AdPulse -
|
|
5
|
-
"description": "
|
|
4
|
+
"title": "RevClamp AdPulse - Google Ads Transparency MCP & Actor",
|
|
5
|
+
"description": "Resolve verified Google Ads advertiser entities from any brand or domain and extract active or historical ad creatives (Text, Image, Video), ad copy, and run dates with zero browser overhead. Independent intelligence tool by RevClamp; not affiliated with or endorsed by Google LLC.",
|
|
6
6
|
"version": "1.0",
|
|
7
7
|
"buildTag": "latest",
|
|
8
8
|
"minMemoryMbytes": 256,
|
|
@@ -13,5 +13,66 @@
|
|
|
13
13
|
"memoryMbytes": 256
|
|
14
14
|
},
|
|
15
15
|
"input": "./input_schema.json",
|
|
16
|
+
"output": {
|
|
17
|
+
"actorOutputSchemaVersion": 1,
|
|
18
|
+
"title": "Google Ads Transparency Creatives & Run Summary",
|
|
19
|
+
"description": "Extracted Google Ads Transparency creative records (Default Dataset) and run billing/telemetry summary (Key-Value Store OUTPUT).",
|
|
20
|
+
"properties": {
|
|
21
|
+
"creatives": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"title": "Extracted Ad Creatives",
|
|
24
|
+
"description": "Verified advertiser ad creatives (Text, Image, Video), ad copy, destination domains, and active run dates.",
|
|
25
|
+
"template": "{{links.apiDefaultDatasetUrl}}/items"
|
|
26
|
+
},
|
|
27
|
+
"runSummary": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"title": "Run Summary & Telemetry (OUTPUT)",
|
|
30
|
+
"description": "Structured execution summary, resolved advertiser entities, pagination cursor, and billing receipt summary.",
|
|
31
|
+
"template": "{{links.apiDefaultKeyValueStoreUrl}}/records/OUTPUT"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"storages": {
|
|
36
|
+
"dataset": {
|
|
37
|
+
"actorSpecification": 1,
|
|
38
|
+
"fields": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"properties": {
|
|
41
|
+
"advertiserId": { "type": "string", "title": "Advertiser ID" },
|
|
42
|
+
"advertiserName": { "type": "string", "title": "Advertiser Name" },
|
|
43
|
+
"creativeId": { "type": "string", "title": "Creative ID" },
|
|
44
|
+
"format": { "type": "string", "title": "Ad Format" },
|
|
45
|
+
"regionCode": { "type": "string", "title": "Region Code" },
|
|
46
|
+
"firstSeenAtIso": { "type": "string", "title": "First Seen (ISO)" },
|
|
47
|
+
"lastSeenAtIso": { "type": "string", "title": "Last Seen (ISO)" },
|
|
48
|
+
"daysActive": { "type": "integer", "title": "Days Active" },
|
|
49
|
+
"previewUrl": { "type": "string", "title": "Preview Asset URL" },
|
|
50
|
+
"transparencyUrl": { "type": "string", "title": "Transparency Center URL" }
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"views": {
|
|
54
|
+
"overview": {
|
|
55
|
+
"title": "Ad Creatives Overview",
|
|
56
|
+
"transformation": {
|
|
57
|
+
"fields": [
|
|
58
|
+
"advertiserId",
|
|
59
|
+
"advertiserName",
|
|
60
|
+
"creativeId",
|
|
61
|
+
"format",
|
|
62
|
+
"regionCode",
|
|
63
|
+
"firstSeenAtIso",
|
|
64
|
+
"lastSeenAtIso",
|
|
65
|
+
"daysActive",
|
|
66
|
+
"previewUrl",
|
|
67
|
+
"transparencyUrl"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"display": {
|
|
71
|
+
"component": "table"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
16
77
|
"dockerfile": "../Dockerfile"
|
|
17
78
|
}
|
package/.actor/input_schema.json
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
"description": "Brand name or domain to match against verified Google Ads Transparency advertiser entities (e.g. 'nike.com', 'Stripe', 'shopify.com').",
|
|
10
10
|
"maxLength": 512,
|
|
11
11
|
"editor": "textfield",
|
|
12
|
+
"default": "Stripe",
|
|
13
|
+
"prefill": "Stripe",
|
|
12
14
|
"example": "nike.com"
|
|
13
15
|
},
|
|
14
16
|
"advertiserId": {
|
|
@@ -62,7 +64,8 @@
|
|
|
62
64
|
"description": "Maximum number of deduplicated ad creatives to extract in this run (1 to 200).",
|
|
63
65
|
"minimum": 1,
|
|
64
66
|
"maximum": 200,
|
|
65
|
-
"default": 40
|
|
67
|
+
"default": 40,
|
|
68
|
+
"prefill": 10
|
|
66
69
|
},
|
|
67
70
|
"pageToken": {
|
|
68
71
|
"title": "Next-Page Cursor Token (Optional)",
|
package/actor_config.json
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"manifest": {
|
|
23
23
|
"name": "google-ads-transparency-mcp-actor",
|
|
24
|
-
"version": "1.0.
|
|
24
|
+
"version": "1.0.3",
|
|
25
25
|
"title": "RevClamp AdPulse - For Google Ads Transparency Center (MCP & Apify Actor)",
|
|
26
26
|
"websiteUrl": "https://revclamp.com",
|
|
27
27
|
"description": "Find verified Google Ads advertiser entities and extract active or historical ad creatives (Text, Image, Video), preview links, and run dates without browser or proxy overhead. Independent intelligence tool by RevClamp; not affiliated with or endorsed by Google LLC."
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revclamp/google-ads-transparency-mcp-actor",
|
|
3
3
|
"mcpName": "com.revclamp/google-ads-transparency-mcp-actor",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "RevClamp AdPulse - For Google Ads Transparency Center (MCP & Apify Actor). Independent intelligence tool by RevClamp; not affiliated with or endorsed by Google LLC.",
|
|
7
7
|
"homepage": "https://revclamp.com",
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "com.revclamp/google-ads-transparency-mcp-actor",
|
|
4
4
|
"title": "RevClamp AdPulse (Google Ads Transparency MCP)",
|
|
5
5
|
"description": "Google Ads Transparency Center advertiser & creative intelligence MCP server by RevClamp.",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.3",
|
|
7
7
|
"websiteUrl": "https://revclamp.com",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/lhphong09t2/revclamp-mcp",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"registryType": "npm",
|
|
28
28
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
29
29
|
"identifier": "@revclamp/google-ads-transparency-mcp-actor",
|
|
30
|
-
"version": "1.0.
|
|
30
|
+
"version": "1.0.3",
|
|
31
31
|
"transport": {
|
|
32
32
|
"type": "stdio"
|
|
33
33
|
}
|
package/src/apify_actor.mjs
CHANGED
|
@@ -82,12 +82,25 @@ export function computeRunEconomicsTelemetry({
|
|
|
82
82
|
Number(process.env.ACTOR_MEMORY_MBYTES || process.env.APIFY_MEMORY_MBYTES) ||
|
|
83
83
|
ACTOR_PRICING_CONTRACT.maxMemoryMb,
|
|
84
84
|
requestCount = 1,
|
|
85
|
+
unitPriceUsdc = ACTOR_PRICING_CONTRACT.priceUsdc,
|
|
86
|
+
actorStartFeeUsdc = ACTOR_PRICING_CONTRACT.actorStartFeeUsdc,
|
|
85
87
|
} = {}) {
|
|
86
88
|
const safeCount = Math.max(0, Math.floor(Number(billableCount) || 0));
|
|
87
89
|
const safeChargedCount = Math.max(0, Math.floor(Number(chargedEventCount) || 0));
|
|
88
90
|
const safeDurationMs = Math.max(1, Number(durationMs) || 1);
|
|
89
91
|
const safeMemoryMb = Math.min(1024, Math.max(64, Number(memoryMb) || 256));
|
|
90
92
|
const safeRequests = Math.max(1, Math.floor(Number(requestCount) || 1));
|
|
93
|
+
const resolvedUnitPriceUsdc =
|
|
94
|
+
Number.isFinite(Number(unitPriceUsdc)) && Number(unitPriceUsdc) > 0
|
|
95
|
+
? Number(unitPriceUsdc)
|
|
96
|
+
: ACTOR_PRICING_CONTRACT.priceUsdc;
|
|
97
|
+
const resolvedNetPriceUsdc = Number(
|
|
98
|
+
(resolvedUnitPriceUsdc * (1 - ACTOR_PRICING_CONTRACT.platformFeeRate)).toFixed(6)
|
|
99
|
+
);
|
|
100
|
+
const resolvedStartFeeUsdc =
|
|
101
|
+
Number.isFinite(Number(actorStartFeeUsdc)) && Number(actorStartFeeUsdc) >= 0
|
|
102
|
+
? Number(actorStartFeeUsdc)
|
|
103
|
+
: ACTOR_PRICING_CONTRACT.actorStartFeeUsdc;
|
|
91
104
|
|
|
92
105
|
// Apify compute rate: $0.30 per GB-hour; 256MB = 0.25 GB -> $0.075/hour -> $0.00002083/sec
|
|
93
106
|
const computeHours = safeDurationMs / 3600000;
|
|
@@ -95,10 +108,9 @@ export function computeRunEconomicsTelemetry({
|
|
|
95
108
|
(safeMemoryMb / 1024) * computeHours * ACTOR_PRICING_CONTRACT.computeRatePerGbHourUsdc;
|
|
96
109
|
const networkOverheadUsdc = safeRequests * 0.0000002;
|
|
97
110
|
const totalRunCostUsdc = Number((computeCostUsdc + networkOverheadUsdc).toFixed(8));
|
|
98
|
-
const actorStartFeeUsdc = ACTOR_PRICING_CONTRACT.actorStartFeeUsdc;
|
|
99
111
|
|
|
100
112
|
const configuredFloorMultiplier = Number(
|
|
101
|
-
(
|
|
113
|
+
(resolvedNetPriceUsdc / ACTOR_PRICING_CONTRACT.costCeilingUsdc).toFixed(2)
|
|
102
114
|
);
|
|
103
115
|
|
|
104
116
|
if (safeCount === 0) {
|
|
@@ -109,7 +121,7 @@ export function computeRunEconomicsTelemetry({
|
|
|
109
121
|
durationMs: safeDurationMs,
|
|
110
122
|
memoryMb: safeMemoryMb,
|
|
111
123
|
requestCount: safeRequests,
|
|
112
|
-
actorStartFeeUsdc,
|
|
124
|
+
actorStartFeeUsdc: resolvedStartFeeUsdc,
|
|
113
125
|
totalRunCostUsdc,
|
|
114
126
|
actualCostPerEventUsdc: 0,
|
|
115
127
|
costCeilingUsdc: ACTOR_PRICING_CONTRACT.costCeilingUsdc,
|
|
@@ -123,11 +135,11 @@ export function computeRunEconomicsTelemetry({
|
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
const actualCostPerEventUsdc = Number((totalRunCostUsdc / safeCount).toFixed(8));
|
|
126
|
-
const grossRevenueUsdc = Number((safeCount *
|
|
127
|
-
const netRevenueUsdc = Number((safeCount *
|
|
138
|
+
const grossRevenueUsdc = Number((safeCount * resolvedUnitPriceUsdc).toFixed(6));
|
|
139
|
+
const netRevenueUsdc = Number((safeCount * resolvedNetPriceUsdc).toFixed(6));
|
|
128
140
|
const effectiveMarginMultiplier =
|
|
129
141
|
actualCostPerEventUsdc > 0
|
|
130
|
-
? Number((
|
|
142
|
+
? Number((resolvedNetPriceUsdc / actualCostPerEventUsdc).toFixed(2))
|
|
131
143
|
: configuredFloorMultiplier;
|
|
132
144
|
|
|
133
145
|
const withinCostCeiling = actualCostPerEventUsdc <= ACTOR_PRICING_CONTRACT.costCeilingUsdc;
|
|
@@ -142,7 +154,7 @@ export function computeRunEconomicsTelemetry({
|
|
|
142
154
|
durationMs: safeDurationMs,
|
|
143
155
|
memoryMb: safeMemoryMb,
|
|
144
156
|
requestCount: safeRequests,
|
|
145
|
-
actorStartFeeUsdc,
|
|
157
|
+
actorStartFeeUsdc: resolvedStartFeeUsdc,
|
|
146
158
|
totalRunCostUsdc,
|
|
147
159
|
actualCostPerEventUsdc,
|
|
148
160
|
costCeilingUsdc: ACTOR_PRICING_CONTRACT.costCeilingUsdc,
|
|
@@ -203,9 +215,25 @@ export async function loadApifyActorInput(
|
|
|
203
215
|
return JSON.parse(raw);
|
|
204
216
|
}
|
|
205
217
|
|
|
218
|
+
const isAtHome = env?.APIFY_IS_AT_HOME === '1' || env?.APIFY_IS_AT_HOME === 'true';
|
|
219
|
+
const applyAtHomeSchemaDefaults = (parsedInput) => {
|
|
220
|
+
if (
|
|
221
|
+
isAtHome &&
|
|
222
|
+
parsedInput &&
|
|
223
|
+
typeof parsedInput === 'object' &&
|
|
224
|
+
!Array.isArray(parsedInput) &&
|
|
225
|
+
!Object.prototype.hasOwnProperty.call(parsedInput, 'query') &&
|
|
226
|
+
!Object.prototype.hasOwnProperty.call(parsedInput, 'advertiserId') &&
|
|
227
|
+
!Object.prototype.hasOwnProperty.call(parsedInput, 'pageToken')
|
|
228
|
+
) {
|
|
229
|
+
return { query: 'Stripe', ...parsedInput };
|
|
230
|
+
}
|
|
231
|
+
return parsedInput;
|
|
232
|
+
};
|
|
233
|
+
|
|
206
234
|
if (env?.APIFY_INPUT_PATH && fs.existsSync(env.APIFY_INPUT_PATH)) {
|
|
207
235
|
const raw = fs.readFileSync(env.APIFY_INPUT_PATH, 'utf8');
|
|
208
|
-
return JSON.parse(raw);
|
|
236
|
+
return applyAtHomeSchemaDefaults(raw && raw.trim() ? JSON.parse(raw) : {});
|
|
209
237
|
}
|
|
210
238
|
|
|
211
239
|
const localStorageDir =
|
|
@@ -224,7 +252,8 @@ export async function loadApifyActorInput(
|
|
|
224
252
|
candidateName
|
|
225
253
|
);
|
|
226
254
|
if (fs.existsSync(localInputPath)) {
|
|
227
|
-
|
|
255
|
+
const raw = fs.readFileSync(localInputPath, 'utf8');
|
|
256
|
+
return applyAtHomeSchemaDefaults(raw && raw.trim() ? JSON.parse(raw) : {});
|
|
228
257
|
}
|
|
229
258
|
}
|
|
230
259
|
}
|
|
@@ -254,9 +283,11 @@ export async function loadApifyActorInput(
|
|
|
254
283
|
});
|
|
255
284
|
if (res.status === 200) {
|
|
256
285
|
const text = await res.text();
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
286
|
+
const parsed = text && text.trim() ? JSON.parse(text) : {};
|
|
287
|
+
return applyAtHomeSchemaDefaults(parsed);
|
|
288
|
+
}
|
|
289
|
+
if (res.status === 204 || res.status === 404) {
|
|
290
|
+
return applyAtHomeSchemaDefaults({});
|
|
260
291
|
}
|
|
261
292
|
} finally {
|
|
262
293
|
clearTimeout(timer);
|
|
@@ -310,6 +341,19 @@ export function createApifyPlatformHooks(env = process.env, fetchImpl = globalTh
|
|
|
310
341
|
env?.APIFY_API_BASE_URL,
|
|
311
342
|
env?.APIFY_API_PUBLIC_BASE_URL
|
|
312
343
|
);
|
|
344
|
+
let pricingInfoChargeEvents = null;
|
|
345
|
+
const rawPricingInfoEnv = env?.APIFY_ACTOR_PRICING_INFO || env?.ACTOR_PRICING_INFO;
|
|
346
|
+
if (rawPricingInfoEnv) {
|
|
347
|
+
try {
|
|
348
|
+
const parsedPricing = JSON.parse(String(rawPricingInfoEnv));
|
|
349
|
+
const eventsObj = parsedPricing?.pricingPerEvent?.actorChargeEvents;
|
|
350
|
+
if (eventsObj && typeof eventsObj === 'object') {
|
|
351
|
+
pricingInfoChargeEvents = eventsObj;
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
pricingInfoChargeEvents = null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
313
357
|
const rawMaxChargeStr =
|
|
314
358
|
env?.ACTOR_MAX_TOTAL_CHARGE_USD !== undefined && env?.ACTOR_MAX_TOTAL_CHARGE_USD !== null
|
|
315
359
|
? String(env.ACTOR_MAX_TOTAL_CHARGE_USD).trim()
|
|
@@ -320,23 +364,28 @@ export function createApifyPlatformHooks(env = process.env, fetchImpl = globalTh
|
|
|
320
364
|
.trim()
|
|
321
365
|
.toLowerCase();
|
|
322
366
|
const includeStartFee = includeStartFeeFlag !== '0' && includeStartFeeFlag !== 'false';
|
|
367
|
+
const pricingInfoStartFeeUsd = Number(
|
|
368
|
+
pricingInfoChargeEvents?.['apify-actor-start']?.eventPriceUsd
|
|
369
|
+
);
|
|
323
370
|
const explicitStartFeeUsd =
|
|
324
371
|
env?.ACTOR_START_FEE_USD !== undefined &&
|
|
325
372
|
env?.ACTOR_START_FEE_USD !== null &&
|
|
326
373
|
String(env.ACTOR_START_FEE_USD).trim() !== ''
|
|
327
374
|
? Number(env.ACTOR_START_FEE_USD)
|
|
328
|
-
:
|
|
375
|
+
: Number.isFinite(pricingInfoStartFeeUsd) && pricingInfoStartFeeUsd >= 0
|
|
376
|
+
? pricingInfoStartFeeUsd
|
|
377
|
+
: ACTOR_PRICING_CONTRACT.actorStartFeeUsdc;
|
|
329
378
|
const startFeeMicros =
|
|
330
379
|
includeStartFee && Number.isFinite(explicitStartFeeUsd) && explicitStartFeeUsd >= 0
|
|
331
380
|
? Math.round(explicitStartFeeUsd * 1_000_000)
|
|
332
381
|
: 0;
|
|
333
|
-
const unitPriceMicros = Math.round(ACTOR_PRICING_CONTRACT.priceUsdc * 1_000_000);
|
|
334
382
|
const maxBudgetMicros = hasMaxChargeUsd ? Math.round(maxTotalChargeUsdRaw * 1_000_000) : 0;
|
|
335
|
-
const maxPayableEvents = hasMaxChargeUsd
|
|
336
|
-
? Math.floor(Math.max(0, maxBudgetMicros - startFeeMicros) / unitPriceMicros)
|
|
337
|
-
: Infinity;
|
|
338
383
|
let cumulativeChargedInAdapter = 0;
|
|
339
384
|
let adapterChargeLimitLatched = false;
|
|
385
|
+
hooks.actorStartFeeUsdc =
|
|
386
|
+
includeStartFee && Number.isFinite(explicitStartFeeUsd) && explicitStartFeeUsd >= 0
|
|
387
|
+
? explicitStartFeeUsd
|
|
388
|
+
: 0;
|
|
340
389
|
|
|
341
390
|
if (kvsId) {
|
|
342
391
|
const checkpointRecordUrl = `${apiBase}/v2/key-value-stores/${encodeURIComponent(kvsId)}/records/${ACTOR_PRICING_CONTRACT.checkpointKey}`;
|
|
@@ -470,15 +519,80 @@ export function createApifyPlatformHooks(env = process.env, fetchImpl = globalTh
|
|
|
470
519
|
|
|
471
520
|
if (runId) {
|
|
472
521
|
const chargeUrl = `${apiBase}/v2/actor-runs/${encodeURIComponent(runId)}/charge`;
|
|
522
|
+
const allowUnbilledAtHome = env?.ADPULSE_ALLOW_UNBILLED_AT_HOME === '1';
|
|
523
|
+
let unbilledOwnerTestLatched = false;
|
|
524
|
+
let latchedPlatformEventName = null;
|
|
525
|
+
const resolvePlatformEventAndPrice = (rawEventName) => {
|
|
526
|
+
const candidate = String(rawEventName || ACTOR_PRICING_CONTRACT.eventName);
|
|
527
|
+
const effectiveName =
|
|
528
|
+
latchedPlatformEventName ||
|
|
529
|
+
(() => {
|
|
530
|
+
if (pricingInfoChargeEvents) {
|
|
531
|
+
if (Object.prototype.hasOwnProperty.call(pricingInfoChargeEvents, candidate)) {
|
|
532
|
+
return candidate;
|
|
533
|
+
}
|
|
534
|
+
if (
|
|
535
|
+
candidate === 'ad-creative-scraped' ||
|
|
536
|
+
candidate === 'ad_transparency_creative_retrieved'
|
|
537
|
+
) {
|
|
538
|
+
if (
|
|
539
|
+
Object.prototype.hasOwnProperty.call(
|
|
540
|
+
pricingInfoChargeEvents,
|
|
541
|
+
'ad_transparency_creative_retrieved'
|
|
542
|
+
)
|
|
543
|
+
) {
|
|
544
|
+
return 'ad_transparency_creative_retrieved';
|
|
545
|
+
}
|
|
546
|
+
if (
|
|
547
|
+
Object.prototype.hasOwnProperty.call(
|
|
548
|
+
pricingInfoChargeEvents,
|
|
549
|
+
'ad-creative-scraped'
|
|
550
|
+
)
|
|
551
|
+
) {
|
|
552
|
+
return 'ad-creative-scraped';
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return candidate;
|
|
557
|
+
})();
|
|
558
|
+
const rawEventPrice = Number(pricingInfoChargeEvents?.[effectiveName]?.eventPriceUsd);
|
|
559
|
+
const unitPriceUsdc =
|
|
560
|
+
Number.isFinite(rawEventPrice) && rawEventPrice > 0
|
|
561
|
+
? rawEventPrice
|
|
562
|
+
: ACTOR_PRICING_CONTRACT.priceUsdc;
|
|
563
|
+
return { eventName: effectiveName, unitPriceUsdc };
|
|
564
|
+
};
|
|
565
|
+
hooks.unitPriceUsdc = resolvePlatformEventAndPrice(
|
|
566
|
+
ACTOR_PRICING_CONTRACT.eventName
|
|
567
|
+
).unitPriceUsdc;
|
|
473
568
|
hooks.chargeEvent = async (receipt) => {
|
|
474
569
|
const requestedCount = Math.max(0, Number(receipt?.count) || 0);
|
|
570
|
+
const { eventName: primaryEventName, unitPriceUsdc: resolvedUnitPriceUsdc } =
|
|
571
|
+
resolvePlatformEventAndPrice(receipt?.eventName);
|
|
572
|
+
hooks.unitPriceUsdc = resolvedUnitPriceUsdc;
|
|
573
|
+
if (unbilledOwnerTestLatched) {
|
|
574
|
+
return {
|
|
575
|
+
chargedCount: 0,
|
|
576
|
+
eventChargeLimitReached: false,
|
|
577
|
+
unbilledOwnerTestBypass: true,
|
|
578
|
+
chargedCreativeIds: [],
|
|
579
|
+
eventName: primaryEventName,
|
|
580
|
+
unitPriceUsdc: resolvedUnitPriceUsdc,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
475
583
|
if (adapterChargeLimitLatched) {
|
|
476
584
|
return {
|
|
477
585
|
chargedCount: 0,
|
|
478
586
|
eventChargeLimitReached: true,
|
|
479
587
|
chargedCreativeIds: [],
|
|
588
|
+
eventName: primaryEventName,
|
|
589
|
+
unitPriceUsdc: resolvedUnitPriceUsdc,
|
|
480
590
|
};
|
|
481
591
|
}
|
|
592
|
+
const unitPriceMicros = Math.round(resolvedUnitPriceUsdc * 1_000_000);
|
|
593
|
+
const maxPayableEvents = hasMaxChargeUsd
|
|
594
|
+
? Math.floor(Math.max(0, maxBudgetMicros - startFeeMicros) / unitPriceMicros)
|
|
595
|
+
: Infinity;
|
|
482
596
|
const remainingAllowance = Number.isFinite(maxPayableEvents)
|
|
483
597
|
? Math.max(0, maxPayableEvents - cumulativeChargedInAdapter)
|
|
484
598
|
: requestedCount;
|
|
@@ -493,25 +607,65 @@ export function createApifyPlatformHooks(env = process.env, fetchImpl = globalTh
|
|
|
493
607
|
chargedCount: 0,
|
|
494
608
|
eventChargeLimitReached: Number.isFinite(maxPayableEvents),
|
|
495
609
|
chargedCreativeIds: [],
|
|
610
|
+
eventName: primaryEventName,
|
|
611
|
+
unitPriceUsdc: resolvedUnitPriceUsdc,
|
|
496
612
|
};
|
|
497
613
|
}
|
|
498
614
|
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
try {
|
|
502
|
-
const res = await fetchImpl(chargeUrl, {
|
|
615
|
+
const postChargeRequest = async (eventNameToPost, idempotencyKeyToPost, signal) =>
|
|
616
|
+
fetchImpl(chargeUrl, {
|
|
503
617
|
method: 'POST',
|
|
504
618
|
headers: {
|
|
505
619
|
authorization: `Bearer ${token}`,
|
|
506
620
|
'content-type': 'application/json; charset=utf-8',
|
|
507
|
-
'idempotency-key':
|
|
621
|
+
'idempotency-key': idempotencyKeyToPost,
|
|
508
622
|
},
|
|
509
623
|
body: JSON.stringify({
|
|
510
|
-
eventName:
|
|
624
|
+
eventName: eventNameToPost,
|
|
511
625
|
count: countToCharge,
|
|
512
626
|
}),
|
|
513
|
-
signal
|
|
627
|
+
signal,
|
|
514
628
|
});
|
|
629
|
+
|
|
630
|
+
const controller = new AbortController();
|
|
631
|
+
const timer = setTimeout(() => controller.abort(), 600);
|
|
632
|
+
try {
|
|
633
|
+
let effectiveEventName = primaryEventName;
|
|
634
|
+
let effectiveIdempotencyKey = receipt.idempotencyKey;
|
|
635
|
+
let res = await postChargeRequest(
|
|
636
|
+
primaryEventName,
|
|
637
|
+
receipt.idempotencyKey,
|
|
638
|
+
controller.signal
|
|
639
|
+
);
|
|
640
|
+
if (
|
|
641
|
+
res.status === 404 &&
|
|
642
|
+
!pricingInfoChargeEvents &&
|
|
643
|
+
!latchedPlatformEventName &&
|
|
644
|
+
(primaryEventName === 'ad-creative-scraped' ||
|
|
645
|
+
primaryEventName === 'ad_transparency_creative_retrieved')
|
|
646
|
+
) {
|
|
647
|
+
const aliasEventName =
|
|
648
|
+
primaryEventName === 'ad-creative-scraped'
|
|
649
|
+
? 'ad_transparency_creative_retrieved'
|
|
650
|
+
: 'ad-creative-scraped';
|
|
651
|
+
const aliasIdempotencyKey = `${receipt.idempotencyKey}-${aliasEventName}`;
|
|
652
|
+
const fallbackRes = await postChargeRequest(
|
|
653
|
+
aliasEventName,
|
|
654
|
+
aliasIdempotencyKey,
|
|
655
|
+
controller.signal
|
|
656
|
+
);
|
|
657
|
+
if (fallbackRes.status !== 404) {
|
|
658
|
+
res = fallbackRes;
|
|
659
|
+
effectiveEventName = aliasEventName;
|
|
660
|
+
effectiveIdempotencyKey = aliasIdempotencyKey;
|
|
661
|
+
if (
|
|
662
|
+
(fallbackRes.status >= 200 && fallbackRes.status < 300) ||
|
|
663
|
+
fallbackRes.status === 402
|
|
664
|
+
) {
|
|
665
|
+
latchedPlatformEventName = aliasEventName;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
515
669
|
if (res.status === 402) {
|
|
516
670
|
// Platform enforced spending ceiling (HTTP 402 Payment Required): latch so no further charge POSTs occur
|
|
517
671
|
adapterChargeLimitLatched = true;
|
|
@@ -519,10 +673,50 @@ export function createApifyPlatformHooks(env = process.env, fetchImpl = globalTh
|
|
|
519
673
|
chargedCount: 0,
|
|
520
674
|
eventChargeLimitReached: true,
|
|
521
675
|
chargedCreativeIds: [],
|
|
676
|
+
eventName: effectiveEventName,
|
|
677
|
+
unitPriceUsdc: resolvedUnitPriceUsdc,
|
|
678
|
+
idempotencyKey: effectiveIdempotencyKey,
|
|
522
679
|
};
|
|
523
680
|
}
|
|
681
|
+
if (allowUnbilledAtHome && (res.status === 405 || res.status === 403)) {
|
|
682
|
+
let isUnmonetizedNotAllowed = res.status === 405;
|
|
683
|
+
if (res.status === 403) {
|
|
684
|
+
try {
|
|
685
|
+
const errText = await res.text();
|
|
686
|
+
const errJson = errText && errText.trim() ? JSON.parse(errText) : null;
|
|
687
|
+
isUnmonetizedNotAllowed = errJson?.error?.type === 'operation-not-allowed';
|
|
688
|
+
} catch {
|
|
689
|
+
isUnmonetizedNotAllowed = false;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (isUnmonetizedNotAllowed) {
|
|
693
|
+
unbilledOwnerTestLatched = true;
|
|
694
|
+
return {
|
|
695
|
+
chargedCount: 0,
|
|
696
|
+
eventChargeLimitReached: false,
|
|
697
|
+
unbilledOwnerTestBypass: true,
|
|
698
|
+
chargedCreativeIds: [],
|
|
699
|
+
eventName: effectiveEventName,
|
|
700
|
+
unitPriceUsdc: resolvedUnitPriceUsdc,
|
|
701
|
+
idempotencyKey: effectiveIdempotencyKey,
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
}
|
|
524
705
|
if (res.status < 200 || res.status >= 300) {
|
|
525
|
-
|
|
706
|
+
let errSuffix = '';
|
|
707
|
+
try {
|
|
708
|
+
const errText = await res.text();
|
|
709
|
+
if (errText && errText.trim()) {
|
|
710
|
+
const errJson = JSON.parse(errText);
|
|
711
|
+
const errType = errJson?.error?.type;
|
|
712
|
+
if (typeof errType === 'string' && /^[A-Za-z0-9_.:-]{1,64}$/.test(errType)) {
|
|
713
|
+
errSuffix = ` (${errType})`;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
} catch {
|
|
717
|
+
errSuffix = '';
|
|
718
|
+
}
|
|
719
|
+
throw new Error(`Apify Pay-per-Event charge HTTP ${res.status}${errSuffix}`);
|
|
526
720
|
}
|
|
527
721
|
const rawText = await res.text();
|
|
528
722
|
const parsed = rawText && rawText.trim() ? JSON.parse(rawText) : {};
|
|
@@ -552,14 +746,32 @@ export function createApifyPlatformHooks(env = process.env, fetchImpl = globalTh
|
|
|
552
746
|
Boolean(dataObj?.eventChargeLimitReached) ||
|
|
553
747
|
limitReached ||
|
|
554
748
|
(Number.isFinite(maxPayableEvents) && cumulativeChargedInAdapter >= maxPayableEvents);
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
749
|
+
let subsetIds;
|
|
750
|
+
if (Array.isArray(dataObj?.chargedCreativeIds)) {
|
|
751
|
+
const reqIds = Array.isArray(receipt?.creativeIds) ? receipt.creativeIds : null;
|
|
752
|
+
const uniqueValidIds = new Set(
|
|
753
|
+
dataObj.chargedCreativeIds.filter(
|
|
754
|
+
(id) => typeof id === 'string' && (!reqIds || reqIds.includes(id))
|
|
755
|
+
)
|
|
756
|
+
);
|
|
757
|
+
if (
|
|
758
|
+
dataObj.chargedCreativeIds.length !== resolvedChargedCount ||
|
|
759
|
+
uniqueValidIds.size !== resolvedChargedCount
|
|
760
|
+
) {
|
|
761
|
+
throw new Error(
|
|
762
|
+
'Apify Pay-per-Event charge response contained invalid chargedCreativeIds'
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
subsetIds = Array.from(uniqueValidIds);
|
|
766
|
+
} else if (Array.isArray(receipt?.creativeIds)) {
|
|
767
|
+
subsetIds = receipt.creativeIds.slice(0, resolvedChargedCount);
|
|
768
|
+
}
|
|
560
769
|
return {
|
|
561
770
|
chargedCount: resolvedChargedCount,
|
|
562
771
|
eventChargeLimitReached: resolvedLimitReached,
|
|
772
|
+
eventName: effectiveEventName,
|
|
773
|
+
unitPriceUsdc: resolvedUnitPriceUsdc,
|
|
774
|
+
idempotencyKey: effectiveIdempotencyKey,
|
|
563
775
|
...(subsetIds ? { chargedCreativeIds: subsetIds } : {}),
|
|
564
776
|
};
|
|
565
777
|
} finally {
|
|
@@ -704,7 +916,7 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
704
916
|
'default_run'
|
|
705
917
|
);
|
|
706
918
|
const hasChargeHook = typeof hooks.chargeEvent === 'function';
|
|
707
|
-
|
|
919
|
+
let billingMode =
|
|
708
920
|
hasChargeHook || hooks.isAtHome === true ? 'APIFY_PAY_PER_EVENT' : 'LOCAL_UNBILLED';
|
|
709
921
|
|
|
710
922
|
const maxRunDurationMs = Math.min(
|
|
@@ -1332,6 +1544,8 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1332
1544
|
chargedEventCount: hasChargeHook ? chargedSet.size : 0,
|
|
1333
1545
|
durationMs,
|
|
1334
1546
|
requestCount: Math.max(1, totalRpcRequests),
|
|
1547
|
+
unitPriceUsdc: hooks?.unitPriceUsdc ?? ACTOR_PRICING_CONTRACT.priceUsdc,
|
|
1548
|
+
actorStartFeeUsdc: hooks?.actorStartFeeUsdc,
|
|
1335
1549
|
}),
|
|
1336
1550
|
});
|
|
1337
1551
|
}
|
|
@@ -1355,6 +1569,7 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1355
1569
|
}
|
|
1356
1570
|
};
|
|
1357
1571
|
|
|
1572
|
+
let lastResolvedUnitPriceUsdc = hooks?.unitPriceUsdc ?? ACTOR_PRICING_CONTRACT.priceUsdc;
|
|
1358
1573
|
for (let offset = 0; offset < newBillableCandidates.length; offset += chunkSize) {
|
|
1359
1574
|
const effectiveChunkDeadlineMs = hasChargeHook
|
|
1360
1575
|
? deadlineAtMs - step4ReserveMs
|
|
@@ -1455,6 +1670,28 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1455
1670
|
// Step 4.2: Strict ChargeResult validation (never fallback to ?? unchargedChunk.length)
|
|
1456
1671
|
const chargedCount = chargeResult?.chargedCount;
|
|
1457
1672
|
const eventChargeLimitReached = Boolean(chargeResult?.eventChargeLimitReached);
|
|
1673
|
+
const resolvedReceiptEventName =
|
|
1674
|
+
typeof chargeResult?.eventName === 'string' && chargeResult.eventName
|
|
1675
|
+
? chargeResult.eventName
|
|
1676
|
+
: receipt.eventName;
|
|
1677
|
+
const resolvedReceiptUnitPriceUsdc =
|
|
1678
|
+
Number.isFinite(Number(chargeResult?.unitPriceUsdc)) && Number(chargeResult.unitPriceUsdc) > 0
|
|
1679
|
+
? Number(chargeResult.unitPriceUsdc)
|
|
1680
|
+
: receipt.unitPriceUsdc;
|
|
1681
|
+
lastResolvedUnitPriceUsdc = resolvedReceiptUnitPriceUsdc;
|
|
1682
|
+
const resolvedReceiptIdempotencyKey =
|
|
1683
|
+
typeof chargeResult?.idempotencyKey === 'string' && chargeResult.idempotencyKey
|
|
1684
|
+
? chargeResult.idempotencyKey
|
|
1685
|
+
: receipt.idempotencyKey;
|
|
1686
|
+
const buildFinalReceipt = (actualChargedCount, extra = {}) => ({
|
|
1687
|
+
...receipt,
|
|
1688
|
+
idempotencyKey: resolvedReceiptIdempotencyKey,
|
|
1689
|
+
eventName: resolvedReceiptEventName,
|
|
1690
|
+
unitPriceUsdc: resolvedReceiptUnitPriceUsdc,
|
|
1691
|
+
chunkAmountUsdc: Number((actualChargedCount * resolvedReceiptUnitPriceUsdc).toFixed(6)),
|
|
1692
|
+
chargedCount: actualChargedCount,
|
|
1693
|
+
...extra,
|
|
1694
|
+
});
|
|
1458
1695
|
|
|
1459
1696
|
if (
|
|
1460
1697
|
!chargeResult ||
|
|
@@ -1487,11 +1724,7 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1487
1724
|
|
|
1488
1725
|
const pushRes = await pushDataWithRetry(unchargedChunk);
|
|
1489
1726
|
if (!pushRes.ok) {
|
|
1490
|
-
chargeReceipts.push({
|
|
1491
|
-
...receipt,
|
|
1492
|
-
chargedCount,
|
|
1493
|
-
eventChargeLimitReached,
|
|
1494
|
-
});
|
|
1727
|
+
chargeReceipts.push(buildFinalReceipt(chargedCount, { eventChargeLimitReached }));
|
|
1495
1728
|
finalOk = false;
|
|
1496
1729
|
finalStatus = 'PARTIAL_BILLING_HALT';
|
|
1497
1730
|
haltReason = `Dataset pushData failed after charge (paid items preserved in KVS checkpoint stagedItemsById): ${pushRes.error?.message || String(pushRes.error)}`;
|
|
@@ -1504,11 +1737,7 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1504
1737
|
deliveredCount = deliveredSet.size;
|
|
1505
1738
|
await persistCheckpointPostCharge();
|
|
1506
1739
|
|
|
1507
|
-
chargeReceipts.push({
|
|
1508
|
-
...receipt,
|
|
1509
|
-
chargedCount,
|
|
1510
|
-
eventChargeLimitReached,
|
|
1511
|
-
});
|
|
1740
|
+
chargeReceipts.push(buildFinalReceipt(chargedCount, { eventChargeLimitReached }));
|
|
1512
1741
|
|
|
1513
1742
|
if (eventChargeLimitReached) {
|
|
1514
1743
|
finalStatus = 'CHARGE_LIMIT_REACHED';
|
|
@@ -1516,12 +1745,32 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1516
1745
|
}
|
|
1517
1746
|
} else if (chargedCount === 0) {
|
|
1518
1747
|
inFlightSet = new Set();
|
|
1748
|
+
if (chargeResult?.unbilledOwnerTestBypass === true) {
|
|
1749
|
+
billingMode = 'UNBILLED_OWNER_TEST';
|
|
1750
|
+
await persistCheckpointPostCharge();
|
|
1751
|
+
const pushRes = await pushDataWithRetry(unchargedChunk);
|
|
1752
|
+
if (!pushRes.ok) {
|
|
1753
|
+
finalOk = false;
|
|
1754
|
+
finalStatus = 'PARTIAL_BILLING_HALT';
|
|
1755
|
+
haltReason = `Dataset pushData failed in UNBILLED_OWNER_TEST mode: ${pushRes.error?.message || String(pushRes.error)}`;
|
|
1756
|
+
break;
|
|
1757
|
+
}
|
|
1758
|
+
for (const c of unchargedChunk) {
|
|
1759
|
+
deliveredSet.add(c.creativeId);
|
|
1760
|
+
newlyDeliveredItems.push(c);
|
|
1761
|
+
}
|
|
1762
|
+
deliveredCount = deliveredSet.size;
|
|
1763
|
+
await persistCheckpointPostCharge();
|
|
1764
|
+
chargeReceipts.push(
|
|
1765
|
+
buildFinalReceipt(0, {
|
|
1766
|
+
eventChargeLimitReached: false,
|
|
1767
|
+
unbilledOwnerTestBypass: true,
|
|
1768
|
+
})
|
|
1769
|
+
);
|
|
1770
|
+
continue;
|
|
1771
|
+
}
|
|
1519
1772
|
await persistCheckpointPostCharge();
|
|
1520
|
-
chargeReceipts.push({
|
|
1521
|
-
...receipt,
|
|
1522
|
-
chargedCount: 0,
|
|
1523
|
-
eventChargeLimitReached,
|
|
1524
|
-
});
|
|
1773
|
+
chargeReceipts.push(buildFinalReceipt(0, { eventChargeLimitReached }));
|
|
1525
1774
|
if (eventChargeLimitReached) {
|
|
1526
1775
|
finalOk = true;
|
|
1527
1776
|
finalStatus = 'CHARGE_LIMIT_REACHED';
|
|
@@ -1533,11 +1782,19 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1533
1782
|
break;
|
|
1534
1783
|
} else {
|
|
1535
1784
|
// Partial chunk charge (0 < chargedCount < unchargedChunk.length)
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1785
|
+
const hasExplicitIdsArray = Array.isArray(chargeResult.chargedCreativeIds);
|
|
1786
|
+
const explicitChargedIds = hasExplicitIdsArray
|
|
1787
|
+
? Array.from(
|
|
1788
|
+
new Set(chargeResult.chargedCreativeIds.filter((id) => chunkIds.includes(id)))
|
|
1789
|
+
)
|
|
1538
1790
|
: null;
|
|
1791
|
+
const isValidExplicitChargedIds = Boolean(
|
|
1792
|
+
explicitChargedIds &&
|
|
1793
|
+
explicitChargedIds.length === chargedCount &&
|
|
1794
|
+
chargeResult.chargedCreativeIds.length === chargedCount
|
|
1795
|
+
);
|
|
1539
1796
|
|
|
1540
|
-
if (
|
|
1797
|
+
if (isValidExplicitChargedIds) {
|
|
1541
1798
|
for (const id of explicitChargedIds) {
|
|
1542
1799
|
chargedSet.add(id);
|
|
1543
1800
|
}
|
|
@@ -1548,12 +1805,12 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1548
1805
|
const paidSubset = unchargedChunk.filter((c) => explicitChargedIds.includes(c.creativeId));
|
|
1549
1806
|
const pushRes = await pushDataWithRetry(paidSubset);
|
|
1550
1807
|
if (!pushRes.ok) {
|
|
1551
|
-
chargeReceipts.push(
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1808
|
+
chargeReceipts.push(
|
|
1809
|
+
buildFinalReceipt(chargedCount, {
|
|
1810
|
+
chargedCreativeIds: explicitChargedIds,
|
|
1811
|
+
eventChargeLimitReached,
|
|
1812
|
+
})
|
|
1813
|
+
);
|
|
1557
1814
|
finalOk = false;
|
|
1558
1815
|
finalStatus = 'PARTIAL_BILLING_HALT';
|
|
1559
1816
|
haltReason = `Dataset pushData failed after partial charge (paid items preserved in KVS checkpoint stagedItemsById): ${pushRes.error?.message || String(pushRes.error)}`;
|
|
@@ -1566,12 +1823,12 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1566
1823
|
deliveredCount = deliveredSet.size;
|
|
1567
1824
|
await persistCheckpointPostCharge();
|
|
1568
1825
|
|
|
1569
|
-
chargeReceipts.push(
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1826
|
+
chargeReceipts.push(
|
|
1827
|
+
buildFinalReceipt(chargedCount, {
|
|
1828
|
+
chargedCreativeIds: explicitChargedIds,
|
|
1829
|
+
eventChargeLimitReached,
|
|
1830
|
+
})
|
|
1831
|
+
);
|
|
1575
1832
|
|
|
1576
1833
|
finalOk = eventChargeLimitReached;
|
|
1577
1834
|
finalStatus = eventChargeLimitReached ? 'CHARGE_LIMIT_REACHED' : 'PARTIAL_BILLING_HALT';
|
|
@@ -1579,7 +1836,7 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1579
1836
|
haltReason = `Partial chunk charge (${chargedCount}/${unchargedChunk.length}) without charge limit flag`;
|
|
1580
1837
|
}
|
|
1581
1838
|
break;
|
|
1582
|
-
} else if (eventChargeLimitReached) {
|
|
1839
|
+
} else if (!hasExplicitIdsArray && eventChargeLimitReached) {
|
|
1583
1840
|
// Standard Apify SDK ChargeResult when hitting user's max charge ceiling:
|
|
1584
1841
|
// Attribute charge deterministically to the ordered prefix `unchargedChunk.slice(0, chargedCount)`
|
|
1585
1842
|
// so the customer receives every item they paid for, and leave the remaining tail uncharged.
|
|
@@ -1594,12 +1851,12 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1594
1851
|
|
|
1595
1852
|
const pushRes = await pushDataWithRetry(paidPrefix);
|
|
1596
1853
|
if (!pushRes.ok) {
|
|
1597
|
-
chargeReceipts.push(
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1854
|
+
chargeReceipts.push(
|
|
1855
|
+
buildFinalReceipt(chargedCount, {
|
|
1856
|
+
chargedCreativeIds: paidPrefixIds,
|
|
1857
|
+
eventChargeLimitReached: true,
|
|
1858
|
+
})
|
|
1859
|
+
);
|
|
1603
1860
|
finalOk = false;
|
|
1604
1861
|
finalStatus = 'PARTIAL_BILLING_HALT';
|
|
1605
1862
|
haltReason = `Dataset pushData failed after charge limit prefix (paid items preserved in KVS checkpoint stagedItemsById): ${pushRes.error?.message || String(pushRes.error)}`;
|
|
@@ -1612,12 +1869,12 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1612
1869
|
deliveredCount = deliveredSet.size;
|
|
1613
1870
|
await persistCheckpointPostCharge();
|
|
1614
1871
|
|
|
1615
|
-
chargeReceipts.push(
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1872
|
+
chargeReceipts.push(
|
|
1873
|
+
buildFinalReceipt(chargedCount, {
|
|
1874
|
+
chargedCreativeIds: paidPrefixIds,
|
|
1875
|
+
eventChargeLimitReached: true,
|
|
1876
|
+
})
|
|
1877
|
+
);
|
|
1621
1878
|
|
|
1622
1879
|
finalOk = true;
|
|
1623
1880
|
finalStatus = 'CHARGE_LIMIT_REACHED';
|
|
@@ -1632,11 +1889,11 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1632
1889
|
chargedEventCount = chargedSet.size + chargedCount;
|
|
1633
1890
|
await persistCheckpointPostCharge();
|
|
1634
1891
|
|
|
1635
|
-
chargeReceipts.push(
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1892
|
+
chargeReceipts.push(
|
|
1893
|
+
buildFinalReceipt(chargedCount, {
|
|
1894
|
+
eventChargeLimitReached: false,
|
|
1895
|
+
})
|
|
1896
|
+
);
|
|
1640
1897
|
|
|
1641
1898
|
finalOk = false;
|
|
1642
1899
|
finalStatus = 'PARTIAL_BILLING_HALT';
|
|
@@ -1699,6 +1956,8 @@ export async function runGoogleAdsTransparencyActor(input = {}, hooks = {}) {
|
|
|
1699
1956
|
chargedEventCount,
|
|
1700
1957
|
durationMs,
|
|
1701
1958
|
requestCount: Math.max(1, totalRpcRequests),
|
|
1959
|
+
unitPriceUsdc: lastResolvedUnitPriceUsdc,
|
|
1960
|
+
actorStartFeeUsdc: hooks?.actorStartFeeUsdc,
|
|
1702
1961
|
});
|
|
1703
1962
|
|
|
1704
1963
|
return finalizeActorReturn({
|