Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **`asc subscriptions prices set-batch` sends one request instead of one per territory** — it now sends a single `PATCH /v1/subscriptions/{id}` with every price inlined (`SubscriptionPriceInlineCreate`) instead of one `POST /v1/subscriptionPrices` per territory. Pricing all 175 territories took 175 sequential POSTs (over a minute per subscription), and a failure partway through left some territories priced and others not; Apple now applies the batch all-or-nothing. The REST `POST /api/v1/subscriptions/:id/prices` route benefits too, since it uses the same repository method.
- **Subscription price schedules no longer drop manual prices after the first 50** — `subscription-price-schedule get` (and the read-back after `set-batch`) fetched `/v1/subscriptions/{id}/prices` without a `limit`, so only Apple's default first page came back and the other territories were filled with the first price's equalizations, showing wrong prices where custom per-territory prices were set. It now requests `limit=200` and follows the pagination cursor.
- **The other per-territory price lists no longer stop at 50** — the same truncation affected `iap-price-schedule get` (manual prices), `iap-offer-codes prices list`, `subscription-offer-codes prices list`, `subscription-promotional-offers prices list` and `win-back-offers prices list`. They called Apple without a `limit` and read one page, so any territory past Apple's default 50 was missing. They now request `limit=200` and follow every page through a shared `APIClient.requestAllPages(_:nextCursor:)` helper, which `subscription-price-schedule get` uses too.
- **Offer price lists now show each price's territory and price point** — `iap-offer-codes prices list`, `subscription-offer-codes prices list`, `subscription-promotional-offers prices list` and `win-back-offers prices list` returned entries with only `id` and the offer ID, because Apple sends a price's territory and price point only when the request asks for them with `include=`. The requests now include both, so `territory` and the price-point ID are filled in, over CLI and REST.

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,10 @@ public struct SDKInAppPurchaseOfferCodeRepository: InAppPurchaseOfferCodeReposit
// MARK: - Prices

public func listPrices(offerCodeId: String) async throws -> [Domain.InAppPurchaseOfferCodePrice] {
// One price per territory — follow every page to get all 175.
// One price per territory — follow every page to get all 175. `include` makes Apple
// return each price's territory and price point linkage, which the mapper reads.
let pages = try await client.requestAllPages(
APIEndpoint.v1.inAppPurchaseOfferCodes.id(offerCodeId).prices.get(parameters: .init(limit: 200)),
APIEndpoint.v1.inAppPurchaseOfferCodes.id(offerCodeId).prices.get(parameters: .init(limit: 200, include: [.territory, .pricePoint])),
nextCursor: { $0.meta?.paging.nextCursor }
)
return pages.flatMap(\.data).map { mapPrice($0, offerCodeId: offerCodeId) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,10 @@ public struct SDKSubscriptionOfferCodeRepository: SubscriptionOfferCodeRepositor
// MARK: - Prices

public func listPrices(offerCodeId: String) async throws -> [Domain.SubscriptionOfferCodePrice] {
// One price per territory — follow every page to get all 175.
// One price per territory — follow every page to get all 175. `include` makes Apple
// return each price's territory and price point linkage, which the mapper reads.
let pages = try await client.requestAllPages(
APIEndpoint.v1.subscriptionOfferCodes.id(offerCodeId).prices.get(parameters: .init(limit: 200)),
APIEndpoint.v1.subscriptionOfferCodes.id(offerCodeId).prices.get(parameters: .init(limit: 200, include: [.territory, .subscriptionPricePoint])),
nextCursor: { $0.meta?.paging.nextCursor }
)
return pages.flatMap(\.data).map { mapPrice($0, offerCodeId: offerCodeId) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ public struct SDKSubscriptionPromotionalOfferRepository: SubscriptionPromotional
}

public func listPrices(offerId: String) async throws -> [Domain.SubscriptionPromotionalOfferPrice] {
// One price per territory — follow every page to get all 175.
// One price per territory — follow every page to get all 175. `include` makes Apple
// return each price's territory and price point linkage, which the mapper reads.
let pages = try await client.requestAllPages(
APIEndpoint.v1.subscriptionPromotionalOffers.id(offerId).prices.get(parameters: .init(limit: 200)),
APIEndpoint.v1.subscriptionPromotionalOffers.id(offerId).prices.get(parameters: .init(limit: 200, include: [.territory, .subscriptionPricePoint])),
nextCursor: { $0.meta?.paging.nextCursor }
)
return pages.flatMap(\.data).map { mapPrice($0, offerId: offerId) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,10 @@ public struct SDKWinBackOfferRepository: WinBackOfferRepository, @unchecked Send
}

public func listPrices(offerId: String) async throws -> [Domain.WinBackOfferPrice] {
// One price per territory — follow every page to get all 175.
// One price per territory — follow every page to get all 175. `include` makes Apple
// return each price's territory and price point linkage, which the mapper reads.
let pages = try await client.requestAllPages(
APIEndpoint.v1.winBackOffers.id(offerId).prices.get(parameters: .init(limit: 200)),
APIEndpoint.v1.winBackOffers.id(offerId).prices.get(parameters: .init(limit: 200, include: [.territory, .subscriptionPricePoint])),
nextCursor: { $0.meta?.paging.nextCursor }
)
return pages.flatMap(\.data).map { mapPrice($0, offerId: offerId) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,4 +403,16 @@ struct SDKInAppPurchaseOfferCodeRepositoryTests {
#expect(result.last?.territory == "T174")
#expect(result.last?.pricePointId == "pp-174")
}

@Test func `offer code prices ask Apple for each price's territory and price point`() async throws {
// Apple only links each price to its territory and price point when asked via `include`.
let stub = StubAPIClient()
stub.willReturn(InAppPurchaseOfferPricesResponse(data: [], links: .init(this: "")))

let repo = SDKInAppPurchaseOfferCodeRepository(client: stub)
_ = try await repo.listPrices(offerCodeId: "oc-1")

let query = Dictionary(uniqueKeysWithValues: (stub.lastQuery ?? []).map { ($0.0, $0.1 ?? "") })
#expect(query["include"] == "territory,pricePoint")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -438,4 +438,16 @@ struct SDKSubscriptionOfferCodeRepositoryTests {
#expect(result.last?.territory == "T174")
#expect(result.last?.subscriptionPricePointId == "pp-174")
}

@Test func `offer code prices ask Apple for each price's territory and price point`() async throws {
// Apple only links each price to its territory and price point when asked via `include`.
let stub = StubAPIClient()
stub.willReturn(SubscriptionOfferCodePricesResponse(data: [], links: .init(this: "")))

let repo = SDKSubscriptionOfferCodeRepository(client: stub)
_ = try await repo.listPrices(offerCodeId: "oc-1")

let query = Dictionary(uniqueKeysWithValues: (stub.lastQuery ?? []).map { ($0.0, $0.1 ?? "") })
#expect(query["include"] == "territory,subscriptionPricePoint")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,16 @@ struct SDKSubscriptionPromotionalOfferRepositoryTests {
#expect(result.last?.territory == "T174")
#expect(result.last?.subscriptionPricePointId == "pp-174")
}

@Test func `promotional offer prices ask Apple for each price's territory and price point`() async throws {
// Apple only links each price to its territory and price point when asked via `include`.
let stub = StubAPIClient()
stub.willReturn(SubscriptionPromotionalOfferPricesResponse(data: [], links: .init(this: "")))

let repo = SDKSubscriptionPromotionalOfferRepository(client: stub)
_ = try await repo.listPrices(offerId: "po-1")

let query = Dictionary(uniqueKeysWithValues: (stub.lastQuery ?? []).map { ($0.0, $0.1 ?? "") })
#expect(query["include"] == "territory,subscriptionPricePoint")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,16 @@ struct SDKWinBackOfferRepositoryTests {
#expect(result.last?.territory == "T174")
#expect(result.last?.subscriptionPricePointId == "pp-174")
}

@Test func `win-back offer prices ask Apple for each price's territory and price point`() async throws {
// Apple only links each price to its territory and price point when asked via `include`.
let stub = StubAPIClient()
stub.willReturn(WinBackOfferPricesResponse(data: [], links: .init(this: "")))

let repo = SDKWinBackOfferRepository(client: stub)
_ = try await repo.listPrices(offerId: "wb-1")

let query = Dictionary(uniqueKeysWithValues: (stub.lastQuery ?? []).map { ($0.0, $0.1 ?? "") })
#expect(query["include"] == "territory,subscriptionPricePoint")
}
}
Loading