rubyapp-store-connectcicdbuild-automationfastlane

Setting App prices using Price Point Id (Appstore Connect API) for new Apps


I am building a script to upload new apps to store via fastlane. However, fastlane currently faces issues with setting prices for newly created apps.SO I created ruby scripts to do it manually via api.

First thing I did was to to get price point of my app with help from this appstore documentation. I was able to get the price point for $0USD and I wanted to set it as the price of my app. I used a patch request to this endpoint, however I am unable to do so.

Heres an update of my scripts, If anyone can point me to the right direction.

    lane :setAppPrice do
    auth_token = authAppstoreAPI()
    puts "Token: #{auth_token}"

    app_id = 'app_id_here'
    price_point_id = get_app_price_points(auth_token: auth_token, app_id: app_id)
    

    if price_point_id
        url = URI("https://api.appstoreconnect.apple.com/v1/#{app_id}/appPricePoints/#{price_point_id}")

        # Create the HTTP request for updating the app price
        http = Net::HTTP.new(url.host, url.port)
        http.use_ssl = true

        # Prepare the PATCH request
        headers = {
            "Authorization" => "Bearer #{auth_token}",
            "Content-Type" => "application/json"
        }

        request = Net::HTTP::Patch.new(url, headers)

        # Prepare the body with the new price data
        request.body = {
            data: {
            id: price_point_id,
            type: "appPricePoints",
            attributes: {
                customerPrice: 0.0 # Set your desired new price here
            }
            }
        }.to_json

        # Execute the request
        response = http.request(request)

        # Handle the response
        if response.code == "200"
            UI.success("Successfully updated app price to 0.0 for price point ID #{price_point_id}")
        else
            UI.error("Failed to update app price: #{response.body}")
        end
    end
  end

  lane :get_app_price_points do |options|

    require 'json'
    require 'net/http'
  
    auth_token = options[:auth_token]  # Your App Store Connect API token
    app_id = options[:app_id]          # The app ID for which you want to list subscriptions
  
    # Define the URL for fetching subscriptions
    url = URI("https://api.appstoreconnect.apple.com/v1/apps/#{app_id}/appPricePoints?filter%5Bterritory%5D=USA,CAN&include=territory")
  
    # Create the HTTP request
    http = Net::HTTP.new(url.host, url.port)
    http.use_ssl = true
  
    # Set up headers for the App Store Connect API request
    headers = {
      "Authorization" => "Bearer #{auth_token}",
      "Content-Type" => "application/json"
    }
  
    # Prepare the GET request
    request = Net::HTTP::Get.new(url, headers)
  
    # Execute the request
    response = http.request(request)
  
    # Handle the response
    if response.code == "200"
        app_prices = JSON.parse(response.body)
        # UI.message("Subscriptions for app #{app_id}: #{response.body}")

        # Use the find method to get the free price point ID directly
        free_price_point = app_prices["data"].find do |price_point|
            price_point["attributes"]["customerPrice"] == "0.0"
        end

        free_price_point_id = free_price_point ? free_price_point["id"] : nil
        UI.message("Free Price Point ID: #{free_price_point_id}")

        # Return the ID of the price point with 0.0 price
        free_price_point_id
    else
      UI.error("Failed to fetch subscriptions: #{response.body}")
    end
  end

Solution

  • Okay finally I was able to set prices on the apps after scouring and big help from the apple developer forums here.

    I created custom fastlane lanes to do all of it. I basically set free price ($0.00) for 2 territories (USA and CAN). Feel free to modify these for your needs.

    Here are the custom fastlane lanes.

    Main lane to set the price:

    lane :setAppPrice do
        auth_token = authAppstoreAPI() #your appstore api token
        puts "Token: #{auth_token}"
    
        app_id = 'your_app_id_here'
    
    
        #
        # Have to get a price point ID for each territory.
        #
        # We had to individually get the price point ID for each territory
        # even though we can merge it into one request. This is because
        # when setting prices through appPriceSchedules, api response with 
        # a 'baseTerritory must have a single manual price'.
        #
        # These is very helpful if you need to set prices for different territories.
        #
        # Note: This is only to set prices to 0.0 for all
        # territories.
        # 
        territories= ["USA", "CAN"] # Add more territories if needed
        territories.each do |territory|
            price_point_id = get_app_price_points(auth_token: auth_token, app_id: app_id, territory_id: territory)
            setAppPriceToZero(auth_token: auth_token, app_id: app_id, territory_id: territory, price_point_id: price_point_id)
        end
    end
    

    The lane to set price:

    lane :setAppPriceToZero do |options|
        require 'json'
        require 'net/http'
      
        auth_token = options[:auth_token] 
        app_id = options[:app_id]
        territory_id = options[:territory_id]
        price_point_id = options[:price_point_id]
    
        UI.user_error!("Missing parameters: auth_token, app_id, price_point_id and territory_id") unless auth_token && app_id && territory_id && price_point_id
    
        UI.message "Setting app price to 0.0 for app #{app_id} in territory #{territory_id} with price point ID #{price_point_id}"
           
    
    
        url = URI("https://api.appstoreconnect.apple.com/v1/appPriceSchedules")
    
        http = Net::HTTP.new(url.host, url.port)
        http.use_ssl = true
    
        headers = {
            "Authorization" => "Bearer #{auth_token}",
            "Content-Type" => "application/json"
        }
        request = Net::HTTP::Post.new(url, headers)
        payload = {
            "data": {
                "type": "appPriceSchedules",
                "relationships": {
                    "app": {
                        "data": {
                            "type": "apps",
                            "id": app_id
                        }
                    },
                    "baseTerritory": {
                        "data": {
                            "type": "territories",
                            "id": territory_id
                        }
                    },
                    "manualPrices": {
                        "data": [
                            {
                                "id": "manualPrice-0",
                                "type": "appPrices"
                            }
                        ]
                    }
                }
            },
            "included": [
                {
                    "id": "manualPrice-0",
                    "type": "appPrices",
    
                    #
                    # Uncomment the following lines if you want to set a start and end date
                    #
    
                    # "attributes": {
                    #     "startDate": "NONE",
                    #     "endDate": "NONE"
                    # },
                    "relationships": {
                        "appPricePoint": {
                            "data": {
                                "type": "appPricePoints",
                                "id": price_point_id
                            }
                        }
                    }
                }
            ]
        }
    
        request.body = payload.to_json
        response = http.request(request)
    
        UI.message "Response: #{response.body} & #{response.code}"
      end
    

    Then the lane to get free($0.0) price_point_id:

      #
      # Get free price point for a territory
      #
      lane :get_app_price_points do |options|
    
        require 'json'
        require 'net/http'
      
        auth_token = options[:auth_token]  # Your App Store Connect API token
        app_id = options[:app_id]          # The app ID for which you want to list subscriptions
        territory_id = options[:territory_id] # The territory ID for which you want to list subscriptions
    
        # all params are required
        UI.user_error!("Missing parameters: auth_token, app_id and territory_id") unless auth_token && app_id && territory_id
    
    
        # if need be you can combine multiple territories in one request,
        # example: territory%5D=USA,CAN,GBR
        url = URI("https://api.appstoreconnect.apple.com/v1/apps/#{app_id}/appPricePoints?filter%5Bterritory%5D=#{territory_id}&include=territory")
      
        # Create the HTTP request
        http = Net::HTTP.new(url.host, url.port)
        http.use_ssl = true
      
        # Set up headers for the App Store Connect API request
        headers = {
          "Authorization" => "Bearer #{auth_token}",
          "Content-Type" => "application/json"
        }
      
        # Prepare the GET request
        request = Net::HTTP::Get.new(url, headers)
      
        # Execute the request
        response = http.request(request)
      
        # Handle the response
        if response.code == "200"
            app_prices = JSON.parse(response.body)
    
            # Use the find method to get the free price point ID directly
            free_price_point = app_prices["data"].find do |price_point|
                price_point["attributes"]["customerPrice"] == "0.0"
            end
    
            free_price_point_id = free_price_point ? free_price_point["id"] : nil
            UI.message("Free Price Point ID for #{territory_id}: #{free_price_point_id}")
    
            # Return the ID of the price point with 0.0 price
            free_price_point_id
        else
          UI.error("Failed to fetch subscriptions: #{response.body}")
        end
      end
    

    EDIT You can combine it with your existing workflows. For example, after you run the produce(...) action lane you can get the app id and then just pass it to setAppPrice lane:

    produce(....) 
    app_identifier = lane_context[SharedValues::PRODUCE_APPLE_ID]
    setAppPrice(app_id: app_identifier)