@daemux/store-automator 0.10.42 → 0.10.44
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.
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "App Store & Google Play automation for Flutter apps",
|
|
8
|
-
"version": "0.10.
|
|
8
|
+
"version": "0.10.44"
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "store-automator",
|
|
13
13
|
"source": "./plugins/store-automator",
|
|
14
14
|
"description": "3 agents for app store publishing: reviewer, meta-creator, media-designer",
|
|
15
|
-
"version": "0.10.
|
|
15
|
+
"version": "0.10.44",
|
|
16
16
|
"keywords": [
|
|
17
17
|
"flutter",
|
|
18
18
|
"app-store",
|
package/package.json
CHANGED
|
@@ -93,16 +93,19 @@ def create_subscription_availability(
|
|
|
93
93
|
|
|
94
94
|
def get_subscription_prices(headers: dict, sub_id: str) -> list:
|
|
95
95
|
"""List existing prices for a subscription."""
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
timeout=TIMEOUT
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
96
|
+
url: str | None = f"{BASE_URL}/subscriptions/{sub_id}/prices"
|
|
97
|
+
params: dict | None = {"include": "subscriptionPricePoint", "limit": 200}
|
|
98
|
+
all_prices: list = []
|
|
99
|
+
while url:
|
|
100
|
+
resp = requests.get(url, headers=headers, params=params, timeout=TIMEOUT)
|
|
101
|
+
if not resp.ok:
|
|
102
|
+
print_api_errors(resp, f"get prices for subscription {sub_id}")
|
|
103
|
+
return all_prices
|
|
104
|
+
data = resp.json()
|
|
105
|
+
all_prices.extend(data.get("data", []))
|
|
106
|
+
url = data.get("links", {}).get("next")
|
|
107
|
+
params = None
|
|
108
|
+
return all_prices
|
|
106
109
|
|
|
107
110
|
|
|
108
111
|
def get_price_points_for_territory(
|
|
@@ -153,13 +156,31 @@ def find_price_point_by_amount(
|
|
|
153
156
|
return None
|
|
154
157
|
|
|
155
158
|
|
|
159
|
+
def get_price_point_equalizations(headers: dict, price_point_id: str) -> list:
|
|
160
|
+
"""Get equalized price points for all territories from a base price point."""
|
|
161
|
+
url: str | None = f"{BASE_URL}/subscriptionPricePoints/{price_point_id}/equalizations"
|
|
162
|
+
params: dict | None = {"include": "territory", "limit": 200}
|
|
163
|
+
all_points: list = []
|
|
164
|
+
while url:
|
|
165
|
+
resp = requests.get(url, headers=headers, params=params, timeout=TIMEOUT)
|
|
166
|
+
if not resp.ok:
|
|
167
|
+
print_api_errors(resp, "get price point equalizations")
|
|
168
|
+
return all_points
|
|
169
|
+
data = resp.json()
|
|
170
|
+
all_points.extend(data.get("data", []))
|
|
171
|
+
url = data.get("links", {}).get("next")
|
|
172
|
+
params = None
|
|
173
|
+
return all_points
|
|
174
|
+
|
|
175
|
+
|
|
156
176
|
def create_subscription_price(
|
|
157
177
|
headers: dict,
|
|
158
178
|
sub_id: str,
|
|
159
179
|
price_point_id: str,
|
|
180
|
+
territory_id: str,
|
|
160
181
|
start_date: str | None = None,
|
|
161
182
|
) -> dict | None:
|
|
162
|
-
"""Create a price entry for a subscription using a price point ID."""
|
|
183
|
+
"""Create a price entry for a subscription using a price point ID and territory."""
|
|
163
184
|
resp = requests.post(
|
|
164
185
|
f"{BASE_URL}/subscriptionPrices",
|
|
165
186
|
json={"data": {
|
|
@@ -170,6 +191,9 @@ def create_subscription_price(
|
|
|
170
191
|
"subscriptionPricePoint": {
|
|
171
192
|
"data": {"type": "subscriptionPricePoints", "id": price_point_id},
|
|
172
193
|
},
|
|
194
|
+
"territory": {
|
|
195
|
+
"data": {"type": "territories", "id": territory_id},
|
|
196
|
+
},
|
|
173
197
|
},
|
|
174
198
|
}},
|
|
175
199
|
headers=headers,
|
|
@@ -178,7 +202,6 @@ def create_subscription_price(
|
|
|
178
202
|
if not resp.ok:
|
|
179
203
|
print_api_errors(resp, f"create price for subscription {sub_id}")
|
|
180
204
|
return None
|
|
181
|
-
print(f" Set price for subscription {sub_id} (price point: {price_point_id})")
|
|
182
205
|
return resp.json().get("data")
|
|
183
206
|
|
|
184
207
|
|
|
@@ -19,6 +19,7 @@ import hashlib
|
|
|
19
19
|
import json
|
|
20
20
|
import os
|
|
21
21
|
import sys
|
|
22
|
+
import time
|
|
22
23
|
|
|
23
24
|
from asc_iap_api import (
|
|
24
25
|
create_group_localization,
|
|
@@ -38,6 +39,7 @@ from asc_subscription_setup import (
|
|
|
38
39
|
create_subscription_availability,
|
|
39
40
|
create_subscription_price,
|
|
40
41
|
find_price_point_by_amount,
|
|
42
|
+
get_price_point_equalizations,
|
|
41
43
|
get_price_points_for_territory,
|
|
42
44
|
get_review_screenshot,
|
|
43
45
|
get_subscription_availability,
|
|
@@ -173,40 +175,99 @@ def _sync_availability(headers: dict, sub_id: str, sub_config: dict) -> None:
|
|
|
173
175
|
|
|
174
176
|
|
|
175
177
|
def _sync_pricing(headers: dict, sub_id: str, sub_config: dict) -> None:
|
|
176
|
-
"""Set subscription prices
|
|
178
|
+
"""Set subscription prices for all territories using Apple's equalization.
|
|
179
|
+
|
|
180
|
+
Finds the base price point (USD/USA), then uses the equalizations
|
|
181
|
+
endpoint to get Apple-calculated prices for all other territories.
|
|
182
|
+
"""
|
|
177
183
|
prices = sub_config.get("prices", {})
|
|
178
184
|
if not prices:
|
|
179
185
|
print(" WARNING: No prices configured, skipping pricing", file=sys.stderr)
|
|
180
186
|
return
|
|
181
187
|
|
|
188
|
+
# Build set of territories that already have prices
|
|
182
189
|
existing_prices = get_subscription_prices(headers, sub_id)
|
|
183
|
-
|
|
184
|
-
|
|
190
|
+
priced_territories: set[str] = set()
|
|
191
|
+
for ep in existing_prices:
|
|
192
|
+
pp_rel = ep.get("relationships", {}).get("subscriptionPricePoint", {})
|
|
193
|
+
pp_id = pp_rel.get("data", {}).get("id", "")
|
|
194
|
+
if "_" in pp_id:
|
|
195
|
+
priced_territories.add(pp_id.rsplit("_", 1)[-1])
|
|
196
|
+
|
|
197
|
+
# Use first configured currency as base (typically USD -> USA)
|
|
198
|
+
base_currency = next(iter(prices))
|
|
199
|
+
base_amount = prices[base_currency]
|
|
200
|
+
base_territory = CURRENCY_TO_TERRITORY.get(base_currency)
|
|
201
|
+
if not base_territory:
|
|
202
|
+
print(f" WARNING: Unknown base currency '{base_currency}'", file=sys.stderr)
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
# Find the base price point
|
|
206
|
+
price_points = get_price_points_for_territory(headers, sub_id, base_territory)
|
|
207
|
+
base_point = find_price_point_by_amount(price_points, base_amount)
|
|
208
|
+
if not base_point:
|
|
209
|
+
sample = [pp.get("attributes", {}).get("customerPrice", "?") for pp in price_points[:5]]
|
|
210
|
+
print(
|
|
211
|
+
f" WARNING: No price point matching {base_amount} for {base_territory}"
|
|
212
|
+
f" (API returned {len(price_points)} points, first prices: {sample})",
|
|
213
|
+
file=sys.stderr,
|
|
214
|
+
)
|
|
185
215
|
return
|
|
186
216
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
217
|
+
created, skipped, failed = _apply_equalized_prices(
|
|
218
|
+
headers, sub_id, base_point, base_territory, base_amount, base_currency, priced_territories,
|
|
219
|
+
)
|
|
220
|
+
print(f" Pricing: {created} set, {skipped} existed, {failed} failed")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _apply_equalized_prices(
|
|
224
|
+
headers: dict, sub_id: str, base_point: dict,
|
|
225
|
+
base_territory: str, base_amount: str, base_currency: str,
|
|
226
|
+
priced_territories: set[str],
|
|
227
|
+
) -> tuple[int, int, int]:
|
|
228
|
+
"""Set base price then apply Apple-equalized prices for all territories."""
|
|
229
|
+
created = 0
|
|
230
|
+
skipped = 0
|
|
231
|
+
failed = 0
|
|
232
|
+
|
|
233
|
+
# Set base territory price if not already set
|
|
234
|
+
if base_territory not in priced_territories:
|
|
235
|
+
result = create_subscription_price(headers, sub_id, base_point["id"], base_territory)
|
|
236
|
+
if result:
|
|
237
|
+
created += 1
|
|
238
|
+
print(f" Set base price {base_amount} {base_currency} for {base_territory}")
|
|
239
|
+
else:
|
|
240
|
+
failed += 1
|
|
241
|
+
print(f" WARNING: Failed to set base price for {base_territory}", file=sys.stderr)
|
|
242
|
+
return created, skipped, failed
|
|
243
|
+
else:
|
|
244
|
+
skipped += 1
|
|
245
|
+
|
|
246
|
+
# Get equalized prices for all other territories
|
|
247
|
+
equalized = get_price_point_equalizations(headers, base_point["id"])
|
|
248
|
+
if not equalized:
|
|
249
|
+
print(" WARNING: No equalizations returned", file=sys.stderr)
|
|
250
|
+
return created, skipped, failed
|
|
251
|
+
|
|
252
|
+
# Set price for each equalized territory
|
|
253
|
+
for eq_point in equalized:
|
|
254
|
+
territory_id = eq_point.get("relationships", {}).get(
|
|
255
|
+
"territory", {}
|
|
256
|
+
).get("data", {}).get("id")
|
|
257
|
+
if not territory_id:
|
|
258
|
+
failed += 1
|
|
191
259
|
continue
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if not point:
|
|
195
|
-
sample = [
|
|
196
|
-
pp.get("attributes", {}).get("customerPrice", "?")
|
|
197
|
-
for pp in price_points[:5]
|
|
198
|
-
]
|
|
199
|
-
print(
|
|
200
|
-
f" WARNING: No price point matching {amount} for {territory}"
|
|
201
|
-
f" (API returned {len(price_points)} points, first prices: {sample})",
|
|
202
|
-
file=sys.stderr,
|
|
203
|
-
)
|
|
260
|
+
if territory_id in priced_territories:
|
|
261
|
+
skipped += 1
|
|
204
262
|
continue
|
|
205
|
-
result = create_subscription_price(headers, sub_id,
|
|
263
|
+
result = create_subscription_price(headers, sub_id, eq_point["id"], territory_id)
|
|
206
264
|
if result:
|
|
207
|
-
|
|
265
|
+
created += 1
|
|
208
266
|
else:
|
|
209
|
-
|
|
267
|
+
failed += 1
|
|
268
|
+
time.sleep(0.05)
|
|
269
|
+
|
|
270
|
+
return created, skipped, failed
|
|
210
271
|
|
|
211
272
|
|
|
212
273
|
def _sync_review_screenshot(
|