You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Salesforce Commerce Cloud SDK的单例客户端令牌刷新失效问题

问题:SFCC访问令牌过期后未触发自动刷新逻辑,请求失败

我在Next.js服务端使用Salesforce commerce-sdk实现了单例PublicSFCCProductClient类,用于调用Salesforce Commerce Cloud API。该类设计为在access_token过期时自动刷新令牌并重试请求,但实际access_token过期后未触发刷新逻辑,请求失败。

PublicSFCCProductClient类实现代码

export class PublicSFCCProductClient {
        private static instance: PublicSFCCProductClient;
        private client: Product.ShopperProducts | null = null;
        private refreshToken: TokenResponse["refresh_token"] | null = null;

        private constructor() {}

        private static async getParameters(): Promise<ClientConfig> {
          const parameters = sfccParametersSchema.parse({
            clientId: process.env.SFDC_CLIENT_ID,
            organizationId: process.env.SFDC_ORGANIZATIONID,
            shortCode: process.env.SFDC_SHORTCODE,
            siteId: process.env.SFDC_SITEID
          });

          return {
            parameters
          };
        }

        private static setAuthorization(
          config: ClientConfig,
          token: string
        ): ClientConfig {
          return {
            ...config,
            headers: {
              ...config.headers,
              authorization: `Bearer ${token}`
            }
          };
        }

        private static async getGuestTokenResponse(
          config: ClientConfig
        ): Promise<TokenResponse> {
          try {
            const clientSecret = z.string().parse(process.env.SFDC_SECRET);
            const slasClient = new Customer.ShopperLogin(config);

            const guestTokenResponse = await slasHelpers.loginGuestUserPrivate(
              slasClient,
              { clientSecret }
            );
            return guestTokenResponse;
          } catch (e: any) {
            console.error("Error logging guest customer");
            console.error(await e.response.json());
            throw e;
          }
        }

        private static async refreshAccessToken(
          config: ClientConfig,
          refreshToken: string
        ): Promise<TokenResponse> {
          const clientSecret = z.string().parse(process.env.SFDC_SECRET);
          const slasClient = new Customer.ShopperLogin(config);

          try {
            const refreshTokenResponse = await slasHelpers.refreshAccessTokenPrivate(
              slasClient,
              { clientSecret },
              { refreshToken }
            );
            return refreshTokenResponse;
          } catch (e: any) {
            console.error("Error refreshing token");
            console.error(await e.response.json());
            throw e;
          }
        }

        private async createSFCCProductClient(): Promise<Product.ShopperProducts> {
          const parameters = await PublicSFCCProductClient.getParameters();
          const tokenResponse = await PublicSFCCProductClient.getGuestTokenResponse(
            parameters
          );
          this.refreshToken = tokenResponse.refresh_token;

          const configWithToken = PublicSFCCProductClient.setAuthorization(
            parameters,
            tokenResponse.access_token
          );
          const client = new Product.ShopperProducts(configWithToken);
          return client;
        }

        public async apiCallWithTokenRefresh(apiCall: () => Promise<any>) {
          try {
            return await apiCall();
          } catch (e: any) {
            if (e?.response?.status === 401 && this.refreshToken) {
              // Access token expired, refresh it and retry the request
              console.error("access token expired, try to refresh", e);
              const parameters = await PublicSFCCProductClient.getParameters();
              const refreshedTokenResponse =
                await PublicSFCCProductClient.refreshAccessToken(
                  parameters,
                  this.refreshToken
                );

              this.refreshToken = refreshedTokenResponse.refresh_token;

              const newConfigWithToken = PublicSFCCProductClient.setAuthorization(
                parameters,
                refreshedTokenResponse.access_token
              );

              this.client = new Product.ShopperProducts(newConfigWithToken);

              console.log(
                "new accessToken issued",
                refreshedTokenResponse.access_token
              );

              // Retry the request with the new access token
              return apiCall();
            }
            throw e;
          }
        }
}

单例实例使用代码

const publicSFCCProductClientInstance =
        PublicSFCCProductClient.getInstance();
      const publicProductsList = await getPublicSFCCProductsList({
        clientInstance: publicSFCCProductClientInstance,
        productIds: productsIds,
        currency: cookies.currency,
        locale: cookies.locale
      });

getPublicSFCCProductsList函数实现代码

export async function getPublicSFCCProductsList({
      clientInstance,
      productIds,
      currency,
      locale,
      expand
    }: {
      clientInstance: PublicSFCCProductClient;
      productIds: string;
      currency: string;
      locale: string;
      expand?: string[];
    }): Promise<Product.ShopperProducts.ProductResult | undefined> {
      console.log(`Getting products: [${productIds}]`);
      const publicSFCCProductClient = await clientInstance.getClient();

      const products = await clientInstance.apiCallWithTokenRefresh(async () => {
        try {
          return await publicSFCCProductClient.getProducts({
            parameters: {
              ids: productIds,
              currency,
              locale,
              expand: !!expand
                ? expand
                : ["images", "prices", "availability", "variations"]
            }
          });
        } catch (error) {
          console.error("error fetching products", error);
        }
      });

      return products;
    }

产品可正常获取,但access_token过期后未触发刷新逻辑,请求失败,无法定位问题原因。

内容的提问来源于stack exchange,提问作者ufollettu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 12:23:13