From 49cd6098e031d6c9f705e2cf30c286bfc882f6de Mon Sep 17 00:00:00 2001 From: Alfonso Noriega Date: Fri, 12 Jun 2026 15:09:56 +0200 Subject: [PATCH 1/3] Fetch preview store claim URL --- .changeset/preview-store-claim-url.md | 5 + .../cli/commands/store/create/preview.test.ts | 2 + .../store/create/preview/client.test.ts | 62 +++++++++- .../services/store/create/preview/client.ts | 106 ++++++++++++++++-- .../store/create/preview/index.test.ts | 42 +++++++ .../services/store/create/preview/index.ts | 14 ++- .../store/create/preview/result.test.ts | 15 ++- .../services/store/create/preview/result.ts | 4 + 8 files changed, 239 insertions(+), 11 deletions(-) create mode 100644 .changeset/preview-store-claim-url.md diff --git a/.changeset/preview-store-claim-url.md b/.changeset/preview-store-claim-url.md new file mode 100644 index 00000000000..9fd96ad72f9 --- /dev/null +++ b/.changeset/preview-store-claim-url.md @@ -0,0 +1,5 @@ +--- +'@shopify/store': minor +--- + +Fetch and display the claim URL after creating a preview store. diff --git a/packages/store/src/cli/commands/store/create/preview.test.ts b/packages/store/src/cli/commands/store/create/preview.test.ts index 902ad86c8c2..79f7c63ed4e 100644 --- a/packages/store/src/cli/commands/store/create/preview.test.ts +++ b/packages/store/src/cli/commands/store/create/preview.test.ts @@ -26,6 +26,8 @@ describe('store create preview command', () => { subdomain: 'x.myshopify.com', country: 'US', storefrontUrl: 'https://x.myshopify.com', + saveUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token', + }, } vi.mocked(createPreviewStoreCommand).mockResolvedValueOnce(result) diff --git a/packages/store/src/cli/services/store/create/preview/client.test.ts b/packages/store/src/cli/services/store/create/preview/client.test.ts index a8ac4d42c8b..22e233229e9 100644 --- a/packages/store/src/cli/services/store/create/preview/client.test.ts +++ b/packages/store/src/cli/services/store/create/preview/client.test.ts @@ -1,8 +1,10 @@ import { CLI_INSTANCE_HEADER, CLI_VERSION_HEADER, + claimPreviewStore, createPreviewStore, getOrCreateCliInstanceId, + previewStoreClaimHeaders, previewStoreCreateHeaders, } from './client.js' import {shopifyFetch} from '@shopify/cli-kit/node/http' @@ -59,6 +61,18 @@ describe('preview store client', () => { }) }) + test('builds claim request headers with the Admin API token', () => { + expect(previewStoreClaimHeaders('instance-1', 'shpat_token')).toEqual({ + Accept: 'application/json', + 'Content-Type': 'application/json', + 'User-Agent': `Shopify CLI; v=${CLI_KIT_VERSION}`, + [CLI_INSTANCE_HEADER]: 'instance-1', + [CLI_VERSION_HEADER]: CLI_KIT_VERSION, + authorization: 'shpat_token', + 'X-Shopify-Access-Token': 'shpat_token', + }) + }) + test('POSTs to /services/preview-stores with optional name and country variables and no authorization', async () => { vi.mocked(shopifyFetch).mockResolvedValueOnce( response(201, { @@ -124,7 +138,42 @@ describe('preview store client', () => { await expect(createPreviewStore({}, {storage: inMemoryStorage('instance-1')})).rejects.toThrow(message) }) - test('rejects malformed success responses without leaking the admin API token or access URL', async () => { + test('POSTs to /services/preview-stores/:shop_id/claim with the Admin API token', async () => { + vi.mocked(shopifyFetch).mockResolvedValueOnce( + response(201, {claim_url: 'https://admin.shopify.com/store-transfer/accept/claim-token'}), + ) + + const got = await claimPreviewStore( + {shopId: '123', adminApiToken: 'shpat_token'}, + {storage: inMemoryStorage('instance-1')}, + ) + + expect(shopifyFetch).toHaveBeenCalledWith('https://app.shopify.com/services/preview-stores/123/claim', { + method: 'POST', + headers: expect.objectContaining({ + [CLI_INSTANCE_HEADER]: 'instance-1', + authorization: 'shpat_token', + 'X-Shopify-Access-Token': 'shpat_token', + }), + body: JSON.stringify({}), + }) + expect(got).toEqual({claimUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token'}) + }) + + test('sends optional email when requesting a preview store claim URL', async () => { + vi.mocked(shopifyFetch).mockResolvedValueOnce( + response(201, {claim_url: 'https://admin.shopify.com/store-transfer/accept/claim-token'}), + ) + + await claimPreviewStore( + {shopId: '123', adminApiToken: 'shpat_token', email: 'merchant@example.com'}, + {storage: inMemoryStorage('instance-1')}, + ) + + expect(vi.mocked(shopifyFetch).mock.calls[0]![1]!.body).toBe(JSON.stringify({email: 'merchant@example.com'})) + }) + + test('rejects malformed create responses without leaking the admin API token or access URL', async () => { vi.mocked(shopifyFetch).mockResolvedValueOnce( response(201, { shop: {id: 123}, @@ -177,4 +226,15 @@ describe('preview store client', () => { expect(error.tryMessage).not.toContain('access-token') expect(error.tryMessage).not.toContain('shpat_token') }) + + test('rejects malformed claim responses without leaking the claim URL', async () => { + vi.mocked(shopifyFetch).mockResolvedValueOnce(response(201, {claim_url: 123})) + + await expect( + claimPreviewStore({shopId: '123', adminApiToken: 'shpat_token'}, {storage: inMemoryStorage('instance-1')}), + ).rejects.toMatchObject({ + message: 'Preview store claim URL response is missing required fields.', + tryMessage: expect.stringContaining('"claim_url":"[REDACTED]"'), + }) + }) }) diff --git a/packages/store/src/cli/services/store/create/preview/client.ts b/packages/store/src/cli/services/store/create/preview/client.ts index 8a8bf0cfde2..aef12da3450 100644 --- a/packages/store/src/cli/services/store/create/preview/client.ts +++ b/packages/store/src/cli/services/store/create/preview/client.ts @@ -12,6 +12,10 @@ interface PreviewStoreClientStorageSchema { cliInstanceId?: string } +interface PreviewStoreRequestOptions { + storage?: LocalStorage +} + let _clientStorage: LocalStorage | undefined function clientStorage() { @@ -19,9 +23,7 @@ function clientStorage() { return _clientStorage } -export interface PreviewStoreClientOptions { - storage?: LocalStorage -} +export interface PreviewStoreClientOptions extends PreviewStoreRequestOptions {} interface PreviewStoreCreateRequest { name?: string @@ -54,6 +56,20 @@ interface RawPreviewStoreCreateResponse { access_url?: unknown } +interface PreviewStoreClaimRequest { + shopId: string + adminApiToken: string + email?: string +} + +export interface PreviewStoreClaimResponse { + claimUrl: string +} + +interface RawPreviewStoreClaimResponse { + claim_url?: unknown +} + interface RawPreviewStoreErrorResponse { error_code?: string message?: string @@ -70,7 +86,7 @@ export function getOrCreateCliInstanceId( return next } -export function previewStoreCreateHeaders(cliInstanceId: string): Record { +function previewStoreBaseHeaders(cliInstanceId: string): Record { return { Accept: 'application/json', 'Content-Type': 'application/json', @@ -80,6 +96,18 @@ export function previewStoreCreateHeaders(cliInstanceId: string): Record { + return previewStoreBaseHeaders(cliInstanceId) +} + +export function previewStoreClaimHeaders(cliInstanceId: string, adminApiToken: string): Record { + return { + ...previewStoreBaseHeaders(cliInstanceId), + authorization: adminApiToken, + 'X-Shopify-Access-Token': adminApiToken, + } +} + export async function createPreviewStore( request: PreviewStoreCreateRequest, options: PreviewStoreClientOptions = {}, @@ -122,7 +150,41 @@ export async function createPreviewStore( ) } - return narrowResponse(parsed) + return narrowCreateResponse(parsed) +} + +export async function claimPreviewStore( + request: PreviewStoreClaimRequest, + options: PreviewStoreRequestOptions = {}, +): Promise { + const fqdn = await appManagementFqdn() + const url = `https://${fqdn}/services/preview-stores/${request.shopId}/claim` + const body = JSON.stringify({...(request.email ? {email: request.email} : {})}) + + const response = await shopifyFetch(url, { + method: 'POST', + headers: previewStoreClaimHeaders(getOrCreateCliInstanceId(options.storage), request.adminApiToken), + body, + }) + + const rawText = await response.text() + if (!response.ok) { + const error = previewStoreClaimError(response.status, rawText) + throw new AbortError(error.message, error.tryMessage) + } + + let parsed: RawPreviewStoreClaimResponse + try { + parsed = JSON.parse(rawText) as RawPreviewStoreClaimResponse + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error' + throw new AbortError( + 'Preview store claim URL request returned a non-JSON response.', + `Parse error: ${message}. Body (truncated): ${redactPreviewStoreRawText(rawText).slice(0, 500)}`, + ) + } + + return narrowClaimResponse(parsed) } function previewStoreError(status: number, rawText: string): {message: string; tryMessage?: string} { @@ -183,7 +245,17 @@ function parseErrorBody(rawText: string): RawPreviewStoreErrorResponse { } } -function narrowResponse(parsed: RawPreviewStoreCreateResponse): PreviewStoreCreateResponse { +function previewStoreClaimError(status: number, rawText: string): {message: string; tryMessage?: string} { + const parsed = parseErrorBody(rawText) + const redactedRawText = redactPreviewStoreRawText(rawText) + + return { + message: `Preview store claim URL request failed with HTTP ${status}.`, + tryMessage: parsed.message ?? (redactedRawText.length > 0 ? redactedRawText.slice(0, 1000) : 'No response body returned.'), + } +} + +function narrowCreateResponse(parsed: RawPreviewStoreCreateResponse): PreviewStoreCreateResponse { const shop = parsed.shop const id = typeof shop?.id === 'string' || typeof shop?.id === 'number' ? String(shop.id) : undefined const name = typeof shop?.name === 'string' ? shop.name : undefined @@ -208,6 +280,19 @@ function narrowResponse(parsed: RawPreviewStoreCreateResponse): PreviewStoreCrea } } +function narrowClaimResponse(parsed: RawPreviewStoreClaimResponse): PreviewStoreClaimResponse { + const claimUrl = typeof parsed.claim_url === 'string' ? parsed.claim_url : undefined + + if (!claimUrl) { + throw new AbortError( + 'Preview store claim URL response is missing required fields.', + `Got: ${JSON.stringify(redactPreviewStoreClaimResponse(parsed)).slice(0, 500)}`, + ) + } + + return {claimUrl} +} + function redactPreviewStoreResponse(parsed: RawPreviewStoreCreateResponse): RawPreviewStoreCreateResponse { return { ...parsed, @@ -216,9 +301,16 @@ function redactPreviewStoreResponse(parsed: RawPreviewStoreCreateResponse): RawP } } +function redactPreviewStoreClaimResponse(parsed: RawPreviewStoreClaimResponse): RawPreviewStoreClaimResponse { + return { + ...parsed, + ...(parsed.claim_url ? {claim_url: '[REDACTED]'} : {}), + } +} + function redactPreviewStoreRawText(rawText: string): string { return rawText .replace(/(["']?(?:admin_api_token|adminApiToken)["']?\s*:\s*["'])[^"']+/gi, '$1[REDACTED]') - .replace(/(["']?(?:access_url|accessUrl)["']?\s*:\s*["'])[^"']+/gi, '$1[REDACTED]') + .replace(/(["']?(?:access_url|accessUrl|claim_url|claimUrl)["']?\s*:\s*["'])[^"']+/gi, '$1[REDACTED]') .replace(/([?&](?:token|access_token)=)[^&\s"'<>]+/gi, '$1[REDACTED]') } diff --git a/packages/store/src/cli/services/store/create/preview/index.test.ts b/packages/store/src/cli/services/store/create/preview/index.test.ts index b10400f9aff..c3c4f099618 100644 --- a/packages/store/src/cli/services/store/create/preview/index.test.ts +++ b/packages/store/src/cli/services/store/create/preview/index.test.ts @@ -7,6 +7,9 @@ describe('preview store create service', () => { const setStoredStoreAppSession = vi.fn() const recordStoreFqdnMetadata = vi.fn() const setLastSeenUserId = vi.fn() + const claimPreviewStore = vi.fn(async () => ({ + claimUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token', + })) const result = await createPreviewStoreCommand( {name: 'Lavender Candles', country: 'US'}, @@ -17,6 +20,7 @@ describe('preview store create service', () => { adminApiToken: 'shpat_token', accessUrl: 'https://app.shopify.com/auth/preview-store?token=access-token', })), + claimPreviewStore, setStoredStoreAppSession, recordStoreFqdnMetadata, setLastSeenUserId, @@ -54,8 +58,10 @@ describe('preview store create service', () => { subdomain: 'x12y45z.myshopify.com', country: 'US', storefrontUrl: 'https://app.shopify.com/auth/preview-store?token=access-token', + saveUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token', }, }) + expect(claimPreviewStore).toHaveBeenCalledWith({shopId: '123', adminApiToken: 'shpat_token'}, undefined) }) test('uses the shop id as the preview user id when no placeholder account uuid is returned', async () => { @@ -70,6 +76,9 @@ describe('preview store create service', () => { adminApiToken: 'shpat_token', accessUrl: 'https://app.shopify.com/auth/preview-store?token=access-token', })), + claimPreviewStore: vi.fn(async () => ({ + claimUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token', + })), setStoredStoreAppSession, recordStoreFqdnMetadata: vi.fn(), setLastSeenUserId, @@ -83,6 +92,33 @@ describe('preview store create service', () => { expect(setLastSeenUserId).toHaveBeenCalledWith(`${PREVIEW_USER_ID_PREFIX}123`) }) + test('passes client options to both create and claim requests', async () => { + const client = {} as any + const createPreviewStore = vi.fn(async () => ({ + shop: {id: '123', name: 'Lavender Candles', domain: 'x12y45z.myshopify.com'}, + adminApiToken: 'shpat_token', + accessUrl: 'https://app.shopify.com/auth/preview-store?token=access-token', + })) + const claimPreviewStore = vi.fn(async () => ({ + claimUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token', + })) + + await createPreviewStoreCommand( + {client}, + { + createPreviewStore, + claimPreviewStore, + setStoredStoreAppSession: vi.fn(), + recordStoreFqdnMetadata: vi.fn(), + setLastSeenUserId: vi.fn(), + now: () => new Date('2026-06-08T12:00:00.000Z'), + }, + ) + + expect(createPreviewStore).toHaveBeenCalledWith({name: undefined, country: undefined}, client) + expect(claimPreviewStore).toHaveBeenCalledWith({shopId: '123', adminApiToken: 'shpat_token'}, client) + }) + test('persists a store session and returns success when recording store metadata fails', async () => { const setStoredStoreAppSession = vi.fn() const recordStoreFqdnMetadata = vi.fn(async () => { @@ -98,6 +134,9 @@ describe('preview store create service', () => { adminApiToken: 'shpat_token', accessUrl: 'https://app.shopify.com/auth/preview-store?token=access-token', })), + claimPreviewStore: vi.fn(async () => ({ + claimUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token', + })), setStoredStoreAppSession, recordStoreFqdnMetadata, setLastSeenUserId, @@ -124,6 +163,9 @@ describe('preview store create service', () => { createPreviewStore: vi.fn(async () => { throw new Error('Preview store creation failed.') }), + claimPreviewStore: vi.fn(async () => ({ + claimUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token', + })), setStoredStoreAppSession, recordStoreFqdnMetadata, setLastSeenUserId, diff --git a/packages/store/src/cli/services/store/create/preview/index.ts b/packages/store/src/cli/services/store/create/preview/index.ts index e2a2dfa8d52..47d405cfcd2 100644 --- a/packages/store/src/cli/services/store/create/preview/index.ts +++ b/packages/store/src/cli/services/store/create/preview/index.ts @@ -1,4 +1,4 @@ -import {PreviewStoreClientOptions, PreviewStoreCreateResponse, createPreviewStore} from './client.js' +import {PreviewStoreClientOptions, PreviewStoreCreateResponse, claimPreviewStore, createPreviewStore} from './client.js' import {STORE_AUTH_APP_CLIENT_ID} from '../../auth/config.js' import {setStoredStoreAppSession} from '../../auth/session-store.js' import {recordStoreFqdnMetadata} from '../../attribution.js' @@ -14,6 +14,7 @@ interface CreatePreviewStoreInput { interface CreatePreviewStoreDependencies { createPreviewStore: typeof createPreviewStore + claimPreviewStore: typeof claimPreviewStore setStoredStoreAppSession: typeof setStoredStoreAppSession recordStoreFqdnMetadata: typeof recordStoreFqdnMetadata setLastSeenUserId: typeof setLastSeenUserId @@ -29,11 +30,13 @@ export interface CreatePreviewStoreResult { subdomain: string country?: string storefrontUrl: string + saveUrl: string } } const defaultDependencies: CreatePreviewStoreDependencies = { createPreviewStore, + claimPreviewStore, setStoredStoreAppSession, recordStoreFqdnMetadata, setLastSeenUserId, @@ -53,7 +56,7 @@ export async function createPreviewStoreCommand( input.client, ) - return persistPreviewStoreSession(response, input.country, resolvedDependencies) + return persistPreviewStoreSession(response, input.country, resolvedDependencies, input.client) } function previewUserId(response: PreviewStoreCreateResponse): string { @@ -64,6 +67,7 @@ async function persistPreviewStoreSession( response: PreviewStoreCreateResponse, country: string | undefined, dependencies: CreatePreviewStoreDependencies, + client: PreviewStoreClientOptions | undefined, ): Promise { const acquiredAt = dependencies.now().toISOString() const userId = previewUserId(response) @@ -93,6 +97,11 @@ async function persistPreviewStoreSession( // Store metadata is best-effort; credentials and access URL are already persisted. } + const claim = await dependencies.claimPreviewStore( + {shopId: response.shop.id, adminApiToken: response.adminApiToken}, + client, + ) + return { status: 'success', message: @@ -103,6 +112,7 @@ async function persistPreviewStoreSession( subdomain: response.shop.domain, ...(country ? {country} : {}), storefrontUrl: response.accessUrl, + saveUrl: claim.claimUrl, }, } } diff --git a/packages/store/src/cli/services/store/create/preview/result.test.ts b/packages/store/src/cli/services/store/create/preview/result.test.ts index 3f17ac2dd39..8fa1c4d35d8 100644 --- a/packages/store/src/cli/services/store/create/preview/result.test.ts +++ b/packages/store/src/cli/services/store/create/preview/result.test.ts @@ -22,11 +22,12 @@ const result = { subdomain: 'x12y45z.myshopify.com', country: 'US', storefrontUrl: 'https://x12y45z.myshopify.com/?foo=bar', + saveUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token', }, } describe('preview store create result presenter', () => { - test('writes JSON output with the storefront URL and no save URL', () => { + test('writes JSON output with the storefront and save URLs', () => { writeCreatePreviewStoreResult(result, 'json') expect(outputResult).toHaveBeenCalledWith( @@ -41,9 +42,11 @@ describe('preview store create result presenter', () => { subdomain: 'x12y45z.myshopify.com', country: 'US', storefrontUrl: 'https://x12y45z.myshopify.com/?foo=bar', + saveUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token', }, next_steps: [ 'Open your store (https://x12y45z.myshopify.com/?foo=bar) to preview the storefront.', + 'Create an account (https://admin.shopify.com/store-transfer/accept/claim-token) for free to save progress.', 'Use `shopify store execute` to add products, collections, pages, and more.', 'Use `shopify theme pull` and `shopify theme push` to edit your store design.', ], @@ -88,6 +91,16 @@ describe('preview store create result presenter', () => { }, ' to preview the storefront.', ], + [ + 'Create ', + { + link: { + label: 'an account', + url: 'https://admin.shopify.com/store-transfer/accept/claim-token', + }, + }, + ' for free to save progress.', + ], ['Use ', {command: 'shopify store execute'}, ' to add products, collections, pages, and more.'], [ 'Use ', diff --git a/packages/store/src/cli/services/store/create/preview/result.ts b/packages/store/src/cli/services/store/create/preview/result.ts index dbf33aeb5fd..0a5e3868512 100644 --- a/packages/store/src/cli/services/store/create/preview/result.ts +++ b/packages/store/src/cli/services/store/create/preview/result.ts @@ -35,6 +35,10 @@ function previewStoreNextSteps(result: CreatePreviewStoreResult): PreviewStoreNe json: `Open your store (${result.store.storefrontUrl}) to preview the storefront.`, text: ['Open ', {link: {label: 'your store', url: result.store.storefrontUrl}}, ' to preview the storefront.'], }, + { + json: `Create an account (${result.store.saveUrl}) for free to save progress.`, + text: ['Create ', {link: {label: 'an account', url: result.store.saveUrl}}, ' for free to save progress.'], + }, { json: 'Use `shopify store execute` to add products, collections, pages, and more.', text: ['Use ', {command: 'shopify store execute'}, ' to add products, collections, pages, and more.'], From 6479b7905285e883172b447cb2bdbd1a82378915 Mon Sep 17 00:00:00 2001 From: Alfonso Noriega Date: Fri, 12 Jun 2026 15:25:44 +0200 Subject: [PATCH 2/3] Fetch preview store claim URL Assisted-By: devx/425e6a4b-6ec9-421c-8f98-ab1725d0b996 --- packages/store/src/cli/commands/store/create/preview.test.ts | 1 - .../store/src/cli/services/store/create/preview/client.ts | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/store/src/cli/commands/store/create/preview.test.ts b/packages/store/src/cli/commands/store/create/preview.test.ts index 79f7c63ed4e..94daeaf1450 100644 --- a/packages/store/src/cli/commands/store/create/preview.test.ts +++ b/packages/store/src/cli/commands/store/create/preview.test.ts @@ -27,7 +27,6 @@ describe('store create preview command', () => { country: 'US', storefrontUrl: 'https://x.myshopify.com', saveUrl: 'https://admin.shopify.com/store-transfer/accept/claim-token', - }, } vi.mocked(createPreviewStoreCommand).mockResolvedValueOnce(result) diff --git a/packages/store/src/cli/services/store/create/preview/client.ts b/packages/store/src/cli/services/store/create/preview/client.ts index aef12da3450..afde146e655 100644 --- a/packages/store/src/cli/services/store/create/preview/client.ts +++ b/packages/store/src/cli/services/store/create/preview/client.ts @@ -62,7 +62,7 @@ interface PreviewStoreClaimRequest { email?: string } -export interface PreviewStoreClaimResponse { +interface PreviewStoreClaimResponse { claimUrl: string } @@ -251,7 +251,8 @@ function previewStoreClaimError(status: number, rawText: string): {message: stri return { message: `Preview store claim URL request failed with HTTP ${status}.`, - tryMessage: parsed.message ?? (redactedRawText.length > 0 ? redactedRawText.slice(0, 1000) : 'No response body returned.'), + tryMessage: + parsed.message ?? (redactedRawText.length > 0 ? redactedRawText.slice(0, 1000) : 'No response body returned.'), } } From f900b6817fdc28804be171af75b602219335662b Mon Sep 17 00:00:00 2001 From: Alfonso Noriega Date: Wed, 17 Jun 2026 18:24:35 +0200 Subject: [PATCH 3/3] Update client.ts --- packages/store/src/cli/services/store/create/preview/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/store/src/cli/services/store/create/preview/client.ts b/packages/store/src/cli/services/store/create/preview/client.ts index afde146e655..718fd2af474 100644 --- a/packages/store/src/cli/services/store/create/preview/client.ts +++ b/packages/store/src/cli/services/store/create/preview/client.ts @@ -158,7 +158,7 @@ export async function claimPreviewStore( options: PreviewStoreRequestOptions = {}, ): Promise { const fqdn = await appManagementFqdn() - const url = `https://${fqdn}/services/preview-stores/${request.shopId}/claim` + const url = `https://${fqdn}/services/preview-stores/${encodeURIComponent(request.shopId)}/claim` const body = JSON.stringify({...(request.email ? {email: request.email} : {})}) const response = await shopifyFetch(url, {