Bug in Shopify App Pricing

Hey team,

The new Shopify App Pricing doesn’t work for some reason.

The merchant selects the subscription, then is redirected to the /welcome page, but there is no charge. This merchant is trying a private plan. So now I’m not sure if this happening on all plans or not. What is the best way to investigate?

There is no update in Partner Dashboard. The store didn’t activated any plan. The store didn’t have any plan activated, while it was redirected to the /welcome page succesfully.

No subscription was created / activated.

app(prod)* shop.wss do
app(prod)*   client = ShopifyAPI::Clients::Graphql::Admin.new(session: ShopifyAPI::Context.active_session)
app(prod)"   query = <<~GQL
app(prod)"     {
app(prod)"       currentAppInstallation {
app(prod)"         activeSubscriptions {
app(prod)"           id
app(prod)"           name
app(prod)"           status
app(prod)"           test
app(prod)"           currentPeriodEnd
app(prod)"           lineItems {
app(prod)"             id
app(prod)"             plan {
app(prod)"               pricingDetails {
app(prod)"                 __typename
app(prod)"                 ... on AppRecurringPricing {
app(prod)"                   price { amount currencyCode }
app(prod)"                   interval
app(prod)"                 }
app(prod)"               }
app(prod)"             }
app(prod)"           }
app(prod)"         }
app(prod)"       }
app(prod)"     }
app(prod)*   GQL
app(prod)*   client.query(query: query)
app(prod)> end
=> 
#<ShopifyAPI::Clients::HttpResponse:0x00007c94e87c6ab0
 @api_call_limit=nil,
 @body=
  {"data" => {"currentAppInstallation" => 
{"activeSubscriptions" => []}},
   "extensions" =>
    {"cost" =>
      {"requestedQueryCost" => 14,
       "actualQueryCost" => 2,
       "throttleStatus" =>
        {"maximumAvailable" => 2000.0, "curre
ntlyAvailable" => 1998, "restoreRate" => 
100.0}}}},
 @code=200,
 @headers=

Merchants are redirected to the App Pricing hosted page, to /welcome page, without a charge_id. The test plan is working on my end, I get the charge_id, but merchants have issues.

Can someone look into this, please?

Thank you

Hey @app are you still seeing this issue? Have you been able to replicate it at all?

Hey @KyleG-Shopify, thank you very much for looking into it.

I will ask the merchant who experienced this issue to try again today and I will come back with updates, but this merchant had a previous plan using Billing API, then cancelled, then installed again using Managed Pricing and it didn’t worked. After I get back from the merchant and I will update you.

Note: Another merchant did a fresh install and it worked (a few days ago). The merchant activated the plan successfully (like my test subscriptions).

Thank you!

Hey @KyleG-Shopify,

Unfortunately it didn’t worked. The merchant tried 4 times.

If it helps, I can send you the merchant myshopify domain in a DM.

Thank you!

Thanks @app, is the second screenshot what is shown after the try again button is pressed?

Hey @KyleG-Shopify,

The second screenshot is our alter bot when billing fails.

The try again button goes back to the App Pricing hosted page.

The merchant did the full billing flow 4 times, however, there are no updates in the partner dashboard.

A bit of history, the merchant had a previous charge (created with old Billing API about 2 years ago). He cancelled that plan and tried again after we migrated to Shopify App Pricing. We got one install in the meantime (from another merchant) and it worked, but for this merchant it doesn’t work. I guess I will have to try again when his old billing period ends.

Below is the /welcome method and fetch_subscription_status method

<s-button variant="primary"  href="<%= hosted_pricing_url(@shop_origin) %>" target="_top">
   Try again (Shopify App Pricing)
</s-button>
def hosted_pricing_url(shop_domain)
    store_handle = shop_store_handle(shop_domain)
    app_handle   = ENV.fetch("SHOPIFY_APP_HANDLE")

    "https://admin.shopify.com/store/#{store_handle}/charges/#{app_handle}/pricing_plans"
  end
def welcome
    Rails.logger.info "Billing#welcome params: #{params.inspect}"

    @shop_domain = params[:shop] || @shop_origin
    @plan_handle = params[:plan_handle]

    unless @plan_handle.present?
      Rails.logger.warn("Billing#welcome called without plan_handle for shop=#{@shop_domain}")
      flash.now[:error] = "We couldn't determine your plan. Please contact support."
      return render :welcome
    end

    internal_plan = case @plan_handle
                    when "shopify-test" then "growth"
                    else @plan_handle
                    end

    sub_status = current_shop.fetch_subscription_status
    if sub_status[:source] == :none
      Rails.logger.warn(
        "Billing#welcome: No active subscription found for shop=#{@shop_domain} " \
        "plan_handle=#{@plan_handle}. Blocking activation."
      )

      begin
        message = "⚠️ BILLING ERROR: #{@shop_domain} hit /welcome with plan_handle=#{@plan_handle} " \
                  "but NO active subscription found on Shopify. Activation BLOCKED.\n" \
                  "Error Details: #{sub_status[:error] || 'None'}"
        SuperspeedRoom.new(message: message).send
      rescue => e
        Rails.logger.error("Slack alert failed: #{e.message}")
      end

      @subscription_error = true
      return render :welcome
    end

    Rails.logger.info(
      "Billing#welcome: Subscription verified (#{sub_status[:source]}) for shop=#{@shop_domain} " \
      "name=#{sub_status[:name]} price=#{sub_status[:price]}"
    )
    current_shop.update(billing_flag: nil) if current_shop.respond_to?(:billing_flag)

    enable_store_from_plan(internal_plan)

    @internal_plan = internal_plan
    @page_limit    = current_shop.page_limit
  end
# frozen_string_literal: true

module SubscriptionStatus
  extend ActiveSupport::Concern

  # Returns a hash with subscription info from the GraphQL API:
  # { source: :graphql | :none, name:, price:, status:, billing_on:, trial_days:, raw: }
  def fetch_subscription_status
    graphql_sub = fetch_graphql_subscription
    if graphql_sub
      return {
        source: :graphql,
        id: graphql_sub["id"],
        name: graphql_sub["name"],
        price: graphql_sub.dig("lineItems", 0, "plan", "pricingDetails", "price", "amount"),
        status: graphql_sub["status"],
        billing_on: graphql_sub["currentPeriodEnd"],
        trial_days: nil,
        trial_ends_on: graphql_sub["trialDays"],
        activated_on: graphql_sub["createdAt"],
        raw: graphql_sub
      }
    end

    # No subscription found
    { source: :none }
  rescue => e
    Rails.logger.error("fetch_subscription_status error for #{shopify_domain}: #{e.class} - #{e.message}")
    { source: :none, error: e.message }
  end

  private

  def fetch_graphql_subscription
    result = wss do
      client = ShopifyAPI::Clients::Graphql::Admin.new(session: ShopifyAPI::Context.active_session)
      client.query(query: ACTIVE_SUBSCRIPTIONS_QUERY)
    end

    subs = result&.body&.dig("data", "currentAppInstallation", "activeSubscriptions")
    subs&.first
  rescue => e
    Rails.logger.error("fetch_graphql_subscription error for #{shopify_domain}: #{e.class} - #{e.message}")
    nil
  end

  ACTIVE_SUBSCRIPTIONS_QUERY = <<~GQL
    {
      currentAppInstallation {
        activeSubscriptions {
          id
          name
          status
          createdAt
          currentPeriodEnd
          trialDays
          test
          lineItems {
            id
            plan {
              pricingDetails {
                __typename
                ... on AppRecurringPricing {
                  price { amount currencyCode }
                  interval
                }
                ... on AppUsagePricing {
                  balanceUsed { amount currencyCode }
                  cappedAmount { amount currencyCode }
                }
              }
            }
          }
        }
      }
    }
  GQL
end

Thank you very much for your time!

Thanks for sharing those code examples. For the merchant with the old cancelled Billing API subscription, is that former billing cycle fully expired? I want to rule out any replacement or deferral happening with the billing for the new charge.

Now, looking at the code you shared, a likely reason your fetch_subscription_status method returns an empty array is that it queries the Admin API’s currentAppInstallation.activeSubscriptions, which only returns legacy Billing API subscriptions, not Shopify App Pricing contracts.

Can you test with the Partner API’s activeSubscription(appId:, shopId:) query instead? The migration guide walks through the changes here: Migrating to Shopify App Pricing, and the full query reference is here: Active Subscription.

Also, the missing charge_id in your redirect is expected. Only plan_handle and shop are passed now, which is documented on the Shopify App Pricing page under the “For apps enrolled before April 2026” section.

Thank you, @KyleG-Shopify.

The old cancelled plan should expire in July 10th and it was cancelled in June 25. I guess I will have to ask the merchant to try again after July 10. There were zero updates in Shopify Partner page for this shop, despite the number of failed attempts.

For some reason, I missed those parts from the migration and I will update the code.

The currentAppInstallation.activeSubscriptions worked correctly for this charge id: 28911239338 (installed from Shopify App Pricing)

"return_url": "https://admin.shopify.com/store/[shopify_domain]/apps/superspeed-1/welcome?plan_handle=starter",
"decorated_return_url": "https://admin.shopify.com/store/[shopify_domain]/apps/superspeed-1/welcome?charge_id=28911239338&amp;plan_handle=starter"

I just tested the current fetch_subscription_status method from our production kamal console and it works for both (old Billing API) and (new Shopify App Pricing).
It returns the active plan for all shops.

We enrolled to the new Shopify App Pricing around mid June (about 2-3 weeks ago).

During my test test payment, here is what I can see in the URL.
The charge_id appears to be in the url

https://admin.shopify.com/store/superspeed-test17/apps/superspeed-1/welcome?plan_handle=shopify-test&charge_id=57654706458

I will update our code and will ask the merchant to try again after July 10, then will post here more details.

Thank you so much for your time!

Thanks for sharing what you’ve done. Do let us know what happens after the 10th.