After the Speech Route paywall finally loaded products, I still had a screen full of rules that were not about layout.
Guideline 3.1.2 wants the subscription named, the length visible, the price visible, and working links to terms and privacy. Apple's wording is in the close-button rejection. One implementation detail sits under all of that: the numbers on the screen have to be the numbers StoreKit returns.
I do not hardcode $9.99 or per month any more. I did enough subscription work on that first app to see why a string in the view is a trap.
The price is localised. Your string is not.
A hardcoded "$9.99" is correct for one storefront. The moment the app is for sale outside that storefront, the amount Apple charges is different, and the paywall is lying.
A reviewer in another country, or a customer who is, sees a price that does not match the sheet Apple will present. That is a review problem and a support problem. StoreKit already has the localised price and the period on the product. Reading those values is the way the screen stays true when you change pricing later.
Duration belongs with the amount. "$9.99" fails the 3.1.2 list; "$9.99 per month" is the shape Apple describes. If that period is a copy-pasted label, it will drift from the product. If it comes from StoreKit, it cannot.
Trials cannot out-shout the billed amount
The same screen has a second way to fail that is about emphasis, not missing fields.
If a free trial or intro price is the loudest thing on the paywall and the real recurring charge is small and grey, Apple treats it as misleading. The billed amount has to be the most conspicuous number — size, colour, position. "7 days free" as a headline with the price underneath is the layout that gets rejected. "7-day free trial, then $9.99 per month" keeps the charge attached.
I would rather pull that $9.99 per month from the product than restyle a hardcoded string until it looks honest.
What the Speech Route screen taught me to read
Once products loaded, the next rejection was not StoreKit. It was the screen: you could not leave it, and the information 3.1.2 lists had to be on it before purchase. I wrote that up as Guideline 3.1.2.
The rule I kept for every paywall after that is mechanical. Display name, period, and price come from the StoreKit product. Privacy and terms are real URLs, not files. Dismissal works on a device. I do not type a dollar amount into a Text widget and hope the storefront agrees.
If products never load, this does not matter yet. Fix the empty list first. Then take the numbers from the products you finally have.