---
title: OAuth Callback API
description: Mağaza sahibi uygulamayı yükledikten sonra çağrılan ve access token alan callback API'sini yazın.
---

İhtiyacımız olan ikinci API, mağaza sahibi uygulamayı mağazasına yüklemeyi onayladıktan sonra çağrılan OAuth callback API'sidir.

## ikas API Helper'ları

Callback'i oluşturmadan önce, callback API'sinde kullanılacağı için `getIkas` fonksiyonunu yazmamız gerekiyor. `getIkas` fonksiyonu yalnızca bir `ikas` objesi oluşturup döndürür. İhtiyaç duyduğunuz her yerde yeni bir `ikas` objesi oluşturabilirsiniz, ancak kod tekrarını azaltmak için bu işi bir helper fonksiyonla yapıyoruz.

- `lib` klasörü altında `ikas-api.ts` adında bir dosya oluşturun ve aşağıdaki kodu yapıştırın.

```typescript title="File: lib/ikas-api.ts"
import { ikas, OAuthAPI, OnCheckTokenCallback } from '@ikas/api-client';
import moment from 'moment';
import { AuthToken, RedisDB } from './redis';
import { config } from '@/globals/config';

/**
 * This is a helper function that creates a new `ikas` object with your app config and a token object
 * We use this function every time before we need to make an API call to ikas
 *
 * @param token
 */
export function getIkas(token: AuthToken): ikas<AuthToken> {
  return new ikas({
    clientId: config.appId,
    clientSecret: config.appSecret,
    accessToken: token.access_token,
    tokenData: token,
    onCheckToken,
  });
}
/**
 * This function is called before every API call by the ikas client to let the app check the token expiration date and refresh it.
 * Otherwise, ikas APIs will return an Unauthorized error on API response, and you need to check token expiration manually.
 *
 * @param token AuthToken model
 */
const onCheckToken: OnCheckTokenCallback<AuthToken> = async (token) => {
  if (token) {
    const now = new Date();
    const expireDate = new Date(token.expireDate);
    // Check if token expired and refresh and update token on db if it is expired
    if (now.getTime() >= expireDate.getTime()) {
      const response = await OAuthAPI.refreshToken(
        {
          refresh_token: token.refresh_token,
          client_id: config.appId,
          client_secret: config.appSecret,
        },
        {
          storeName: 'api',
        },
      );
      if (response.data) {
        const expireDate = moment().add(response.data.expires_in, 'seconds').toDate().toISOString();
        token.access_token = response.data.access_token;
        token.refresh_token = response.data.refresh_token;
        token.token_type = response.data.token_type;
        token.expires_in = response.data.expires_in;
        token.expireDate = expireDate;
        // Save updated token
        await RedisDB.token.set(token.authorizedAppId, token);
        return { accessToken: token.access_token, tokenData: token };
      }
    }
  }
  return { accessToken: undefined };
};
```

## Callback API Handler'ını Oluşturma

- Önce `pages/api/oauth` klasörü içinde `callback.ts` dosyasını oluşturun.
- Ardından aşağıdaki kodu bu dosyaya kopyalayın.

```typescript title="File: pages/api/oauth/callback.ts"
import type { NextApiRequest, NextApiResponse } from 'next';
import { OAuthAPI, WebhookScope } from '@ikas/api-client';
import { createRouter } from 'next-connect';
import moment from 'moment';
import { ApiErrorResponse } from '@/globals/types';
import { AuthToken, RedisDB } from '@/lib/redis';
import { config } from '@/globals/config';
import { getIkas } from '@/lib/ikas-api';

type CallbackApiRequest = {
  storeName: string;
  code: string;
  state: string;
};
type CallbackApiResponse = ApiErrorResponse;

const router = createRouter<NextApiRequest, NextApiResponse<CallbackApiResponse>>();

/**
 * OAuth callback API. This API is called when a merchant accepts to install the app to his/her store.
 * You need to check the state variable if it is generated by your app before proceeding.
 */
router.get(async (req, res) => {
  try {
    // Validate if code, storeName and state exists on req.query
    if (!req.query.code) res.status(400).json({ statusCode: 400, message: 'code is required' });
    if (!req.query.storeName) res.status(400).json({ statusCode: 400, message: 'storeName is required' });
    if (!req.query.state) res.status(400).json({ statusCode: 400, message: 'state is required' });
    const { code, storeName, state } = req.query as CallbackApiRequest;

    // load your saved state variable from a temporary key-value database
    // like Redis, Memcached and validate it
    let savedState = await RedisDB.state.get(state);
    if (state == savedState) {
      // OAuthAPI.getTokenWithAuthorizationCode send code to ikas
      // and retrieve access token and refresh token
      const codeResponse = await OAuthAPI.getTokenWithAuthorizationCode(
        {
          code,
          client_id: config.appId,
          client_secret: config.appSecret,
          redirect_uri: config.callbackUrl,
        },
        {
          storeName,
        },
      );

      if (codeResponse && codeResponse.status == 200 && codeResponse.data) {
        const expireDate = moment().add(codeResponse.data.expires_in, 'seconds').toDate().toISOString();
        const token: AuthToken = {
          ...codeResponse.data,
          expireDate,
          authorizedAppId: '',
        };
        const ikas = getIkas(token);
        const meResponse = await ikas.adminApi.queries.me({});
        if (meResponse.isSuccess && meResponse.data) {
          token.authorizedAppId = meResponse.data.id!;
          // Save token to db, so you can use it later
          await RedisDB.token.set(token.authorizedAppId, token);
          // Register your app for product create webhooks
          const webhookRes = await ikas.adminApi.mutations.saveWebhook({
            variables: {
              input: {
                endpoint: `${config.deployUrl}/api/ikas/webhook`,
                scopes: [WebhookScope.PRODUCT_CREATED],
              },
            },
          });
          if (!webhookRes.isSuccess) console.error('Webhook save error', webhookRes.error || webhookRes.errors);
          // Redirect back to ikas admin dashboard
          res.redirect(`https://${storeName}.myikas.com/admin/authorized-app/${token.authorizedAppId}`);
          return;
        }
      }
    }
    res.redirect(`/authorize-store?storeName=${storeName}&status=fail`);
  } catch (err: any) {
    res.status(500).json({ statusCode: 500, message: err.message });
  }
});

export default router.handler({
  onError: (err: any, req, res) => {
    console.error(err.stack);
    res.status(err.statusCode || 500).end(err.message);
  },
});
```

### Kodun Açıklaması

```typescript
// load your saved state variable from a temporary key-value database
// like Redis, Memcached and validate it
let savedState = await RedisDB.state.get(state);
if (state == savedState) {
  //...
}
res.redirect(`/authorize-store?storeName=${storeName}&status=fail`);
```

- Önce `state` değişkenini Redis'ten okuyup isteğin query parametrelerinden gelen değerle karşılaştırıyoruz.
- `state` Redis'te yoksa mağaza yetkilendirme sayfasına geri dönmeliyiz.

```typescript
// OAuthAPI.getTokenWithAuthorizationCode send code to ikas
// and retrieve access token and refresh token
const codeResponse = await OAuthAPI.getTokenWithAuthorizationCode(
  {
    code,
    client_id: config.appId,
    client_secret: config.appSecret,
    redirect_uri: config.callbackUrl,
  },
  {
    storeName,
  },
);
if (codeResponse && codeResponse.status == 200 && codeResponse.data) {
  //...
}
```

- `state` doğrulandıktan sonra `OAuthAPI.getTokenWithAuthorizationCode` fonksiyonu ile access token istiyoruz. Bu fonksiyon, `https://${storeName}.myikas.com/api/admin/oauth/token` URL'sine form data ile bir POST isteği gönderir.
- Yanıt başarıyla alınırsa `access_token` değerini kaydedip ikas API'ye erişirken kullanabiliriz.

```typescript
const expireDate = moment().add(codeResponse.data.expires_in, 'seconds').toDate().toISOString();
const token: AuthToken = {
  ...codeResponse.data,
  expireDate,
  authorizedAppId: '',
};
const ikas = getIkas(token);
const meResponse = await ikas.adminApi.queries.me({});
if (meResponse.isSuccess && meResponse.data) {
  token.authorizedAppId = meResponse.data.id!;
  // Save token to db, so you can use it later
  await RedisDB.token.set(token.authorizedAppId, token);
  //...
}
```

- `codeResponse` ile, `access_token` için hesaplanan son kullanma tarihini de içeren bir `AuthToken` objesi oluşturuyoruz.
- Ardından ikas API'yi çağırmak için bu token ile bir `ikas` objesi oluşturuyoruz.
- `access_token` değerini test etmek için `me` query'sini çağırıyoruz. Query başarılı olursa token objesinin `authorizedAppId` alanını dolduruyoruz.
- Son olarak token'ı `authorizedAppId` key'i ile Redis'e kaydediyoruz.

### Uygulamanız İçin Webhook Oluşturma (İsteğe Bağlı)

```typescript
// Register your app for product create webhooks
const webhookRes = await ikas.adminApi.mutations.saveWebhook({
  variables: {
    input: {
      endpoint: `${config.deployUrl}/api/ikas/webhook`,
      scopes: [WebhookScope.PRODUCT_CREATED],
    },
  },
});
if (!webhookRes.isSuccess) console.error('Webhook save error', webhookRes.error || webhookRes.errors);
```

- `saveWebhook` mutation'ı ile ürün oluşturma webhook'una kayıt oluyoruz. Tek bir webhook URL'si için birden fazla scope gönderebilirsiniz.

```typescript
// Redirect back to ikas admin dashboard
res.redirect(`https://${storeName}.myikas.com/admin/authorized-app/${token.authorizedAppId}`);
return;
```

- Bu uygulama ikas Dashboard içinde bir iframe'de çalışan dahili (internal) bir uygulama olduğu için kullanıcıyı ikas Dashboard'daki yetkilendirilmiş uygulama sayfasına yönlendiriyoruz.
