Skip to content

React Native Integration

Support

If you require any help during the integration or have any questions, don’t hesitate to reach out to us!

Getting Started

Resolving the SDK

To resolve the SDK with scoped access from the anybill-npm registry:

  1. Add a .npmrc file to the root directory of your project containing the following:
bash
@anybill:registry=https://anybill.jfrog.io/artifactory/api/npm/anybill_react_native_sdk/
//anybill.jfrog.io/artifactory/api/npm/anybill_react_native_sdk/:_authToken=<your_auth_token>

The authToken can be obtained from jfrog. Login with the credentials we sent you, then navigate to Artifactory -> Artifacts -> Select the desired product and click "Set Me Up" on the top right.

After validating your password you should be able to copy the authToken.


Then include the SDK in your project by executing the install command of your package manager
bash
npm install @anybill/react-native

Initialization

Importing the SDK

After installing, import the AnybillReactNSDK:

ts
import { AnybillReactNSDK, ApiEnv } from '@anybill/react-native';

Initialization

Initialize the anybill SDK, ideally in the earliest entry point of your application (e.g. index.js). init is an instance method with the signature init(apiEnv: ApiEnv, clientId: string): Promise<void>:

ts
const sdk = new AnybillReactNSDK();
await sdk.init(ApiEnv.Production, "<CLIENT_ID>");

The ApiEnv enum provides the following values:

  • ApiEnv.Test
  • ApiEnv.Staging
  • ApiEnv.Production

The AnybillReactNSDK instance additionally offers the following helper methods:

  • getApiEnv(): ApiEnv | undefined - Returns the currently configured environment.
  • getClientId(): string | undefined - Returns the currently configured client id.
  • clearConfig(): void - Clears the stored configuration.

Wrap your application's root component with the AnybillProvider context provider. This pattern ensures that state and methods managed by the Auth- and ReceiptProvider are accessible throughout your component tree via React Context.

ts
import { AnybillProvider } from "@anybill/react-native";

const App = () => {
  return (
    <AnybillProvider>
      {/* Your app components go here */}
    </AnybillProvider>
  );
};

SDK functionality

The SDK exposes two main hooks:

  • useAuth() → authentication and user-related methods
  • useReceipts() → receipt retrieval and management

Error Handling

The anybill SDK uses a custom error handling model. All methods return a Promise<AnybillResult<T>> including the return object on success or information about the error which occurred.

ts
class AnybillResult<T = void> {
  // Narrows the result; `data` is of type `T` inside the branch.
  isSuccess(): this is AnybillResult<T> & { data: T };
  // Narrows the result; `error` is of type `AnybillError` inside the branch.
  isFailure(): this is AnybillResult<T> & { error: AnybillError };

  get data(): T | undefined;
  get error(): AnybillError | undefined;
  // HTTP status code of the response.
  get status(): number;
  // Response headers, only available on success.
  get headers(): Record<string, unknown> | undefined;
}

Result objects of a failed call contain an AnybillError, which extends the JavaScript Error:

ts
interface AnybillError extends Error {
  variant: AnybillErrorVariant; // "receipts-delete-failed" | "Default"
  code: number;                 // HTTP status code (0 if no response)
  title?: string;               // Optional error title returned by the API
  traceId?: string;             // Optional trace id for support requests
  type: AnybillErrorType;
}

enum AnybillErrorType {
  genericError = "genericError",
  networkError = "networkError",
  invalidRefreshTokenError = "invalidRefreshTokenError",
  noUserError = "noUserError",
  unknown = "unknown",
}

Since error and title are optional, always use optional chaining when accessing them outside of an isFailure() branch. AnybillErrorType and AnybillErrorVariant are currently not exported by the package, so compare error.type against its string value.

Example usage of the error model based on the user info method of useAuth():

ts
const { getUser } = useAuth();

const result = await getUser();
if (result.isSuccess()) {
  console.log(result.data.id);
} else if (result.isFailure()) {
  switch (String(result.error.type)) {
    case "invalidRefreshTokenError":
      // Retrieve new authentication information from the anybill Partner Platform API
      break;
    case "networkError":
      // Network Error Handling
      break;
    default:
      console.error(result.error.title ?? result.error.message);
  }
}

TIP

When using the Token Based Login you'll have to check for a failing Refresh Token Call on the first anybill API Call you invoke. When the error is triggered (error.type equals "invalidRefreshTokenError") you'll have to retrieve new authentication information from the anybill Partner Platform API.

Important Note: We strongly advise against re-fetching the authentication token from our Partner Platform API on every app launch. Doing so can generate excessive network traffic, negating the performance benefits provided by the mobile SDK's token caching and session management capabilities.

Back to top

Authentication

The anybill SDK handles authentication seamlessly within its internal processes. Once a user successfully authenticates, an Access Token and a Refresh Token are securely stored in the device's local keystore:

  • Access Token: Valid for 24 hours and used to authorize user requests to the anybill API.

  • Refresh Token: Valid for 90 days and used to renew the Access Token upon expiration. When the Refresh Token expires, the user will need to reauthenticate.

This automated process minimizes the need for manual token handling, ensuring a smooth and secure experience for both users and developers.

Authenticate User

Use token information obtained from the Partner Platform API to initialize the SDK and authenticate the user without requiring credentials.

For detailed instructions on how to retrieve the token from the Partner Platform API, refer to the Partner Platform API documentation.

  • Get a token from the anybill Partner API by linking your account system to an anybill id.
  • Create an instance of TokenUser with the received token-information. Note the constructor parameter order: new TokenUser(accessToken, expiresIn, refreshToken).
  • Login the TokenUser with the anybill sdk
ts
    const { loginWithToken } = useAuth();

    const result = await YourApi.getTokenForUser()

    const tokenUser = new TokenUser(
      result.accessToken,
      result.expiresIn,
      result.refreshToken,
    );

    const loginResult = await loginWithToken(tokenUser);
        
    if (loginResult.isSuccess()) {
      // Continue
    } else {
      // Handle error
      showErrorSnackbar(loginResult.error?.title)
    }

Back to top

Retrieve user information

Once a user is authenticated, you can retrieve information about the anybill user using the anybill SDK.

ts
    const { getUser } = useAuth();
    const [user, setUser] = React.useState<UserInformationDto | null>(null);

    const userResult = await getUser();

    if (userResult.isSuccess()) {
      setUser(userResult.data);
    } else {
        // Handle error
      showErrorSnackbar(userResult.error?.title)
    }

Retrieve user QR code

The getUserQrCode() method returns a QrCodeData object containing the userId and the actions (string array) which can be encoded into a QR code, e.g. to identify the user at the point of sale.

ts
    const { getUserQrCode } = useAuth();

    const qrResult = await getUserQrCode();

    if (qrResult.isSuccess()) {
      const { userId, actions } = qrResult.data;
      // Encode userId and actions into a QR code
    } else {
      // Handle error
    }

Back to top

Logout and account deletion

Logging out an user deletes all of user's app data including cached receipts, authentication information and app settings (of the anybill sdk).

ts
    const { logout } = useAuth();

    const res = await logout();

    if (res.isSuccess()) {
      console.log("User logged out");
    }

Delete user

The deleteUser() method deletes the currently authenticated user account on the anybill system. After a successful deletion the locally stored tokens are cleared as well.

WARNING

Do not call this method if you are creating the users in your backend. In this case the user account has to be deleted via the Partner Platform API.

ts
    const { deleteUser } = useAuth();

    const res = await deleteUser();

    if (res.isSuccess()) {
      console.log("User deleted");
    } else {
      // Handle error
    }

Back to top

Receipts

Retrieving receipts

The anybill SDK offers two distinct approaches for fetching user receipts:

  1. Direct API Access: Use the const { getReceipts } = useReceipts(); method to directly access the API. This approach allows you to implement custom fetching and pagination logic based on your specific requirements.

  2. Optimized SDK Caching Process: Leverage the SDK's built-in caching and optimized pagination for efficient receipt retrieval by using the initiateReceiptQuery() and continueReceiptQuery() methods in combination with an exposed observable receipt list. This approach simplifies the retrieval process and reduces the need for manual pagination handling.

Detailed information about both approaches is provided below:

Direct API Access

The getReceipts(take?, orderBy?, orderDirection?, continuationToken?) method allows you to retrieve user receipts with pagination support. The result includes the receipts, the total count of available receipts, and a continuation token that can be used to fetch subsequent batches.

You can customize the request with the following parameters (all optional):

  • take: Specifies the number of receipts to fetch in each batch. The default and maximum value is 100.

  • orderBy: Specifies the field used for ordering the receipts. Currently, only Date is available, which is also the default.

  • orderDirection: Defines the sort direction for the receipts, either Ascending or Descending. Defaults to Descending.

  • continuationToken: A nullable token used for paginating through the query results. If null or omitted, a new query is initiated. To continue fetching from the previous result, use the continuationToken provided in the last response. When no further page exists, the SDK sets continuationToken of the returned ContinuationReceiptList to the literal string "finished". Check for this sentinel to stop paginating.

TIP

Important:

Due to database restrictions, you must specify the orderBy and orderDirection parameters for every page of the query.

Also, remember to reset the continuationToken if you modify any query parameters.

Example implementation:

tsx
  const { getReceipts } = useReceipts();

  const [receipts, setReceipts] = useState<ReceiptDto[]>([]);
  const [loading, setLoading] = useState(false);
  const [hasMore, setHasMore] = useState(true);
  const [continuationToken, setContinuationToken] = useState<string | null>(null);


  async function getInitialReceipts() {
    setLoading(true);
    setContinuationToken(null);

    const result: AnybillResult<ContinuationReceiptList> = await getReceipts(50, OrderBy.Date, OrderByDirection.Ascending);
    if (result.isSuccess()) {
      setReceipts(result.data.receipts);
      setContinuationToken(result.data.continuationToken ?? null);
      if (result.data.continuationToken === "finished") {
        setHasMore(false);
      }
    }
    setLoading(false);
  }


  useEffect(() => {
    getInitialReceipts();
  }, []);


  async function getMoreReceipts() {
    if (!hasMore || !continuationToken || continuationToken === "finished") return;

    setLoading(true);
    const result: AnybillResult<ContinuationReceiptList> = await getReceipts(
      20,
      OrderBy.Date,
      OrderByDirection.Ascending,
      continuationToken
    );
    if (result.isSuccess()) {
      setReceipts((prev) => [...prev, ...result.data.receipts]);
      setContinuationToken(result.data.continuationToken ?? null);
      if (result.data.continuationToken === "finished") {
        setHasMore(false);
      }
    }
    setLoading(false);
  }

Optimized SDK Caching Process

The anybill SDK offers an optimized receipt pagination process with automatic caching, providing efficient querying and display of receipts. This feature stores receipts in a local database, allowing for quicker access and better performance. Receipt actions such as deletion, edits, or marking receipts as favorites are automatically updated in the cached receipt list, making it easy to integrate receipt-related features without manual updates to the displayed list.

To enable this, the useReceipts() hook exposes a state object, observableReceipts, which represents a live, up-to-date view of the cached receipts. The initiateReceiptQuery and continueReceiptQuery methods allow you to refresh or extend the receipt list with new data as needed.

Similar to direct API access, you can customize the query with the following parameters:

  • take: Specifies the number of receipts to retrieve in each batch, with a maximum of 100 by default.
  • orderBy: Specifies the field for ordering receipts. Currently, only Date is supported.
  • orderDirection: Defines the sort direction for receipts, either Ascending or Descending.

TIP

Note on Pagination and Sorting

The continuation token and query management are handled internally by the SDK, so there is no need for manual handling to load additional pages.

If you wish to change the sorting or direction of the receipt list, use the initiateReceiptQuery() method again. This will reset the locally cached receipts and update the state object accordingly.

Fetch first page / Update receipt list

The initiateReceiptQuery method resets any existing query and caches the newly fetched receipts in the local database. We recommend calling this method in the following scenarios to ensure an up-to-date receipt list:

  • Initial Display of Receipt List
    When the receipt list is displayed for the first time in a session (e.g., when the user navigates to the receipt list view), this method should be called to display the latest receipt data. Note that this call is unnecessary after actions such as editing, deleting, or marking a receipt as favorite, as these are automatically handled within the SDK.

  • New Receipt Received
    If a new receipt is issued while the user is in the app, the list should be refreshed upon notification of the new receipt (e.g., triggered by a webhook event). This ensures the receipt list reflects the latest transactions.

  • Manual User Update
    For scenarios where a user manually refreshes the list, such as through a "pull-to-refresh" gesture or a refresh button, use this method to re-fetch the latest data for the first page.

  • Change in Sort Order
    When changing the sorting parameters of the receipt list (e.g., switching the sort order), call this method with the new parameters. This will reset the cache to reflect the updated sorting criteria.

WARNING

Cache Reset Consideration

As this method resets the cache and fills it with a new first page, any previously cached pages will be cleared and must be re-fetched. Avoid calling this method when navigating back to the receipt list from a single receipt view to prevent unnecessary reloading.

Fetch next page

To retrieve the next batch of receipts in the existing query, use continueReceiptQuery(). This method automatically applies the previously retrieved continuation token to fetch the subsequent set of receipts, seamlessly updating both the cached receipt list and the associated state object.

To easily combine the caching function with filtering and querying of the receipt we recommending displaying the receipts in one unified list and not implementing actual pages with this method. To automatically fetch new receipts when the user scrolls to the end of the list we can use the following implementation:

Example Implementation

tsx

    const { observableReceipts, initiateReceiptQuery, continueReceiptQuery } = useReceipts();
    const [refreshing, setRefreshing] = useState(false);
    const [loadingMore, setLoadingMore] = useState(false);
    const [error, setError] = useState<string | null>(null);

    // On component mount load initial receipts 
    useEffect(() => {
        loadInitialReceipts();
    }, []);

    const loadInitialReceipts = async () => {
        setError(null);
        const result = await initiateReceiptQuery();
        if (result.isFailure()) {
            setError(result.error?.message || 'Failed to load receipts');
        }
    };

    // Loading more receipts, when scrolled to the bottom.
    const onEndReached = async () => {
        if (loadingMore) return;
        setLoadingMore(true);
        const result = await continueReceiptQuery();
        if (result.isFailure()) {
            console.error('Failed to load more receipts:', result.error?.message);
        }
        setLoadingMore(false);
    };

    // Pull to refresh
    const onRefresh = async () => {
        setRefreshing(true);
        setError(null);
        const result = await initiateReceiptQuery();
        if (result.isFailure()) {
            setError(result.error?.message || 'Failed to refresh receipts');
        }
        setRefreshing(false);
    };


    return (
      <ThemedView style={styles.container}>
          <SafeAreaView style={styles.safeArea} edges={['top']}>
              <ThemedView style={styles.header}>
                  <ThemedText type="title" style={styles.headerTitle}>Receipts</ThemedText>
                  <ThemedText type="subtitle" style={styles.headerSubtitle}>
                      Your digital receipts
                  </ThemedText>
              </ThemedView>

              <FlatList
                  data={observableReceipts || []}
                  renderItem={renderReceiptItem}
                  keyExtractor={(item, index) => item.id || index.toString()}
                  refreshControl={
                      <RefreshControl
                          refreshing={refreshing}
                          onRefresh={onRefresh}
                          tintColor="#007AFF"
                          colors={['#007AFF']}
                      />
                  }
                  onEndReached={onEndReached}
                  onEndReachedThreshold={0.5}
                  ListFooterComponent={renderListFooter}
                  ListEmptyComponent={!refreshing ? renderEmptyState : null}
                  contentContainerStyle={[
                      styles.listContent,
                      (!observableReceipts || observableReceipts.length === 0) && styles.emptyListContent
                  ]}
                  showsVerticalScrollIndicator={false}
                  bounces={true}
              />
          </SafeAreaView>
      </ThemedView>
  );

Back to top

Retrieving a single receipt

The getReceipt(receiptId, useCache?) method retrieves a specific receipt by its ID. With useCache set to true (the default) the SDK first tries to resolve the receipt from the local cache and only calls the API if it is not cached yet. Pass false to always fetch the latest version from the API; the result is then written to the cache as well.

ts
    const { getReceipt } = useReceipts();

    async function loadReceipt(receiptId: string) {
      const response = await getReceipt(receiptId);
      if (response.isSuccess()) {
        const receipt: ReceiptDto = response.data;
        // Continue
      } else {
        // Handle error
      }
    }

Marking Receipts

The anybill SDK provides following methods to edit or mark receipts:

Mark a receipt as favourite

Allowing to mark a receipt as favourite toggling the receipt.misc.isFavourite flag.

ts
   const { toggleIsFavourite } = useReceipts();

    async function toggleFavourite(receiptId: string) {
      const response = await toggleIsFavourite(receiptId);
      if (response.isSuccess()) {
         // Continue
        } else {
          // Handle error
        }
    }

Set custom note for receipt

Using the updateReceiptNote() method, a custom note can be set for a receipt which can be retrieved in the Receipt.Misc.Note field. This field can later on be used for querying and filtering the receipt list.

ts

     const { updateReceiptNote } = useReceipts();

    async function updateNote(receiptId: string, note?: string) {
      const response = await updateReceiptNote(receiptId, note);
      if (response.isSuccess()) {
         // Continue
        } else {
          // Handle error
        }
    }

TIP

When implementing with the Optimized SDK Caching Process of the sdk, the receipt list does not have to be updated when editing the receipt. This is handled internally in the SDK.

Export as PDF

To ensure legally compliant receipts, the original receipt must be retrievable as a PDF file. The structured data provided by the anybill SDK does not represent the original receipt but serves to display relevant receipt information.

To access the original receipt, the anybill SDK offers the method getReceiptPdf(receiptId, isPrintedVersion?, includeReturnReceipts?), which generates the PDF on our system. The method is typed as Promise<AnybillResult<Uint8Array | string>>; on success the SDK currently always returns the binary PDF as a Uint8Array. Because of the union type you have to narrow response.data (e.g. response.data as Uint8Array or an instanceof Uint8Array check) before passing it on.

Parameters for Customization:

isPrintedVersion:
Generates a multi-page DIN A4 PDF version of the receipt, making it easier for end users to print a physical copy of the receipt, such as for return processes that require paper receipts.

includeReturnReceipts:
Includes all return receipts linked in Receipt.Misc.ReceiptReferences within the generated PDF. Note that this option can significantly increase the duration of the API call, as multiple PDFs must be generated and merged.

Example implementation using react-native-pdf

tsx
    import Pdf from 'react-native-pdf';

    const [pdfData, setPdfData] = useState<string | null>(null);
    const [showPdfModal, setShowPdfModal] = useState(false);
    const [pdfError, setPdfError] = useState<string | null>(null);


    const handleDisplayPDF = () => {
        if (!receiptId.trim()) {
            setResult('Please enter a receipt ID');
            return;
        }
        handleApiCall(async () => {
            const response = await getReceiptPdf(
                receiptId.trim(),
                false,
                false
            );
            if (response.isSuccess()) {
                setResult('PDF loaded for display');

                // Convert Uint8Array to base64 string for display
                const uint8Array = response.data as Uint8Array;
                const pdfBase64 = Buffer.from(uint8Array).toString('base64');
                setPdfData(pdfBase64);
                setPdfError(null);
                setShowPdfModal(true);
            } else {
                console.log("Error loading PDF: ", response.error);
                setPdfError('Failed to load PDF');
            }
            return response;
        });
    };

    return (
        <ScrollView>
            <View>
              ...
              <Modal
                  visible={showPdfModal}
                  animationType="slide"
                  presentationStyle="pageSheet"
                  onRequestClose={() => setShowPdfModal(false)}
              >
                  <View style={styles.modalContainer}>
                      <View style={styles.modalHeader}>
                          <Text style={styles.modalTitle}>Receipt PDF</Text>
                          <TouchableOpacity
                              style={styles.closeButton}
                              onPress={() => setShowPdfModal(false)}
                          >
                              <Text style={styles.closeButtonText}>✕</Text>
                          </TouchableOpacity>
                      </View>
                      {pdfData && !pdfError ? (
                          <Pdf
                              source={{ uri: `data:application/pdf;base64,${pdfData}` }}
                              style={styles.pdfViewer}
                              onError={(error) => {
                                  console.log('PDF Error:', error);
                                  setPdfError('Error displaying PDF');
                              }}
                          />
                      ) : pdfError ? (
                          <View style={styles.errorContainer}>
                              <Text style={styles.errorText}>{pdfError}</Text>
                              <TouchableOpacity
                                  style={styles.retryButton}
                                  onPress={() => {
                                      setPdfError(null);
                                      if (receiptId.trim()) {
                                          handleDisplayPDF();
                                      }
                                  }}
                              >
                                  <Text style={styles.retryButtonText}>Retry</Text>
                              </TouchableOpacity>
                          </View>
                      ) : (
                          <View style={styles.loadingContainer}>
                              <Text style={styles.loadingText}>Loading PDF...</Text>
                          </View>
                      )}
                  </View>
              </Modal>
          </View>
      </ScrollView>
      );

Back to top

Export receipts via email

The exportReceipts(receiptIds?) method creates a ZIP archive containing the receipts of the current user and sends an email with a download link for that archive to the user. If receiptIds is omitted (or an empty array is passed), all receipts of the user are exported; otherwise only the specified receipts are included.

ts
    const { exportReceipts } = useReceipts();

    async function exportAllReceipts() {
      const response = await exportReceipts();
      if (response.isSuccess()) {
        // Email has been sent
      } else {
        // Handle error
      }
    }

Back to top

Add Receipt

The addReceipt(receiptId, fetchNewReceipt?) method allows you to add a receipt to the currently authenticated user's account. On success the result contains the ID of the added receipt.

The second parameter fetchNewReceipt (default true) specifies whether the newly added receipt should be fetched from the API and added to the local cache and observableReceipts. Pass false if you do not use the Optimized SDK Caching Process or want to refresh the list yourself.

ts
  const { addReceipt } = useReceipts();

   async function addReceiptById(receiptId: string) {
        const response = await addReceipt(receiptId);

        if (response.isSuccess()) {
          // Continue
        } else {
          // Handle error
        }
        return response;
    };

Anybill Applink is our way to enable linking into mobile apps from our receipt website by directly opening the app or redirecting to the app stores while persisting data. If you are integrating our sdk and want to make use of this feature, please contact us beforehand.

To automatically add a receipt and open the app, that integrates the anybill sdk, you have to set up universal links/android variant.

iOS:

Enable the capability 'Associated Domains' and register the domain "applinks:applink.anybill.de". Additionally the bundle identifier of your application has to be in the apple-app-site-association file of our server hosting the app link web page (If that is not the case please contact us).

Android:

  1. Acquire Applink URL provided by anybill with your unique path pattern by contacting us beforehand:
xml

<intent-filter android:label="@string/app_name" android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />

    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />

    <!-- Add your unique path pattern provided by anybill -->
    <data android:scheme="https" android:host="applink.anybill.de"
        android:pathPattern="/${your_path_pattern}" />
</intent-filter>
  1. Generate Digital Asset Links file using the Android Link Assistant:

Android Link Assistant

  1. Provide the returned file to anybill:
json

[
  {
    "relation": [
      "delegate_permission/common.handle_all_urls"
    ],
    "target": {
      "namespace": "android_app",
      "package_name": "de.anybill.ui_react_native_integration_example",
      "sha256_cert_fingerprints": [
        "5D:78:62:8E:4B:6A:E8:33:BE:9A:94:0B:7D:24:30:4E:79:DF:D3:8B:E7:0C:42:8B:FD:72:3F:1D:36:BC:F6:C3"
      ]
    }
  }
]

⚠️ To enable anybill AppLink for your debug and release version, you'll have to generate multiple files and provide anybill both of them!

Next you have to get the link that opened your application. The anybill SDK provides a utility class, that helps with parsing and persisting the link.

ts

  // Initialize the SDK
  useEffect(() => {
    const sdk = new AnybillReactNSDK();
    sdk.init(ApiEnv.Test, '4d8b1eb1-a1c6-45da-a271-5ab1167d6018');
  }, []);

  // Register app link listener
  useEffect(() => {
    const handleAppLinkData = async (url?: string) => {
      if (url) {
        // Checks if the url is a valid anybill app link URL. This method returns the AnybillURLData and also caches it (accessible via `AnybillAppLink.getCachedAppLinkData()`).
        await AnybillAppLink.checkForAnybillAppLink(url);
      } else {
         // Checks if its the first app start and if so, validates if the clipboard contains an anybill app link, that was persisted over the app store/play store install. This method also returns and caches the AnybillURLData.
        await AnybillAppLink.checkForAnybillAppLinkData();
      }
    };
  // Register app link listener
  useEffect(() => {
    const handleAppLinkData = async (url?: string) => {
      
      let appLinkData: AnybillURLData | null = null;

      if (url) {
        // Checks if the url is a valid anybill app link URL. This method returns the AnybillURLData and also caches it (accessible via `AnybillAppLink.getCachedAppLinkData()`).
        appLinkData = await AnybillAppLink.checkForAnybillAppLink(url);
      }
      // React native might return an initialURL on first app start, so this makes sure the clipboard is checked anyways.
      if (url && !appLinkData) {
        // Checks if its the first app start and if so, validates if the clipboard contains an anybill app link, that was persisted over the app store/play store install. This method also returns and caches the AnybillURLData.
        await AnybillAppLink.checkForAnybillAppLinkData();
      }
    };


    (async () => {
      // Handler when the app is closed 
      const initial = await Linking.getInitialURL();
      await handleAppLinkData(initial || undefined);
    })();

    // Handler when the app is open or in background.
    const sub = Linking.addEventListener('url', async ({ url }) => {
      await handleAppLinkData(url);
    });
    return () => sub.remove();
  }, []);

If you are in the context of the AnybillProvider, you can add the receipt directly with the returned AnybillURLData. In the example above the listeners are outside of the AnybillProvider context. Therefore we check on mounting the root page, if there is cached app link information available.

ts

const checkCachedAppLinkData = async () => {
        const cachedData = await AnybillAppLink.getCachedAppLinkData();

        const receiptId = cachedData?.receiptId;

        if (receiptId) {
            const addReceiptResult = await addReceipt(receiptId);

            if (addReceiptResult.isFailure()) {
                // Handle error
            } else {
                // If the receipt is added, make sure to clear the cached information.
                await AnybillAppLink.clearCachedAppLinkData();
            }
        } else {
            console.log('No cached app link data');
        }
    };


    useEffect(() => {
        loadInitialReceipts();
        checkCachedAppLinkData();
    }, []);

TIP

By using the cached variant, you can add the receipt to the user account, when the loginWithToken call succeeds or if the user is already logged in.

Deleting receipts

The anybill SDK provides functionality to delete either a single receipt or a batch of receipts. When utilizing the optimized caching process, receipts are automatically removed from the local cache, ensuring the observable data is kept up-to-date without requiring manual intervention.

Methods for Deleting Receipts

deleteReceipt():
Deletes a single receipt from the user’s receipt list. This method can be used for operations where only one specific receipt needs to be removed.

deleteReceipts():
Deletes multiple receipts (up to 100) from the user’s receipt list in a single operation.

Example implementation

ts
   async function handleDeleteReceipts(receiptIds: string[]) {
        if (receiptIds.length === 0) return;

        const response = await deleteReceipts(receiptIds);
        if (response.isSuccess()) {
          // Continue
        } else {
          // Handle error
        }
        return response;
    };

Filtering receipt list

The anybill SDK delivers receipts as structured data, enabling you to filter by any receipt field seamlessly. This allows you to display all relevant results dynamically, without the need to explicitly initiate API calls with query parameters.

Common use cases include filtering the receipt list for favorites or searching for specific string values.

For an easy query of the receipt list the anybill SDK provides an function called searchReceipts(receipts: ReceiptDto[], search: string). The method filters store name, address, amount, note, and line and discount descriptions.

Example implementation of allowing to simultaneously query for a string value

tsx

    const { observableReceipts } = useReceipts();
    const [searchQuery, setSearchQuery] = useState('');

    const filteredReceipts = useMemo(() => {
        if (!observableReceipts) return [];
        return searchReceipts(observableReceipts, searchQuery);
    }, [observableReceipts, searchQuery]);

    return (
        <ThemedView style={styles.container}>
            <SafeAreaView style={styles.safeArea} edges={['top']}>
                <ThemedView style={styles.header}>
                    <ThemedText type="title" style={styles.headerTitle}>Receipts</ThemedText>
                    <ThemedText type="subtitle" style={styles.headerSubtitle}>
                        Your digital receipts
                    </ThemedText>

                    <TextInput
                        style={styles.searchInput}
                        placeholder="Search receipts..."
                        value={searchQuery}
                        onChangeText={setSearchQuery}
                        clearButtonMode="while-editing"
                        returnKeyType="search"
                    />
                </ThemedView>

                <FlatList
                    data={filteredReceipts}
                    renderItem={renderReceiptItem}
                    keyExtractor={(item, index) => item.id || index.toString()}
                    refreshControl={
                        <RefreshControl
                            refreshing={refreshing}
                            onRefresh={onRefresh}
                            tintColor="#007AFF"
                            colors={['#007AFF']}
                        />
                    }
                    onEndReached={onEndReached}
                    onEndReachedThreshold={0.5}
                    ListFooterComponent={renderListFooter}
                    ListEmptyComponent={!refreshing ? renderEmptyState : null}
                    contentContainerStyle={[
                        styles.listContent,
                        filteredReceipts.length === 0 && styles.emptyListContent
                    ]}
                    showsVerticalScrollIndicator={false}
                    bounces={true}
                />
            </SafeAreaView>
        </ThemedView>
    );

Back to top