From adca106bb9d9c4edb6f7302849cbdb9fb4075272 Mon Sep 17 00:00:00 2001 From: chetan Date: Mon, 12 Oct 2026 00:31:34 +0530 Subject: [PATCH 01/15] docs: track payments with trackEvent, remove capturePayment and removePayment The SDK guides, API reference, SDK-less guides, feature pages and testing guide now send payments through trackEvent / capture-event with amount, payment_id and currency. The event name becomes the payment type. removePayment and remove-captured-payment are no longer documented, and there is no replacement. The Appbrew page is unchanged because its tracker still calls capturePayment and removePayment. Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/event-capture.mdx | 58 +++- api-reference/revenue-tracking.mdx | 93 +++--- ecommerce-manager/google-commerce-manager.mdx | 36 ++- ecommerce-manager/meta-commerce-manager.mdx | 2 +- features/events-tab.mdx | 4 +- features/skadnetwork-integration.mdx | 2 +- features/web-to-app-journeys.mdx | 6 +- sdk-less/android-minimal.mdx | 11 +- sdk-less/android-quickstart.mdx | 76 ++--- sdk-less/api-reference.mdx | 67 ++-- sdk-less/ios-device-data.mdx | 2 +- sdk-less/ios-minimal.mdx | 13 +- sdk-less/ios-quickstart.mdx | 74 ++--- sdk/android.mdx | 135 +++----- sdk/capacitor.mdx | 94 +++--- sdk/cordova.mdx | 131 ++++---- sdk/expo.mdx | 4 +- sdk/flutter.mdx | 163 ++++------ sdk/ios.mdx | 149 ++++----- sdk/react-native.mdx | 143 ++++---- sdk/unity.mdx | 306 +++++++----------- testing/event-and-revenue-testing.mdx | 65 ++-- 22 files changed, 703 insertions(+), 931 deletions(-) diff --git a/api-reference/event-capture.mdx b/api-reference/event-capture.mdx index f0a416c3..c7f6b5ba 100644 --- a/api-reference/event-capture.mdx +++ b/api-reference/event-capture.mdx @@ -14,13 +14,13 @@ icon: "shuffle" See [Linkrunner Agent Skills](/ai-agent-skills). -This documentation is for tracking custom events from your backend only! For tracking events from your app please go through the [Flutter](https://docs.linkrunner.io/sdk/flutter/usage#tracking-custom-events) or [React Native](https://docs.linkrunner.io/sdk/react-native/usage#tracking-custom-events) documentation. +Use this API to send custom events and payments from your backend. To track them from your app, use `trackEvent` in your platform's SDK, for example [Android](/sdk/android), [iOS](/sdk/ios), [React Native](/sdk/react-native), or [Flutter](/sdk/flutter). - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Each request must identify the user with `user_id` or `install_instance_id`. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. For capturing revenue, it is recommended to use the [capture-payment](/api-reference/revenue-tracking) API instead of capture-event. + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Each request must identify the user with `user_id` or `install_instance_id`. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, send the [payment fields](#payment-fields) with the event. -Use [Test Custom Events and Payments](/testing/event-and-revenue-testing) to decide whether an action belongs in this API or the Revenue Tracking API. +Use [Test Custom Events and Payments](/testing/event-and-revenue-testing) to decide whether an action is a custom event or a payment. ## Base URL @@ -69,6 +69,36 @@ POST: /capture-event | install_instance_id | string | **Required** unless `user_id` is sent. Linkrunner's ID for the install | | event_id | string | **Optional**. Your own unique identifier for the event, useful for deduplication and correlating with your backend | | client_ip_address | string | **Optional**. Public IP of the user's device when the event happened. See [End-user IP address](#end-user-ip-address) | +| amount | number | **Optional**. Payment amount. Send with `payment_id` and `currency` to record a payment. See [Payment fields](#payment-fields) | +| payment_id | string | **Optional**. Your unique transaction ID. Send with `amount` and `currency` | +| currency | string | **Optional**. 3-letter ISO 4217 code of `amount`, for example `USD` or `INR`. Send with `amount` and `payment_id` | +| status | string | **Optional**. Payment status. Used only when the payment fields are sent. Defaults to `PAYMENT_COMPLETED` | + +### Payment fields + +Send `amount`, `payment_id`, and `currency` at the top level of the request, next to `event_name`, when the event is a payment. Linkrunner then stores the event as revenue, and `event_name` becomes the payment type you see in reports. Without these fields, the request is a custom event. + +```json +{ + "event_name": "purchase_completed", + "amount": 149.99, + "payment_id": "ORD-12345", + "currency": "USD", + "event_data": { + "product_id": "ABC123" + }, + "user_id": "user_12345" +} +``` + +- **Send all three or none.** If you send only one or two of `amount`, `payment_id`, and `currency`, the request fails with `400` and the message `amount, payment_id and currency must be sent together`. +- **`amount`** must be a number of zero or more. A numeric string such as `"149.99"` is also accepted. +- **`payment_id`** must be a non-empty string. Use a unique value for each transaction. Retrying a request with the same `payment_id`, `event_name`, and `status` does not create a second payment, and the API still returns `200`. +- **`currency`** must be a 3-letter ISO 4217 code. Lowercase is accepted and stored in uppercase. +- **`status`** accepts `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. If you leave it out, the payment is `PAYMENT_COMPLETED`. +- **`event_data`**, if sent, must be a JSON object. + +Only `PAYMENT_COMPLETED` payments are sent to ad networks as revenue. An `amount` inside `event_data` does not make the event a payment. ### End-user IP address @@ -79,8 +109,8 @@ These calls come from your server, so Linkrunner never sends your server's IP to ### Responses -1. **200** Event captured successfully -2. **400** Missing required parameters, or neither `user_id` nor `install_instance_id` was sent +1. **200** Event or payment captured successfully +2. **400** Missing required parameters, neither `user_id` nor `install_instance_id` was sent, or the payment fields are incomplete or invalid 3. **401** Invalid server key #### Sample Response @@ -114,7 +144,9 @@ Here are some common event names you might want to track: ## Revenue Sharing with Ad Networks -To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions: +For a payment, send the [payment fields](#payment-fields). Linkrunner records the revenue and shares completed payments with ad networks. + +For a custom event that is not a payment, you can still pass a value to ad networks like Google Ads and Meta by including an `amount` parameter as a number in `event_data`. This lets ad networks optimize campaigns on the value of the conversion, but Linkrunner does not record it as revenue: ```json { @@ -142,7 +174,7 @@ To enable revenue sharing with ad networks like Google Ads and Meta, include an ## Example -### Tracking a Purchase Event +### Tracking a Purchase ```javascript // Using fetch API @@ -154,11 +186,11 @@ fetch("https://api.linkrunner.io/api/v1/capture-event", { }, body: JSON.stringify({ event_name: "purchase_completed", + amount: 125.99, + payment_id: "ORD-12345", + currency: "USD", event_data: { - order_id: "ORD-12345", product_ids: ["P-001", "P-002"], - total_amount: 125.99, - currency: "USD", payment_method: "credit_card", }, user_id: "user_12345", @@ -178,8 +210,6 @@ The API will return appropriate HTTP status codes along with error messages when - **429 Too Many Requests**: You've exceeded the rate limit, please try again later - **500 Internal Server Error**: Contact support if this persists -For any help please reach out to [support@linkrunner.io](mailto:support@linkrunner.io) - ## Meta Ecommerce Events If you are tracking Ecommerce events (like `add_to_cart` or `view_content`) to sync with Meta Catalog Sales, **you must first map your custom event with the standard commerce event in the Linkrunner Dashboard** before sending the event. @@ -218,4 +248,8 @@ For comprehensive details on each field requirement, refer to our [Meta Commerce } ``` +For a purchase, send the same `event_data` together with the [payment fields](#payment-fields), and map the event name with **Purchase**. + To verify your events are being correctly received by Meta, please follow our [Testing Ecommerce Events](/ecommerce-manager/meta-commerce-manager#testing-ecommerce-events) guide. + +**Need help?** Contact [support@linkrunner.io](mailto:support@linkrunner.io) diff --git a/api-reference/revenue-tracking.mdx b/api-reference/revenue-tracking.mdx index 5bd47fd2..a86fe285 100644 --- a/api-reference/revenue-tracking.mdx +++ b/api-reference/revenue-tracking.mdx @@ -26,42 +26,41 @@ In the request header add the below attribute: linkrunner-key: YOUR-SERVER-KEY ``` -## Capture payment +## Capture a payment + +Send the payment to the [capture-event](/api-reference/event-capture) endpoint with `amount`, `payment_id`, and `currency` at the top level. `event_name` becomes the payment type you see in reports, so use a name that describes the payment, such as `purchase_completed` or `subscription_renewed`. ``` -POST: /capture-payment +POST: /capture-event ``` ### Request Body ```json { - "user_id": "666", - "payment_id": "ABC", // required, must be unique for each transaction - "amount": 25096, // Send amount in one currency only - "type": "FIRST_PAYMENT", // optional - // "type": "SECOND_PAYMENT", // optional - "status": "PAYMENT_COMPLETED" // optional + "event_name": "purchase_completed", + "amount": 250.96, + "payment_id": "ORD-12345", + "currency": "USD", + "user_id": "user_12345" } ``` -**NOTE:** If you accept payments in multiple currencies convert them to one currency before calling the API. - -**Deduplication:** We perform idempotent deduplication using a combination of `type` and `payment_id`. For each unique combination of payment type and payment ID, only one record will be created. If you send multiple requests with the same `type` and `payment_id` combination, only the first will be recorded and subsequent ones will be ignored. To ensure proper deduplication, always provide a unique `payment_id` for each transaction of a given type. - -### Payment types +| Parameter | Type | Description | +| ---------- | ------ | ----------- | +| event_name | string | **Required**. Name of the payment, stored as the payment type | +| amount | number | **Required** for a payment. Zero or more. A numeric string is also accepted | +| payment_id | string | **Required** for a payment. Your unique transaction ID | +| currency | string | **Required** for a payment. 3-letter ISO 4217 code, for example `USD` or `INR` | +| status | string | **Optional**. Defaults to `PAYMENT_COMPLETED` | +| user_id | string | **Required** unless `install_instance_id` is sent | +| install_instance_id | string | **Required** unless `user_id` is sent. Linkrunner's ID for the install | +| event_data | object | **Optional**. Additional data, such as ecommerce fields | +| client_ip_address | string | **Optional**. See [End-user IP address](#end-user-ip-address) | -- **FIRST_PAYMENT**: User's first payment -- **SECOND_PAYMENT**: User's second payment -- **WALLET_TOPUP**: Adding funds to a wallet -- **FUNDS_WITHDRAWAL**: Withdrawing funds -- **SUBSCRIPTION_CREATED**: Subscription started -- **SUBSCRIPTION_RENEWED**: Subscription renewed -- **ONE_TIME**: One-time payment -- **RECURRING**: Recurring payment -- **DEFAULT**: Generic/unspecified payment type +Send `amount`, `payment_id`, and `currency` together. If you send only one or two of them, the request fails with `400` and the message `amount, payment_id and currency must be sent together`. If you send none of them, the request is stored as a custom event, not a payment. An `amount` inside `event_data` does not make the event a payment. -If `type` is omitted, it defaults to **DEFAULT**. +**Deduplication:** Use a unique `payment_id` for each transaction. Retrying a request with the same `payment_id`, `event_name`, and `status` does not create a second payment, and the API still returns `200`. ### Payment status @@ -70,7 +69,7 @@ If `type` is omitted, it defaults to **DEFAULT**. - **PAYMENT_FAILED**: Payment attempt failed - **PAYMENT_CANCELLED**: Payment was cancelled -If `status` is omitted, it defaults to **PAYMENT_COMPLETED**. +If `status` is omitted, it defaults to **PAYMENT_COMPLETED**. Only `PAYMENT_COMPLETED` payments are sent to ad networks. ### End-user IP address @@ -81,37 +80,19 @@ These calls come from your server, so Linkrunner never sends your server's IP to ### Responses -1. **201** Payment captured -2. **401** Invalid server key - -## Remove captured payment - -``` -POST: /remove-payment -``` - -### Request Body - -```json -{ - "user_id": "666", - "payment_id": "ABC" -} -``` - -**NOTE:** `user_id` or `payment_id` is required in order to remove a payment entry. If you pass `user_id`, all the payments attributed to that user will be removed! - -### Responses - -1. **200** Payment entry/entries deleted -2. **400** No payment found with the given payment id or user id +1. **200** Payment captured. The response is the same as for a [custom event](/api-reference/event-capture#sample-response) +2. **400** Missing or invalid fields, or the payment fields were not sent together 3. **401** Invalid server key + + The older `POST /capture-payment` endpoint still works for existing integrations. New integrations should send payments through `/capture-event`. + + ## Meta Ecommerce Events If you are tracking `Purchase` events to sync with Meta Catalog Sales, **you must first map the purchase event with the standard commerce event in the Linkrunner Dashboard** before sending it. -_Note: Any Payment event you send should be mapped with **Purchase** for Commerce Event Manager. For example, map **FIRST_PAYMENT** with **Purchase** or **DEFAULT** with **Purchase**._ +_Note: Any payment you send should be mapped with **Purchase** for Commerce Event Manager. For example, map **purchase_completed** with **Purchase**._ While you can include any custom attributes in the `event_data` object, Meta requires specific fields for ecommerce events to correctly attribute catalog sales. Your `event_data` object should include the following fields: @@ -119,11 +100,11 @@ For comprehensive details on each field requirement, refer to our [Meta Commerce ```json { - "user_id": "666", - "payment_id": "ABC", - "amount": 25096, - "type": "DEFAULT", // Event is mapped with the Purchase in the Linkrunner Meta Integration Settings - "status": "PAYMENT_COMPLETED", + "event_name": "purchase_completed", + "user_id": "user_12345", + "payment_id": "ORD-12345", + "amount": 2000.0, + "currency": "INR", "event_data": { "content_ids": ["whshct4mwc"], "contents": [ @@ -142,6 +123,8 @@ For comprehensive details on each field requirement, refer to our [Meta Commerce } ``` -_Note: In the example above, the `type` field is omitted from the request body, which means it defaults to **DEFAULT**. Ensure this default payment type is properly mapped with **Purchase** in your Linkrunner dashboard._ +_Note: In the example above, map **purchase_completed** with **Purchase** in your Linkrunner dashboard before sending it._ To verify your events are being correctly received by Meta, please follow our [Testing Ecommerce Events](/ecommerce-manager/meta-commerce-manager#testing-ecommerce-events) guide. + +**Need help?** Contact [support@linkrunner.io](mailto:support@linkrunner.io) diff --git a/ecommerce-manager/google-commerce-manager.mdx b/ecommerce-manager/google-commerce-manager.mdx index 24dd7e69..e1123025 100644 --- a/ecommerce-manager/google-commerce-manager.mdx +++ b/ecommerce-manager/google-commerce-manager.mdx @@ -83,13 +83,13 @@ There are **3 essential events** to map: - Your view item event (e.g., `view_item`, `item_viewed`) → maps to **`view_item`** - Your add to cart event (e.g., `add_to_cart`) → maps to **`add_to_cart`** -- Your purchase event (e.g., `FIRST_PAYMENT`, `DEFAULT`) → maps to **`ecommerce_purchase`** +- Your purchase event (e.g., `purchase_completed`) → maps to **`ecommerce_purchase`** _In the Linkrunner Dashboard, go to your Google Ads integration settings and map each of your custom event names to the corresponding standard Google event._ ## Understanding `event_data` for Google -When sending events via the [Capture Event API](/api-reference/event-capture), [Capture Payment API](/api-reference/revenue-tracking), or the Linkrunner SDK (`trackEvent` and `capturePayment` methods), you need to include specific fields in `event_data` so Google can correctly attribute conversions and match products in your Merchant Center catalog. +When sending events via the [Capture Event API](/api-reference/event-capture) or the Linkrunner SDK `trackEvent` method, you need to include specific fields in `event_data` so Google can correctly attribute conversions and match products in your Merchant Center catalog. ### Required Fields @@ -169,36 +169,38 @@ await linkrunner.trackEvent( ### ecommerce_purchase -Fired when a user completes a purchase. +Fired when a user completes a purchase. Send it as a payment: pass `amount`, `paymentId`, and `currency` with the event so Linkrunner records the revenue. ```javascript SDK -await linkrunner.capturePayment({ - amount: 2298.00, - userId: "user_456", - paymentId: "order_98765", - type: "FIRST_PAYMENT", // Map this payment type to "ecommerce_purchase" in the Linkrunner Dashboard - status: "PAYMENT_COMPLETED", - eventData: { +await linkrunner.trackEvent( + "purchase_completed", // Map this event to "ecommerce_purchase" in the Linkrunner Dashboard + { content_ids: ["sku_blue_tshirt", "sku_red_shoes"], // Must match Merchant Center ID (offer_id) value: 2298, // Required currency: "INR", // Required num_items: 2, // Required order_id: "order_98765", // Required for purchase events }, -}); + undefined, // eventId + { + amount: 2298.0, + paymentId: "order_98765", + currency: "INR", + }, +); ``` ```json API -// POST /capture-payment +// POST /capture-event { "token": "your_project_token", + "event_name": "purchase_completed", "payment_id": "order_98765", "user_id": "user_456", "amount": 2298.00, - "type": "FIRST_PAYMENT", - "status": "PAYMENT_COMPLETED", + "currency": "INR", "install_instance_id": "abc-123", "event_data": { "content_ids": ["sku_blue_tshirt", "sku_red_shoes"], // Must match Merchant Center ID (offer_id) @@ -219,7 +221,7 @@ The `content_ids` you send must exactly match the **ID (`offer_id`)** of the pro -The `amount` field in `capturePayment` and the `value` field in `event_data` should be consistent. Both represent the total order value. Google reads `value` from `event_data` for conversion reporting. +The payment `amount` and the `value` field in `event_data` should be consistent. Both represent the total order value. Google reads `value` from `event_data` for conversion reporting. ## Verifying Events in Google Ads @@ -235,4 +237,6 @@ It can take up to **24 hours** for conversion data to appear after your first ev --- -> **Note:** The `event_data` fields above can be sent via the Linkrunner SDK (`trackEvent` and `capturePayment` methods) or directly via the APIs. For full request schemas, refer to the [Capture Event API](/api-reference/event-capture) and [Capture Payment API](/api-reference/revenue-tracking) documentation. +> **Note:** The `event_data` fields above can be sent via the Linkrunner SDK `trackEvent` method or directly via the API. For full request schemas, refer to the [Capture Event API](/api-reference/event-capture) and [Revenue Tracking API](/api-reference/revenue-tracking) documentation. + +**Need help?** Contact [support@linkrunner.io](mailto:support@linkrunner.io) diff --git a/ecommerce-manager/meta-commerce-manager.mdx b/ecommerce-manager/meta-commerce-manager.mdx index b82efd2e..60fd05c3 100644 --- a/ecommerce-manager/meta-commerce-manager.mdx +++ b/ecommerce-manager/meta-commerce-manager.mdx @@ -79,7 +79,7 @@ View the dataset ID and check the events list. There are **3 essential events** - `Purchase` (processed via the [Capture Payment API](/api-reference/revenue-tracking) or SDK) - `AddToCart` and `ViewContent` (processed via the [Event Capture API](/api-reference/event-capture) or SDK) -_Note: Any event you want to send for these actions should be mapped with their respective standard commerce manager events in the Linkrunner Dashboard. For example, map your custom add to cart event (e.g., **add_to_cart**) with **AddToCart**, your view event (e.g., **item_viewed** or **view_content**) with **ViewContent**, and your payment event (e.g., **FIRST_PAYMENT** or **DEFAULT**) with **Purchase**._ +_Note: Any event you want to send for these actions should be mapped with their respective standard commerce manager events in the Linkrunner Dashboard. For example, map your custom add to cart event (e.g., **add_to_cart**) with **AddToCart**, your view event (e.g., **item_viewed** or **view_content**) with **ViewContent**, and your payment event (the event name you send with the payment, e.g., **purchase**) with **Purchase**._ Make sure you have integrated the appropriate APIs in your application to send these events. diff --git a/features/events-tab.mdx b/features/events-tab.mdx index 39639ee8..640ca44b 100644 --- a/features/events-tab.mdx +++ b/features/events-tab.mdx @@ -152,8 +152,8 @@ If this sub-tab is empty, nothing has arrived yet. The empty state links to the empties every step below it. - The amount, currency, and payment ID come from the `capturePayment` call in your app. Confirm your app is - passing them. See [Revenue Tracking](/sdk/react-native#revenue-tracking). + The amount, currency, and payment ID come from the payment your app passes to `trackEvent`. Confirm your app + sends all three. See your platform's SDK guide, for example [React Native](/sdk/react-native). diff --git a/features/skadnetwork-integration.mdx b/features/skadnetwork-integration.mdx index c017206f..3146be67 100644 --- a/features/skadnetwork-integration.mdx +++ b/features/skadnetwork-integration.mdx @@ -43,7 +43,7 @@ Make sure your app is using at least these SDK versions before enabling SKAN. Li ### How the SDK updates conversion values -You don't need to call any SKAN APIs yourself. On first launch the SDK registers an initial conversion value (0/low), and every `trackEvent`, `capturePayment`, and `signup` call returns the updated conversion value from Linkrunner, which the SDK applies automatically. On iOS 17.4+ the SDK uses Apple's AdAttributionKit; on iOS 14.0–17.3 it falls back to SKAdNetwork. +You don't need to call any SKAN APIs yourself. On first launch the SDK registers an initial conversion value (0/low), and every `trackEvent` (including payments) and `signup` call returns the updated conversion value from Linkrunner, which the SDK applies automatically. On iOS 17.4+ the SDK uses Apple's AdAttributionKit; on iOS 14.0–17.3 it falls back to SKAdNetwork. ### Add Ad Network Identifiers diff --git a/features/web-to-app-journeys.mdx b/features/web-to-app-journeys.mdx index 98a7fc9a..725cb1e2 100644 --- a/features/web-to-app-journeys.mdx +++ b/features/web-to-app-journeys.mdx @@ -18,7 +18,7 @@ One thing does the bridging: a **Linkrunner link on your custom subdomain**, pla - **If the app is installed**, the link opens the app directly. Your app reports the open with [`handleDeeplink`](/sdk/react-native#handle-deeplink), which records a re-engagement against the campaign. - **If the app is not installed**, the link routes the user to the store. After they install and open the app, [`getAttributionData`](/sdk/react-native#getting-attribution-data) returns the attributed campaign and the deferred deep link so you can drop them into the right screen. -Everything after the open is the mobile SDK's job: `init` records the app open, [`signup`](/sdk/react-native#user-identification-required) registers the user, [`trackEvent`](/sdk/react-native#tracking-custom-events) captures actions, and [`capturePayment`](/sdk/react-native#revenue-tracking) captures revenue. +Everything after the open is the mobile SDK's job: `init` records the app open, [`signup`](/sdk/react-native#user-identification-required) registers the user, [`trackEvent`](/sdk/react-native#tracking-custom-events) captures actions, and [`trackEvent` with a payment](/sdk/react-native) captures revenue. For Meta campaigns, use the dedicated handoff instead of building the link yourself. Linkrunner auto-creates and attributes the campaign for you. See [Meta Web to App](/ad-networks/meta-web-to-app). @@ -43,7 +43,7 @@ These journeys open the app directly through the Linkrunner link. You track the | --- | --- | --- | --- | | **Website → App Open** | Link click + app re-engagement via `handleDeeplink` | Campaigns → **Clicks**; switch **Install Attribution** to **Retargeting** | Deep linking configured; `handleDeeplink` called on every launch; "Open App" button uses a Linkrunner link | | **Web content → specific screen** | Same, with a deep link **path** that routes in-app (e.g. `/product/123`) | Same as above; in-screen actions show in event columns | App routes match the link's paths; encode the path in the link | -| **Web CTA → in-app conversion** | Re-engagement + in-app [`trackEvent`](/sdk/react-native#tracking-custom-events) or [`capturePayment`](/sdk/react-native#revenue-tracking) | Custom **event columns**; **Revenue**, **ROAS**, **Paying Users** | Call `signup` first; instrument the conversion event or payment | +| **Web CTA → in-app conversion** | Re-engagement + in-app [`trackEvent`](/sdk/react-native#tracking-custom-events), with a payment for purchases | Custom **event columns**; **Revenue**, **ROAS**, **Paying Users** | Call `signup` first; instrument the conversion event or payment | | **CRM → app** (email, SMS, WhatsApp, push) | One tagged link per channel + downstream events | One **campaign per channel**: Clicks, Reengagements, and event columns | A Linkrunner link per channel; for push, carry the link in the notification payload | @@ -62,7 +62,7 @@ These journeys go through the store. Deferred deep linking carries the original | --- | --- | --- | --- | | **Website → Install** | Click → install, with the attributed campaign from `getAttributionData` | **Overview** stat cards (All / Attributed / Organic Installs); Campaigns → **Installs** | Linkrunner link as the store target; `init` + `getAttributionData` after first open | | **Website → Install → Signup** | Install + user registration | **Sign-ups** and **Conversion** (sign-ups / installs) columns; user list in the campaign drawer | Call [`signup`](/sdk/react-native#user-identification-required) with `user_data.id` | -| **Website → Install → Purchase** | Install + revenue | **Revenue**, **ROAS**, **Paying Users**, **Daywise Revenue** columns | [`capturePayment`](/sdk/react-native#revenue-tracking); convert to one currency; map the event for Meta/Google ROAS | +| **Website → Install → Purchase** | Install + revenue | **Revenue**, **ROAS**, **Paying Users**, **Daywise Revenue** columns | [`trackEvent` with a payment](/sdk/react-native) (amount, payment ID, currency); map the event for Meta/Google ROAS | | **Website → Install → Onboarding** | Install + activation event | Custom **event column**; [Events](https://dashboard.linkrunner.io/dashboard/events) | Define the activation event and fire it with [`trackEvent`](/sdk/react-native#tracking-custom-events) | | **Specific page → Install → Same context** | Deferred deep link carrying the page context through the install | Campaign **Installs**; the link resolves via `getAttributionData().deeplink` | Enable deferred deep linking on the campaign; pass the path into the link; parse `deeplink` in-app | | **Referral link → Install** | Install + referral conversion | Campaign per referral; event column | [Referral tracking](/features/referral-codes); add `?referrer=` to the link; handle it in-app | diff --git a/sdk-less/android-minimal.mdx b/sdk-less/android-minimal.mdx index eb4cb140..5d05c251 100644 --- a/sdk-less/android-minimal.mdx +++ b/sdk-less/android-minimal.mdx @@ -138,10 +138,11 @@ object LinkrunnerMinimal { base(context).put("event_name", name).apply { if (data != null) put("event_data", data) } ) - suspend fun capturePayment(context: Context, userId: String, amount: Double, paymentId: String) = + // A payment is a capture-event with amount, payment_id, and currency sent together + suspend fun trackPayment(context: Context, name: String, amount: Double, paymentId: String, currency: String) = post( - "/api/client/capture-payment", - base(context).put("user_id", userId).put("amount", amount).put("payment_id", paymentId).put("status", "PAYMENT_COMPLETED") + "/api/client/capture-event", + base(context).put("event_name", name).put("amount", amount).put("payment_id", paymentId).put("currency", currency) ) } ``` @@ -185,9 +186,9 @@ CoroutineScope(Dispatchers.IO).launch { LinkrunnerMinimal.trackEvent(context, "purchase_initiated", JSONObject().put("amount", 99.99)) } -// 5. Capture a payment +// 5. Track a payment CoroutineScope(Dispatchers.IO).launch { - LinkrunnerMinimal.capturePayment(context, userId = "user_123", amount = 99.99, paymentId = "payment_123") + LinkrunnerMinimal.trackPayment(context, "purchase_completed", amount = 99.99, paymentId = "payment_123", currency = "USD") } ``` diff --git a/sdk-less/android-quickstart.mdx b/sdk-less/android-quickstart.mdx index 8fdfe27b..46d35909 100644 --- a/sdk-less/android-quickstart.mdx +++ b/sdk-less/android-quickstart.mdx @@ -283,6 +283,14 @@ import java.io.IOException import java.util.UUID import java.util.concurrent.TimeUnit +/** Send with trackEvent to record the event as a payment. status defaults to PAYMENT_COMPLETED on the server. */ +data class LinkrunnerPayment( + val amount: Double, + val paymentId: String, + val currency: String, + val status: String? = null, +) + /** * Linkrunner REST client. Mirrors the SDK's API over plain HTTP. * Call configure() once, then use the suspend methods from a background coroutine. @@ -353,38 +361,20 @@ object LinkrunnerClient { suspend fun setUserData(userData: Map): JSONObject = post("/api/client/set-user-data", base().put("user_data", jsonOf(userData))) - suspend fun trackEvent(eventName: String, eventData: Map? = null): JSONObject { - val body = base().put("event_name", eventName) - if (eventData != null) body.put("event_data", jsonOf(eventData)) - return post("/api/client/capture-event", body) - } - - suspend fun capturePayment( - amount: Double, - paymentId: String, - userId: String? = null, - type: String = "DEFAULT", - status: String = "PAYMENT_COMPLETED", + suspend fun trackEvent( + eventName: String, eventData: Map? = null, + payment: LinkrunnerPayment? = null, ): JSONObject { - val body = base().apply { - put("amount", amount) - put("type", type) - put("status", status) - put("payment_id", paymentId) - if (userId != null) put("user_id", userId) - if (eventData != null) put("event_data", jsonOf(eventData)) - } - return post("/api/client/capture-payment", body) - } - - suspend fun removePayment(paymentId: String? = null, userId: String? = null): JSONObject { - require(paymentId != null || userId != null) { "Provide paymentId or userId" } - val body = base().apply { - if (paymentId != null) put("payment_id", paymentId) - if (userId != null) put("user_id", userId) + val body = base().put("event_name", eventName) + if (eventData != null) body.put("event_data", jsonOf(eventData)) + if (payment != null) { + body.put("amount", payment.amount) + body.put("payment_id", payment.paymentId) + body.put("currency", payment.currency) + if (payment.status != null) body.put("status", payment.status) } - return post("/api/client/remove-captured-payment", body) + return post("/api/client/capture-event", body) } suspend fun setPushToken(pushToken: String): JSONObject = @@ -495,25 +485,28 @@ CoroutineScope(Dispatchers.IO).launch { ## 7. Track events and payments +Use `trackEvent` for both. Pass a `LinkrunnerPayment` when the event is a payment, so Linkrunner records it as revenue. The event name becomes the payment type in reports. + ```kotlin // Custom event LinkrunnerClient.trackEvent( "purchase_initiated", - mapOf("product_id" to "12345", "amount" to 99.99) + mapOf("product_id" to "12345") ) // Payment -LinkrunnerClient.capturePayment( - amount = 99.99, - userId = "user_123", - paymentId = "payment_123", - type = "FIRST_PAYMENT" +LinkrunnerClient.trackEvent( + "purchase_completed", + mapOf("product_id" to "12345"), + payment = LinkrunnerPayment( + amount = 99.99, + paymentId = "payment_123", + currency = "USD" + ) ) ``` - - Add `amount` as a number in event or payment data to enable revenue sharing with Google and Meta. - +`currency` is a 3-letter ISO 4217 code. Only completed payments are sent to ad networks. See [capture-event](/sdk-less/api-reference#capture-event) for the field rules and `status` values. ## Other endpoints @@ -524,7 +517,6 @@ LinkrunnerClient.setUserData(mapOf("id" to "user_123", "phone" to "9876543210")) LinkrunnerClient.setPushToken(fcmToken) LinkrunnerClient.setIntegrationData(clevertapId = "YOUR_CLEVERTAP_ID") LinkrunnerClient.handleDeeplink("https://app.example.com/product/123") -LinkrunnerClient.removePayment(paymentId = "payment_123") ``` ## Retries and idempotency @@ -536,11 +528,11 @@ A retry can send the same request twice if the first attempt reached the server | Endpoint | Safe to retry? | Why | | --- | --- | --- | | `init` | Yes | The server deduplicates on `install_instance_id`. | -| `capture-payment` | Yes, with `payment_id` | The server deduplicates on `payment_id`. Always send one. | -| `capture-event` | Not deduplicated | A retried event can be counted twice. | +| `capture-event` with a payment | Yes | The server deduplicates on `payment_id` for the same event name and status. | +| `capture-event` without a payment | Not deduplicated | A retried event can be counted twice. | - `payment_id` is required for `capturePayment`, and a unique value ensures retries cannot double count revenue. Events have no server-side deduplication, so if exact event counts matter, disable retries for those calls. + A unique `paymentId` for each transaction ensures retries cannot double count revenue. Custom events have no server-side deduplication, so if exact event counts matter, disable retries for those calls. ## Troubleshooting diff --git a/sdk-less/api-reference.mdx b/sdk-less/api-reference.mdx index b6b5f4a7..9d705775 100644 --- a/sdk-less/api-reference.mdx +++ b/sdk-less/api-reference.mdx @@ -31,7 +31,7 @@ These fields appear on most requests. Send them on every call once your install - **Call from the device** so Linkrunner sees the device IP. - This reference uses Android examples. On iOS, send `"platform": "IOS"` and a different `device_data` shape (IDFA, IDFV, and `adservices_attribution_token` instead of GAID and the install referrer). The `init`, `trigger`, `capture-event`, and `capture-payment` responses may also include SKAdNetwork values (`fine_conversion_value`, `coarse_conversion_value`, `lock_postback`). See [iOS device data](/sdk-less/ios-device-data). + This reference uses Android examples. On iOS, send `"platform": "IOS"` and a different `device_data` shape (IDFA, IDFV, and `adservices_attribution_token` instead of GAID and the install referrer). The `init`, `trigger`, and `capture-event` responses may also include SKAdNetwork values (`fine_conversion_value`, `coarse_conversion_value`, `lock_postback`). See [iOS device data](/sdk-less/ios-device-data). --- @@ -175,64 +175,45 @@ Updates user details after signup, for example when the user adds a phone number POST /api/client/capture-event ``` -Tracks a custom event. Events are stored for all users, including organic ones. Identify the user with `user_id` or `install_instance_id`. +Tracks a custom event or a payment. Events are stored for all users, including organic ones. Identify the user with `user_id` or `install_instance_id`. | Field | Type | Description | | --- | --- | --- | | `token` | string | **Required**. | -| `event_name` | string | **Required**. | -| `event_data` | object | Optional. Custom attributes. Add `amount` as a number to share revenue with ad networks. | +| `event_name` | string | **Required**. For a payment, this is the payment type shown in reports. | +| `event_data` | object | Optional. Custom attributes. Must be a JSON object. On a custom event, add `amount` as a number to share a value with ad networks. | | `install_instance_id` | string | The persisted UUID. | | `user_id` | string | Optional. | | `platform` | string | `"ANDROID"`. | +| `amount` | number | Payment only. Zero or greater. | +| `payment_id` | string | Payment only. Your unique transaction ID. | +| `currency` | string | Payment only. 3-letter ISO 4217 code, for example `"USD"`. | +| `status` | string | Optional, payment only. Defaults to `PAYMENT_COMPLETED`. | -```json -{ "msg": "Event capture request received!", "status": 200, "data": { "message": "Event capture request received!" } } -``` - -## capture-payment +To record a payment, send `amount`, `payment_id`, and `currency` together at the top level. Sending only one or two of them returns `400` with `amount, payment_id and currency must be sent together`. Without them, the request is a custom event. An `amount` inside `event_data` does not make the request a payment. -``` -POST /api/client/capture-payment -``` - -Tracks revenue. Like events, the user must be attributed via `signup`. - -| Field | Type | Description | -| --- | --- | --- | -| `token` | string | **Required**. | -| `amount` | number | Payment amount. Must be zero or greater. | -| `user_id` | string | The paying user. | -| `payment_id` | string | **Required**. Used to deduplicate. Sending the same `payment_id` twice is ignored. | -| `type` | string | Optional. Defaults to `DEFAULT`. | -| `status` | string | Optional. Defaults to `PAYMENT_COMPLETED`. | -| `event_data` | object | Optional. Ecommerce attributes. | -| `install_instance_id` | string | The persisted UUID. | -| `platform` | string | `"ANDROID"`. | - -`type` values: `FIRST_PAYMENT`, `SECOND_PAYMENT`, `ONE_TIME`, `RECURRING`, `WALLET_TOPUP`, `FUNDS_WITHDRAWAL`, `SUBSCRIPTION_CREATED`, `SUBSCRIPTION_RENEWED`, `SUBSCRIPTION_CANCELLED`, `SUBSCRIPTION_REFUNDED`, `SUBSCRIPTION_BILLING_ISSUE`, `DEFAULT`. +Retrying with the same `payment_id`, `event_name`, and `status` does not create a second payment and still returns `200`. Only `PAYMENT_COMPLETED` payments are sent to ad networks. `status` values: `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, `PAYMENT_CANCELLED`. ```json -{ "msg": "Payment capture request received!", "status": 201, "data": { "message": "Payment capture request received!" } } +{ + "token": "YOUR_PROJECT_TOKEN", + "platform": "ANDROID", + "install_instance_id": "f1e2...c9", + "user_id": "user_123", + "event_name": "purchase_completed", + "amount": 99.99, + "payment_id": "payment_123", + "currency": "USD" +} ``` -## remove-captured-payment +The response is the same for a custom event and a payment: +```json +{ "msg": "Event capture request received!", "status": 200, "data": { "message": "Event capture request received!" } } ``` -POST /api/client/remove-captured-payment -``` - -Removes a captured payment, for a refund. Provide `payment_id` or `user_id`. - -| Field | Type | Description | -| --- | --- | --- | -| `token` | string | **Required**. | -| `payment_id` | string | The payment to remove. | -| `user_id` | string | Remove payments for this user. | -| `install_instance_id` | string | The persisted UUID. | -| `platform` | string | `"ANDROID"`. | ## update-push-token @@ -289,7 +270,7 @@ Reports a deeplink open for remarketing and reattribution. Call when your app op | Code | Meaning | | --- | --- | -| `400` | Missing required field or invalid token. | +| `400` | Missing required field, invalid token, or incomplete payment fields. | | `401` | Invalid token (on `attribution-data`). | | `429` | Rate limit exceeded (25 req/s per token). | | `500` | Server error. Retry with backoff. | diff --git a/sdk-less/ios-device-data.mdx b/sdk-less/ios-device-data.mdx index 13092a89..26be7b8f 100644 --- a/sdk-less/ios-device-data.mdx +++ b/sdk-less/ios-device-data.mdx @@ -87,7 +87,7 @@ let idfv = await UIDevice.current.identifierForVendor?.uuidString ## SKAdNetwork -iOS measures ad-driven installs through SKAdNetwork. After you call `init`, `signup`, `capture-event`, or `capture-payment`, the response can include a conversion value: +iOS measures ad-driven installs through SKAdNetwork. After you call `init`, `signup`, or `capture-event`, the response can include a conversion value: ```json { diff --git a/sdk-less/ios-minimal.mdx b/sdk-less/ios-minimal.mdx index 8e500f6b..480c3648 100644 --- a/sdk-less/ios-minimal.mdx +++ b/sdk-less/ios-minimal.mdx @@ -98,14 +98,15 @@ enum LinkrunnerMinimal { return try await post("/api/client/capture-event", body) } + // A payment is a capture-event with amount, payment_id, and currency sent together @discardableResult - static func capturePayment(userId: String, amount: Double, paymentId: String) async throws -> [String: Any] { + static func trackPayment(_ name: String, amount: Double, paymentId: String, currency: String) async throws -> [String: Any] { var body = base() - body["user_id"] = userId + body["event_name"] = name body["amount"] = amount body["payment_id"] = paymentId - body["status"] = "PAYMENT_COMPLETED" - return try await post("/api/client/capture-payment", body) + body["currency"] = currency + return try await post("/api/client/capture-event", body) } } ``` @@ -151,8 +152,8 @@ Task { try? await LinkrunnerMinimal.signup(userId: "user_123") } // 4. Track an event Task { try? await LinkrunnerMinimal.trackEvent("purchase_initiated", data: ["amount": 99.99]) } -// 5. Capture a payment -Task { try? await LinkrunnerMinimal.capturePayment(userId: "user_123", amount: 99.99, paymentId: "payment_123") } +// 5. Track a payment +Task { try? await LinkrunnerMinimal.trackPayment("purchase_completed", amount: 99.99, paymentId: "payment_123", currency: "USD") } ``` diff --git a/sdk-less/ios-quickstart.mdx b/sdk-less/ios-quickstart.mdx index 81feebe4..f7f3a362 100644 --- a/sdk-less/ios-quickstart.mdx +++ b/sdk-less/ios-quickstart.mdx @@ -109,11 +109,25 @@ final class LinkrunnerClient { return try await post("/api/client/set-user-data", body) } + /// Pass a payment to record the event as revenue. A nil status defaults to PAYMENT_COMPLETED on the server. + struct Payment { + let amount: Double + let paymentId: String + let currency: String + var status: String? = nil + } + @discardableResult - func trackEvent(_ name: String, data eventData: [String: Any]? = nil) async throws -> [String: Any] { + func trackEvent(_ name: String, data eventData: [String: Any]? = nil, payment: Payment? = nil) async throws -> [String: Any] { var body = base() body["event_name"] = name if let eventData { body["event_data"] = eventData } + if let payment { + body["amount"] = payment.amount + body["payment_id"] = payment.paymentId + body["currency"] = payment.currency + if let status = payment.status { body["status"] = status } + } body["device_data"] = await deviceData() body["time_since_app_install"] = timeSinceInstall() let res = try await post("/api/client/capture-event", body) @@ -121,38 +135,6 @@ final class LinkrunnerClient { return res } - @discardableResult - func capturePayment( - amount: Double, - userId: String, - paymentId: String, - type: String = "DEFAULT", - status: String = "PAYMENT_COMPLETED", - eventData: [String: Any]? = nil - ) async throws -> [String: Any] { - var body = base() - body["amount"] = amount - body["user_id"] = userId - body["type"] = type - body["status"] = status - body["payment_id"] = paymentId - if let eventData { body["event_data"] = eventData } - body["time_since_app_install"] = timeSinceInstall() - body["data"] = ["device_data": await deviceData()] - let res = try await post("/api/client/capture-payment", body) - applySKAN(res) - return res - } - - @discardableResult - func removePayment(userId: String, paymentId: String? = nil) async throws -> [String: Any] { - var body = base() - body["user_id"] = userId - if let paymentId { body["payment_id"] = paymentId } - body["data"] = ["device_data": await deviceData()] - return try await post("/api/client/remove-captured-payment", body) - } - @discardableResult func setPushToken(_ pushToken: String) async throws -> [String: Any] { var body = base() @@ -345,29 +327,28 @@ Task { ## 5. Track events and payments +Use `trackEvent` for both. Pass a `Payment` when the event is a payment, so Linkrunner records it as revenue. The event name becomes the payment type in reports. + ```swift // Custom event Task { try? await LinkrunnerClient.shared.trackEvent( "purchase_initiated", - data: ["product_id": "12345", "amount": 99.99] + data: ["product_id": "12345"] ) } // Payment Task { - try? await LinkrunnerClient.shared.capturePayment( - amount: 99.99, - userId: "user_123", - paymentId: "payment_123", - type: "FIRST_PAYMENT" + try? await LinkrunnerClient.shared.trackEvent( + "purchase_completed", + data: ["product_id": "12345"], + payment: .init(amount: 99.99, paymentId: "payment_123", currency: "USD") ) } ``` - - Add `amount` as a number in event or payment data to enable revenue sharing with Google and Meta. - +`currency` is a 3-letter ISO 4217 code. Only completed payments are sent to ad networks. See [capture-event](/sdk-less/api-reference#capture-event) for the field rules and `status` values. ## Other endpoints @@ -376,12 +357,11 @@ try? await LinkrunnerClient.shared.setUserData(["id": "user_123", "phone": "9876 try? await LinkrunnerClient.shared.setPushToken(apnsOrFcmToken) try? await LinkrunnerClient.shared.setIntegrationData(clevertapId: "YOUR_CLEVERTAP_ID") try? await LinkrunnerClient.shared.handleDeeplink("https://app.example.com/product/123") -try? await LinkrunnerClient.shared.removePayment(userId: "user_123", paymentId: "payment_123") ``` ## SKAdNetwork -`init`, `signup`, `capture-event`, and `capture-payment` responses can include a conversion value (`fine_conversion_value`, `coarse_conversion_value`, `lock_postback`). The client applies it automatically through `applySKAN`, which calls `SKAdNetwork.updatePostbackConversionValue`. You do not need to manage conversion values yourself. See [Collecting device data](/sdk-less/ios-device-data#skadnetwork) for how this fits with Apple's attribution. +`init`, `signup`, and `capture-event` responses can include a conversion value (`fine_conversion_value`, `coarse_conversion_value`, `lock_postback`). The client applies it automatically through `applySKAN`, which calls `SKAdNetwork.updatePostbackConversionValue`. You do not need to manage conversion values yourself. See [Collecting device data](/sdk-less/ios-device-data#skadnetwork) for how this fits with Apple's attribution. ## Retries and idempotency @@ -392,11 +372,11 @@ A retry can send the same request twice if the first attempt reached the server | Endpoint | Safe to retry? | Why | | --- | --- | --- | | `init` | Yes | The server deduplicates on `install_instance_id`. | -| `capture-payment` | Yes, with `payment_id` | The server deduplicates on `payment_id`. Always send one. | -| `capture-event` | Not deduplicated | A retried event can be counted twice. | +| `capture-event` with a payment | Yes | The server deduplicates on `payment_id` for the same event name and status. | +| `capture-event` without a payment | Not deduplicated | A retried event can be counted twice. | - `paymentId` is required for `capturePayment`, and a unique value ensures retries cannot double count revenue. Events have no server-side deduplication, so if exact event counts matter, disable retries for those calls. + A unique `paymentId` for each transaction ensures retries cannot double count revenue. Custom events have no server-side deduplication, so if exact event counts matter, disable retries for those calls. ## Troubleshooting diff --git a/sdk/android.mdx b/sdk/android.mdx index 472c70cb..bf93fc35 100644 --- a/sdk/android.mdx +++ b/sdk/android.mdx @@ -28,7 +28,7 @@ Add the Linkrunner SDK to your app's `build.gradle` file: ```gradle dependencies { - implementation 'io.linkrunner:android-sdk:4.2.0' + implementation 'io.linkrunner:android-sdk:5.0.0' } ``` @@ -461,68 +461,58 @@ private fun setIntegrationData() { ## Revenue Tracking - Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. + Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) or [`.setUserData`](#setting-user-data) first so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. -### Capturing Payments +### Tracking Payments -Track payment information with the following details: +To record revenue, pass an `EventPayment` to `trackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires Android SDK 5.0.0 or later. ```kotlin -private fun capturePayment() { +import io.linkrunner.sdk.LinkRunner +import io.linkrunner.sdk.models.EventPayment +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +private fun trackPurchase() { CoroutineScope(Dispatchers.IO).launch { try { - val paymentData = CapturePaymentRequest( - paymentId = "payment_123", // Required: Unique payment identifier - userId = "user123", // Required: User identifier - amount = 99.99, // Required: Payment amount - type = PaymentType.FIRST_PAYMENT, // Optional: Defaults to DEFAULT - // type = PaymentType.SECOND_PAYMENT, // Optional: Defaults to DEFAULT - status = PaymentStatus.PAYMENT_COMPLETED, // Optional: Defaults to PAYMENT_COMPLETED - eventData = mapOf( // Optional: Ecommerce/custom event data - "content_ids" to listOf("product_123"), - "content_type" to "product", - "currency" to "USD", - "value" to 99.99, - "num_items" to 1, - "order_id" to "order_12345", - "contents" to listOf( - mapOf( - "id" to "product_123", - "quantity" to 1, - "item_price" to 99.99 - ) - ) - ) + val payment = EventPayment( + amount = 99.99, + paymentId = "payment_123", + currency = "USD" ) - val result = LinkRunner.getInstance().capturePayment(paymentData) + val result = LinkRunner.getInstance().trackEvent( + eventName = "purchase", + payment = payment + ) result.onSuccess { - println("Payment captured successfully") + println("Payment event queued") }.onFailure { error -> - println("Error capturing payment: ${error.message}") + println("Error tracking payment: ${error.message}") } - } catch (e: Exception) { - println("Exception capturing payment: ${e.message}") + } catch (e: IllegalArgumentException) { + println("Invalid payment: ${e.message}") } } } ``` -#### Available Payment Types +You can pass `eventData` and `eventId` in the same call, as with any other [custom event](#tracking-custom-events). + +#### `EventPayment` Parameters + +| Parameter | Type | Required | Description | +| ----------- | --------------- | -------- | ----------- | +| `amount` | `Double` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `paymentId` | `String` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | Payment status. Defaults to `PAYMENT_COMPLETED` when you leave it out. | -| Type | Description | -| ---------------------- | ------------------------ | -| `FIRST_PAYMENT` | User's first payment | -| `SECOND_PAYMENT` | User's second payment | -| `WALLET_TOPUP` | Adding funds to wallet | -| `FUNDS_WITHDRAWAL` | Withdrawing funds | -| `SUBSCRIPTION_CREATED` | New subscription created | -| `SUBSCRIPTION_RENEWED` | Subscription renewal | -| `ONE_TIME` | One-time payment | -| `RECURRING` | Recurring payment | -| `DEFAULT` | Default payment type | +The `EventPayment` constructor throws `IllegalArgumentException` when a value breaks these rules, and nothing is sent. #### Available Payment Statuses @@ -533,33 +523,11 @@ private fun capturePayment() { | `PAYMENT_FAILED` | Payment failed | | `PAYMENT_CANCELLED` | Payment was cancelled | -### Removing Payments +To set a status, import `io.linkrunner.sdk.models.PaymentStatus` and pass it, for example `status = PaymentStatus.PAYMENT_INITIATED`. Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. -To remove or refund a payment: - -```kotlin -private fun removePayment() { - CoroutineScope(Dispatchers.IO).launch { - try { - // Either paymentId or userId must be provided - val removeRequest = RemovePaymentRequest( - paymentId = "payment_123", // Optional: Payment ID to remove - userId = "user123" // Optional: User ID to remove payments for - ) - - val result = LinkRunner.getInstance().removePayment(removeRequest) - - result.onSuccess { - println("Payment removed successfully") - }.onFailure { error -> - println("Error removing payment: ${error.message}") - } - } catch (e: Exception) { - println("Exception removing payment: ${e.message}") - } - } -} -``` + + Apps on Android SDK 4.x keep using `capturePayment` and continue to work unchanged. To move to 5.0.0, see the migration steps in the [Android SDK changelog](https://github.com/linkrunner-labs/linkrunner-android/blob/main/CHANGELOG.md). + ## Tracking Custom Events @@ -568,7 +536,7 @@ private fun removePayment() { - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. For capturing revenue, it is recommended to use the [`.capturePayment`](#revenue-tracking) method instead of `.trackEvent`. + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, pass a payment to `.trackEvent`. See [Revenue Tracking](#revenue-tracking). Track custom events in your app: @@ -705,18 +673,14 @@ private fun trackViewContent() { ### Payment / Purchase Example -Use the `capturePayment` method to send a `Purchase` event containing the ecommerce payload: +Use `trackEvent` with an `EventPayment` to send a `Purchase` event containing the ecommerce payload: ```kotlin -private fun capturePurchase() { +private fun trackPurchase() { CoroutineScope(Dispatchers.IO).launch { try { - val paymentData = CapturePaymentRequest( - paymentId = "payment_456", - userId = "user123", - amount = 49.99, - type = PaymentType.FIRST_PAYMENT, // Map this payment type to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard. See: /ecommerce-manager/meta-commerce-manager, /ecommerce-manager/google-commerce-manager - status = PaymentStatus.PAYMENT_COMPLETED, + LinkRunner.getInstance().trackEvent( + eventName = "purchase", // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard. See: /ecommerce-manager/meta-commerce-manager, /ecommerce-manager/google-commerce-manager eventData = mapOf( "content_ids" to listOf("product_123"), "contents" to listOf( @@ -731,11 +695,15 @@ private fun capturePurchase() { "value" to 49.99, "num_items" to 1, "order_id" to "order_abc123" // Required for Purchase events + ), + payment = EventPayment( + amount = 49.99, + paymentId = "payment_456", + currency = "USD" ) ) - LinkRunner.getInstance().capturePayment(paymentData) - } catch (e: Exception) { - println("Exception capturing purchase: ${e.message}") + } catch (e: IllegalArgumentException) { + println("Invalid payment: ${e.message}") } } } @@ -983,8 +951,7 @@ Connect Firebase Cloud Messaging (FCM) with Linkrunner | [`LinkRunner.getInstance().setAdditionalData`](/sdk/android#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`LinkRunner.getInstance().setUserData`](/sdk/android#setting-user-data) | Optional | Authentication or profile update logic | After `signup`, when additional user details become available | | [`LinkRunner.getInstance().trackEvent`](/sdk/android#tracking-custom-events) | Optional | Throughout app | When specific user actions occur, including [ecommerce events](/sdk/android#ecommerce-events) | -| [`LinkRunner.getInstance().capturePayment`](/sdk/android#capturing-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/android#ecommerce-events) | -| [`LinkRunner.getInstance().removePayment`](/sdk/android#removing-payments) | Optional | Refund flow | When payment needs to be removed | +| [`LinkRunner.getInstance().trackEvent` with `payment`](/sdk/android#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/android#ecommerce-events) | | [`LinkRunner.getInstance().setPushToken`](/sdk/android#uninstall-tracking) | Optional | Push notification setup | When FCM token is available | | [`LinkRunner.getInstance().setConsent`](/sdk/android#google-ads-consent) | Optional | App initialization or consent flow | Before `init`, and again when consent changes | | [`LinkRunner.getInstance().enablePIIHashing`](/sdk/android#enhanced-privacy-controls) | Optional | Privacy settings | When you want to enable PII hashing | diff --git a/sdk/capacitor.mdx b/sdk/capacitor.mdx index bad92a53..bb55d6cc 100644 --- a/sdk/capacitor.mdx +++ b/sdk/capacitor.mdx @@ -349,69 +349,61 @@ This method allows you to connect user identities across different analytics and ## Revenue Tracking - Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. + Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) or [`.setUserData`](#setting-user-data) first so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. -### Capture Payment +### Tracking Payments -Use this method to capture payment information: +To record revenue, pass a `payment` as the fourth argument to `trackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `capacitor-linkrunner` 3.0.0 or later. ```typescript import linkrunner from "capacitor-linkrunner"; -const capturePayment = async () => { - await linkrunner.capturePayment({ - amount: 100, // Payment amount - userId: "user123", // User identifier - paymentId: "payment456", // Required: Unique payment identifier - type: "FIRST_PAYMENT", // Optional: Payment type - // type: "SECOND_PAYMENT", // Optional: Payment type - status: "PAYMENT_COMPLETED", // Optional: Payment status - }); +const trackPurchase = async () => { + try { + await linkrunner.trackEvent( + "purchase", // Event name, used as the payment type + { plan: "pro" }, // Optional: Event data + undefined, // Optional: Event ID + { + amount: 499, + paymentId: "order_123", + currency: "INR", + } + ); + } catch (error) { + console.error("Error tracking payment:", error); + } }; ``` -#### Parameters for `linkrunner.capturePayment` - -- `amount`: number (required) - The payment amount -- `userId`: string (required) - Identifier for the user making the payment -- `paymentId`: string (required) - Unique identifier for the payment, used to deduplicate transactions -- `type`: string (optional) - Type of payment. Available options: - - `FIRST_PAYMENT` - First payment made by the user - - `WALLET_TOPUP` - Adding funds to a wallet - - `FUNDS_WITHDRAWAL` - Withdrawing funds - - `SUBSCRIPTION_CREATED` - New subscription created - - `SUBSCRIPTION_RENEWED` - Subscription renewal - - `ONE_TIME` - One-time payment - - `RECURRING` - Recurring payment - - `DEFAULT` - Default type (used if not specified) -- `status`: string (optional) - Status of the payment. Available options: - - `PAYMENT_INITIATED` - Payment has been initiated - - `PAYMENT_COMPLETED` - Payment completed successfully (default if not specified) - - `PAYMENT_FAILED` - Payment attempt failed - - `PAYMENT_CANCELLED` - Payment was cancelled - -### Removing Payments - -Remove payment records (for refunds or cancellations): +You can pass `eventData` and `eventId` in the same call, as with any other [custom event](#tracking-custom-events). -```typescript -import linkrunner from "capacitor-linkrunner"; +#### `payment` Parameters -const removePayment = async () => { - await linkrunner.removePayment({ - userId: "user123", // User identifier - paymentId: "payment456", // Optional: Unique payment identifier - }); -}; -``` +| Parameter | Type | Required | Description | +| ----------- | -------- | -------- | ----------- | +| `amount` | `number` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `string` | No | Payment status. Defaults to `PAYMENT_COMPLETED` when you leave it out. | + +The promise returned by `trackEvent` rejects when a value breaks these rules, and nothing is sent. -#### Parameters for `linkrunner.removePayment` +#### Available Payment Statuses -- `userId`: string (optional) - Identifier for the user whose payment is being removed. If only `userId` is provided, all payments for that user will be removed. -- `paymentId`: string (optional) - Unique identifier for the payment to be removed +| Status | Description | +| ------------------- | ------------------------------ | +| `PAYMENT_INITIATED` | Payment process started | +| `PAYMENT_COMPLETED` | Payment successfully completed | +| `PAYMENT_FAILED` | Payment failed | +| `PAYMENT_CANCELLED` | Payment was cancelled | -Note: At least one of `paymentId` or `userId` must be provided when calling `removePayment`. +Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. In TypeScript, you can import the `EventPayment` and `PaymentStatus` types from `capacitor-linkrunner`. + + + Apps on `capacitor-linkrunner` 2.x keep using `capturePayment` and continue to work unchanged. To move to 3.0.0, see the migration steps in the [capacitor-linkrunner changelog](https://github.com/linkrunner-labs/capacitor-linkrunner/blob/main/CHANGELOG.md). + ## Tracking Custom Events @@ -420,7 +412,7 @@ Note: At least one of `paymentId` or `userId` must be provided when calling `rem - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. For capturing revenue, it is recommended to use the [`.capturePayment`](#revenue-tracking) method instead of `.trackEvent`. + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, pass a [`payment`](#tracking-payments) to `.trackEvent`. Track custom events in your app: @@ -442,6 +434,7 @@ const trackEvent = async () => { - `eventName`: string (required) - Name of the event to track - `eventData`: object (optional) - Key-value pairs for additional event data, including Meta ecommerce properties - `eventId`: string | number (optional) - Your own unique identifier for the event, useful for deduplication and correlating with your backend +- `payment`: object (optional). Records revenue with the event. See [Tracking Payments](#tracking-payments). ### Revenue Sharing with Ad Networks @@ -644,8 +637,7 @@ Connect APNs with Linkrunner | [`linkrunner.setAdditionalData`](/sdk/capacitor#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`linkrunner.setUserData`](/sdk/capacitor#setting-user-data) | Optional | Profile update flow after `signup` | When additional user details become available | | [`linkrunner.trackEvent`](/sdk/capacitor#tracking-custom-events) | Optional | Throughout app | When specific user actions occur | -| [`linkrunner.capturePayment`](/sdk/capacitor#capture-payment) | Optional | Payment processing | When user makes a payment | -| [`linkrunner.removePayment`](/sdk/capacitor#removing-payments) | Optional | Refund flow | When payment needs to be removed | +| [`linkrunner.trackEvent` with `payment`](/sdk/capacitor#tracking-payments) | Optional | Payment processing | When user makes a payment | | [`linkrunner.setPushToken`](/sdk/capacitor#uninstall-tracking) | Optional | Push notification setup | When push token is available | | [`linkrunner.enablePIIHashing`](/sdk/capacitor#enhanced-privacy-controls) | Optional | Privacy configuration | When enabling PII hashing | diff --git a/sdk/cordova.mdx b/sdk/cordova.mdx index 10637cab..37d564c4 100644 --- a/sdk/cordova.mdx +++ b/sdk/cordova.mdx @@ -345,81 +345,57 @@ This method allows you to connect user identities across different analytics and ## Revenue Tracking - Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. + Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) or [`.setUserData`](#setting-user-data) first so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. -### Capture Payment +### Tracking Payments -Use this method to capture payment information: +To record revenue, pass a `payment` as the fourth argument to `trackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `cordova-linkrunner` 2.0.0 or later. ```javascript -linkrunner.capturePayment({ - amount: 100, // Payment amount - userId: "user123", // User identifier - paymentId: "payment456", // Required: Unique payment identifier - type: "FIRST_PAYMENT", // Optional: Payment type - status: "PAYMENT_COMPLETED", // Optional: Payment status - eventData: { // Optional: Ecommerce/custom event data - content_ids: ["product_123"], - content_type: "product", - currency: "USD", - value: 99.99, - num_items: 1, - order_id: "order_12345", - contents: [ - { - id: "product_123", - quantity: 1, - item_price: 99.99 - } - ] +linkrunner.trackEvent( + "purchase", // Event name, used as the payment type + { plan: "pro" }, // Optional: Event data + null, // Optional: Event ID + { + amount: 499, + paymentId: "order_123", + currency: "INR" } -}).then(function () { - console.log("Payment captured"); +).then(function () { + console.log("Payment tracked"); +}).catch(function (error) { + console.error("Error tracking payment:", error); }); ``` -#### Parameters for `linkrunner.capturePayment` - -- `amount`: number (required) - The payment amount -- `userId`: string (required) - Identifier for the user making the payment -- `paymentId`: string (required) - Unique identifier for the payment, used to deduplicate transactions -- `type`: string (optional) - Type of payment. Available options: - - `FIRST_PAYMENT` - First payment made by the user - - `SECOND_PAYMENT` - Second payment made by the user - - `WALLET_TOPUP` - Adding funds to a wallet - - `FUNDS_WITHDRAWAL` - Withdrawing funds - - `SUBSCRIPTION_CREATED` - New subscription created - - `SUBSCRIPTION_RENEWED` - Subscription renewal - - `ONE_TIME` - One-time payment - - `RECURRING` - Recurring payment - - `DEFAULT` - Default type (used if not specified) -- `status`: string (optional) - Status of the payment. Available options: - - `PAYMENT_INITIATED` - Payment has been initiated - - `PAYMENT_COMPLETED` - Payment completed successfully (default if not specified) - - `PAYMENT_FAILED` - Payment attempt failed - - `PAYMENT_CANCELLED` - Payment was cancelled -- `eventData`: object (optional) - Key-value pairs for additional event data, including Meta ecommerce properties. - -### Removing Payments - -Remove payment records (for refunds or cancellations): +You can pass `eventData` and `eventId` in the same call, as with any other [custom event](#tracking-custom-events). -```javascript -linkrunner.removePayment({ - userId: "user123", // User identifier - paymentId: "payment456" // Optional: Unique payment identifier -}).then(function () { - console.log("Payment removed"); -}); -``` +#### `payment` Parameters + +| Parameter | Type | Required | Description | +| ----------- | -------- | -------- | ----------- | +| `amount` | `number` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `string` | No | Payment status. Defaults to `PAYMENT_COMPLETED` when you leave it out. | -#### Parameters for `linkrunner.removePayment` +The promise returned by `trackEvent` rejects when a value breaks these rules, and nothing is sent. -- `userId`: string (optional) - Identifier for the user whose payment is being removed. If only `userId` is provided, all payments for that user will be removed. -- `paymentId`: string (optional) - Unique identifier for the payment to be removed +#### Available Payment Statuses -Note: At least one of `paymentId` or `userId` must be provided when calling `removePayment`. +| Status | Description | +| ------------------- | ------------------------------ | +| `PAYMENT_INITIATED` | Payment process started | +| `PAYMENT_COMPLETED` | Payment successfully completed | +| `PAYMENT_FAILED` | Payment failed | +| `PAYMENT_CANCELLED` | Payment was cancelled | + +Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. + + + Apps on `cordova-linkrunner` 1.x keep using `capturePayment` and continue to work unchanged. To move to 2.0.0, see the migration steps in the [cordova-linkrunner changelog](https://github.com/linkrunner-labs/cordova-linkrunner-sdk/blob/main/CHANGELOG.md). + ## Ecommerce Events @@ -485,16 +461,12 @@ linkrunner.trackEvent( ### Payment / Purchase Example -Use the `capturePayment` method to send a `Purchase` event containing the ecommerce payload: +Use `trackEvent` with a [`payment`](#tracking-payments) to send a `Purchase` event containing the ecommerce payload: ```javascript -linkrunner.capturePayment({ - amount: 49.99, - userId: "user123", - paymentId: "payment_456", - type: "FIRST_PAYMENT", // Map this payment type to "Purchase" in the Linkrunner Dashboard - status: "PAYMENT_COMPLETED", - eventData: { +linkrunner.trackEvent( + "purchase", // Map this event to "Purchase" in the Linkrunner Dashboard + { content_ids: ["product_123"], contents: [ { @@ -508,11 +480,18 @@ linkrunner.capturePayment({ value: 49.99, num_items: 1, order_id: "order_abc123" // Required for Purchase events + }, + null, + { + amount: 49.99, + paymentId: "payment_456", + currency: "USD", + status: "PAYMENT_COMPLETED" } -}).then(function () { - console.log("Purchase captured successfully"); +).then(function () { + console.log("Purchase tracked successfully"); }).catch(function (error) { - console.error("Error capturing purchase:", error); + console.error("Error tracking purchase:", error); }); ``` @@ -525,7 +504,7 @@ linkrunner.capturePayment({ - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. For capturing revenue, it is recommended to use the [`.capturePayment`](#revenue-tracking) method instead of `.trackEvent`. + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, pass a [`payment`](#tracking-payments) to `.trackEvent`. Track custom events in your app: @@ -545,6 +524,7 @@ linkrunner.trackEvent( - `eventName`: string (required) - Name of the event to track - `eventData`: object (optional) - Key-value pairs for additional event data, including Meta ecommerce properties - `eventId`: string | number (optional) - Your own unique identifier for the event, useful for deduplication and correlating with your backend +- `payment`: object (optional). Records revenue with the event. See [Tracking Payments](#tracking-payments). ### Revenue Sharing with Ad Networks @@ -750,8 +730,7 @@ Connect APNs with Linkrunner | [`linkrunner.setAdditionalData`](/sdk/cordova#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`linkrunner.setUserData`](/sdk/cordova#setting-user-data) | Optional | Profile update flow after `signup` | When additional user details become available | | [`linkrunner.trackEvent`](/sdk/cordova#tracking-custom-events) | Optional | Throughout app, including ecommerce flows | When specific user actions occur, including AddToCart or ViewContent events | -| [`linkrunner.capturePayment`](/sdk/cordova#capture-payment) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | -| [`linkrunner.removePayment`](/sdk/cordova#removing-payments) | Optional | Refund flow | When payment needs to be removed | +| [`linkrunner.trackEvent` with `payment`](/sdk/cordova#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | | [`linkrunner.setPushToken`](/sdk/cordova#uninstall-tracking) | Optional | Push notification setup | When push token is available | | [`linkrunner.enablePIIHashing`](/sdk/cordova#enhanced-privacy-controls) | Optional | Privacy configuration | When enabling PII hashing | diff --git a/sdk/expo.mdx b/sdk/expo.mdx index 1197b66f..189dd952 100644 --- a/sdk/expo.mdx +++ b/sdk/expo.mdx @@ -26,6 +26,7 @@ This guide will help you install, configure, and use the Linkrunner SDK in your - Expo SDK 52.0 or higher - Node.js 18.0 or higher - React Native 0.70 or higher +- `rn-linkrunner` 4.0.0 or higher when you use `expo-linkrunner` 4.0.0 or higher ## Installation @@ -195,8 +196,7 @@ Expo uses the `rn-linkrunner` runtime. The function links below open the impleme | [`linkrunner.setAdditionalData`](/sdk/react-native#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`linkrunner.setUserData`](/sdk/react-native#setting-user-data) | Optional | Profile update flow after `signup` | When additional user details become available | | [`linkrunner.trackEvent`](/sdk/react-native#tracking-custom-events) | Optional | Throughout app, including ecommerce flows | When specific user actions occur, including AddToCart or ViewContent events | -| [`linkrunner.capturePayment`](/sdk/react-native#capture-payment) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | -| [`linkrunner.removePayment`](/sdk/react-native#removing-payments) | Optional | Refund flow | When payment needs to be removed | +| [`linkrunner.trackEvent` with `payment`](/sdk/react-native#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | | [`linkrunner.setPushToken`](/sdk/react-native#uninstall-tracking) | Optional | Push notification setup | When FCM/APNs token is available | | [`linkrunner.setConsent`](/sdk/react-native#google-integrated-conversion-measurement-optional) | Optional | App initialization or consent flow | Before `init`, and again when consent changes | | [`linkrunner.enablePIIHashing`](/sdk/react-native#enhanced-privacy-controls) | Optional | Privacy configuration | When enabling PII hashing | diff --git a/sdk/flutter.mdx b/sdk/flutter.mdx index 1485dc69..ef6823b7 100644 --- a/sdk/flutter.mdx +++ b/sdk/flutter.mdx @@ -357,7 +357,7 @@ If the platform's identifier function is not called, you must provide a user ide - posthogDistinctId for PostHog - The model classes (`LRUserData`, `LRCapturePayment`, `LRRemovePayment`, `LRConsent`) are not re-exported by `linkrunner.dart`. Import each one directly, for example `import 'package:linkrunner/models/lr_user_data.dart';`. + The model classes (`LRUserData`, `LRConsent`, `EventPayment`, `PaymentStatus`) are not re-exported by `linkrunner.dart`. Import `package:linkrunner/main.dart`, which exports `linkrunner.dart` and all models, or import each model directly, for example `import 'package:linkrunner/models/lr_user_data.dart';`. ```dart @@ -573,94 +573,59 @@ This method allows you to connect user identities across different analytics and ## Revenue Tracking - Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. + Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) or [`.setUserData`](#setting-user-data) first so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. -### Capturing Payments +### Tracking Payments -Track payment information: +To record revenue, pass an `EventPayment` to `trackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `linkrunner` 5.0.0 or later. ```dart -Future capturePayment() async { +import 'package:linkrunner/main.dart'; + +Future trackPurchase() async { try { - await LinkRunner().capturePayment( - capturePayment: LRCapturePayment( - amount: 99.99, // Required: Payment amount - userId: 'user123', // Required: User identifier - paymentId: 'payment456', // Required: Unique payment identifier - type: PaymentType.FIRST_PAYMENT, // Optional: Payment type - status: PaymentStatus.PAYMENT_COMPLETED, // Optional: Payment status - eventData: { // Optional: Ecommerce/custom event data - 'content_ids': ['product_123'], - 'content_type': 'product', - 'currency': 'USD', - 'value': 99.99, - 'num_items': 1, - 'order_id': 'order_12345', - 'contents': [ - { - 'id': 'product_123', - 'quantity': 1, - 'item_price': 99.99 - } - ] - }, + await LinkRunner().trackEvent( + eventName: 'purchase', // Event name, used as the payment type + payment: EventPayment( + amount: 499.0, + paymentId: 'order_123', + currency: 'INR', ), ); - print('Payment captured successfully'); } catch (e) { - print('Error capturing payment: $e'); + print('Error tracking payment: $e'); } } ``` -#### Parameters for `LRCapturePayment` - -- `amount`: double (required) - The payment amount -- `userId`: String (required) - Identifier for the user making the payment -- `paymentId`: String (required) - Unique identifier for the payment, used to deduplicate transactions -- `type`: PaymentType (optional) - Type of payment. Available options: - - `PaymentType.FIRST_PAYMENT` - First payment made by the user - - `PaymentType.WALLET_TOPUP` - Adding funds to a wallet - - `PaymentType.FUNDS_WITHDRAWAL` - Withdrawing funds - - `PaymentType.SUBSCRIPTION_CREATED` - New subscription created - - `PaymentType.SUBSCRIPTION_RENEWED` - Subscription renewal - - `PaymentType.ONE_TIME` - One-time payment - - `PaymentType.RECURRING` - Recurring payment - - `PaymentType.DEFAULT_PAYMENT` - Default type (used if not specified) -- `status`: PaymentStatus (optional) - Status of the payment. Available options: - - `PaymentStatus.PAYMENT_INITIATED` - Payment has been initiated - - `PaymentStatus.PAYMENT_COMPLETED` - Payment completed successfully (default if not specified) - - `PaymentStatus.PAYMENT_FAILED` - Payment attempt failed - - `PaymentStatus.PAYMENT_CANCELLED` - Payment was cancelled -- `eventData`: `Map` (optional) - Key-value pairs for additional event data, including ecommerce properties for Meta and Google. - -### Removing Payments - -Remove payment records (for refunds or cancellations): +You can pass `eventData` and `eventId` in the same call, as with any other [custom event](#tracking-custom-events). -```dart -Future removePayment() async { - try { - await LinkRunner().removePayment( - removePayment: LRRemovePayment( - userId: 'user123', // Either userId or paymentId must be provided - paymentId: 'payment456', // Optional: Unique payment identifier - ), - ); - print('Payment removed successfully'); - } catch (e) { - print('Error removing payment: $e'); - } -} -``` +#### `EventPayment` Parameters + +| Parameter | Type | Required | Description | +| ----------- | --------------- | -------- | ----------- | +| `amount` | `double` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `paymentId` | `String` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | Payment status. Defaults to `PAYMENT_COMPLETED` when you leave it out. | + +The `EventPayment` constructor throws `ArgumentError` when a value breaks these rules, and nothing is sent. -#### Parameters for `LRRemovePayment` +#### Available Payment Statuses -- `userId`: String (required) - Identifier for the user whose payment is being removed -- `paymentId`: String (optional) - Unique identifier for the payment to be removed +| Status | Description | +| --------------------------------- | ------------------------------ | +| `PaymentStatus.PAYMENT_INITIATED` | Payment process started | +| `PaymentStatus.PAYMENT_COMPLETED` | Payment successfully completed | +| `PaymentStatus.PAYMENT_FAILED` | Payment failed | +| `PaymentStatus.PAYMENT_CANCELLED` | Payment was cancelled | -Note: Either `paymentId` or `userId` must be provided when calling `removePayment`. If only `userId` is provided, all payments for that user will be removed. +Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. + + + Apps on `linkrunner` 4.x keep using `capturePayment` and continue to work unchanged. To move to 5.0.0, see the migration steps in the [linkrunner changelog](https://pub.dev/packages/linkrunner/changelog). + ## Ecommerce Events @@ -734,38 +699,40 @@ Future trackViewContent() async { ### Payment / Purchase Example -Use the `capturePayment` method to send a `Purchase` event containing the ecommerce payload: +Use `trackEvent` with an [`EventPayment`](#tracking-payments) to send a `Purchase` event containing the ecommerce payload: ```dart -Future capturePurchase() async { +import 'package:linkrunner/main.dart'; + +Future trackPurchase() async { try { - await LinkRunner().capturePayment( - capturePayment: LRCapturePayment( + await LinkRunner().trackEvent( + eventName: 'purchase', // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard. See: /ecommerce-manager/meta-commerce-manager, /ecommerce-manager/google-commerce-manager + eventData: { + 'content_ids': ['product_123'], + 'contents': [ + { + 'id': 'product_123', // Matches content_ids + 'quantity': 1, + 'item_price': 49.99 + } + ], + 'content_type': 'product', + 'currency': 'USD', + 'value': 49.99, + 'num_items': 1, + 'order_id': 'order_abc123' // Required for Purchase events + }, + payment: EventPayment( amount: 49.99, - userId: 'user123', paymentId: 'payment_456', - type: PaymentType.FIRST_PAYMENT, // Map this payment type to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard. See: /ecommerce-manager/meta-commerce-manager, /ecommerce-manager/google-commerce-manager + currency: 'USD', status: PaymentStatus.PAYMENT_COMPLETED, - eventData: { - 'content_ids': ['product_123'], - 'contents': [ - { - 'id': 'product_123', // Matches content_ids - 'quantity': 1, - 'item_price': 49.99 - } - ], - 'content_type': 'product', - 'currency': 'USD', - 'value': 49.99, - 'num_items': 1, - 'order_id': 'order_abc123' // Required for Purchase events - }, ), ); - print('Purchase captured successfully'); + print('Purchase tracked successfully'); } catch (e) { - print('Error capturing purchase: $e'); + print('Error tracking purchase: $e'); } } ``` @@ -779,7 +746,7 @@ Future capturePurchase() async { - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. For capturing revenue, it is recommended to use the [`.capturePayment`](#revenue-tracking) method instead of `.trackEvent`. + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, pass an [`EventPayment`](#tracking-payments) to `.trackEvent`. Track custom events in your app: @@ -808,6 +775,7 @@ Future trackEvent() async { - `eventName`: `String` (required) - Name of the event to track - `eventData`: `Map` (optional) - Key-value pairs for additional event data, including Meta ecommerce properties - `eventId`: `Object` (optional) - Your own unique identifier for the event (`String` or `num`), useful for deduplication and correlating with your backend +- `payment`: `EventPayment` (optional). Records revenue with the event. See [Tracking Payments](#tracking-payments). ### Revenue Sharing with Ad Networks @@ -1056,8 +1024,7 @@ Connect APNs with Linkrunner | [`LinkRunner().setAdditionalData`](/sdk/flutter#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`LinkRunner().setUserData`](/sdk/flutter#setting-user-data) | Optional | Authentication or profile update logic | After `signup`, when additional user details become available | | [`LinkRunner().trackEvent`](/sdk/flutter#tracking-custom-events) | Optional | Throughout app | When specific user actions occur, including [ecommerce events](/sdk/flutter#ecommerce-events) | -| [`LinkRunner().capturePayment`](/sdk/flutter#capturing-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/flutter#ecommerce-events) | -| [`LinkRunner().removePayment`](/sdk/flutter#removing-payments) | Optional | Refund flow | When payment needs to be removed | +| [`LinkRunner().trackEvent` with `payment`](/sdk/flutter#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/flutter#ecommerce-events) | | [`LinkRunner().setPushToken`](/sdk/flutter#uninstall-tracking) | Optional | Push notification setup | When FCM (Android) or APNs (iOS) token is available | | [`LinkRunner().setConsent`](/sdk/flutter#google-integrated-conversion-measurement-optional) | Optional | App initialization or consent flow | Before `init`, and again when consent changes | | [`LinkRunner().enablePIIHashing`](/sdk/flutter#enhanced-privacy-controls) | Optional | Privacy settings | When you want to enable PII hashing | diff --git a/sdk/ios.mdx b/sdk/ios.mdx index 16024ea2..4e717fc0 100644 --- a/sdk/ios.mdx +++ b/sdk/ios.mdx @@ -39,7 +39,7 @@ Alternatively, you can add the package dependency to your `Package.swift` file: ```swift dependencies: [ - .package(url: "https://github.com/linkrunner-labs/linkrunner-ios.git", from: "4.2.0") + .package(url: "https://github.com/linkrunner-labs/linkrunner-ios.git", from: "5.0.0") ] ``` @@ -514,57 +514,47 @@ func setAdditionalData() async { ## Revenue Tracking - Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. + Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) or [`.setUserData`](#setting-user-data) first so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. -### Capturing Payments +### Tracking Payments -Track payment information: +To record revenue, pass an `EventPayment` to `trackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires LinkrunnerKit 5.0.0 or later. ```swift -func capturePayment() async { - await LinkrunnerSDK.shared.capturePayment( - amount: 99.99, // Payment amount - userId: "user123", // User identifier - paymentId: "payment456", // Required: Unique payment identifier - type: .firstPayment, // optional - status: .completed, // optional - eventData: [ // Optional: Ecommerce/custom event data - "content_ids": ["product_123"], - "content_type": "product", - "currency": "USD", - "value": 99.99, - "num_items": 1, - "order_id": "order_12345", - "contents": [ - [ - "id": "product_123", - "quantity": 1, - "item_price": 99.99 - ] - ] - ] - ) - print("Payment captured successfully") -} -``` +import LinkrunnerKit -#### Available Payment Types +func trackPurchase() async { + do { + let payment = try EventPayment( + amount: 99.99, + paymentId: "payment_123", + currency: "USD" + ) -```swift -public enum PaymentType: String, Sendable { - case firstPayment = "FIRST_PAYMENT" - case secondPayment = "SECOND_PAYMENT" - case walletTopup = "WALLET_TOPUP" - case fundsWithdrawal = "FUNDS_WITHDRAWAL" - case subscriptionCreated = "SUBSCRIPTION_CREATED" - case subscriptionRenewed = "SUBSCRIPTION_RENEWED" - case oneTime = "ONE_TIME" - case recurring = "RECURRING" - case `default` = "DEFAULT" + await LinkrunnerSDK.shared.trackEvent( + eventName: "purchase", + payment: payment + ) + } catch { + print("Invalid payment: \(error)") + } } ``` +You can pass `eventData` and `eventId` in the same call, as with any other [custom event](#tracking-custom-events). + +#### `EventPayment` Parameters + +| Parameter | Type | Required | Description | +| ----------- | --------------- | -------- | ----------- | +| `amount` | `Double` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `paymentId` | `String` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | Payment status. Defaults to `PAYMENT_COMPLETED` when you leave it out. | + +The `EventPayment` initializer throws `EventPaymentError.invalidAmount`, `.invalidPaymentId`, or `.invalidCurrency` when a value breaks these rules, and nothing is sent. + #### Available Payment Statuses ```swift @@ -576,19 +566,11 @@ public enum PaymentStatus: String, Sendable { } ``` -### Removing Payments +To set a status, pass it to the initializer, for example `status: .initiated`. Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. -Remove payment records (for refunds or cancellations): - -```swift -func removePayment() async { - await LinkrunnerSDK.shared.removePayment( - userId: "user123", // User identifier - paymentId: "payment456" // Optional: Unique payment identifier - ) - print("Payment removed successfully") -} -``` + + Apps on LinkrunnerKit 4.x keep using `capturePayment` and continue to work unchanged. To move to 5.0.0, see the migration steps in the [iOS SDK changelog](https://github.com/linkrunner-labs/linkrunner-ios/blob/main/CHANGELOG.md). + ## Tracking Custom Events @@ -597,7 +579,7 @@ func removePayment() async { - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. For capturing revenue, it is recommended to use the [`.capturePayment`](#revenue-tracking) method instead of `.trackEvent`. + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, pass a payment to `.trackEvent`. See [Revenue Tracking](#revenue-tracking). Track custom events in your app: @@ -710,33 +692,39 @@ func trackViewContent() async { ### Payment / Purchase Example -Use the `capturePayment` method to send a `Purchase` event containing the ecommerce payload: +Use `trackEvent` with an `EventPayment` to send a `Purchase` event containing the ecommerce payload: ```swift -func capturePurchase() async { - await LinkrunnerSDK.shared.capturePayment( - amount: 49.99, - userId: "user123", - paymentId: "payment_456", - type: .firstPayment, // Map this payment type to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard - status: .completed, - eventData: [ - "content_ids": ["product_123"], - "contents": [ - [ - "id": "product_123", // Matches content_ids - "quantity": 1, - "item_price": 49.99 - ] +func trackPurchase() async { + do { + let payment = try EventPayment( + amount: 49.99, + paymentId: "payment_456", + currency: "USD" + ) + + await LinkrunnerSDK.shared.trackEvent( + eventName: "purchase", // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard + eventData: [ + "content_ids": ["product_123"], + "contents": [ + [ + "id": "product_123", // Matches content_ids + "quantity": 1, + "item_price": 49.99 + ] + ], + "content_type": "product", + "currency": "USD", + "value": 49.99, + "num_items": 1, + "order_id": "order_abc123" // Required for Purchase events ], - "content_type": "product", - "currency": "USD", - "value": 49.99, - "num_items": 1, - "order_id": "order_abc123" // Required for Purchase events - ] - ) - print("Purchase captured successfully") + payment: payment + ) + } catch { + print("Invalid payment: \(error)") + } } ``` @@ -886,8 +874,7 @@ Connect APNs with Linkrunner | [`LinkrunnerSDK.shared.setAdditionalData`](/sdk/ios#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`LinkrunnerSDK.shared.setUserData`](/sdk/ios#setting-user-data) | Optional | Authentication or profile update logic | After `signup`, when additional user details become available | | [`LinkrunnerSDK.shared.trackEvent`](/sdk/ios#tracking-custom-events) | Optional | Throughout app | When specific user actions occur, including [ecommerce events](/sdk/ios#ecommerce-events) | -| [`LinkrunnerSDK.shared.capturePayment`](/sdk/ios#capturing-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/ios#ecommerce-events) | -| [`LinkrunnerSDK.shared.removePayment`](/sdk/ios#removing-payments) | Optional | Refund flow | When payment needs to be removed | +| [`LinkrunnerSDK.shared.trackEvent` with `payment`](/sdk/ios#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/ios#ecommerce-events) | | [`LinkrunnerSDK.shared.setPushToken`](/sdk/ios#uninstall-tracking) | Optional | Push notification setup | When APNs token is available | | [`LinkrunnerSDK.shared.setConsent`](/sdk/ios#consent) | Optional | App initialization or consent flow | Before `initialize`, and again when consent changes | | [`LinkrunnerSDK.shared.enablePIIHashing`](/sdk/ios#enhanced-privacy-controls) | Optional | Privacy settings | When you want to enable PII hashing | diff --git a/sdk/react-native.mdx b/sdk/react-native.mdx index 34f13837..42800235 100644 --- a/sdk/react-native.mdx +++ b/sdk/react-native.mdx @@ -431,81 +431,61 @@ This method allows you to connect user identities across different analytics and ## Revenue Tracking - Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. + Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) or [`.setUserData`](#setting-user-data) first so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. -### Capture Payment - -Use this method to capture payment information: - -```js -const capturePayment = async () => { - await linkrunner.capturePayment({ - amount: 100, // Payment amount - userId: "user123", // User identifier - paymentId: "payment456", // Required: Unique payment identifier - type: "FIRST_PAYMENT", // Optional: Payment type - status: "PAYMENT_COMPLETED", // Optional: Payment status - eventData: { - // Optional: Ecommerce/custom event data - content_ids: ["product_123"], - content_type: "product", - currency: "USD", - value: 99.99, - num_items: 1, - order_id: "order_12345", - contents: [ - { - id: "product_123", - quantity: 1, - item_price: 99.99, - }, - ], - }, - }); -}; -``` +### Tracking Payments -#### Parameters for `linkrunner.capturePayment` - -- `amount`: number (required) - The payment amount -- `userId`: string (required) - Identifier for the user making the payment -- `paymentId`: string (required) - Unique identifier for the payment, used to deduplicate transactions -- `type`: string (optional) - Type of payment. Available options: - - `FIRST_PAYMENT` - First payment made by the user - - `WALLET_TOPUP` - Adding funds to a wallet - - `FUNDS_WITHDRAWAL` - Withdrawing funds - - `SUBSCRIPTION_CREATED` - New subscription created - - `SUBSCRIPTION_RENEWED` - Subscription renewal - - `ONE_TIME` - One-time payment - - `RECURRING` - Recurring payment - - `DEFAULT` - Default type (used if not specified) -- `status`: string (optional) - Status of the payment. Available options: - - `PAYMENT_INITIATED` - Payment has been initiated - - `PAYMENT_COMPLETED` - Payment completed successfully (default if not specified) - - `PAYMENT_FAILED` - Payment attempt failed - - `PAYMENT_CANCELLED` - Payment was cancelled -- `eventData`: object (optional) - Key-value pairs for additional event data, including ecommerce properties for Meta and Google. - -### Removing Payments - -Remove payment records (for refunds or cancellations): +To record revenue, pass a `payment` as the fourth argument to `trackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `rn-linkrunner` 4.0.0 or later. ```javascript -const removePayment = async () => { - await linkrunner.removePayment({ - userId: "user123", // User identifier - paymentId: "payment456", // Optional: Unique payment identifier - }); +import linkrunner from "rn-linkrunner"; + +const trackPurchase = async () => { + try { + await linkrunner.trackEvent( + "purchase", // Event name, used as the payment type + { plan: "pro" }, // Optional: Event data + undefined, // Optional: Event ID + { + amount: 499, + paymentId: "order_123", + currency: "INR", + }, + ); + } catch (error) { + console.error("Error tracking payment:", error); + } }; ``` -#### Parameters for `Linkrunner.removePayment` +You can pass `eventData` and `eventId` in the same call, as with any other [custom event](#tracking-custom-events). + +#### `payment` Parameters -- `userId`: String (required) - Identifier for the user whose payment is being removed -- `paymentId`: String (optional) - Unique identifier for the payment to be removed +| Parameter | Type | Required | Description | +| ----------- | -------- | -------- | ----------- | +| `amount` | `number` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `string` | No | Payment status. Defaults to `PAYMENT_COMPLETED` when you leave it out. | -Note: `userId` is always required. `paymentId` is optional, and if you omit it all payments for that user are removed. +`trackEvent` throws when a value breaks these rules, and nothing is sent. + +#### Available Payment Statuses + +| Status | Description | +| ------------------- | ------------------------------ | +| `PAYMENT_INITIATED` | Payment process started | +| `PAYMENT_COMPLETED` | Payment successfully completed | +| `PAYMENT_FAILED` | Payment failed | +| `PAYMENT_CANCELLED` | Payment was cancelled | + +Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. In TypeScript, you can import the `EventPayment` and `PaymentStatus` types from `rn-linkrunner`. + + + Apps on `rn-linkrunner` 3.x keep using `capturePayment` and continue to work unchanged. To move to 4.0.0, see the migration steps in the [rn-linkrunner changelog](https://github.com/RathodDarshil/rn-linkrunner/blob/main/CHANGELOG.md). + ## Ecommerce Events @@ -579,18 +559,14 @@ const trackViewContent = async () => { ### Payment / Purchase Example -Use the `capturePayment` method to send a `Purchase` event containing the ecommerce payload: +Use `trackEvent` with a [`payment`](#tracking-payments) to send a `Purchase` event containing the ecommerce payload: ```javascript -const capturePurchase = async () => { +const trackPurchase = async () => { try { - await linkrunner.capturePayment({ - amount: 49.99, - userId: "user123", - paymentId: "payment_456", - type: "FIRST_PAYMENT", // Map this payment type to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard. See: /ecommerce-manager/meta-commerce-manager, /ecommerce-manager/google-commerce-manager - status: "PAYMENT_COMPLETED", - eventData: { + await linkrunner.trackEvent( + "purchase", // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard. See: /ecommerce-manager/meta-commerce-manager, /ecommerce-manager/google-commerce-manager + { content_ids: ["product_123"], contents: [ { @@ -605,10 +581,17 @@ const capturePurchase = async () => { num_items: 1, order_id: "order_abc123", // Required for Purchase events }, - }); - console.log("Purchase captured successfully"); + undefined, + { + amount: 49.99, + paymentId: "payment_456", + currency: "USD", + status: "PAYMENT_COMPLETED", + }, + ); + console.log("Purchase tracked successfully"); } catch (error) { - console.error("Error capturing purchase:", error); + console.error("Error tracking purchase:", error); } }; ``` @@ -622,7 +605,7 @@ const capturePurchase = async () => { - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. For capturing revenue, it is recommended to use the [`.capturePayment`](#revenue-tracking) method instead of `.trackEvent`. + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, pass a [`payment`](#tracking-payments) to `.trackEvent`. Track custom events in your app: @@ -642,6 +625,7 @@ const trackEvent = async () => { - `eventName`: string (required) - Name of the event to track - `eventData`: object (optional) - Key-value pairs for additional event data, including Meta ecommerce properties - `eventId`: string | number (optional) - Your own unique identifier for the event, useful for deduplication and correlating with your backend +- `payment`: object (optional). Records revenue with the event. See [Tracking Payments](#tracking-payments). ### Revenue Sharing with Ad Networks @@ -853,8 +837,7 @@ Connect APNs with Linkrunner | [`linkrunner.setAdditionalData`](/sdk/react-native#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`linkrunner.setUserData`](/sdk/react-native#setting-user-data) | Optional | Profile update flow after `signup` | When additional user details become available | | [`linkrunner.trackEvent`](/sdk/react-native#tracking-custom-events) | Optional | Throughout app, including ecommerce flows | When specific user actions occur, including AddToCart or ViewContent events | -| [`linkrunner.capturePayment`](/sdk/react-native#capture-payment) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | -| [`linkrunner.removePayment`](/sdk/react-native#removing-payments) | Optional | Refund flow | When payment needs to be removed | +| [`linkrunner.trackEvent` with `payment`](/sdk/react-native#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | | [`linkrunner.setPushToken`](/sdk/react-native#uninstall-tracking) | Optional | Push notification setup | When FCM/APNs token is available | | [`linkrunner.setConsent`](/sdk/react-native#google-integrated-conversion-measurement-optional) | Optional | App initialization or consent flow | Before `init`, and again when consent changes | | [`linkrunner.enablePIIHashing`](/sdk/react-native#enhanced-privacy-controls) | Optional | Privacy configuration | When enabling PII hashing | diff --git a/sdk/unity.mdx b/sdk/unity.mdx index 199fa13e..4d1a76e7 100644 --- a/sdk/unity.mdx +++ b/sdk/unity.mdx @@ -59,7 +59,7 @@ In your Unity project, create or edit the file `Assets/Plugins/Android/mainTempl ```gradle dependencies { - implementation 'io.linkrunner:android-sdk:4.2.0' + implementation 'io.linkrunner:android-sdk:5.0.0' } ``` @@ -117,10 +117,8 @@ import io.linkrunner.sdk.LinkRunnerCallback; import io.linkrunner.sdk.models.ConsentStatus; import io.linkrunner.sdk.models.LinkrunnerConsent; import io.linkrunner.sdk.models.request.UserDataRequest; -import io.linkrunner.sdk.models.request.CapturePaymentRequest; -import io.linkrunner.sdk.models.request.RemovePaymentRequest; +import io.linkrunner.sdk.models.EventPayment; import io.linkrunner.sdk.models.IntegrationData; -import io.linkrunner.sdk.models.PaymentType; import io.linkrunner.sdk.models.PaymentStatus; import io.linkrunner.sdk.models.response.AttributionData; @@ -157,29 +155,15 @@ public class LinkrunnerBridge { return map; } - private static PaymentType parsePaymentType(String type) { - if (type == null) return PaymentType.DEFAULT; - switch (type) { - case "FIRST_PAYMENT": return PaymentType.FIRST_PAYMENT; - case "SECOND_PAYMENT": return PaymentType.SECOND_PAYMENT; - case "WALLET_TOPUP": return PaymentType.WALLET_TOPUP; - case "FUNDS_WITHDRAWAL": return PaymentType.FUNDS_WITHDRAWAL; - case "SUBSCRIPTION_CREATED": return PaymentType.SUBSCRIPTION_CREATED; - case "SUBSCRIPTION_RENEWED": return PaymentType.SUBSCRIPTION_RENEWED; - case "ONE_TIME": return PaymentType.ONE_TIME; - case "RECURRING": return PaymentType.RECURRING; - default: return PaymentType.DEFAULT; - } - } - + // Returns null for an empty status so Linkrunner applies its default (PAYMENT_COMPLETED) private static PaymentStatus parsePaymentStatus(String status) { - if (status == null) return PaymentStatus.PAYMENT_COMPLETED; + if (status == null) return null; switch (status) { case "PAYMENT_INITIATED": return PaymentStatus.PAYMENT_INITIATED; case "PAYMENT_COMPLETED": return PaymentStatus.PAYMENT_COMPLETED; case "PAYMENT_FAILED": return PaymentStatus.PAYMENT_FAILED; case "PAYMENT_CANCELLED": return PaymentStatus.PAYMENT_CANCELLED; - default: return PaymentStatus.PAYMENT_COMPLETED; + default: return null; } } @@ -326,47 +310,32 @@ public class LinkrunnerBridge { ); } - public static void capturePayment(String userId, double amount, String paymentId, - String type, String status, String eventDataJson) { - Map eventData = jsonToMap(eventDataJson); - CapturePaymentRequest request = new CapturePaymentRequest( - paymentId.isEmpty() ? null : paymentId, - userId, - amount, - parsePaymentType(type), - parsePaymentStatus(status), - eventData.isEmpty() ? null : eventData - ); - LinkrunnerJava lr = LinkrunnerJava.getInstance(); - lr.capturePayment( - request, - new LinkRunnerCallback() { - public void onSuccess(Void result) { - sendToUnity("OnCapturePaymentComplete", "success"); - } - public void onError(String error) { - Log.e(TAG, "CapturePayment failed: " + error); - sendToUnity("OnCapturePaymentComplete", "error:" + error); - } - } - ); - } + public static void trackEventWithPayment(String eventName, String eventDataJson, String eventId, + double amount, String paymentId, String currency, + String status) { + EventPayment payment; + try { + payment = new EventPayment(amount, paymentId, currency, parsePaymentStatus(status)); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Invalid payment: " + e.getMessage()); + sendToUnity("OnTrackEventComplete", "error:" + e.getMessage()); + return; + } - public static void removePayment(String userId, String paymentId) { - RemovePaymentRequest request = new RemovePaymentRequest( - paymentId.isEmpty() ? null : paymentId, - userId.isEmpty() ? null : userId - ); + Map eventData = jsonToMap(eventDataJson); LinkrunnerJava lr = LinkrunnerJava.getInstance(); - lr.removePayment( - request, + lr.trackEvent( + eventName, + eventData.isEmpty() ? null : eventData, + eventId.isEmpty() ? null : eventId, + payment, new LinkRunnerCallback() { public void onSuccess(Void result) { - sendToUnity("OnRemovePaymentComplete", "success"); + sendToUnity("OnTrackEventComplete", "success"); } public void onError(String error) { - Log.e(TAG, "RemovePayment failed: " + error); - sendToUnity("OnRemovePaymentComplete", "error:" + error); + Log.e(TAG, "TrackEvent with payment failed: " + error); + sendToUnity("OnTrackEventComplete", "error:" + error); } } ); @@ -464,7 +433,7 @@ After building your Unity project for iOS and opening the generated Xcode projec ``` https://github.com/linkrunner-labs/linkrunner-ios.git ``` -3. Select the latest version (4.1.0+) +3. Select the latest version (5.0.0+) 4. Click **Add Package** and choose the **LinkrunnerKitStatic** library @@ -551,28 +520,10 @@ private func parseUserData(_ dict: [String: Any]) -> UserData { ) } -private func parsePaymentType(_ str: String?) -> PaymentType { - switch str { - case "FIRST_PAYMENT": return .firstPayment - case "SECOND_PAYMENT": return .secondPayment - case "WALLET_TOPUP": return .walletTopup - case "FUNDS_WITHDRAWAL": return .fundsWithdrawal - case "SUBSCRIPTION_CREATED": return .subscriptionCreated - case "SUBSCRIPTION_RENEWED": return .subscriptionRenewed - case "ONE_TIME": return .oneTime - case "RECURRING": return .recurring - default: return .default - } -} - -private func parsePaymentStatus(_ str: String?) -> PaymentStatus { - switch str { - case "PAYMENT_INITIATED": return .initiated - case "PAYMENT_COMPLETED": return .completed - case "PAYMENT_FAILED": return .failed - case "PAYMENT_CANCELLED": return .cancelled - default: return .completed - } +// Returns nil for an empty status so Linkrunner applies its default (PAYMENT_COMPLETED) +private func parsePaymentStatus(_ str: String?) -> PaymentStatus? { + guard let str = str else { return nil } + return PaymentStatus(rawValue: str) } // MARK: - C-callable functions exposed to Unity @@ -673,42 +624,39 @@ func linkrunnerTrackEvent(_ eventName: UnsafePointer, } } -@_cdecl("_LinkrunnerCapturePayment") -func linkrunnerCapturePayment(_ userId: UnsafePointer, - _ amount: Double, - _ paymentId: UnsafePointer?, - _ type: UnsafePointer?, - _ status: UnsafePointer?, - _ eventDataJson: UnsafePointer?) { - let uid = String(cString: userId) - let pid = toOptionalString(paymentId) - let paymentType = parsePaymentType(toOptionalString(type)) - let paymentStatus = parsePaymentStatus(toOptionalString(status)) +@_cdecl("_LinkrunnerTrackEventWithPayment") +func linkrunnerTrackEventWithPayment(_ eventName: UnsafePointer, + _ eventDataJson: UnsafePointer?, + _ eventId: UnsafePointer?, + _ amount: Double, + _ paymentId: UnsafePointer?, + _ currency: UnsafePointer?, + _ status: UnsafePointer?) { + let name = String(cString: eventName) let eventData = toDict(eventDataJson) + let eid = toOptionalString(eventId) - Task { - await LinkrunnerSDK.shared.capturePayment( + let payment: EventPayment + do { + payment = try EventPayment( amount: amount, - userId: uid, - // LinkrunnerKit 4.x requires a payment id. An empty value is rejected by the SDK. - paymentId: pid ?? "", - type: paymentType, - status: paymentStatus, - eventData: eventData.isEmpty ? nil : eventData + paymentId: toOptionalString(paymentId) ?? "", + currency: toOptionalString(currency) ?? "", + status: parsePaymentStatus(toOptionalString(status)) ) - sendToUnity("OnCapturePaymentComplete", "success") + } catch { + sendToUnity("OnTrackEventComplete", "error:\(error)") + return } -} - -@_cdecl("_LinkrunnerRemovePayment") -func linkrunnerRemovePayment(_ userId: UnsafePointer, - _ paymentId: UnsafePointer?) { - let uid = String(cString: userId) - let pid = toOptionalString(paymentId) Task { - await LinkrunnerSDK.shared.removePayment(userId: uid, paymentId: pid) - sendToUnity("OnRemovePaymentComplete", "success") + await LinkrunnerSDK.shared.trackEvent( + eventName: name, + eventData: eventData.isEmpty ? nil : eventData, + eventId: eid, + payment: payment + ) + sendToUnity("OnTrackEventComplete", "success") } } @@ -885,8 +833,6 @@ public class LinkrunnerSDK : MonoBehaviour public static event Action OnUserDataSet; public static event Action OnAttributionData; public static event Action OnEventTracked; - public static event Action OnPaymentCaptured; - public static event Action OnPaymentRemoved; public static event Action OnDeeplinkHandled; #if UNITY_IOS && !UNITY_EDITOR @@ -903,10 +849,9 @@ public class LinkrunnerSDK : MonoBehaviour private static extern void _LinkrunnerTrackEvent(string eventName, string eventDataJson, string eventId); [DllImport("__Internal")] - private static extern void _LinkrunnerCapturePayment(string userId, double amount, - string paymentId, string type, string status, string eventDataJson); - [DllImport("__Internal")] - private static extern void _LinkrunnerRemovePayment(string userId, string paymentId); + private static extern void _LinkrunnerTrackEventWithPayment(string eventName, + string eventDataJson, string eventId, double amount, string paymentId, + string currency, string status); [DllImport("__Internal")] private static extern void _LinkrunnerSetPushToken(string pushToken); [DllImport("__Internal")] @@ -1009,45 +954,32 @@ public class LinkrunnerSDK : MonoBehaviour } /// - /// Track a custom event. + /// Track a custom event. Pass a payment to store the event as revenue under the event name. /// - public static void TrackEvent(string eventName, string eventDataJson = "", string eventId = "") - { -#if UNITY_ANDROID && !UNITY_EDITOR - Bridge.CallStatic("trackEvent", eventName, eventDataJson ?? "", eventId ?? ""); -#elif UNITY_IOS && !UNITY_EDITOR - _LinkrunnerTrackEvent(eventName, eventDataJson, eventId); -#else - Debug.Log("[Linkrunner] TrackEvent: " + eventName); -#endif - } - - /// - /// Track a payment/revenue event. - /// - public static void CapturePayment(string userId, double amount, string paymentId, - string type = "DEFAULT", string status = "PAYMENT_COMPLETED", string eventDataJson = "") + public static void TrackEvent(string eventName, string eventDataJson = "", string eventId = "", + EventPayment payment = null) { + if (payment != null) + { + string status = payment.status.HasValue ? payment.status.Value.ToString() : ""; #if UNITY_ANDROID && !UNITY_EDITOR - Bridge.CallStatic("capturePayment", userId, amount, paymentId ?? "", type, status, eventDataJson ?? ""); + Bridge.CallStatic("trackEventWithPayment", eventName, eventDataJson ?? "", eventId ?? "", + payment.amount, payment.paymentId ?? "", payment.currency ?? "", status); #elif UNITY_IOS && !UNITY_EDITOR - _LinkrunnerCapturePayment(userId, amount, paymentId, type, status, eventDataJson); + _LinkrunnerTrackEventWithPayment(eventName, eventDataJson, eventId, + payment.amount, payment.paymentId, payment.currency, status); #else - Debug.Log($"[Linkrunner] CapturePayment: {userId}, {amount}"); + Debug.Log($"[Linkrunner] TrackEvent with payment: {eventName}, {payment.amount} {payment.currency}"); #endif - } + return; + } - /// - /// Remove a previously tracked payment (e.g. refund). - /// - public static void RemovePayment(string userId, string paymentId = "") - { #if UNITY_ANDROID && !UNITY_EDITOR - Bridge.CallStatic("removePayment", userId, paymentId ?? ""); + Bridge.CallStatic("trackEvent", eventName, eventDataJson ?? "", eventId ?? ""); #elif UNITY_IOS && !UNITY_EDITOR - _LinkrunnerRemovePayment(userId, paymentId); + _LinkrunnerTrackEvent(eventName, eventDataJson, eventId); #else - Debug.Log($"[Linkrunner] RemovePayment: {userId}"); + Debug.Log("[Linkrunner] TrackEvent: " + eventName); #endif } @@ -1172,18 +1104,6 @@ public class LinkrunnerSDK : MonoBehaviour OnEventTracked?.Invoke(success, message); } - void OnCapturePaymentComplete(string message) - { - bool success = message == "success"; - OnPaymentCaptured?.Invoke(success, message); - } - - void OnRemovePaymentComplete(string message) - { - bool success = message == "success"; - OnPaymentRemoved?.Invoke(success, message); - } - void OnHandleDeeplinkComplete(string message) { OnDeeplinkHandled?.Invoke(message); @@ -1219,6 +1139,36 @@ public class LinkrunnerUserData public string userCreatedAt; public bool isFirstTimeUser; } + +/// +/// Payment status. Leave it null to use the default, PAYMENT_COMPLETED. +/// +public enum PaymentStatus +{ + PAYMENT_INITIATED, + PAYMENT_COMPLETED, + PAYMENT_FAILED, + PAYMENT_CANCELLED +} + +/// +/// Revenue attached to a tracked event. The event name becomes the payment type. +/// +public class EventPayment +{ + public double amount; + public string paymentId; + public string currency; + public PaymentStatus? status; + + public EventPayment(double amount, string paymentId, string currency, PaymentStatus? status = null) + { + this.amount = amount; + this.paymentId = paymentId; + this.currency = currency; + this.status = status; + } +} ``` --- @@ -1429,40 +1379,31 @@ LinkrunnerSDK.TrackEvent("purchase", eventData, eventId: "purchase_abc123"); When tracking revenue-related events, the `amount` field must be a number (not a string) for ad network revenue optimization to work correctly. -### Capture Payment +### Track Payments + +To record revenue, pass an `EventPayment` to `TrackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires the bridges above, built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 or later. ```csharp -LinkrunnerSDK.CapturePayment( - userId: "user_123", - amount: 9.99, - paymentId: "pay_abc123", - type: "FIRST_PAYMENT", - status: "PAYMENT_COMPLETED" -); - -// With event data -string eventData = "{\"product_id\": \"prod_456\", \"currency\": \"USD\"}"; -LinkrunnerSDK.CapturePayment( - userId: "user_123", - amount: 49.99, - paymentId: "pay_xyz789", - type: "ONE_TIME", - status: "PAYMENT_COMPLETED", - eventDataJson: eventData -); +var payment = new EventPayment(amount: 9.99, paymentId: "pay_abc123", currency: "USD"); +LinkrunnerSDK.TrackEvent("purchase", payment: payment); ``` -**`paymentId`** is required and must be unique for each payment. It is used for idempotent deduplication, so retries cannot double count revenue. +The result arrives through `OnEventTracked`, like any other event. You can pass `eventDataJson` and `eventId` in the same call. -**Payment types:** `FIRST_PAYMENT`, `SECOND_PAYMENT`, `WALLET_TOPUP`, `FUNDS_WITHDRAWAL`, `SUBSCRIPTION_CREATED`, `SUBSCRIPTION_RENEWED`, `ONE_TIME`, `RECURRING`, `DEFAULT` +| Field | Type | Required | Description | +| ----------- | ---------------- | -------- | ----------- | +| `amount` | `double` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus?` | No | `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. Defaults to `PAYMENT_COMPLETED` when you leave it out. | -**Payment statuses:** `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, `PAYMENT_CANCELLED` +If a value breaks these rules, the native SDK rejects the payment, nothing is sent, and `OnEventTracked` reports `false` with the error. Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. -### Remove Payment (Refunds) +The payment is linked to the user you set with `Signup` or `SetUserData`, so call one of them first. -```csharp -LinkrunnerSDK.RemovePayment(userId: "user_123", paymentId: "pay_abc123"); -``` + +Bridges built against Android SDK 4.x or LinkrunnerKit 4.x keep using `capturePayment` and continue to work unchanged. See the [Android](https://github.com/linkrunner-labs/linkrunner-android/blob/main/CHANGELOG.md) and [iOS](https://github.com/linkrunner-labs/linkrunner-ios/blob/main/CHANGELOG.md) changelogs for the 5.0.0 migration. + ### Push Token @@ -1501,8 +1442,7 @@ LinkrunnerSDK.SetDisableAaidCollection(true); | [`GetAttributionData`](/sdk/unity#get-attribution-data) | Optional | After init completes | When you need campaign/deeplink info | | [`SetUserData`](/sdk/unity#set-user-data-on-app-open) | Optional | Profile update flow after `Signup` | When additional user details become available | | [`TrackEvent`](/sdk/unity#track-events) | Optional | Throughout the app | On user actions | -| [`CapturePayment`](/sdk/unity#capture-payment) | Optional | Payment flow | When payment succeeds | -| [`RemovePayment`](/sdk/unity#remove-payment-refunds) | Optional | Refund flow | When payment is reversed | +| [`TrackEvent` with `payment`](/sdk/unity#track-payments) | Optional | Payment flow | When payment succeeds | | [`SetPushToken`](/sdk/unity#push-token) | Optional | After token refresh | When push token changes | | [`SetConsent`](/sdk/unity#set-consent) | Optional | Before `Initialize` | At launch and whenever consent changes | | [`SetAdditionalData`](/sdk/unity#set-additional-data) | Optional | Integration setup | When the CleverTap ID is available | diff --git a/testing/event-and-revenue-testing.mdx b/testing/event-and-revenue-testing.mdx index 251faef1..0bdd3de5 100644 --- a/testing/event-and-revenue-testing.mdx +++ b/testing/event-and-revenue-testing.mdx @@ -12,8 +12,9 @@ Use this guide after [SDK Integration Testing](/testing/integration-testing). It | What happened | App SDK | Server-side API | | --- | --- | --- | | A user action with no authoritative transaction | `trackEvent` | `POST /capture-event` | -| A payment with an amount and unique transaction ID | `capturePayment` | `POST /capture-payment` | -| A captured payment must be removed | `removePayment` | `POST /remove-payment` | +| A payment with an amount and unique transaction ID | `trackEvent` with a payment (`amount`, `paymentId`, `currency`) | `POST /capture-event` with `amount`, `payment_id`, and `currency` | + +When you attach a payment, the event name becomes the payment type in reports. See [Payment fields](/api-reference/event-capture#payment-fields) for the rules. The event name alone is not enough. If `purchase_completed` represents a real transaction, send it as a payment. If it only represents a funnel step with no authoritative transaction, send it as a custom event. @@ -28,7 +29,7 @@ Do not send the same transaction as both a revenue-bearing custom event and a pa - Purchase initiated when it only marks the start of checkout - Payment failure shown for product analytics when you do not need a payment record -### Usually send with `capturePayment` +### Usually send as a payment - Completed purchase - First or second payment @@ -38,7 +39,7 @@ Do not send the same transaction as both a revenue-bearing custom event and a pa - A failed or cancelled transaction when you need it recorded with `PAYMENT_FAILED` or `PAYMENT_CANCELLED` -If you record a failed or cancelled payment, send its final state once. Linkrunner deduplicates payments by the combination of `type` and `payment_id`, so a later call with the same combination is ignored. +If you record a failed or cancelled payment, send its final state once. Linkrunner deduplicates payments by the combination of event name, `payment_id`, and `status`, so a later call with the same combination is ignored. ## Before you test @@ -46,7 +47,7 @@ If you record a failed or cancelled payment, send its final state once. Linkrunn - Complete the [click → install → signup flow](/testing/integration-testing) so the test user is attributed. - Use the same `user_id` that your app registered through `signup`. - Generate a fresh `payment_id` for each real transaction. -- Send `amount` as a number in one reporting currency. +- Send `amount` as a number, with its 3-letter ISO 4217 `currency`, such as `USD`. - Pick one source for each payment, either the app SDK or your backend. - For server-side tests, generate a server key from [**Settings → Data APIs**](https://dashboard.linkrunner.io/settings?p_id=4&s=data-apis). @@ -58,10 +59,10 @@ If you record a failed or cancelled payment, send its final state once. Linkrunn ```text user_id: your attributed test user + event_name: purchase_completed payment_id: lr_test_payment_001 amount: a small test amount - type: DEFAULT - status: PAYMENT_COMPLETED + currency: USD ``` Use a new `payment_id` every time you test a new transaction. @@ -86,22 +87,22 @@ If you record a failed or cancelled payment, send its final state once. Linkrunn - Send the actual transaction through your SDK's `capturePayment` method or the [Revenue Tracking API](/api-reference/revenue-tracking). + Send the actual transaction through your SDK's `trackEvent` method with a payment, or the [Revenue Tracking API](/api-reference/revenue-tracking). Leaving out `status` records the payment as `PAYMENT_COMPLETED`. ```bash - curl -X POST https://api.linkrunner.io/api/v1/capture-payment \ + curl -X POST https://api.linkrunner.io/api/v1/capture-event \ -H "Content-Type: application/json" \ -H "linkrunner-key: YOUR-SERVER-KEY" \ -d '{ + "event_name": "purchase_completed", "user_id": "YOUR_ATTRIBUTED_USER_ID", "payment_id": "lr_test_payment_001", "amount": 1, - "type": "DEFAULT", - "status": "PAYMENT_COMPLETED" + "currency": "USD" }' ``` - A successful API request returns HTTP `201`. + A successful API request returns HTTP `200` with `Event capture request received!`. A `400` with `amount, payment_id and currency must be sent together` means one of the three payment fields is missing. @@ -115,7 +116,7 @@ If you record a failed or cancelled payment, send its final state once. Linkrunn - Send the same payment again with the same `type` and `payment_id`. + Send the same payment again with the same event name, `payment_id`, and `status`. Expected result: the Events Log still contains one payment for that combination. Linkrunner records the first request and ignores later duplicates. @@ -135,18 +136,17 @@ If you record a failed or cancelled payment, send its final state once. Linkrunn | `add_to_cart` | Custom event | `event_name`, `user_id`, product data | | `checkout_started` | Custom event | `event_name`, `user_id` | | `purchase_initiated` as a funnel step | Custom event | `event_name`, `user_id` | -| Completed purchase | Payment | `user_id`, `payment_id`, `amount`, `type`, `status` | -| Subscription renewal with a charge | Payment | `type: SUBSCRIPTION_RENEWED` | -| Wallet top-up | Payment | `type: WALLET_TOPUP` | +| Completed purchase | Payment | `event_name`, `user_id`, `payment_id`, `amount`, `currency` | +| Subscription renewal with a charge | Payment | `event_name: subscription_renewed` | +| Wallet top-up | Payment | `event_name: wallet_topup` | | Failed payment that must be recorded | Payment | `status: PAYMENT_FAILED` | | Cancelled payment that must be recorded | Payment | `status: PAYMENT_CANCELLED` | -| Refund or void of a captured payment | Remove payment | The original `payment_id` | ## Test Meta Purchase forwarding Use this section only when the payment must reach Meta Commerce Manager. -1. In Linkrunner, map the payment type you send, such as `DEFAULT` or `FIRST_PAYMENT`, to Meta's standard `Purchase` event. +1. In Linkrunner, map the payment event name you send, such as `purchase_completed`, to Meta's standard `Purchase` event. 2. Include the required `event_data` fields for `Purchase`, including product IDs, `contents`, `content_type`, `value`, `currency`, `num_items`, and `order_id`. 3. Send a new payment with a new `payment_id`. 4. Check **Meta Commerce Manager → Events**. The real-time hit should appear within about 15 minutes. Full reporting can take a few days. @@ -155,25 +155,6 @@ Use this section only when the payment must reach Meta Commerce Manager. See [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager#understanding-event_data) for the full ecommerce payload. -## Test payment removal - -Remove one test payment using its `payment_id`: - -```bash -curl -X POST https://api.linkrunner.io/api/v1/remove-payment \ - -H "Content-Type: application/json" \ - -H "linkrunner-key: YOUR-SERVER-KEY" \ - -d '{ "payment_id": "lr_test_payment_001" }' -``` - - -The API does not define a partial-refund adjustment flow. Contact support before using `removePayment` for a partial refund. - - - -Do not test removal with only `user_id` unless you intend to remove every payment attributed to that user. - - ## Troubleshooting @@ -182,23 +163,23 @@ Do not test removal with only `user_id` unless you intend to remove every paymen - The app or backend sent it through `trackEvent` or `/capture-event`. Send authoritative transactions through `capturePayment` or `/capture-payment` instead. + The app or backend sent it without the payment fields. Send `amount`, `payment_id`, and `currency` together with the event. An `amount` inside `event_data` does not make the event a payment. - Check whether it reused the same `type` and `payment_id` as an earlier transaction. Linkrunner treats that combination as a duplicate. + Check whether it reused the same event name, `payment_id`, and `status` as an earlier transaction. Linkrunner treats that combination as a duplicate. Confirm the app and backend are not both sending the transaction with different payment IDs. Choose one source, or use the same stable payment ID for safe retries. - - Linkrunner keeps the first record for a `type` and `payment_id` combination. Send the final payment state once instead of sending initiated and completed states with the same combination. + + `status` is part of the deduplication key, so sending `PAYMENT_INITIATED` and then `PAYMENT_COMPLETED` with the same `payment_id` creates two records. Send the final payment state once. Only `PAYMENT_COMPLETED` payments are sent to ad networks. - Confirm the payment type is mapped to `Purchase`, the ecommerce payload includes every required field, and the test has had at least 15 minutes to reach Meta Events Manager. + Confirm the payment event name is mapped to `Purchase`, the payment `status` is `PAYMENT_COMPLETED`, the ecommerce payload includes every required field, and the test has had at least 15 minutes to reach Meta Events Manager. From b594d15bbce69e7c3758ea7d2203b93c0a2dface Mon Sep 17 00:00:00 2001 From: chetan Date: Mon, 12 Oct 2026 03:11:00 +0530 Subject: [PATCH 02/15] docs: add capturePayment migration guide and payment naming notes New page sdk/migrate-to-track-event covers the deprecation, the event naming rule (old payment types keep their mappings, new names need one), before/after code per platform, field mapping and the server API. SDK guides and the API reference link to it and state the naming rule. The revenue sharing sections now send revenue as the payment amount instead of an amount inside eventData. Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/event-capture.mdx | 21 +- api-reference/revenue-tracking.mdx | 4 +- docs.json | 3 +- ecommerce-manager/google-commerce-manager.mdx | 2 +- sdk-less/api-reference.mdx | 2 +- sdk/android.mdx | 45 +-- sdk/capacitor.mdx | 42 ++- sdk/cordova.mdx | 38 +- sdk/expo.mdx | 4 + sdk/flutter.mdx | 34 +- sdk/ios.mdx | 48 ++- sdk/migrate-to-track-event.mdx | 329 ++++++++++++++++++ sdk/react-native.mdx | 42 ++- sdk/unity.mdx | 8 +- 14 files changed, 508 insertions(+), 114 deletions(-) create mode 100644 sdk/migrate-to-track-event.mdx diff --git a/api-reference/event-capture.mdx b/api-reference/event-capture.mdx index c7f6b5ba..a6f957b9 100644 --- a/api-reference/event-capture.mdx +++ b/api-reference/event-capture.mdx @@ -78,6 +78,8 @@ POST: /capture-event Send `amount`, `payment_id`, and `currency` at the top level of the request, next to `event_name`, when the event is a payment. Linkrunner then stores the event as revenue, and `event_name` becomes the payment type you see in reports. Without these fields, the request is a custom event. +If `event_name` is one of the old payment types, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so existing event mappings, reports, and affiliate templates keep working. Any other name is stored exactly as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. See [Choose the event name](/sdk/migrate-to-track-event#choose-the-event-name) for the full list. + ```json { "event_name": "purchase_completed", @@ -144,25 +146,14 @@ Here are some common event names you might want to track: ## Revenue Sharing with Ad Networks -For a payment, send the [payment fields](#payment-fields). Linkrunner records the revenue and shares completed payments with ad networks. +Send revenue as a payment with the [payment fields](#payment-fields). Meta and Google receive the value and currency from the top-level `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. -For a custom event that is not a payment, you can still pass a value to ad networks like Google Ads and Meta by including an `amount` parameter as a number in `event_data`. This lets ad networks optimize campaigns on the value of the conversion, but Linkrunner does not record it as revenue: +Put product details in `event_data`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. -```json -{ - "event_name": "purchase_completed", - "event_data": { - "product_id": "ABC123", - "category": "electronics", - "amount": 149.99 - }, - "user_id": "user_12345" -} -``` +An `amount` inside `event_data` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). - For revenue sharing with ad networks to work properly, ensure the `amount` - parameter is passed as a number, not as a string. + `POST /capture-payment` is deprecated. It keeps working for existing integrations, but new integrations should send payments through `/capture-event`. See [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). ## Best Practices diff --git a/api-reference/revenue-tracking.mdx b/api-reference/revenue-tracking.mdx index a86fe285..7e9e9568 100644 --- a/api-reference/revenue-tracking.mdx +++ b/api-reference/revenue-tracking.mdx @@ -30,6 +30,8 @@ linkrunner-key: YOUR-SERVER-KEY Send the payment to the [capture-event](/api-reference/event-capture) endpoint with `amount`, `payment_id`, and `currency` at the top level. `event_name` becomes the payment type you see in reports, so use a name that describes the payment, such as `purchase_completed` or `subscription_renewed`. +If `event_name` is one of the old payment types, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so existing event mappings, reports, and affiliate templates keep working. Any other name is stored exactly as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. See [Choose the event name](/sdk/migrate-to-track-event#choose-the-event-name) for the full list. + ``` POST: /capture-event ``` @@ -85,7 +87,7 @@ These calls come from your server, so Linkrunner never sends your server's IP to 3. **401** Invalid server key - The older `POST /capture-payment` endpoint still works for existing integrations. New integrations should send payments through `/capture-event`. + `POST /capture-payment` is deprecated. It keeps working for existing integrations, but new integrations should send payments through `/capture-event`. See [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). ## Meta Ecommerce Events diff --git a/docs.json b/docs.json index 3a004539..2b3a64b0 100644 --- a/docs.json +++ b/docs.json @@ -34,7 +34,8 @@ "sdk/appbrew", "sdk/web", "sdk/unity", - "sdk/shopify" + "sdk/shopify", + "sdk/migrate-to-track-event" ] }, { diff --git a/ecommerce-manager/google-commerce-manager.mdx b/ecommerce-manager/google-commerce-manager.mdx index e1123025..80c6a246 100644 --- a/ecommerce-manager/google-commerce-manager.mdx +++ b/ecommerce-manager/google-commerce-manager.mdx @@ -221,7 +221,7 @@ The `content_ids` you send must exactly match the **ID (`offer_id`)** of the pro -The payment `amount` and the `value` field in `event_data` should be consistent. Both represent the total order value. Google reads `value` from `event_data` for conversion reporting. +The payment `amount` and the `value` field in `event_data` should be consistent. Both represent the total order value. For a purchase, Google receives the conversion value and currency from the payment `amount` and `currency`. ## Verifying Events in Google Ads diff --git a/sdk-less/api-reference.mdx b/sdk-less/api-reference.mdx index 9d705775..ac9f02f8 100644 --- a/sdk-less/api-reference.mdx +++ b/sdk-less/api-reference.mdx @@ -181,7 +181,7 @@ Tracks a custom event or a payment. Events are stored for all users, including o | --- | --- | --- | | `token` | string | **Required**. | | `event_name` | string | **Required**. For a payment, this is the payment type shown in reports. | -| `event_data` | object | Optional. Custom attributes. Must be a JSON object. On a custom event, add `amount` as a number to share a value with ad networks. | +| `event_data` | object | Optional. Custom attributes. Must be a JSON object. An `amount` here is not sent to ad networks as revenue. Send the payment fields instead. | | `install_instance_id` | string | The persisted UUID. | | `user_id` | string | Optional. | | `platform` | string | `"ANDROID"`. | diff --git a/sdk/android.mdx b/sdk/android.mdx index bf93fc35..6a1cbc85 100644 --- a/sdk/android.mdx +++ b/sdk/android.mdx @@ -526,7 +526,11 @@ The `EventPayment` constructor throws `IllegalArgumentException` when a value br To set a status, import `io.linkrunner.sdk.models.PaymentStatus` and pass it, for example `status = PaymentStatus.PAYMENT_INITIATED`. Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. - Apps on Android SDK 4.x keep using `capturePayment` and continue to work unchanged. To move to 5.0.0, see the migration steps in the [Android SDK changelog](https://github.com/linkrunner-labs/linkrunner-android/blob/main/CHANGELOG.md). + If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + + + + `capturePayment` is deprecated. Apps on Android SDK 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). ## Tracking Custom Events @@ -549,8 +553,7 @@ private fun trackEvent() { eventName = "purchase_initiated", // Event name eventData = mapOf( // Optional: Event data "product_id" to "12345", - "category" to "electronics", - "amount" to 99.99 // Include amount as a number for revenue sharing with ad networks like Google and Meta + "category" to "electronics" ), eventId = "order_12345" // Optional: Your own unique event identifier ) @@ -575,37 +578,35 @@ private fun trackEvent() { ### Revenue Sharing with Ad Networks -To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions: +Send revenue to ad networks such as Google Ads and Meta as a [payment](#tracking-payments). Meta and Google receive the value and currency from the payment `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. + +Put product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. ```kotlin -private fun trackPurchaseEvent() { +private fun trackPurchase() { CoroutineScope(Dispatchers.IO).launch { try { - val result = LinkRunner.getInstance().trackEvent( - eventName = "purchase_completed", + LinkRunner.getInstance().trackEvent( + eventName = "purchase", eventData = mapOf( - "product_id" to "12345", - "category" to "electronics", - "amount" to 149.99 // Revenue amount as a number + "content_ids" to listOf("product_123"), + "num_items" to 1, + "order_id" to "order_12345" + ), + payment = EventPayment( + amount = 149.99, // Order total, sent to ad networks as the value + paymentId = "order_12345", + currency = "USD" ) ) - - result.onSuccess { - println("Purchase event with revenue tracked successfully") - }.onFailure { error -> - println("Error tracking purchase event: ${error.message}") - } - } catch (e: Exception) { - println("Exception tracking purchase event: ${e.message}") + } catch (e: IllegalArgumentException) { + println("Invalid payment: ${e.message}") } } } ``` - - For revenue sharing with ad networks to work properly, ensure the `amount` parameter is passed as a number (Double - or Int), not as a string. - +An `amount` inside `eventData` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). ## Ecommerce Events diff --git a/sdk/capacitor.mdx b/sdk/capacitor.mdx index bb55d6cc..422e2cbb 100644 --- a/sdk/capacitor.mdx +++ b/sdk/capacitor.mdx @@ -402,7 +402,11 @@ The promise returned by `trackEvent` rejects when a value breaks these rules, an Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. In TypeScript, you can import the `EventPayment` and `PaymentStatus` types from `capacitor-linkrunner`. - Apps on `capacitor-linkrunner` 2.x keep using `capturePayment` and continue to work unchanged. To move to 3.0.0, see the migration steps in the [capacitor-linkrunner changelog](https://github.com/linkrunner-labs/capacitor-linkrunner/blob/main/CHANGELOG.md). + If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + + + + `capturePayment` is deprecated. Apps on `capacitor-linkrunner` 2.x keep using it and continue to work unchanged. To move to 3.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). ## Tracking Custom Events @@ -423,7 +427,7 @@ import linkrunner from "capacitor-linkrunner"; const trackEvent = async () => { await linkrunner.trackEvent( "purchase_initiated", // Event name - { product_id: "12345", category: "electronics", amount: 99.99 }, // Optional: Event data, include amount as a number for revenue sharing with ad networks like Google and Meta + { product_id: "12345", category: "electronics" }, // Optional: Event data "order_12345" // Optional: Your own unique event identifier (string or number) ); }; @@ -438,22 +442,34 @@ const trackEvent = async () => { ### Revenue Sharing with Ad Networks -To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions: +Send revenue to ad networks such as Google Ads and Meta as a [payment](#tracking-payments). Meta and Google receive the value and currency from the payment `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. + +Put product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. ```typescript -const trackPurchaseEvent = async () => { - await linkrunner.trackEvent("purchase_completed", { - product_id: "12345", - category: "electronics", - amount: 149.99, // Revenue amount as a number - }); +const trackPurchase = async () => { + try { + await linkrunner.trackEvent( + "purchase", + { + content_ids: ["product_123"], + num_items: 1, + order_id: "order_12345", + }, + undefined, // Optional: Event ID + { + amount: 149.99, // Order total, sent to ad networks as the value + paymentId: "order_12345", + currency: "USD", + } + ); + } catch (error) { + console.error("Error tracking payment:", error); + } }; ``` - - For revenue sharing with ad networks to work properly, ensure the `amount` parameter is passed as a number, not as a - string. - +An `amount` inside `eventData` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). ## Enhanced Privacy Controls diff --git a/sdk/cordova.mdx b/sdk/cordova.mdx index 37d564c4..529008a6 100644 --- a/sdk/cordova.mdx +++ b/sdk/cordova.mdx @@ -394,7 +394,11 @@ The promise returned by `trackEvent` rejects when a value breaks these rules, an Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. - Apps on `cordova-linkrunner` 1.x keep using `capturePayment` and continue to work unchanged. To move to 2.0.0, see the migration steps in the [cordova-linkrunner changelog](https://github.com/linkrunner-labs/cordova-linkrunner-sdk/blob/main/CHANGELOG.md). + If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + + + + `capturePayment` is deprecated. Apps on `cordova-linkrunner` 1.x keep using it and continue to work unchanged. To move to 2.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). ## Ecommerce Events @@ -512,7 +516,7 @@ Track custom events in your app: ```javascript linkrunner.trackEvent( "purchase_initiated", // Event name - { product_id: "12345", category: "electronics", amount: 99.99 }, // Optional: Event data, include amount as a number for revenue sharing with ad networks like Google and Meta + { product_id: "12345", category: "electronics" }, // Optional: Event data "order_12345" // Optional: Your own unique event identifier (string or number) ).then(function () { console.log("Event tracked"); @@ -528,22 +532,30 @@ linkrunner.trackEvent( ### Revenue Sharing with Ad Networks -To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions: +Send revenue to ad networks such as Google Ads and Meta as a [payment](#tracking-payments). Meta and Google receive the value and currency from the payment `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. + +Put product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. ```javascript -linkrunner.trackEvent("purchase_completed", { - product_id: "12345", - category: "electronics", - amount: 149.99 // Revenue amount as a number -}).then(function () { - console.log("Purchase event tracked"); +linkrunner.trackEvent( + "purchase", + { + content_ids: ["product_123"], + num_items: 1, + order_id: "order_12345" + }, + null, // Optional: Event ID + { + amount: 149.99, // Order total, sent to ad networks as the value + paymentId: "order_12345", + currency: "USD" + } +).catch(function (error) { + console.error("Error tracking payment:", error); }); ``` - - For revenue sharing with ad networks to work properly, ensure the `amount` parameter is passed as a number, not as a - string. - +An `amount` inside `eventData` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). ## Enhanced Privacy Controls diff --git a/sdk/expo.mdx b/sdk/expo.mdx index 189dd952..45bcc2b2 100644 --- a/sdk/expo.mdx +++ b/sdk/expo.mdx @@ -182,6 +182,10 @@ Since `expo-linkrunner` is a config plugin that prepares your project, the actua - Ecommerce Events - Enhanced privacy controls + + `capturePayment` and `removePayment` are deprecated and removed in `rn-linkrunner` 4.0.0. With `expo-linkrunner` 4.0.0, track payments with `trackEvent` and a `payment`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, your existing mappings keep working. Any other name needs a new mapping in the dashboard. See [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). + + ## Function Placement Guide Expo uses the `rn-linkrunner` runtime. The function links below open the implementation details in the React Native guide. diff --git a/sdk/flutter.mdx b/sdk/flutter.mdx index ef6823b7..a8ea0f3c 100644 --- a/sdk/flutter.mdx +++ b/sdk/flutter.mdx @@ -624,7 +624,11 @@ The `EventPayment` constructor throws `ArgumentError` when a value breaks these Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. - Apps on `linkrunner` 4.x keep using `capturePayment` and continue to work unchanged. To move to 5.0.0, see the migration steps in the [linkrunner changelog](https://pub.dev/packages/linkrunner/changelog). + If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + + + + `capturePayment` is deprecated. Apps on `linkrunner` 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). ## Ecommerce Events @@ -759,7 +763,6 @@ Future trackEvent() async { eventData: { // Optional: Event data 'product_id': '12345', 'category': 'electronics', - 'amount': 99.99, // Include amount as a number for revenue sharing with ad networks like Google and Meta }, eventId: 'order_12345', // Optional: Your own unique event identifier (String or num) ); @@ -779,30 +782,33 @@ Future trackEvent() async { ### Revenue Sharing with Ad Networks -To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions: +Send revenue to ad networks such as Google Ads and Meta as a [payment](#tracking-payments). Meta and Google receive the value and currency from the payment `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. + +Put product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. ```dart -Future trackPurchaseEvent() async { +Future trackPurchase() async { try { await LinkRunner().trackEvent( - eventName: 'purchase_completed', + eventName: 'purchase', eventData: { - 'product_id': '12345', - 'category': 'electronics', - 'amount': 149.99, // Revenue amount as a number + 'content_ids': ['product_123'], + 'num_items': 1, + 'order_id': 'order_12345', }, + payment: EventPayment( + amount: 149.99, // Order total, sent to ad networks as the value + paymentId: 'order_12345', + currency: 'USD', + ), ); - print('Purchase event with revenue tracked successfully'); } catch (e) { - print('Error tracking purchase event: $e'); + print('Error tracking payment: $e'); } } ``` - - For revenue sharing with ad networks to work properly, ensure the `amount` parameter is passed as a number (double - or int), not as a string. - +An `amount` inside `eventData` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). ## Enhanced Privacy Controls diff --git a/sdk/ios.mdx b/sdk/ios.mdx index 4e717fc0..ea421d8f 100644 --- a/sdk/ios.mdx +++ b/sdk/ios.mdx @@ -569,7 +569,11 @@ public enum PaymentStatus: String, Sendable { To set a status, pass it to the initializer, for example `status: .initiated`. Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. - Apps on LinkrunnerKit 4.x keep using `capturePayment` and continue to work unchanged. To move to 5.0.0, see the migration steps in the [iOS SDK changelog](https://github.com/linkrunner-labs/linkrunner-ios/blob/main/CHANGELOG.md). + If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + + + + `capturePayment` is deprecated. Apps on LinkrunnerKit 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). ## Tracking Custom Events @@ -590,8 +594,7 @@ func trackEvent() async { eventName: "purchase_initiated", // Event name eventData: [ // Optional: Event data "product_id": "12345", - "category": "electronics", - "amount": 99.99 // Include amount as a number for revenue sharing with ad networks like Google and Meta + "category": "electronics" ], eventId: "order_12345" // Optional: Your own unique event identifier ) @@ -607,26 +610,35 @@ func trackEvent() async { ### Revenue Sharing with Ad Networks -To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions: +Send revenue to ad networks such as Google Ads and Meta as a [payment](#tracking-payments). Meta and Google receive the value and currency from the payment `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. + +Put product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. ```swift -func trackPurchaseEvent() async { - await LinkrunnerSDK.shared.trackEvent( - eventName: "purchase_completed", - eventData: [ - "product_id": "12345", - "category": "electronics", - "amount": 149.99 // Revenue amount as a number - ] - ) - print("Purchase event with revenue tracked successfully") +func trackPurchase() async { + do { + let payment = try EventPayment( + amount: 149.99, // Order total, sent to ad networks as the value + paymentId: "order_12345", + currency: "USD" + ) + + await LinkrunnerSDK.shared.trackEvent( + eventName: "purchase", + eventData: [ + "content_ids": ["product_123"], + "num_items": 1, + "order_id": "order_12345" + ], + payment: payment + ) + } catch { + print("Invalid payment: \(error)") + } } ``` - - For revenue sharing with ad networks to work properly, ensure the `amount` parameter is passed as a number (Double - or Int), not as a string. - +An `amount` inside `eventData` is not sent to ad networks as revenue, but it still counts toward SKAN conversion values. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). ## Ecommerce Events diff --git a/sdk/migrate-to-track-event.mdx b/sdk/migrate-to-track-event.mdx new file mode 100644 index 00000000..77e35cdb --- /dev/null +++ b/sdk/migrate-to-track-event.mdx @@ -0,0 +1,329 @@ +--- +title: "Migrate from capturePayment to trackEvent" +sidebarTitle: "Migrate to trackEvent" +description: "Move payment tracking from capturePayment to trackEvent with a payment, and keep your reports and postback mappings working" +icon: "arrow-right-arrow-left" +--- + + + `capturePayment` and `removePayment` are deprecated and removed in the SDK versions below. Apps on older versions keep working unchanged. `removePayment` has no replacement: removing a payment is not supported in the new versions. + + +| Platform | Package | Last versions with `capturePayment` | `trackEvent` with a payment from | +| --- | --- | --- | --- | +| Android | `io.linkrunner:android-sdk` | 4.x | 5.0.0 | +| iOS | LinkrunnerKit | 4.x | 5.0.0 | +| React Native | `rn-linkrunner` | 3.x | 4.0.0 | +| Flutter | `linkrunner` | 4.x | 5.0.0 | +| Expo | `expo-linkrunner` | 3.x | 4.0.0, with `rn-linkrunner` 4.0.0 or later | +| Capacitor | `capacitor-linkrunner` | 2.x | 3.0.0 | +| Cordova | `cordova-linkrunner` | 1.x | 2.0.0 | +| Unity | [Unity bridges](/sdk/unity#track-payments) | Bridges built on Android SDK 4.x or LinkrunnerKit 4.x | Bridges built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 | + +## What changes + +- **One API for events and payments.** You record a payment by passing a `payment` to `trackEvent`. Without a `payment`, `trackEvent` records a custom event as before. +- **Any event name.** The event name becomes the payment type. You are no longer limited to a fixed list of types. +- **Currency is sent.** `currency` is now required with every payment. Older SDKs never sent a currency. +- **The user comes from the SDK.** You no longer pass `userId`. The SDK uses the user you set with `signup`, `setUserData`, or `setCustomerUserId`. + +## Choose the event name + +Linkrunner shows the event name as the payment type in reports and matches it exactly against your event mappings to ad networks and affiliate templates. Choose the name before you change any code. + +If the name is one of the old payment types, in any letter case, Linkrunner stores it as that uppercase type. Your existing mappings, reports, and affiliate templates keep working with no dashboard change. The old types are `FIRST_PAYMENT`, `SECOND_PAYMENT`, `TRIAL_STARTED`, `TRIAL_CANCELLED`, `WALLET_TOPUP`, `FUNDS_WITHDRAWAL`, `SUBSCRIPTION_CREATED`, `SUBSCRIPTION_RENEWED`, `SUBSCRIPTION_CANCELLED`, `SUBSCRIPTION_REFUNDED`, `SUBSCRIPTION_BILLING_ISSUE`, `DEFAULT`, `ONE_TIME`, and `RECURRING`. + +Any other name is stored exactly as you send it, letter case included. Add a mapping for it in the dashboard before it can reach ad networks. + +| You called `capturePayment` with | Pass as `eventName` | Stored as | Mapping change | +| --- | --- | --- | --- | +| `type: FIRST_PAYMENT` (or any old type) | `"FIRST_PAYMENT"` or `"first_payment"` | `FIRST_PAYMENT` | None | +| No `type` (the default) | `"DEFAULT"` or `"default"` | `DEFAULT` | None | +| Any type, and you want a new name | `"purchase"` | `purchase` | Add a mapping for `purchase` | + +A payment with the same `paymentId`, name, and status counts as one payment, so retries are safe. The same `paymentId` sent under two different names counts as two payments. Pick one name per payment and keep it. + +## Update your code + +The examples are fragments. Replace your `capturePayment` call with the `trackEvent` call in the same function, with the same coroutine or async context. They keep the old `FIRST_PAYMENT` type, so no mapping changes. Use your own amount, payment ID, and currency. + + + + Before (Android SDK 4.x): + + ```kotlin + val paymentData = CapturePaymentRequest( + paymentId = "payment_123", + userId = "user123", + amount = 99.99, + type = PaymentType.FIRST_PAYMENT, + status = PaymentStatus.PAYMENT_COMPLETED + ) + LinkRunner.getInstance().capturePayment(paymentData) + ``` + + After (Android SDK 5.0.0): + + ```kotlin + import io.linkrunner.sdk.models.EventPayment + import io.linkrunner.sdk.models.PaymentStatus + + try { + LinkRunner.getInstance().trackEvent( + eventName = "FIRST_PAYMENT", + payment = EventPayment( + amount = 99.99, + paymentId = "payment_123", + currency = "USD", + status = PaymentStatus.PAYMENT_COMPLETED + ) + ) + } catch (e: IllegalArgumentException) { + println("Invalid payment: ${e.message}") + } + ``` + + See [Tracking Payments](/sdk/android#tracking-payments) for the full reference. + + + Before (LinkrunnerKit 4.x): + + ```swift + await LinkrunnerSDK.shared.capturePayment( + amount: 99.99, + userId: "user123", + paymentId: "payment456", + type: .firstPayment, + status: .completed + ) + ``` + + After (LinkrunnerKit 5.0.0): + + ```swift + do { + let payment = try EventPayment( + amount: 99.99, + paymentId: "payment456", + currency: "USD", + status: .completed + ) + await LinkrunnerSDK.shared.trackEvent( + eventName: "FIRST_PAYMENT", + payment: payment + ) + } catch { + print("Invalid payment: \(error)") + } + ``` + + See [Tracking Payments](/sdk/ios#tracking-payments) for the full reference. + + + Before (`rn-linkrunner` 3.x): + + ```javascript + await linkrunner.capturePayment({ + amount: 100, + userId: "user123", + paymentId: "payment456", + type: "FIRST_PAYMENT", + status: "PAYMENT_COMPLETED", + }); + ``` + + After (`rn-linkrunner` 4.0.0): + + ```javascript + await linkrunner.trackEvent( + "FIRST_PAYMENT", // Event name, used as the payment type + undefined, // Optional: Event data + undefined, // Optional: Event ID + { + amount: 100, + paymentId: "payment456", + currency: "USD", + status: "PAYMENT_COMPLETED", + }, + ); + ``` + + See [Tracking Payments](/sdk/react-native#tracking-payments) for the full reference. + + + Before (`linkrunner` 4.x): + + ```dart + await LinkRunner().capturePayment( + capturePayment: LRCapturePayment( + amount: 99.99, + userId: 'user123', + paymentId: 'payment456', + type: PaymentType.FIRST_PAYMENT, + status: PaymentStatus.PAYMENT_COMPLETED, + ), + ); + ``` + + After (`linkrunner` 5.0.0): + + ```dart + await LinkRunner().trackEvent( + eventName: 'FIRST_PAYMENT', // Event name, used as the payment type + payment: EventPayment( + amount: 99.99, + paymentId: 'payment456', + currency: 'USD', + status: PaymentStatus.PAYMENT_COMPLETED, + ), + ); + ``` + + See [Tracking Payments](/sdk/flutter#tracking-payments) for the full reference. + + + Expo apps call `rn-linkrunner`, so the code change is the same as React Native. Upgrade `expo-linkrunner` to 4.0.0 and `rn-linkrunner` to 4.0.0 or later together, then follow the React Native tab. + + See [Tracking Payments](/sdk/react-native#tracking-payments) for the full reference. + + + Before (`capacitor-linkrunner` 2.x): + + ```typescript + await linkrunner.capturePayment({ + amount: 100, + userId: "user123", + paymentId: "payment456", + type: "FIRST_PAYMENT", + status: "PAYMENT_COMPLETED", + }); + ``` + + After (`capacitor-linkrunner` 3.0.0): + + ```typescript + await linkrunner.trackEvent( + "FIRST_PAYMENT", // Event name, used as the payment type + undefined, // Optional: Event data + undefined, // Optional: Event ID + { + amount: 100, + paymentId: "payment456", + currency: "USD", + status: "PAYMENT_COMPLETED", + } + ); + ``` + + See [Tracking Payments](/sdk/capacitor#tracking-payments) for the full reference. + + + Before (`cordova-linkrunner` 1.x): + + ```javascript + linkrunner.capturePayment({ + amount: 100, + userId: "user123", + paymentId: "payment456", + type: "FIRST_PAYMENT", + status: "PAYMENT_COMPLETED" + }); + ``` + + After (`cordova-linkrunner` 2.0.0): + + ```javascript + linkrunner.trackEvent( + "FIRST_PAYMENT", // Event name, used as the payment type + null, // Optional: Event data + null, // Optional: Event ID + { + amount: 100, + paymentId: "payment456", + currency: "USD", + status: "PAYMENT_COMPLETED" + } + ).catch(function (error) { + console.error("Error tracking payment:", error); + }); + ``` + + See [Tracking Payments](/sdk/cordova#tracking-payments) for the full reference. + + + Before (bridges built on native 4.x): + + ```csharp + LinkrunnerSDK.CapturePayment( + userId: "user_123", + amount: 9.99, + paymentId: "pay_abc123", + type: "FIRST_PAYMENT", + status: "PAYMENT_COMPLETED" + ); + ``` + + After (bridges built on native 5.0.0): + + ```csharp + var payment = new EventPayment( + amount: 9.99, + paymentId: "pay_abc123", + currency: "USD", + status: PaymentStatus.PAYMENT_COMPLETED + ); + LinkrunnerSDK.TrackEvent("FIRST_PAYMENT", payment: payment); + ``` + + Update your Android and iOS bridge code as shown in [Track Payments](/sdk/unity#track-payments). + + + +### Field mapping + +| `capturePayment` field | `trackEvent` field | +| --- | --- | +| `paymentId` | `payment.paymentId` | +| `amount` | `payment.amount` | +| `type` | `eventName`. See [Choose the event name](#choose-the-event-name) | +| `status` | `payment.status` | +| `userId` | Removed. The SDK uses the user from `signup`, `setUserData`, or `setCustomerUserId` | +| `eventData` | `eventData` | +| Not available | `payment.currency`, new and required | + +## Payment rules + +- **Required fields.** `amount`, `paymentId`, and `currency` are all required. `amount` must be 0 or greater, `paymentId` must not be blank, and `currency` must be a 3-letter ISO 4217 code such as `USD` or `INR`. +- **Validation.** The SDK rejects an invalid payment before sending it, so nothing reaches Linkrunner. Each SDK page lists the error your code receives. +- **Status.** `status` is optional and defaults to `PAYMENT_COMPLETED`. The values are `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, and `PAYMENT_CANCELLED`. +- **Postbacks.** Only `PAYMENT_COMPLETED` payments are sent to ad networks, and only for event names that have a mapping. + +## Revenue sent to ad networks + +Meta and Google receive the value and currency from the payment `amount` and `currency`. Send the order total as `amount`. Linkrunner does not add up item prices. + +Product details come from `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. See [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager) and [Google Commerce Manager](/ecommerce-manager/google-commerce-manager) for the fields. + +An `amount` inside `eventData` is not sent to ad networks as revenue. On iOS it still counts toward SKAN conversion values. + +Revenue is shared only when **Send Revenue** is turned on for the event's mapping. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). + +## removePayment + +`removePayment` is removed in the new SDK versions and has no replacement. Removing a payment is not supported going forward. + +## Server-side API + +If you send payments from your backend, use `POST /api/v1/capture-event`. It accepts `amount`, `payment_id`, `currency`, and `status` next to `event_name`, and the same naming rule applies. See [Events Capture API](/api-reference/event-capture#payment-fields). + +`POST /api/v1/capture-payment` is deprecated. It keeps working for existing integrations, but new integrations should use `/capture-event`. + +## Checklist before upgrading + +1. Pick an event name for each payment type you send today. Keep the old type name if you want your mappings, reports, and affiliate templates to work with no change. +2. For every new name, add a mapping in the dashboard for each ad network or affiliate that should receive it, and turn on **Send Revenue** where you share revenue. +3. Send `currency` with every payment, and send the order total as `amount`. +4. Remove `removePayment` calls. +5. Test with a debug build, then confirm the payment on the [Events](https://dashboard.linkrunner.io/dashboard/events) page under the expected name. Use [Test Custom Events and Payments](/testing/event-and-revenue-testing) for the full check. + +**Need help?** Contact [support@linkrunner.io](mailto:support@linkrunner.io). diff --git a/sdk/react-native.mdx b/sdk/react-native.mdx index 42800235..8c856d36 100644 --- a/sdk/react-native.mdx +++ b/sdk/react-native.mdx @@ -484,7 +484,11 @@ You can pass `eventData` and `eventId` in the same call, as with any other [cust Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. In TypeScript, you can import the `EventPayment` and `PaymentStatus` types from `rn-linkrunner`. - Apps on `rn-linkrunner` 3.x keep using `capturePayment` and continue to work unchanged. To move to 4.0.0, see the migration steps in the [rn-linkrunner changelog](https://github.com/RathodDarshil/rn-linkrunner/blob/main/CHANGELOG.md). + If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + + + + `capturePayment` is deprecated. Apps on `rn-linkrunner` 3.x keep using it and continue to work unchanged. To move to 4.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). ## Ecommerce Events @@ -614,7 +618,7 @@ Track custom events in your app: const trackEvent = async () => { await linkrunner.trackEvent( "purchase_initiated", // Event name - { product_id: "12345", category: "electronics", amount: 99.99 }, // Optional: Event data, include amount as a number for revenue sharing with ad networks like Google and Meta + { product_id: "12345", category: "electronics" }, // Optional: Event data "order_12345" // Optional: Your own unique event identifier (string or number) ); }; @@ -629,22 +633,34 @@ const trackEvent = async () => { ### Revenue Sharing with Ad Networks -To enable revenue sharing with ad networks like Google Ads and Meta, include an `amount` parameter as a number in your custom event data. This allows the ad networks to optimize campaigns based on the revenue value of conversions: +Send revenue to ad networks such as Google Ads and Meta as a [payment](#tracking-payments). Meta and Google receive the value and currency from the payment `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. + +Put product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. ```javascript -const trackPurchaseEvent = async () => { - await linkrunner.trackEvent("purchase_completed", { - product_id: "12345", - category: "electronics", - amount: 149.99, // Revenue amount as a number - }); +const trackPurchase = async () => { + try { + await linkrunner.trackEvent( + "purchase", + { + content_ids: ["product_123"], + num_items: 1, + order_id: "order_12345", + }, + undefined, // Optional: Event ID + { + amount: 149.99, // Order total, sent to ad networks as the value + paymentId: "order_12345", + currency: "USD", + }, + ); + } catch (error) { + console.error("Error tracking payment:", error); + } }; ``` - - For revenue sharing with ad networks to work properly, ensure the `amount` parameter is passed as a number, not as a - string. - +An `amount` inside `eventData` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). ## SDK Package Version diff --git a/sdk/unity.mdx b/sdk/unity.mdx index 4d1a76e7..c591e161 100644 --- a/sdk/unity.mdx +++ b/sdk/unity.mdx @@ -1376,7 +1376,7 @@ LinkrunnerSDK.TrackEvent("purchase", eventData, eventId: "purchase_abc123"); ``` -When tracking revenue-related events, the `amount` field must be a number (not a string) for ad network revenue optimization to work correctly. +An `amount` inside the event data is not sent to ad networks as revenue. To share revenue, send it as a payment. See [Track Payments](#track-payments). ### Track Payments @@ -1402,7 +1402,11 @@ If a value breaks these rules, the native SDK rejects the payment, nothing is se The payment is linked to the user you set with `Signup` or `SetUserData`, so call one of them first. -Bridges built against Android SDK 4.x or LinkrunnerKit 4.x keep using `capturePayment` and continue to work unchanged. See the [Android](https://github.com/linkrunner-labs/linkrunner-android/blob/main/CHANGELOG.md) and [iOS](https://github.com/linkrunner-labs/linkrunner-ios/blob/main/CHANGELOG.md) changelogs for the 5.0.0 migration. +If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + + + +`capturePayment` is deprecated. Bridges built against Android SDK 4.x or LinkrunnerKit 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). ### Push Token From e3127281e885ad058c1ac29b63b182d937ad2bad Mon Sep 17 00:00:00 2001 From: chetan Date: Mon, 12 Oct 2026 03:11:40 +0530 Subject: [PATCH 03/15] docs: note Flutter DEFAULT_PAYMENT maps to DEFAULT Co-Authored-By: Claude Opus 5.5 (1M context) --- sdk/migrate-to-track-event.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sdk/migrate-to-track-event.mdx b/sdk/migrate-to-track-event.mdx index 77e35cdb..4a47e93f 100644 --- a/sdk/migrate-to-track-event.mdx +++ b/sdk/migrate-to-track-event.mdx @@ -39,8 +39,13 @@ Any other name is stored exactly as you send it, letter case included. Add a map | --- | --- | --- | --- | | `type: FIRST_PAYMENT` (or any old type) | `"FIRST_PAYMENT"` or `"first_payment"` | `FIRST_PAYMENT` | None | | No `type` (the default) | `"DEFAULT"` or `"default"` | `DEFAULT` | None | +| Flutter `PaymentType.DEFAULT_PAYMENT` (the Flutter default) | `"DEFAULT"` | `DEFAULT` | None | | Any type, and you want a new name | `"purchase"` | `purchase` | Add a mapping for `purchase` | + + In Flutter, `DEFAULT_PAYMENT` was always stored as `DEFAULT`. Pass `"DEFAULT"`, not `"DEFAULT_PAYMENT"`. `DEFAULT_PAYMENT` is not an old payment type, so it would be stored as a new name and would not match your existing mappings. + + A payment with the same `paymentId`, name, and status counts as one payment, so retries are safe. The same `paymentId` sent under two different names counts as two payments. Pick one name per payment and keep it. ## Update your code From fbd90e2ffa4752f30e8439ee3a3ca3fe3a7421d1 Mon Sep 17 00:00:00 2001 From: chetan Date: Mon, 12 Oct 2026 03:42:00 +0530 Subject: [PATCH 04/15] docs: make trackPayment the main payment API and keep userId Each SDK guide now leads Tracking Payments with trackPayment (type renamed to eventName, currency required, userId still required, status defaults to PAYMENT_COMPLETED), with trackEvent plus a payment as a short alternative. Every payment example carries userId. The migration guide maps capturePayment to trackPayment field by field. The Unity bridges pass userId to the 5.0.0 EventPayment and the wrapper gains TrackPayment. API pages explain that user_id links a payment to your user. Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/event-capture.mdx | 3 +- api-reference/revenue-tracking.mdx | 4 +- ecommerce-manager/google-commerce-manager.mdx | 3 +- features/events-tab.mdx | 2 +- sdk/android.mdx | 92 ++++++---- sdk/capacitor.mdx | 64 ++++--- sdk/cordova.mdx | 64 ++++--- sdk/expo.mdx | 4 +- sdk/flutter.mdx | 64 ++++--- sdk/ios.mdx | 83 +++++---- sdk/migrate-to-track-event.mdx | 163 ++++++++---------- sdk/react-native.mdx | 62 ++++--- sdk/unity.mdx | 92 ++++++---- testing/event-and-revenue-testing.mdx | 2 +- 14 files changed, 416 insertions(+), 286 deletions(-) diff --git a/api-reference/event-capture.mdx b/api-reference/event-capture.mdx index a6f957b9..8fdfeca8 100644 --- a/api-reference/event-capture.mdx +++ b/api-reference/event-capture.mdx @@ -99,6 +99,7 @@ If `event_name` is one of the old payment types, such as `FIRST_PAYMENT`, `SUBSC - **`currency`** must be a 3-letter ISO 4217 code. Lowercase is accepted and stored in uppercase. - **`status`** accepts `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. If you leave it out, the payment is `PAYMENT_COMPLETED`. - **`event_data`**, if sent, must be a JSON object. +- **`user_id`** links the payment to your user. The SDKs always send it with a payment, so send it from your backend too. A payment identified only by `install_instance_id` is still accepted. Only `PAYMENT_COMPLETED` payments are sent to ad networks as revenue. An `amount` inside `event_data` does not make the event a payment. @@ -153,7 +154,7 @@ Put product details in `event_data`. Meta receives `content_ids`, `contents`, `c An `amount` inside `event_data` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). - `POST /capture-payment` is deprecated. It keeps working for existing integrations, but new integrations should send payments through `/capture-event`. See [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). + `POST /capture-payment` is deprecated. It keeps working for existing integrations, but new integrations should send payments through `/capture-event`. See [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). ## Best Practices diff --git a/api-reference/revenue-tracking.mdx b/api-reference/revenue-tracking.mdx index 7e9e9568..a566d562 100644 --- a/api-reference/revenue-tracking.mdx +++ b/api-reference/revenue-tracking.mdx @@ -55,7 +55,7 @@ POST: /capture-event | payment_id | string | **Required** for a payment. Your unique transaction ID | | currency | string | **Required** for a payment. 3-letter ISO 4217 code, for example `USD` or `INR` | | status | string | **Optional**. Defaults to `PAYMENT_COMPLETED` | -| user_id | string | **Required** unless `install_instance_id` is sent | +| user_id | string | **Required** unless `install_instance_id` is sent. Links the payment to your user. The SDKs always send it with a payment, so send it whenever you have it | | install_instance_id | string | **Required** unless `user_id` is sent. Linkrunner's ID for the install | | event_data | object | **Optional**. Additional data, such as ecommerce fields | | client_ip_address | string | **Optional**. See [End-user IP address](#end-user-ip-address) | @@ -87,7 +87,7 @@ These calls come from your server, so Linkrunner never sends your server's IP to 3. **401** Invalid server key - `POST /capture-payment` is deprecated. It keeps working for existing integrations, but new integrations should send payments through `/capture-event`. See [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). + `POST /capture-payment` is deprecated. It keeps working for existing integrations, but new integrations should send payments through `/capture-event`. See [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). ## Meta Ecommerce Events diff --git a/ecommerce-manager/google-commerce-manager.mdx b/ecommerce-manager/google-commerce-manager.mdx index 80c6a246..cd003cc8 100644 --- a/ecommerce-manager/google-commerce-manager.mdx +++ b/ecommerce-manager/google-commerce-manager.mdx @@ -169,7 +169,7 @@ await linkrunner.trackEvent( ### ecommerce_purchase -Fired when a user completes a purchase. Send it as a payment: pass `amount`, `paymentId`, and `currency` with the event so Linkrunner records the revenue. +Fired when a user completes a purchase. Send it as a payment: pass `amount`, `paymentId`, `currency`, and `userId` with the event so Linkrunner records the revenue. @@ -188,6 +188,7 @@ await linkrunner.trackEvent( amount: 2298.0, paymentId: "order_98765", currency: "INR", + userId: "user_456", }, ); ``` diff --git a/features/events-tab.mdx b/features/events-tab.mdx index 640ca44b..da08a513 100644 --- a/features/events-tab.mdx +++ b/features/events-tab.mdx @@ -152,7 +152,7 @@ If this sub-tab is empty, nothing has arrived yet. The empty state links to the empties every step below it. - The amount, currency, and payment ID come from the payment your app passes to `trackEvent`. Confirm your app + The amount, currency, and payment ID come from the payment your app sends with `trackPayment`. Confirm your app sends all three. See your platform's SDK guide, for example [React Native](/sdk/react-native). diff --git a/sdk/android.mdx b/sdk/android.mdx index 6a1cbc85..e81f9c24 100644 --- a/sdk/android.mdx +++ b/sdk/android.mdx @@ -461,58 +461,52 @@ private fun setIntegrationData() { ## Revenue Tracking - Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) or [`.setUserData`](#setting-user-data) first so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. + Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Pass your user ID as `userId` so each payment is linked to the user who paid. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. ### Tracking Payments -To record revenue, pass an `EventPayment` to `trackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires Android SDK 5.0.0 or later. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires Android SDK 5.0.0 or later. ```kotlin import io.linkrunner.sdk.LinkRunner -import io.linkrunner.sdk.models.EventPayment import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch private fun trackPurchase() { CoroutineScope(Dispatchers.IO).launch { - try { - val payment = EventPayment( - amount = 99.99, - paymentId = "payment_123", - currency = "USD" - ) - - val result = LinkRunner.getInstance().trackEvent( - eventName = "purchase", - payment = payment - ) + val result = LinkRunner.getInstance().trackPayment( + eventName = "purchase", + paymentId = "payment_123", + userId = "user_123", + amount = 99.99, + currency = "USD" + ) - result.onSuccess { - println("Payment event queued") - }.onFailure { error -> - println("Error tracking payment: ${error.message}") - } - } catch (e: IllegalArgumentException) { - println("Invalid payment: ${e.message}") + result.onSuccess { + println("Payment event queued") + }.onFailure { error -> + println("Error tracking payment: ${error.message}") } } } ``` -You can pass `eventData` and `eventId` in the same call, as with any other [custom event](#tracking-custom-events). +#### `trackPayment` Parameters -#### `EventPayment` Parameters +| Parameter | Type | Required | Description | +| ----------- | ------------------ | -------- | ----------- | +| `eventName` | `String` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | +| `paymentId` | `String` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `userId` | `String` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `amount` | `Double` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | Payment status. Defaults to `PAYMENT_COMPLETED`. | +| `eventData` | `Map` | No | Additional data, such as product details for [ecommerce events](#ecommerce-events). | +| `eventId` | `String` | No | Your own unique identifier for the event. | -| Parameter | Type | Required | Description | -| ----------- | --------------- | -------- | ----------- | -| `amount` | `Double` | Yes | Payment amount. Must be a finite number, 0 or greater. | -| `paymentId` | `String` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | -| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | Payment status. Defaults to `PAYMENT_COMPLETED` when you leave it out. | - -The `EventPayment` constructor throws `IllegalArgumentException` when a value breaks these rules, and nothing is sent. +When a value breaks these rules, `trackPayment` returns a failed `Result` with an `IllegalArgumentException`, and nothing is sent. #### Available Payment Statuses @@ -529,8 +523,32 @@ To set a status, import `io.linkrunner.sdk.models.PaymentStatus` and pass it, fo If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. +#### Track a payment with `trackEvent` + +You can also pass an `EventPayment` to `trackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields and records the same payment. The `EventPayment` constructor throws `IllegalArgumentException` for an invalid value. + +```kotlin +import io.linkrunner.sdk.models.EventPayment + +CoroutineScope(Dispatchers.IO).launch { + try { + LinkRunner.getInstance().trackEvent( + eventName = "purchase", + payment = EventPayment( + amount = 99.99, + paymentId = "payment_123", + currency = "USD", + userId = "user_123" + ) + ) + } catch (e: IllegalArgumentException) { + println("Invalid payment: ${e.message}") + } +} +``` + - `capturePayment` is deprecated. Apps on Android SDK 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). + `capturePayment` is deprecated. Apps on Android SDK 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). ## Tracking Custom Events @@ -540,7 +558,7 @@ To set a status, import `io.linkrunner.sdk.models.PaymentStatus` and pass it, fo - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, pass a payment to `.trackEvent`. See [Revenue Tracking](#revenue-tracking). + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, use [`trackPayment`](#tracking-payments). Track custom events in your app: @@ -596,7 +614,8 @@ private fun trackPurchase() { payment = EventPayment( amount = 149.99, // Order total, sent to ad networks as the value paymentId = "order_12345", - currency = "USD" + currency = "USD", + userId = "user_123" ) ) } catch (e: IllegalArgumentException) { @@ -700,7 +719,8 @@ private fun trackPurchase() { payment = EventPayment( amount = 49.99, paymentId = "payment_456", - currency = "USD" + currency = "USD", + userId = "user_123" ) ) } catch (e: IllegalArgumentException) { @@ -952,7 +972,7 @@ Connect Firebase Cloud Messaging (FCM) with Linkrunner | [`LinkRunner.getInstance().setAdditionalData`](/sdk/android#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`LinkRunner.getInstance().setUserData`](/sdk/android#setting-user-data) | Optional | Authentication or profile update logic | After `signup`, when additional user details become available | | [`LinkRunner.getInstance().trackEvent`](/sdk/android#tracking-custom-events) | Optional | Throughout app | When specific user actions occur, including [ecommerce events](/sdk/android#ecommerce-events) | -| [`LinkRunner.getInstance().trackEvent` with `payment`](/sdk/android#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/android#ecommerce-events) | +| [`LinkRunner.getInstance().trackPayment`](/sdk/android#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/android#ecommerce-events) | | [`LinkRunner.getInstance().setPushToken`](/sdk/android#uninstall-tracking) | Optional | Push notification setup | When FCM token is available | | [`LinkRunner.getInstance().setConsent`](/sdk/android#google-ads-consent) | Optional | App initialization or consent flow | Before `init`, and again when consent changes | | [`LinkRunner.getInstance().enablePIIHashing`](/sdk/android#enhanced-privacy-controls) | Optional | Privacy settings | When you want to enable PII hashing | diff --git a/sdk/capacitor.mdx b/sdk/capacitor.mdx index 422e2cbb..09b28df4 100644 --- a/sdk/capacitor.mdx +++ b/sdk/capacitor.mdx @@ -349,46 +349,45 @@ This method allows you to connect user identities across different analytics and ## Revenue Tracking - Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) or [`.setUserData`](#setting-user-data) first so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. + Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Pass your user ID as `userId` so each payment is linked to the user who paid. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. ### Tracking Payments -To record revenue, pass a `payment` as the fourth argument to `trackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `capacitor-linkrunner` 3.0.0 or later. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `capacitor-linkrunner` 3.0.0 or later. -```typescript +```javascript import linkrunner from "capacitor-linkrunner"; const trackPurchase = async () => { try { - await linkrunner.trackEvent( - "purchase", // Event name, used as the payment type - { plan: "pro" }, // Optional: Event data - undefined, // Optional: Event ID - { - amount: 499, - paymentId: "order_123", - currency: "INR", - } - ); + await linkrunner.trackPayment({ + eventName: "purchase", + paymentId: "order_123", + userId: "user_123", + amount: 499, + currency: "INR", + }); } catch (error) { console.error("Error tracking payment:", error); } }; ``` -You can pass `eventData` and `eventId` in the same call, as with any other [custom event](#tracking-custom-events). - -#### `payment` Parameters +#### `trackPayment` Parameters | Parameter | Type | Required | Description | | ----------- | -------- | -------- | ----------- | -| `amount` | `number` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `eventName` | `string` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | | `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `userId` | `string` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `amount` | `number` | Yes | Payment amount. Must be a finite number, 0 or greater. | | `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `string` | No | Payment status. Defaults to `PAYMENT_COMPLETED` when you leave it out. | +| `status` | `string` | No | Payment status. Defaults to `PAYMENT_COMPLETED`. | +| `eventData` | `object` | No | Additional data, such as product details. | +| `eventId` | `string` | No | Your own unique identifier for the event. | -The promise returned by `trackEvent` rejects when a value breaks these rules, and nothing is sent. +The promise returned by `trackPayment` rejects when a value breaks these rules, and nothing is sent. #### Available Payment Statuses @@ -399,14 +398,32 @@ The promise returned by `trackEvent` rejects when a value breaks these rules, an | `PAYMENT_FAILED` | Payment failed | | `PAYMENT_CANCELLED` | Payment was cancelled | -Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. In TypeScript, you can import the `EventPayment` and `PaymentStatus` types from `capacitor-linkrunner`. +Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. In TypeScript, you can import the `TrackPaymentOptions`, `EventPayment`, and `PaymentStatus` types from `capacitor-linkrunner`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. +#### Track a payment with `trackEvent` + +You can also pass a `payment` as the fourth argument to `trackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields, follows the same rules, and records the same payment. + +```javascript +await linkrunner.trackEvent( + "purchase", // Event name, used as the payment type + undefined, // Optional: Event data + undefined, // Optional: Event ID + { + amount: 499, + paymentId: "order_123", + currency: "INR", + userId: "user_123", + } +); +``` + - `capturePayment` is deprecated. Apps on `capacitor-linkrunner` 2.x keep using it and continue to work unchanged. To move to 3.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). + `capturePayment` is deprecated. Apps on `capacitor-linkrunner` 2.x keep using it and continue to work unchanged. To move to 3.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). ## Tracking Custom Events @@ -416,7 +433,7 @@ Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. In TypeScrip - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, pass a [`payment`](#tracking-payments) to `.trackEvent`. + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, use [`trackPayment`](#tracking-payments). Track custom events in your app: @@ -461,6 +478,7 @@ const trackPurchase = async () => { amount: 149.99, // Order total, sent to ad networks as the value paymentId: "order_12345", currency: "USD", + userId: "user_123", } ); } catch (error) { @@ -653,7 +671,7 @@ Connect APNs with Linkrunner | [`linkrunner.setAdditionalData`](/sdk/capacitor#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`linkrunner.setUserData`](/sdk/capacitor#setting-user-data) | Optional | Profile update flow after `signup` | When additional user details become available | | [`linkrunner.trackEvent`](/sdk/capacitor#tracking-custom-events) | Optional | Throughout app | When specific user actions occur | -| [`linkrunner.trackEvent` with `payment`](/sdk/capacitor#tracking-payments) | Optional | Payment processing | When user makes a payment | +| [`linkrunner.trackPayment`](/sdk/capacitor#tracking-payments) | Optional | Payment processing | When user makes a payment | | [`linkrunner.setPushToken`](/sdk/capacitor#uninstall-tracking) | Optional | Push notification setup | When push token is available | | [`linkrunner.enablePIIHashing`](/sdk/capacitor#enhanced-privacy-controls) | Optional | Privacy configuration | When enabling PII hashing | diff --git a/sdk/cordova.mdx b/sdk/cordova.mdx index 529008a6..332efb0c 100644 --- a/sdk/cordova.mdx +++ b/sdk/cordova.mdx @@ -345,42 +345,40 @@ This method allows you to connect user identities across different analytics and ## Revenue Tracking - Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) or [`.setUserData`](#setting-user-data) first so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. + Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Pass your user ID as `userId` so each payment is linked to the user who paid. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. ### Tracking Payments -To record revenue, pass a `payment` as the fourth argument to `trackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `cordova-linkrunner` 2.0.0 or later. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `cordova-linkrunner` 2.0.0 or later. ```javascript -linkrunner.trackEvent( - "purchase", // Event name, used as the payment type - { plan: "pro" }, // Optional: Event data - null, // Optional: Event ID - { - amount: 499, - paymentId: "order_123", - currency: "INR" - } -).then(function () { +linkrunner.trackPayment({ + eventName: "purchase", + paymentId: "order_123", + userId: "user_123", + amount: 499, + currency: "INR" +}).then(function () { console.log("Payment tracked"); }).catch(function (error) { console.error("Error tracking payment:", error); }); ``` -You can pass `eventData` and `eventId` in the same call, as with any other [custom event](#tracking-custom-events). - -#### `payment` Parameters +#### `trackPayment` Parameters | Parameter | Type | Required | Description | | ----------- | -------- | -------- | ----------- | -| `amount` | `number` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `eventName` | `string` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | | `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `userId` | `string` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `amount` | `number` | Yes | Payment amount. Must be a finite number, 0 or greater. | | `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `string` | No | Payment status. Defaults to `PAYMENT_COMPLETED` when you leave it out. | +| `status` | `string` | No | Payment status. Defaults to `PAYMENT_COMPLETED`. | +| `eventData` | `object` | No | Additional data, such as product details for [ecommerce events](#ecommerce-events). | -The promise returned by `trackEvent` rejects when a value breaks these rules, and nothing is sent. +The promise returned by `trackPayment` rejects when a value breaks these rules, and nothing is sent. #### Available Payment Statuses @@ -397,8 +395,28 @@ Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. +#### Track a payment with `trackEvent` + +You can also pass a `payment` as the fourth argument to `trackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields, follows the same rules, and records the same payment. + +```javascript +linkrunner.trackEvent( + "purchase", // Event name, used as the payment type + null, // Optional: Event data + null, // Optional: Event ID + { + amount: 499, + paymentId: "order_123", + currency: "INR", + userId: "user_123" + } +).catch(function (error) { + console.error("Error tracking payment:", error); +}); +``` + - `capturePayment` is deprecated. Apps on `cordova-linkrunner` 1.x keep using it and continue to work unchanged. To move to 2.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). + `capturePayment` is deprecated. Apps on `cordova-linkrunner` 1.x keep using it and continue to work unchanged. To move to 2.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). ## Ecommerce Events @@ -490,6 +508,7 @@ linkrunner.trackEvent( amount: 49.99, paymentId: "payment_456", currency: "USD", + userId: "user_123", status: "PAYMENT_COMPLETED" } ).then(function () { @@ -508,7 +527,7 @@ linkrunner.trackEvent( - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, pass a [`payment`](#tracking-payments) to `.trackEvent`. + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, use [`trackPayment`](#tracking-payments). Track custom events in your app: @@ -548,7 +567,8 @@ linkrunner.trackEvent( { amount: 149.99, // Order total, sent to ad networks as the value paymentId: "order_12345", - currency: "USD" + currency: "USD", + userId: "user_123" } ).catch(function (error) { console.error("Error tracking payment:", error); @@ -742,7 +762,7 @@ Connect APNs with Linkrunner | [`linkrunner.setAdditionalData`](/sdk/cordova#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`linkrunner.setUserData`](/sdk/cordova#setting-user-data) | Optional | Profile update flow after `signup` | When additional user details become available | | [`linkrunner.trackEvent`](/sdk/cordova#tracking-custom-events) | Optional | Throughout app, including ecommerce flows | When specific user actions occur, including AddToCart or ViewContent events | -| [`linkrunner.trackEvent` with `payment`](/sdk/cordova#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | +| [`linkrunner.trackPayment`](/sdk/cordova#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | | [`linkrunner.setPushToken`](/sdk/cordova#uninstall-tracking) | Optional | Push notification setup | When push token is available | | [`linkrunner.enablePIIHashing`](/sdk/cordova#enhanced-privacy-controls) | Optional | Privacy configuration | When enabling PII hashing | diff --git a/sdk/expo.mdx b/sdk/expo.mdx index 45bcc2b2..2f069412 100644 --- a/sdk/expo.mdx +++ b/sdk/expo.mdx @@ -183,7 +183,7 @@ Since `expo-linkrunner` is a config plugin that prepares your project, the actua - Enhanced privacy controls - `capturePayment` and `removePayment` are deprecated and removed in `rn-linkrunner` 4.0.0. With `expo-linkrunner` 4.0.0, track payments with `trackEvent` and a `payment`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, your existing mappings keep working. Any other name needs a new mapping in the dashboard. See [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). + `capturePayment` and `removePayment` are deprecated and removed in `rn-linkrunner` 4.0.0. With `expo-linkrunner` 4.0.0, track payments with `trackPayment`. It takes the same fields as `capturePayment`, including `userId`, with `type` renamed to `eventName` and a new required `currency`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, your existing mappings keep working. Any other name needs a new mapping in the dashboard. See [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). ## Function Placement Guide @@ -200,7 +200,7 @@ Expo uses the `rn-linkrunner` runtime. The function links below open the impleme | [`linkrunner.setAdditionalData`](/sdk/react-native#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`linkrunner.setUserData`](/sdk/react-native#setting-user-data) | Optional | Profile update flow after `signup` | When additional user details become available | | [`linkrunner.trackEvent`](/sdk/react-native#tracking-custom-events) | Optional | Throughout app, including ecommerce flows | When specific user actions occur, including AddToCart or ViewContent events | -| [`linkrunner.trackEvent` with `payment`](/sdk/react-native#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | +| [`linkrunner.trackPayment`](/sdk/react-native#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | | [`linkrunner.setPushToken`](/sdk/react-native#uninstall-tracking) | Optional | Push notification setup | When FCM/APNs token is available | | [`linkrunner.setConsent`](/sdk/react-native#google-integrated-conversion-measurement-optional) | Optional | App initialization or consent flow | Before `init`, and again when consent changes | | [`linkrunner.enablePIIHashing`](/sdk/react-native#enhanced-privacy-controls) | Optional | Privacy configuration | When enabling PII hashing | diff --git a/sdk/flutter.mdx b/sdk/flutter.mdx index a8ea0f3c..d3e7bf23 100644 --- a/sdk/flutter.mdx +++ b/sdk/flutter.mdx @@ -573,25 +573,24 @@ This method allows you to connect user identities across different analytics and ## Revenue Tracking - Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) or [`.setUserData`](#setting-user-data) first so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. + Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Pass your user ID as `userId` so each payment is linked to the user who paid. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. ### Tracking Payments -To record revenue, pass an `EventPayment` to `trackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `linkrunner` 5.0.0 or later. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `linkrunner` 5.0.0 or later. ```dart import 'package:linkrunner/main.dart'; Future trackPurchase() async { try { - await LinkRunner().trackEvent( - eventName: 'purchase', // Event name, used as the payment type - payment: EventPayment( - amount: 499.0, - paymentId: 'order_123', - currency: 'INR', - ), + await LinkRunner().trackPayment( + eventName: 'purchase', + paymentId: 'order_123', + userId: 'user_123', + amount: 499.0, + currency: 'INR', ); } catch (e) { print('Error tracking payment: $e'); @@ -599,18 +598,19 @@ Future trackPurchase() async { } ``` -You can pass `eventData` and `eventId` in the same call, as with any other [custom event](#tracking-custom-events). - -#### `EventPayment` Parameters +#### `trackPayment` Parameters -| Parameter | Type | Required | Description | -| ----------- | --------------- | -------- | ----------- | -| `amount` | `double` | Yes | Payment amount. Must be a finite number, 0 or greater. | -| `paymentId` | `String` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | -| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | Payment status. Defaults to `PAYMENT_COMPLETED` when you leave it out. | +| Parameter | Type | Required | Description | +| ----------- | ---------------------- | -------- | ----------- | +| `eventName` | `String` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | +| `paymentId` | `String` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `userId` | `String` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `amount` | `double` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | Payment status. Defaults to `PaymentStatus.PAYMENT_COMPLETED`. | +| `eventData` | `Map` | No | Additional data, such as product details for [ecommerce events](#ecommerce-events). | -The `EventPayment` constructor throws `ArgumentError` when a value breaks these rules, and nothing is sent. +`trackPayment` throws `ArgumentError` when a value breaks these rules, and nothing is sent. #### Available Payment Statuses @@ -624,11 +624,27 @@ The `EventPayment` constructor throws `ArgumentError` when a value breaks these Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. - If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. If you called `capturePayment` with no type or with `PaymentType.DEFAULT_PAYMENT`, pass `'DEFAULT'`. +#### Track a payment with `trackEvent` + +You can also pass an `EventPayment` to `trackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields and records the same payment. The `EventPayment` constructor throws `ArgumentError` for an invalid value. + +```dart +await LinkRunner().trackEvent( + eventName: 'purchase', // Event name, used as the payment type + payment: EventPayment( + amount: 499.0, + paymentId: 'order_123', + currency: 'INR', + userId: 'user_123', + ), +); +``` + - `capturePayment` is deprecated. Apps on `linkrunner` 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). + `capturePayment` is deprecated. Apps on `linkrunner` 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). ## Ecommerce Events @@ -731,6 +747,7 @@ Future trackPurchase() async { amount: 49.99, paymentId: 'payment_456', currency: 'USD', + userId: 'user_123', status: PaymentStatus.PAYMENT_COMPLETED, ), ); @@ -750,7 +767,7 @@ Future trackPurchase() async { - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, pass an [`EventPayment`](#tracking-payments) to `.trackEvent`. + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, use [`trackPayment`](#tracking-payments). Track custom events in your app: @@ -800,6 +817,7 @@ Future trackPurchase() async { amount: 149.99, // Order total, sent to ad networks as the value paymentId: 'order_12345', currency: 'USD', + userId: 'user_123', ), ); } catch (e) { @@ -1030,7 +1048,7 @@ Connect APNs with Linkrunner | [`LinkRunner().setAdditionalData`](/sdk/flutter#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`LinkRunner().setUserData`](/sdk/flutter#setting-user-data) | Optional | Authentication or profile update logic | After `signup`, when additional user details become available | | [`LinkRunner().trackEvent`](/sdk/flutter#tracking-custom-events) | Optional | Throughout app | When specific user actions occur, including [ecommerce events](/sdk/flutter#ecommerce-events) | -| [`LinkRunner().trackEvent` with `payment`](/sdk/flutter#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/flutter#ecommerce-events) | +| [`LinkRunner().trackPayment`](/sdk/flutter#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/flutter#ecommerce-events) | | [`LinkRunner().setPushToken`](/sdk/flutter#uninstall-tracking) | Optional | Push notification setup | When FCM (Android) or APNs (iOS) token is available | | [`LinkRunner().setConsent`](/sdk/flutter#google-integrated-conversion-measurement-optional) | Optional | App initialization or consent flow | Before `init`, and again when consent changes | | [`LinkRunner().enablePIIHashing`](/sdk/flutter#enhanced-privacy-controls) | Optional | Privacy settings | When you want to enable PII hashing | diff --git a/sdk/ios.mdx b/sdk/ios.mdx index ea421d8f..8844f355 100644 --- a/sdk/ios.mdx +++ b/sdk/ios.mdx @@ -514,46 +514,41 @@ func setAdditionalData() async { ## Revenue Tracking - Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) or [`.setUserData`](#setting-user-data) first so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. + Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Pass your user ID as `userId` so each payment is linked to the user who paid. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. ### Tracking Payments -To record revenue, pass an `EventPayment` to `trackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires LinkrunnerKit 5.0.0 or later. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires LinkrunnerKit 5.0.0 or later. ```swift import LinkrunnerKit func trackPurchase() async { - do { - let payment = try EventPayment( - amount: 99.99, - paymentId: "payment_123", - currency: "USD" - ) - - await LinkrunnerSDK.shared.trackEvent( - eventName: "purchase", - payment: payment - ) - } catch { - print("Invalid payment: \(error)") - } + await LinkrunnerSDK.shared.trackPayment( + eventName: "purchase", + paymentId: "payment_123", + userId: "user_123", + amount: 99.99, + currency: "USD" + ) } ``` -You can pass `eventData` and `eventId` in the same call, as with any other [custom event](#tracking-custom-events). +#### `trackPayment` Parameters -#### `EventPayment` Parameters +| Parameter | Type | Required | Description | +| ----------- | -------------------- | -------- | ----------- | +| `eventName` | `String` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | +| `paymentId` | `String` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `userId` | `String` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `amount` | `Double` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | Payment status. Defaults to `.completed` (`PAYMENT_COMPLETED`). | +| `eventData` | `SendableDictionary` | No | Additional data, such as product details for [ecommerce events](#ecommerce-events). | +| `eventId` | `String` | No | Your own unique identifier for the event. | -| Parameter | Type | Required | Description | -| ----------- | --------------- | -------- | ----------- | -| `amount` | `Double` | Yes | Payment amount. Must be a finite number, 0 or greater. | -| `paymentId` | `String` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | -| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | Payment status. Defaults to `PAYMENT_COMPLETED` when you leave it out. | - -The `EventPayment` initializer throws `EventPaymentError.invalidAmount`, `.invalidPaymentId`, or `.invalidCurrency` when a value breaks these rules, and nothing is sent. +`trackPayment` does not throw. When a value breaks these rules, the payment is not sent, and debug builds print the reason to the console. #### Available Payment Statuses @@ -566,14 +561,36 @@ public enum PaymentStatus: String, Sendable { } ``` -To set a status, pass it to the initializer, for example `status: .initiated`. Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. +To set a status, pass it to `trackPayment`, for example `status: .initiated`. Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. +#### Track a payment with `trackEvent` + +You can also pass an `EventPayment` to `trackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields and records the same payment. The `EventPayment` initializer throws `EventPaymentError.invalidAmount`, `.invalidPaymentId`, or `.invalidCurrency` for an invalid value. + +```swift +do { + let payment = try EventPayment( + amount: 99.99, + paymentId: "payment_123", + currency: "USD", + userId: "user_123" + ) + + await LinkrunnerSDK.shared.trackEvent( + eventName: "purchase", + payment: payment + ) +} catch { + print("Invalid payment: \(error)") +} +``` + - `capturePayment` is deprecated. Apps on LinkrunnerKit 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). + `capturePayment` is deprecated. Apps on LinkrunnerKit 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). ## Tracking Custom Events @@ -583,7 +600,7 @@ To set a status, pass it to the initializer, for example `status: .initiated`. O - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, pass a payment to `.trackEvent`. See [Revenue Tracking](#revenue-tracking). + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, use [`trackPayment`](#tracking-payments). Track custom events in your app: @@ -620,7 +637,8 @@ func trackPurchase() async { let payment = try EventPayment( amount: 149.99, // Order total, sent to ad networks as the value paymentId: "order_12345", - currency: "USD" + currency: "USD", + userId: "user_123" ) await LinkrunnerSDK.shared.trackEvent( @@ -712,7 +730,8 @@ func trackPurchase() async { let payment = try EventPayment( amount: 49.99, paymentId: "payment_456", - currency: "USD" + currency: "USD", + userId: "user_123" ) await LinkrunnerSDK.shared.trackEvent( @@ -886,7 +905,7 @@ Connect APNs with Linkrunner | [`LinkrunnerSDK.shared.setAdditionalData`](/sdk/ios#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`LinkrunnerSDK.shared.setUserData`](/sdk/ios#setting-user-data) | Optional | Authentication or profile update logic | After `signup`, when additional user details become available | | [`LinkrunnerSDK.shared.trackEvent`](/sdk/ios#tracking-custom-events) | Optional | Throughout app | When specific user actions occur, including [ecommerce events](/sdk/ios#ecommerce-events) | -| [`LinkrunnerSDK.shared.trackEvent` with `payment`](/sdk/ios#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/ios#ecommerce-events) | +| [`LinkrunnerSDK.shared.trackPayment`](/sdk/ios#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/ios#ecommerce-events) | | [`LinkrunnerSDK.shared.setPushToken`](/sdk/ios#uninstall-tracking) | Optional | Push notification setup | When APNs token is available | | [`LinkrunnerSDK.shared.setConsent`](/sdk/ios#consent) | Optional | App initialization or consent flow | Before `initialize`, and again when consent changes | | [`LinkrunnerSDK.shared.enablePIIHashing`](/sdk/ios#enhanced-privacy-controls) | Optional | Privacy settings | When you want to enable PII hashing | diff --git a/sdk/migrate-to-track-event.mdx b/sdk/migrate-to-track-event.mdx index 4a47e93f..73398f98 100644 --- a/sdk/migrate-to-track-event.mdx +++ b/sdk/migrate-to-track-event.mdx @@ -1,7 +1,7 @@ --- -title: "Migrate from capturePayment to trackEvent" -sidebarTitle: "Migrate to trackEvent" -description: "Move payment tracking from capturePayment to trackEvent with a payment, and keep your reports and postback mappings working" +title: "Migrate from capturePayment to trackPayment" +sidebarTitle: "Migrate to trackPayment" +description: "Move payment tracking from capturePayment to trackPayment, and keep your reports and postback mappings working" icon: "arrow-right-arrow-left" --- @@ -9,7 +9,7 @@ icon: "arrow-right-arrow-left" `capturePayment` and `removePayment` are deprecated and removed in the SDK versions below. Apps on older versions keep working unchanged. `removePayment` has no replacement: removing a payment is not supported in the new versions. -| Platform | Package | Last versions with `capturePayment` | `trackEvent` with a payment from | +| Platform | Package | Last versions with `capturePayment` | `trackPayment` from | | --- | --- | --- | --- | | Android | `io.linkrunner:android-sdk` | 4.x | 5.0.0 | | iOS | LinkrunnerKit | 4.x | 5.0.0 | @@ -22,10 +22,10 @@ icon: "arrow-right-arrow-left" ## What changes -- **One API for events and payments.** You record a payment by passing a `payment` to `trackEvent`. Without a `payment`, `trackEvent` records a custom event as before. -- **Any event name.** The event name becomes the payment type. You are no longer limited to a fixed list of types. +- **`trackPayment` replaces `capturePayment`.** It takes the same fields, so the change is small. +- **`type` becomes `eventName`.** The event name becomes the payment type. You are no longer limited to a fixed list of types. - **Currency is sent.** `currency` is now required with every payment. Older SDKs never sent a currency. -- **The user comes from the SDK.** You no longer pass `userId`. The SDK uses the user you set with `signup`, `setUserData`, or `setCustomerUserId`. +- **`userId` stays.** Pass the same user ID you passed to `capturePayment`. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`, as before. ## Choose the event name @@ -50,7 +50,7 @@ A payment with the same `paymentId`, name, and status counts as one payment, so ## Update your code -The examples are fragments. Replace your `capturePayment` call with the `trackEvent` call in the same function, with the same coroutine or async context. They keep the old `FIRST_PAYMENT` type, so no mapping changes. Use your own amount, payment ID, and currency. +The examples are fragments. Replace your `capturePayment` call with the `trackPayment` call in the same function, with the same coroutine or async context. They keep the old `FIRST_PAYMENT` type, so no mapping changes. Use your own amount, payment ID, user ID, and currency. @@ -70,25 +70,21 @@ The examples are fragments. Replace your `capturePayment` call with the `trackEv After (Android SDK 5.0.0): ```kotlin - import io.linkrunner.sdk.models.EventPayment import io.linkrunner.sdk.models.PaymentStatus - try { - LinkRunner.getInstance().trackEvent( - eventName = "FIRST_PAYMENT", - payment = EventPayment( - amount = 99.99, - paymentId = "payment_123", - currency = "USD", - status = PaymentStatus.PAYMENT_COMPLETED - ) - ) - } catch (e: IllegalArgumentException) { - println("Invalid payment: ${e.message}") + LinkRunner.getInstance().trackPayment( + eventName = "FIRST_PAYMENT", + paymentId = "payment_123", + userId = "user123", + amount = 99.99, + currency = "USD", + status = PaymentStatus.PAYMENT_COMPLETED + ).onFailure { error -> + println("Error tracking payment: ${error.message}") } ``` - See [Tracking Payments](/sdk/android#tracking-payments) for the full reference. + An invalid value returns a failed `Result` instead of throwing. See [Tracking Payments](/sdk/android#tracking-payments) for the full reference. Before (LinkrunnerKit 4.x): @@ -106,20 +102,14 @@ The examples are fragments. Replace your `capturePayment` call with the `trackEv After (LinkrunnerKit 5.0.0): ```swift - do { - let payment = try EventPayment( - amount: 99.99, - paymentId: "payment456", - currency: "USD", - status: .completed - ) - await LinkrunnerSDK.shared.trackEvent( - eventName: "FIRST_PAYMENT", - payment: payment - ) - } catch { - print("Invalid payment: \(error)") - } + await LinkrunnerSDK.shared.trackPayment( + eventName: "FIRST_PAYMENT", + paymentId: "payment456", + userId: "user123", + amount: 99.99, + currency: "USD", + status: .completed + ) ``` See [Tracking Payments](/sdk/ios#tracking-payments) for the full reference. @@ -140,17 +130,14 @@ The examples are fragments. Replace your `capturePayment` call with the `trackEv After (`rn-linkrunner` 4.0.0): ```javascript - await linkrunner.trackEvent( - "FIRST_PAYMENT", // Event name, used as the payment type - undefined, // Optional: Event data - undefined, // Optional: Event ID - { - amount: 100, - paymentId: "payment456", - currency: "USD", - status: "PAYMENT_COMPLETED", - }, - ); + await linkrunner.trackPayment({ + eventName: "FIRST_PAYMENT", + amount: 100, + userId: "user123", + paymentId: "payment456", + currency: "USD", + status: "PAYMENT_COMPLETED", + }); ``` See [Tracking Payments](/sdk/react-native#tracking-payments) for the full reference. @@ -173,14 +160,13 @@ The examples are fragments. Replace your `capturePayment` call with the `trackEv After (`linkrunner` 5.0.0): ```dart - await LinkRunner().trackEvent( - eventName: 'FIRST_PAYMENT', // Event name, used as the payment type - payment: EventPayment( - amount: 99.99, - paymentId: 'payment456', - currency: 'USD', - status: PaymentStatus.PAYMENT_COMPLETED, - ), + await LinkRunner().trackPayment( + eventName: 'FIRST_PAYMENT', + amount: 99.99, + userId: 'user123', + paymentId: 'payment456', + currency: 'USD', + status: PaymentStatus.PAYMENT_COMPLETED, ); ``` @@ -207,17 +193,14 @@ The examples are fragments. Replace your `capturePayment` call with the `trackEv After (`capacitor-linkrunner` 3.0.0): ```typescript - await linkrunner.trackEvent( - "FIRST_PAYMENT", // Event name, used as the payment type - undefined, // Optional: Event data - undefined, // Optional: Event ID - { - amount: 100, - paymentId: "payment456", - currency: "USD", - status: "PAYMENT_COMPLETED", - } - ); + await linkrunner.trackPayment({ + eventName: "FIRST_PAYMENT", + amount: 100, + userId: "user123", + paymentId: "payment456", + currency: "USD", + status: "PAYMENT_COMPLETED", + }); ``` See [Tracking Payments](/sdk/capacitor#tracking-payments) for the full reference. @@ -238,17 +221,14 @@ The examples are fragments. Replace your `capturePayment` call with the `trackEv After (`cordova-linkrunner` 2.0.0): ```javascript - linkrunner.trackEvent( - "FIRST_PAYMENT", // Event name, used as the payment type - null, // Optional: Event data - null, // Optional: Event ID - { - amount: 100, - paymentId: "payment456", - currency: "USD", - status: "PAYMENT_COMPLETED" - } - ).catch(function (error) { + linkrunner.trackPayment({ + eventName: "FIRST_PAYMENT", + amount: 100, + userId: "user123", + paymentId: "payment456", + currency: "USD", + status: "PAYMENT_COMPLETED" + }).catch(function (error) { console.error("Error tracking payment:", error); }); ``` @@ -271,35 +251,40 @@ The examples are fragments. Replace your `capturePayment` call with the `trackEv After (bridges built on native 5.0.0): ```csharp - var payment = new EventPayment( + LinkrunnerSDK.TrackPayment( + eventName: "FIRST_PAYMENT", + userId: "user_123", amount: 9.99, paymentId: "pay_abc123", currency: "USD", status: PaymentStatus.PAYMENT_COMPLETED ); - LinkrunnerSDK.TrackEvent("FIRST_PAYMENT", payment: payment); ``` - Update your Android and iOS bridge code as shown in [Track Payments](/sdk/unity#track-payments). + Update your Android and iOS bridge code and C# wrapper as shown in [Track Payments](/sdk/unity#track-payments). ### Field mapping -| `capturePayment` field | `trackEvent` field | +| `capturePayment` field | `trackPayment` field | | --- | --- | -| `paymentId` | `payment.paymentId` | -| `amount` | `payment.amount` | | `type` | `eventName`. See [Choose the event name](#choose-the-event-name) | -| `status` | `payment.status` | -| `userId` | Removed. The SDK uses the user from `signup`, `setUserData`, or `setCustomerUserId` | +| `paymentId` | `paymentId` | +| `userId` | `userId`, still required. An empty string falls back to the user saved by `signup` or `setUserData` | +| `amount` | `amount` | +| `status` | `status`, optional. Defaults to `PAYMENT_COMPLETED` | | `eventData` | `eventData` | -| Not available | `payment.currency`, new and required | +| Not available | `currency`, new and required | + +### Alternative: `trackEvent` with a payment + +If you already call `trackEvent` for the same action, you can attach the payment to it instead. Pass a payment object (`EventPayment` on Android, iOS, Flutter, and Unity) with `amount`, `paymentId`, `currency`, `userId`, and an optional `status`. It records the same payment as `trackPayment`. Each SDK page shows the call under **Tracking Payments**. ## Payment rules -- **Required fields.** `amount`, `paymentId`, and `currency` are all required. `amount` must be 0 or greater, `paymentId` must not be blank, and `currency` must be a 3-letter ISO 4217 code such as `USD` or `INR`. -- **Validation.** The SDK rejects an invalid payment before sending it, so nothing reaches Linkrunner. Each SDK page lists the error your code receives. +- **Required fields.** `eventName`, `paymentId`, `userId`, `amount`, and `currency` are all required. `amount` must be 0 or greater, `paymentId` must not be blank, and `currency` must be a 3-letter ISO 4217 code such as `USD` or `INR`. `userId` can be an empty string, in which case the SDK uses the user saved by `signup` or `setUserData`. +- **Validation.** The SDK rejects an invalid payment before sending it, so nothing reaches Linkrunner. Each SDK page explains how the failure is reported to your code. - **Status.** `status` is optional and defaults to `PAYMENT_COMPLETED`. The values are `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, and `PAYMENT_CANCELLED`. - **Postbacks.** Only `PAYMENT_COMPLETED` payments are sent to ad networks, and only for event names that have a mapping. @@ -319,7 +304,7 @@ Revenue is shared only when **Send Revenue** is turned on for the event's mappin ## Server-side API -If you send payments from your backend, use `POST /api/v1/capture-event`. It accepts `amount`, `payment_id`, `currency`, and `status` next to `event_name`, and the same naming rule applies. See [Events Capture API](/api-reference/event-capture#payment-fields). +If you send payments from your backend, use `POST /api/v1/capture-event`. It accepts `amount`, `payment_id`, `currency`, and `status` next to `event_name` and `user_id`, and the same naming rule applies. See [Events Capture API](/api-reference/event-capture#payment-fields). `POST /api/v1/capture-payment` is deprecated. It keeps working for existing integrations, but new integrations should use `/capture-event`. diff --git a/sdk/react-native.mdx b/sdk/react-native.mdx index 8c856d36..00a7774c 100644 --- a/sdk/react-native.mdx +++ b/sdk/react-native.mdx @@ -431,46 +431,44 @@ This method allows you to connect user identities across different analytics and ## Revenue Tracking - Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) or [`.setUserData`](#setting-user-data) first so payments are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. + Revenue is stored for all users, including organic ones. Payments from users with no matching click are stored without campaign attribution. Pass your user ID as `userId` so each payment is linked to the user who paid. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. ### Tracking Payments -To record revenue, pass a `payment` as the fourth argument to `trackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `rn-linkrunner` 4.0.0 or later. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `rn-linkrunner` 4.0.0 or later. ```javascript import linkrunner from "rn-linkrunner"; const trackPurchase = async () => { try { - await linkrunner.trackEvent( - "purchase", // Event name, used as the payment type - { plan: "pro" }, // Optional: Event data - undefined, // Optional: Event ID - { - amount: 499, - paymentId: "order_123", - currency: "INR", - }, - ); + await linkrunner.trackPayment({ + eventName: "purchase", + paymentId: "order_123", + userId: "user_123", + amount: 499, + currency: "INR", + }); } catch (error) { console.error("Error tracking payment:", error); } }; ``` -You can pass `eventData` and `eventId` in the same call, as with any other [custom event](#tracking-custom-events). - -#### `payment` Parameters +#### `trackPayment` Parameters | Parameter | Type | Required | Description | | ----------- | -------- | -------- | ----------- | -| `amount` | `number` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `eventName` | `string` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | | `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `userId` | `string` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `amount` | `number` | Yes | Payment amount. Must be a finite number, 0 or greater. | | `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `string` | No | Payment status. Defaults to `PAYMENT_COMPLETED` when you leave it out. | +| `status` | `string` | No | Payment status. Defaults to `PAYMENT_COMPLETED`. | +| `eventData` | `object` | No | Additional data, such as product details for [ecommerce events](#ecommerce-events). | -`trackEvent` throws when a value breaks these rules, and nothing is sent. +`trackPayment` throws when a value breaks these rules, and nothing is sent. #### Available Payment Statuses @@ -481,14 +479,32 @@ You can pass `eventData` and `eventId` in the same call, as with any other [cust | `PAYMENT_FAILED` | Payment failed | | `PAYMENT_CANCELLED` | Payment was cancelled | -Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. In TypeScript, you can import the `EventPayment` and `PaymentStatus` types from `rn-linkrunner`. +Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. In TypeScript, you can import the `TrackPaymentParams`, `EventPayment`, and `PaymentStatus` types from `rn-linkrunner`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. +#### Track a payment with `trackEvent` + +You can also pass a `payment` as the fourth argument to `trackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields, follows the same rules, and records the same payment. + +```javascript +await linkrunner.trackEvent( + "purchase", // Event name, used as the payment type + undefined, // Optional: Event data + undefined, // Optional: Event ID + { + amount: 499, + paymentId: "order_123", + currency: "INR", + userId: "user_123", + }, +); +``` + - `capturePayment` is deprecated. Apps on `rn-linkrunner` 3.x keep using it and continue to work unchanged. To move to 4.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). + `capturePayment` is deprecated. Apps on `rn-linkrunner` 3.x keep using it and continue to work unchanged. To move to 4.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). ## Ecommerce Events @@ -590,6 +606,7 @@ const trackPurchase = async () => { amount: 49.99, paymentId: "payment_456", currency: "USD", + userId: "user_123", status: "PAYMENT_COMPLETED", }, ); @@ -609,7 +626,7 @@ const trackPurchase = async () => { - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, pass a [`payment`](#tracking-payments) to `.trackEvent`. + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, use [`trackPayment`](#tracking-payments). Track custom events in your app: @@ -652,6 +669,7 @@ const trackPurchase = async () => { amount: 149.99, // Order total, sent to ad networks as the value paymentId: "order_12345", currency: "USD", + userId: "user_123", }, ); } catch (error) { @@ -853,7 +871,7 @@ Connect APNs with Linkrunner | [`linkrunner.setAdditionalData`](/sdk/react-native#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`linkrunner.setUserData`](/sdk/react-native#setting-user-data) | Optional | Profile update flow after `signup` | When additional user details become available | | [`linkrunner.trackEvent`](/sdk/react-native#tracking-custom-events) | Optional | Throughout app, including ecommerce flows | When specific user actions occur, including AddToCart or ViewContent events | -| [`linkrunner.trackEvent` with `payment`](/sdk/react-native#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | +| [`linkrunner.trackPayment`](/sdk/react-native#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | | [`linkrunner.setPushToken`](/sdk/react-native#uninstall-tracking) | Optional | Push notification setup | When FCM/APNs token is available | | [`linkrunner.setConsent`](/sdk/react-native#google-integrated-conversion-measurement-optional) | Optional | App initialization or consent flow | Before `init`, and again when consent changes | | [`linkrunner.enablePIIHashing`](/sdk/react-native#enhanced-privacy-controls) | Optional | Privacy configuration | When enabling PII hashing | diff --git a/sdk/unity.mdx b/sdk/unity.mdx index c591e161..9beacea7 100644 --- a/sdk/unity.mdx +++ b/sdk/unity.mdx @@ -155,15 +155,14 @@ public class LinkrunnerBridge { return map; } - // Returns null for an empty status so Linkrunner applies its default (PAYMENT_COMPLETED) + // Falls back to PAYMENT_COMPLETED for an empty or unknown status private static PaymentStatus parsePaymentStatus(String status) { - if (status == null) return null; + if (status == null) return PaymentStatus.PAYMENT_COMPLETED; switch (status) { case "PAYMENT_INITIATED": return PaymentStatus.PAYMENT_INITIATED; - case "PAYMENT_COMPLETED": return PaymentStatus.PAYMENT_COMPLETED; case "PAYMENT_FAILED": return PaymentStatus.PAYMENT_FAILED; case "PAYMENT_CANCELLED": return PaymentStatus.PAYMENT_CANCELLED; - default: return null; + default: return PaymentStatus.PAYMENT_COMPLETED; } } @@ -312,10 +311,11 @@ public class LinkrunnerBridge { public static void trackEventWithPayment(String eventName, String eventDataJson, String eventId, double amount, String paymentId, String currency, - String status) { + String userId, String status) { EventPayment payment; try { - payment = new EventPayment(amount, paymentId, currency, parsePaymentStatus(status)); + payment = new EventPayment(amount, paymentId, currency, + userId == null ? "" : userId, parsePaymentStatus(status)); } catch (IllegalArgumentException e) { Log.e(TAG, "Invalid payment: " + e.getMessage()); sendToUnity("OnTrackEventComplete", "error:" + e.getMessage()); @@ -520,10 +520,10 @@ private func parseUserData(_ dict: [String: Any]) -> UserData { ) } -// Returns nil for an empty status so Linkrunner applies its default (PAYMENT_COMPLETED) -private func parsePaymentStatus(_ str: String?) -> PaymentStatus? { - guard let str = str else { return nil } - return PaymentStatus(rawValue: str) +// Falls back to PAYMENT_COMPLETED for an empty or unknown status +private func parsePaymentStatus(_ str: String?) -> PaymentStatus { + guard let str = str else { return .completed } + return PaymentStatus(rawValue: str) ?? .completed } // MARK: - C-callable functions exposed to Unity @@ -631,6 +631,7 @@ func linkrunnerTrackEventWithPayment(_ eventName: UnsafePointer, _ amount: Double, _ paymentId: UnsafePointer?, _ currency: UnsafePointer?, + _ userId: UnsafePointer?, _ status: UnsafePointer?) { let name = String(cString: eventName) let eventData = toDict(eventDataJson) @@ -642,6 +643,7 @@ func linkrunnerTrackEventWithPayment(_ eventName: UnsafePointer, amount: amount, paymentId: toOptionalString(paymentId) ?? "", currency: toOptionalString(currency) ?? "", + userId: toOptionalString(userId) ?? "", status: parsePaymentStatus(toOptionalString(status)) ) } catch { @@ -851,7 +853,7 @@ public class LinkrunnerSDK : MonoBehaviour [DllImport("__Internal")] private static extern void _LinkrunnerTrackEventWithPayment(string eventName, string eventDataJson, string eventId, double amount, string paymentId, - string currency, string status); + string currency, string userId, string status); [DllImport("__Internal")] private static extern void _LinkrunnerSetPushToken(string pushToken); [DllImport("__Internal")] @@ -961,13 +963,13 @@ public class LinkrunnerSDK : MonoBehaviour { if (payment != null) { - string status = payment.status.HasValue ? payment.status.Value.ToString() : ""; + string status = payment.status.ToString(); #if UNITY_ANDROID && !UNITY_EDITOR Bridge.CallStatic("trackEventWithPayment", eventName, eventDataJson ?? "", eventId ?? "", - payment.amount, payment.paymentId ?? "", payment.currency ?? "", status); + payment.amount, payment.paymentId ?? "", payment.currency ?? "", payment.userId ?? "", status); #elif UNITY_IOS && !UNITY_EDITOR _LinkrunnerTrackEventWithPayment(eventName, eventDataJson, eventId, - payment.amount, payment.paymentId, payment.currency, status); + payment.amount, payment.paymentId, payment.currency, payment.userId ?? "", status); #else Debug.Log($"[Linkrunner] TrackEvent with payment: {eventName}, {payment.amount} {payment.currency}"); #endif @@ -983,6 +985,16 @@ public class LinkrunnerSDK : MonoBehaviour #endif } + /// + /// Track a payment. The event name becomes the payment type in reports and mappings. + /// An empty userId falls back to the user saved by Signup or SetUserData. + /// + public static void TrackPayment(string eventName, string paymentId, string userId, double amount, + string currency, PaymentStatus status = PaymentStatus.PAYMENT_COMPLETED, string eventDataJson = "") + { + TrackEvent(eventName, eventDataJson, "", new EventPayment(amount, paymentId, currency, userId, status)); + } + /// /// Set the push notification token (Firebase/APNs). /// @@ -1141,7 +1153,7 @@ public class LinkrunnerUserData } /// -/// Payment status. Leave it null to use the default, PAYMENT_COMPLETED. +/// Payment status. Defaults to PAYMENT_COMPLETED. /// public enum PaymentStatus { @@ -1159,13 +1171,16 @@ public class EventPayment public double amount; public string paymentId; public string currency; - public PaymentStatus? status; + public string userId; + public PaymentStatus status; - public EventPayment(double amount, string paymentId, string currency, PaymentStatus? status = null) + public EventPayment(double amount, string paymentId, string currency, string userId, + PaymentStatus status = PaymentStatus.PAYMENT_COMPLETED) { this.amount = amount; this.paymentId = paymentId; this.currency = currency; + this.userId = userId; this.status = status; } } @@ -1381,32 +1396,47 @@ An `amount` inside the event data is not sent to ad networks as revenue. To shar ### Track Payments -To record revenue, pass an `EventPayment` to `TrackEvent`. Linkrunner stores the event as a payment and uses the event name as its payment type, so you can use any name, such as `purchase` or `subscription_renewed`. Requires the bridges above, built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 or later. +Use `TrackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires the bridges above, built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 or later. ```csharp -var payment = new EventPayment(amount: 9.99, paymentId: "pay_abc123", currency: "USD"); -LinkrunnerSDK.TrackEvent("purchase", payment: payment); +LinkrunnerSDK.TrackPayment( + eventName: "purchase", + paymentId: "pay_abc123", + userId: "user_123", + amount: 9.99, + currency: "USD" +); ``` -The result arrives through `OnEventTracked`, like any other event. You can pass `eventDataJson` and `eventId` in the same call. +The result arrives through `OnEventTracked`, like any other event. -| Field | Type | Required | Description | -| ----------- | ---------------- | -------- | ----------- | -| `amount` | `double` | Yes | Payment amount. Must be a finite number, 0 or greater. | -| `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | -| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus?` | No | `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. Defaults to `PAYMENT_COMPLETED` when you leave it out. | +| Parameter | Type | Required | Description | +| --------------- | --------------- | -------- | ----------- | +| `eventName` | `string` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | +| `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | +| `userId` | `string` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `Signup` or `SetUserData`. | +| `amount` | `double` | Yes | Payment amount. Must be a finite number, 0 or greater. | +| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. Defaults to `PAYMENT_COMPLETED`. | +| `eventDataJson` | `string` | No | Additional data as a JSON string, such as product details. | If a value breaks these rules, the native SDK rejects the payment, nothing is sent, and `OnEventTracked` reports `false` with the error. Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. -The payment is linked to the user you set with `Signup` or `SetUserData`, so call one of them first. - If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. +#### Track a payment with `TrackEvent` + +You can also pass an `EventPayment` to `TrackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields and records the same payment. Use it when you also need an `eventId`. + +```csharp +var payment = new EventPayment(amount: 9.99, paymentId: "pay_abc123", currency: "USD", userId: "user_123"); +LinkrunnerSDK.TrackEvent("purchase", payment: payment); +``` + -`capturePayment` is deprecated. Bridges built against Android SDK 4.x or LinkrunnerKit 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackEvent](/sdk/migrate-to-track-event). +`capturePayment` is deprecated. Bridges built against Android SDK 4.x or LinkrunnerKit 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). ### Push Token @@ -1446,7 +1476,7 @@ LinkrunnerSDK.SetDisableAaidCollection(true); | [`GetAttributionData`](/sdk/unity#get-attribution-data) | Optional | After init completes | When you need campaign/deeplink info | | [`SetUserData`](/sdk/unity#set-user-data-on-app-open) | Optional | Profile update flow after `Signup` | When additional user details become available | | [`TrackEvent`](/sdk/unity#track-events) | Optional | Throughout the app | On user actions | -| [`TrackEvent` with `payment`](/sdk/unity#track-payments) | Optional | Payment flow | When payment succeeds | +| [`TrackPayment`](/sdk/unity#track-payments) | Optional | Payment flow | When payment succeeds | | [`SetPushToken`](/sdk/unity#push-token) | Optional | After token refresh | When push token changes | | [`SetConsent`](/sdk/unity#set-consent) | Optional | Before `Initialize` | At launch and whenever consent changes | | [`SetAdditionalData`](/sdk/unity#set-additional-data) | Optional | Integration setup | When the CleverTap ID is available | diff --git a/testing/event-and-revenue-testing.mdx b/testing/event-and-revenue-testing.mdx index 0bdd3de5..56bfd8ed 100644 --- a/testing/event-and-revenue-testing.mdx +++ b/testing/event-and-revenue-testing.mdx @@ -12,7 +12,7 @@ Use this guide after [SDK Integration Testing](/testing/integration-testing). It | What happened | App SDK | Server-side API | | --- | --- | --- | | A user action with no authoritative transaction | `trackEvent` | `POST /capture-event` | -| A payment with an amount and unique transaction ID | `trackEvent` with a payment (`amount`, `paymentId`, `currency`) | `POST /capture-event` with `amount`, `payment_id`, and `currency` | +| A payment with an amount and unique transaction ID | `trackPayment` (`eventName`, `paymentId`, `userId`, `amount`, `currency`) | `POST /capture-event` with `amount`, `payment_id`, and `currency` | When you attach a payment, the event name becomes the payment type in reports. See [Payment fields](/api-reference/event-capture#payment-fields) for the rules. From 8ed252bf8b72e7f48e126fba4eb7df212441399c Mon Sep 17 00:00:00 2001 From: chetan Date: Mon, 12 Oct 2026 03:51:11 +0530 Subject: [PATCH 05/15] docs: add a full payment tracking guide Add sdk/track-payments with how a payment flows from trackPayment to reports, ad networks and analytics, a fields table with why each field is required, a payment status section with a lifecycle walk-through, and examples for every SDK (one-step purchase with products, status lifecycle, subscription renewal with an old type name) plus how each SDK reports an invalid payment, ad network revenue rules, deduplication, verification steps and an FAQ. Migrate page: inline field comments, eventData product details and a with-status example in every platform tab, PaymentStatus constants for JS SDKs, and payment rules that say why each field is required. Link the guide from each SDK's Tracking Payments section, the revenue tracking API page and the events and payments testing page. Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/revenue-tracking.mdx | 4 +- docs.json | 1 + sdk/android.mdx | 2 +- sdk/capacitor.mdx | 2 +- sdk/cordova.mdx | 2 +- sdk/expo.mdx | 2 +- sdk/flutter.mdx | 2 +- sdk/ios.mdx | 2 +- sdk/migrate-to-track-event.mdx | 382 +++++++-- sdk/react-native.mdx | 2 +- sdk/track-payments.mdx | 1033 +++++++++++++++++++++++++ sdk/unity.mdx | 2 +- testing/event-and-revenue-testing.mdx | 4 +- 13 files changed, 1345 insertions(+), 95 deletions(-) create mode 100644 sdk/track-payments.mdx diff --git a/api-reference/revenue-tracking.mdx b/api-reference/revenue-tracking.mdx index a566d562..969b6474 100644 --- a/api-reference/revenue-tracking.mdx +++ b/api-reference/revenue-tracking.mdx @@ -10,6 +10,8 @@ icon: "money-bill" Use [Test Custom Events and Payments](/testing/event-and-revenue-testing) to verify event classification, payment fields, and deduplication. +To track payments from your app, and for how each status affects revenue and ad networks, see [Track payments](/sdk/track-payments). + ## Base URL ``` @@ -71,7 +73,7 @@ Send `amount`, `payment_id`, and `currency` together. If you send only one or tw - **PAYMENT_FAILED**: Payment attempt failed - **PAYMENT_CANCELLED**: Payment was cancelled -If `status` is omitted, it defaults to **PAYMENT_COMPLETED**. Only `PAYMENT_COMPLETED` payments are sent to ad networks. +If `status` is omitted, it defaults to **PAYMENT_COMPLETED**. Only `PAYMENT_COMPLETED` payments are sent to ad networks. See [Payment status](/sdk/track-payments#payment-status) for what each status does in reports and how to track a payment lifecycle. ### End-user IP address diff --git a/docs.json b/docs.json index 2b3a64b0..03e5849f 100644 --- a/docs.json +++ b/docs.json @@ -35,6 +35,7 @@ "sdk/web", "sdk/unity", "sdk/shopify", + "sdk/track-payments", "sdk/migrate-to-track-event" ] }, diff --git a/sdk/android.mdx b/sdk/android.mdx index e81f9c24..f3e3eda8 100644 --- a/sdk/android.mdx +++ b/sdk/android.mdx @@ -466,7 +466,7 @@ private fun setIntegrationData() { ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires Android SDK 5.0.0 or later. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires Android SDK 5.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. ```kotlin import io.linkrunner.sdk.LinkRunner diff --git a/sdk/capacitor.mdx b/sdk/capacitor.mdx index 09b28df4..1cc1614f 100644 --- a/sdk/capacitor.mdx +++ b/sdk/capacitor.mdx @@ -354,7 +354,7 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `capacitor-linkrunner` 3.0.0 or later. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `capacitor-linkrunner` 3.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. ```javascript import linkrunner from "capacitor-linkrunner"; diff --git a/sdk/cordova.mdx b/sdk/cordova.mdx index 332efb0c..b16a97f8 100644 --- a/sdk/cordova.mdx +++ b/sdk/cordova.mdx @@ -350,7 +350,7 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `cordova-linkrunner` 2.0.0 or later. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `cordova-linkrunner` 2.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. ```javascript linkrunner.trackPayment({ diff --git a/sdk/expo.mdx b/sdk/expo.mdx index 2f069412..fde58ec3 100644 --- a/sdk/expo.mdx +++ b/sdk/expo.mdx @@ -183,7 +183,7 @@ Since `expo-linkrunner` is a config plugin that prepares your project, the actua - Enhanced privacy controls - `capturePayment` and `removePayment` are deprecated and removed in `rn-linkrunner` 4.0.0. With `expo-linkrunner` 4.0.0, track payments with `trackPayment`. It takes the same fields as `capturePayment`, including `userId`, with `type` renamed to `eventName` and a new required `currency`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, your existing mappings keep working. Any other name needs a new mapping in the dashboard. See [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). + `capturePayment` and `removePayment` are deprecated and removed in `rn-linkrunner` 4.0.0. With `expo-linkrunner` 4.0.0, track payments with `trackPayment`. It takes the same fields as `capturePayment`, including `userId`, with `type` renamed to `eventName` and a new required `currency`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, your existing mappings keep working. Any other name needs a new mapping in the dashboard. See [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). For status, examples, and how payments reach ad networks, see [Track payments](/sdk/track-payments). ## Function Placement Guide diff --git a/sdk/flutter.mdx b/sdk/flutter.mdx index d3e7bf23..18fbe9dd 100644 --- a/sdk/flutter.mdx +++ b/sdk/flutter.mdx @@ -578,7 +578,7 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `linkrunner` 5.0.0 or later. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `linkrunner` 5.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. ```dart import 'package:linkrunner/main.dart'; diff --git a/sdk/ios.mdx b/sdk/ios.mdx index 8844f355..1f51bfd0 100644 --- a/sdk/ios.mdx +++ b/sdk/ios.mdx @@ -519,7 +519,7 @@ func setAdditionalData() async { ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires LinkrunnerKit 5.0.0 or later. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires LinkrunnerKit 5.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. ```swift import LinkrunnerKit diff --git a/sdk/migrate-to-track-event.mdx b/sdk/migrate-to-track-event.mdx index 73398f98..b1df370a 100644 --- a/sdk/migrate-to-track-event.mdx +++ b/sdk/migrate-to-track-event.mdx @@ -20,6 +20,8 @@ icon: "arrow-right-arrow-left" | Cordova | `cordova-linkrunner` | 1.x | 2.0.0 | | Unity | [Unity bridges](/sdk/unity#track-payments) | Bridges built on Android SDK 4.x or LinkrunnerKit 4.x | Bridges built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 | +This page covers the code change. For how payments work, what each status does, and examples for every case, see [Track payments](/sdk/track-payments). + ## What changes - **`trackPayment` replaces `capturePayment`.** It takes the same fields, so the change is small. @@ -50,7 +52,11 @@ A payment with the same `paymentId`, name, and status counts as one payment, so ## Update your code -The examples are fragments. Replace your `capturePayment` call with the `trackPayment` call in the same function, with the same coroutine or async context. They keep the old `FIRST_PAYMENT` type, so no mapping changes. Use your own amount, payment ID, user ID, and currency. +The examples are fragments. Replace your `capturePayment` call with the `trackPayment` call in the same function, with the same coroutine or async context. They keep the old `FIRST_PAYMENT` type, so no mapping changes. Use your own amount, payment ID, user ID, currency, and products. + +The **After** examples send product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`, and Google receives `content_ids` as item IDs. `amount` stays the order total. If you do not use [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager) or [Google Commerce Manager](/ecommerce-manager/google-commerce-manager), you can leave `eventData` out. + +In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by the package. Plain strings such as `"PAYMENT_COMPLETED"` also work. @@ -58,11 +64,11 @@ The examples are fragments. Replace your `capturePayment` call with the `trackPa ```kotlin val paymentData = CapturePaymentRequest( - paymentId = "payment_123", - userId = "user123", - amount = 99.99, - type = PaymentType.FIRST_PAYMENT, - status = PaymentStatus.PAYMENT_COMPLETED + paymentId = "payment_123", // your order ID + userId = "user123", // your user ID + amount = 99.99, // total the user paid + type = PaymentType.FIRST_PAYMENT, // becomes eventName in trackPayment + status = PaymentStatus.PAYMENT_COMPLETED // same values in trackPayment ) LinkRunner.getInstance().capturePayment(paymentData) ``` @@ -73,17 +79,38 @@ The examples are fragments. Replace your `capturePayment` call with the `trackPa import io.linkrunner.sdk.models.PaymentStatus LinkRunner.getInstance().trackPayment( - eventName = "FIRST_PAYMENT", - paymentId = "payment_123", - userId = "user123", - amount = 99.99, - currency = "USD", - status = PaymentStatus.PAYMENT_COMPLETED + eventName = "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings + paymentId = "payment_123", // your order ID, used to deduplicate + userId = "user123", // required, links the payment to your user + amount = 99.99, // order total the user paid + currency = "USD", // new, required: 3-letter ISO 4217 code + status = PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData = mapOf( + "content_ids" to listOf("sku_123"), + "contents" to listOf(mapOf("id" to "sku_123", "quantity" to 1, "item_price" to 99.99)), + "content_type" to "product", + "num_items" to 1, + "order_id" to "payment_123" + ) ).onFailure { error -> println("Error tracking payment: ${error.message}") } ``` + With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: + + ```kotlin + LinkRunner.getInstance().trackPayment( + eventName = "FIRST_PAYMENT", // same name as the rest of this payment + paymentId = "payment_123", // same order ID as the rest of this payment + userId = "user123", // required, links the payment to your user + amount = 99.99, // order total the user tried to pay + currency = "USD", // required, 3-letter ISO 4217 code + status = PaymentStatus.PAYMENT_FAILED // stored, not revenue, not sent to ad networks + ) + ``` + An invalid value returns a failed `Result` instead of throwing. See [Tracking Payments](/sdk/android#tracking-payments) for the full reference. @@ -91,11 +118,11 @@ The examples are fragments. Replace your `capturePayment` call with the `trackPa ```swift await LinkrunnerSDK.shared.capturePayment( - amount: 99.99, - userId: "user123", - paymentId: "payment456", - type: .firstPayment, - status: .completed + amount: 99.99, // total the user paid + userId: "user123", // your user ID + paymentId: "payment456", // your order ID + type: .firstPayment, // becomes eventName in trackPayment + status: .completed // same values in trackPayment ) ``` @@ -103,12 +130,33 @@ The examples are fragments. Replace your `capturePayment` call with the `trackPa ```swift await LinkrunnerSDK.shared.trackPayment( - eventName: "FIRST_PAYMENT", - paymentId: "payment456", - userId: "user123", - amount: 99.99, - currency: "USD", - status: .completed + eventName: "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings + paymentId: "payment456", // your order ID, used to deduplicate + userId: "user123", // required, links the payment to your user + amount: 99.99, // order total the user paid + currency: "USD", // new, required: 3-letter ISO 4217 code + status: .completed, // optional, default; only completed payments go to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: [ + "content_ids": ["sku_123"], + "contents": [["id": "sku_123", "quantity": 1, "item_price": 99.99]], + "content_type": "product", + "num_items": 1, + "order_id": "payment456" + ] + ) + ``` + + With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: + + ```swift + await LinkrunnerSDK.shared.trackPayment( + eventName: "FIRST_PAYMENT", // same name as the rest of this payment + paymentId: "payment456", // same order ID as the rest of this payment + userId: "user123", // required, links the payment to your user + amount: 99.99, // order total the user tried to pay + currency: "USD", // required, 3-letter ISO 4217 code + status: .failed // stored, not revenue, not sent to ad networks ) ``` @@ -119,24 +167,47 @@ The examples are fragments. Replace your `capturePayment` call with the `trackPa ```javascript await linkrunner.capturePayment({ - amount: 100, - userId: "user123", - paymentId: "payment456", - type: "FIRST_PAYMENT", - status: "PAYMENT_COMPLETED", + amount: 100, // total the user paid + userId: "user123", // your user ID + paymentId: "payment456", // your order ID + type: "FIRST_PAYMENT", // becomes eventName in trackPayment + status: "PAYMENT_COMPLETED", // same values in trackPayment }); ``` After (`rn-linkrunner` 4.0.0): + ```javascript + import linkrunner, { PaymentStatus } from "rn-linkrunner"; + + await linkrunner.trackPayment({ + eventName: "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings + paymentId: "payment456", // your order ID, used to deduplicate + userId: "user123", // required, links the payment to your user + amount: 100, // order total the user paid + currency: "USD", // new, required: 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["sku_123"], + contents: [{ id: "sku_123", quantity: 1, item_price: 100 }], + content_type: "product", + num_items: 1, + order_id: "payment456", + }, + }); + ``` + + With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: + ```javascript await linkrunner.trackPayment({ - eventName: "FIRST_PAYMENT", - amount: 100, - userId: "user123", - paymentId: "payment456", - currency: "USD", - status: "PAYMENT_COMPLETED", + eventName: "FIRST_PAYMENT", // same name as the rest of this payment + paymentId: "payment456", // same order ID as the rest of this payment + userId: "user123", // required, links the payment to your user + amount: 100, // order total the user tried to pay + currency: "USD", // required, 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_FAILED, // stored, not revenue, not sent to ad networks }); ``` @@ -148,11 +219,11 @@ The examples are fragments. Replace your `capturePayment` call with the `trackPa ```dart await LinkRunner().capturePayment( capturePayment: LRCapturePayment( - amount: 99.99, - userId: 'user123', - paymentId: 'payment456', - type: PaymentType.FIRST_PAYMENT, - status: PaymentStatus.PAYMENT_COMPLETED, + amount: 99.99, // total the user paid + userId: 'user123', // your user ID + paymentId: 'payment456', // your order ID + type: PaymentType.FIRST_PAYMENT, // becomes eventName in trackPayment + status: PaymentStatus.PAYMENT_COMPLETED, // same values in trackPayment ), ); ``` @@ -161,19 +232,90 @@ The examples are fragments. Replace your `capturePayment` call with the `trackPa ```dart await LinkRunner().trackPayment( - eventName: 'FIRST_PAYMENT', - amount: 99.99, - userId: 'user123', - paymentId: 'payment456', - currency: 'USD', - status: PaymentStatus.PAYMENT_COMPLETED, + eventName: 'FIRST_PAYMENT', // was "type"; becomes the payment type in reports and mappings + paymentId: 'payment456', // your order ID, used to deduplicate + userId: 'user123', // required, links the payment to your user + amount: 99.99, // order total the user paid + currency: 'USD', // new, required: 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + 'content_ids': ['sku_123'], + 'contents': [ + {'id': 'sku_123', 'quantity': 1, 'item_price': 99.99}, + ], + 'content_type': 'product', + 'num_items': 1, + 'order_id': 'payment456', + }, + ); + ``` + + With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: + + ```dart + await LinkRunner().trackPayment( + eventName: 'FIRST_PAYMENT', // same name as the rest of this payment + paymentId: 'payment456', // same order ID as the rest of this payment + userId: 'user123', // required, links the payment to your user + amount: 99.99, // order total the user tried to pay + currency: 'USD', // required, 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_FAILED, // stored, not revenue, not sent to ad networks ); ``` See [Tracking Payments](/sdk/flutter#tracking-payments) for the full reference. - Expo apps call `rn-linkrunner`, so the code change is the same as React Native. Upgrade `expo-linkrunner` to 4.0.0 and `rn-linkrunner` to 4.0.0 or later together, then follow the React Native tab. + Expo apps call `rn-linkrunner`, so the code change is the same as React Native. Upgrade `expo-linkrunner` to 4.0.0 and `rn-linkrunner` to 4.0.0 or later together. + + Before (`rn-linkrunner` 3.x): + + ```javascript + await linkrunner.capturePayment({ + amount: 100, // total the user paid + userId: "user123", // your user ID + paymentId: "payment456", // your order ID + type: "FIRST_PAYMENT", // becomes eventName in trackPayment + status: "PAYMENT_COMPLETED", // same values in trackPayment + }); + ``` + + After (`rn-linkrunner` 4.0.0): + + ```javascript + import linkrunner, { PaymentStatus } from "rn-linkrunner"; + + await linkrunner.trackPayment({ + eventName: "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings + paymentId: "payment456", // your order ID, used to deduplicate + userId: "user123", // required, links the payment to your user + amount: 100, // order total the user paid + currency: "USD", // new, required: 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["sku_123"], + contents: [{ id: "sku_123", quantity: 1, item_price: 100 }], + content_type: "product", + num_items: 1, + order_id: "payment456", + }, + }); + ``` + + With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: + + ```javascript + await linkrunner.trackPayment({ + eventName: "FIRST_PAYMENT", // same name as the rest of this payment + paymentId: "payment456", // same order ID as the rest of this payment + userId: "user123", // required, links the payment to your user + amount: 100, // order total the user tried to pay + currency: "USD", // required, 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_FAILED, // stored, not revenue, not sent to ad networks + }); + ``` See [Tracking Payments](/sdk/react-native#tracking-payments) for the full reference. @@ -182,24 +324,47 @@ The examples are fragments. Replace your `capturePayment` call with the `trackPa ```typescript await linkrunner.capturePayment({ - amount: 100, - userId: "user123", - paymentId: "payment456", - type: "FIRST_PAYMENT", - status: "PAYMENT_COMPLETED", + amount: 100, // total the user paid + userId: "user123", // your user ID + paymentId: "payment456", // your order ID + type: "FIRST_PAYMENT", // becomes eventName in trackPayment + status: "PAYMENT_COMPLETED", // same values in trackPayment }); ``` After (`capacitor-linkrunner` 3.0.0): + ```typescript + import linkrunner, { PaymentStatus } from "capacitor-linkrunner"; + + await linkrunner.trackPayment({ + eventName: "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings + paymentId: "payment456", // your order ID, used to deduplicate + userId: "user123", // required, links the payment to your user + amount: 100, // order total the user paid + currency: "USD", // new, required: 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["sku_123"], + contents: [{ id: "sku_123", quantity: 1, item_price: 100 }], + content_type: "product", + num_items: 1, + order_id: "payment456", + }, + }); + ``` + + With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: + ```typescript await linkrunner.trackPayment({ - eventName: "FIRST_PAYMENT", - amount: 100, - userId: "user123", - paymentId: "payment456", - currency: "USD", - status: "PAYMENT_COMPLETED", + eventName: "FIRST_PAYMENT", // same name as the rest of this payment + paymentId: "payment456", // same order ID as the rest of this payment + userId: "user123", // required, links the payment to your user + amount: 100, // order total the user tried to pay + currency: "USD", // required, 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_FAILED, // stored, not revenue, not sent to ad networks }); ``` @@ -210,11 +375,11 @@ The examples are fragments. Replace your `capturePayment` call with the `trackPa ```javascript linkrunner.capturePayment({ - amount: 100, - userId: "user123", - paymentId: "payment456", - type: "FIRST_PAYMENT", - status: "PAYMENT_COMPLETED" + amount: 100, // total the user paid + userId: "user123", // your user ID + paymentId: "payment456", // your order ID + type: "FIRST_PAYMENT", // becomes eventName in trackPayment + status: "PAYMENT_COMPLETED" // same values in trackPayment }); ``` @@ -222,12 +387,35 @@ The examples are fragments. Replace your `capturePayment` call with the `trackPa ```javascript linkrunner.trackPayment({ - eventName: "FIRST_PAYMENT", - amount: 100, - userId: "user123", - paymentId: "payment456", - currency: "USD", - status: "PAYMENT_COMPLETED" + eventName: "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings + paymentId: "payment456", // your order ID, used to deduplicate + userId: "user123", // required, links the payment to your user + amount: 100, // order total the user paid + currency: "USD", // new, required: 3-letter ISO 4217 code + status: linkrunner.PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["sku_123"], + contents: [{ id: "sku_123", quantity: 1, item_price: 100 }], + content_type: "product", + num_items: 1, + order_id: "payment456" + } + }).catch(function (error) { + console.error("Error tracking payment:", error); + }); + ``` + + With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: + + ```javascript + linkrunner.trackPayment({ + eventName: "FIRST_PAYMENT", // same name as the rest of this payment + paymentId: "payment456", // same order ID as the rest of this payment + userId: "user123", // required, links the payment to your user + amount: 100, // order total the user tried to pay + currency: "USD", // required, 3-letter ISO 4217 code + status: linkrunner.PaymentStatus.PAYMENT_FAILED // stored, not revenue, not sent to ad networks }).catch(function (error) { console.error("Error tracking payment:", error); }); @@ -240,24 +428,44 @@ The examples are fragments. Replace your `capturePayment` call with the `trackPa ```csharp LinkrunnerSDK.CapturePayment( - userId: "user_123", - amount: 9.99, - paymentId: "pay_abc123", - type: "FIRST_PAYMENT", - status: "PAYMENT_COMPLETED" + userId: "user_123", // your user ID + amount: 9.99, // total the user paid + paymentId: "pay_abc123", // your order ID + type: "FIRST_PAYMENT", // becomes eventName in TrackPayment + status: "PAYMENT_COMPLETED" // same values in TrackPayment ); ``` After (bridges built on native 5.0.0): ```csharp + // Product details: Meta receives these fields, Google receives content_ids as item IDs + string eventDataJson = + "{\"content_ids\":[\"sku_123\"]," + + "\"contents\":[{\"id\":\"sku_123\",\"quantity\":1,\"item_price\":9.99}]," + + "\"content_type\":\"product\",\"num_items\":1,\"order_id\":\"pay_abc123\"}"; + LinkrunnerSDK.TrackPayment( - eventName: "FIRST_PAYMENT", - userId: "user_123", - amount: 9.99, - paymentId: "pay_abc123", - currency: "USD", - status: PaymentStatus.PAYMENT_COMPLETED + eventName: "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings + paymentId: "pay_abc123", // your order ID, used to deduplicate + userId: "user_123", // required, links the payment to your user + amount: 9.99, // order total the user paid + currency: "USD", // new, required: 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks + eventDataJson: eventDataJson + ); + ``` + + With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: + + ```csharp + LinkrunnerSDK.TrackPayment( + eventName: "FIRST_PAYMENT", // same name as the rest of this payment + paymentId: "pay_abc123", // same order ID as the rest of this payment + userId: "user_123", // required, links the payment to your user + amount: 9.99, // order total the user tried to pay + currency: "USD", // required, 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_FAILED // stored, not revenue, not sent to ad networks ); ``` @@ -283,10 +491,15 @@ If you already call `trackEvent` for the same action, you can attach the payment ## Payment rules -- **Required fields.** `eventName`, `paymentId`, `userId`, `amount`, and `currency` are all required. `amount` must be 0 or greater, `paymentId` must not be blank, and `currency` must be a 3-letter ISO 4217 code such as `USD` or `INR`. `userId` can be an empty string, in which case the SDK uses the user saved by `signup` or `setUserData`. -- **Validation.** The SDK rejects an invalid payment before sending it, so nothing reaches Linkrunner. Each SDK page explains how the failure is reported to your code. -- **Status.** `status` is optional and defaults to `PAYMENT_COMPLETED`. The values are `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, and `PAYMENT_CANCELLED`. -- **Postbacks.** Only `PAYMENT_COMPLETED` payments are sent to ad networks, and only for event names that have a mapping. +Every field has a job. Leaving one out or getting it wrong breaks a specific part of your reporting. + +- **`eventName`** is required because it is the payment type in reports and the key Linkrunner matches against your ad network and affiliate mappings. +- **`paymentId`** is required because Linkrunner uses it to recognize a retried payment, so it must not be blank. Use your order or transaction ID. +- **`userId`** is required because it links the payment to your user for paying users, cohorts, and user-level reports. An empty string uses the user saved by `signup` or `setUserData`. +- **`amount`** is required because it is the revenue in reports and the value ad networks receive. It must be a finite number, 0 or greater. Send the order total. +- **`currency`** is required because Linkrunner stores the payment in it and ad networks receive it with the value. It must be a 3-letter ISO 4217 code such as `USD` or `INR`. +- **`status`** is optional and defaults to `PAYMENT_COMPLETED`. The values are `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, and `PAYMENT_CANCELLED`. Only `PAYMENT_COMPLETED` counts as revenue and is sent to ad networks, and only for event names that have a mapping. See [Payment status](/sdk/track-payments#payment-status). +- **Validation.** The SDK rejects an invalid payment before sending it, so nothing reaches Linkrunner. See [Handle invalid payments](/sdk/track-payments#handle-invalid-payments) for how each SDK reports it. ## Revenue sent to ad networks @@ -314,6 +527,7 @@ If you send payments from your backend, use `POST /api/v1/capture-event`. It acc 2. For every new name, add a mapping in the dashboard for each ad network or affiliate that should receive it, and turn on **Send Revenue** where you share revenue. 3. Send `currency` with every payment, and send the order total as `amount`. 4. Remove `removePayment` calls. -5. Test with a debug build, then confirm the payment on the [Events](https://dashboard.linkrunner.io/dashboard/events) page under the expected name. Use [Test Custom Events and Payments](/testing/event-and-revenue-testing) for the full check. +5. Decide which statuses you send. Leave `status` out for a one-step purchase, or follow the lifecycle in [Payment status](/sdk/track-payments#payment-status). +6. Test with a debug build, then confirm the payment on the [Events](https://dashboard.linkrunner.io/dashboard/events) page under the expected name. Use [Verify it works](/sdk/track-payments#verify-it-works) and [Test Custom Events and Payments](/testing/event-and-revenue-testing) for the full check. **Need help?** Contact [support@linkrunner.io](mailto:support@linkrunner.io). diff --git a/sdk/react-native.mdx b/sdk/react-native.mdx index 00a7774c..07a245ce 100644 --- a/sdk/react-native.mdx +++ b/sdk/react-native.mdx @@ -436,7 +436,7 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `rn-linkrunner` 4.0.0 or later. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `rn-linkrunner` 4.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. ```javascript import linkrunner from "rn-linkrunner"; diff --git a/sdk/track-payments.mdx b/sdk/track-payments.mdx new file mode 100644 index 00000000..11429e15 --- /dev/null +++ b/sdk/track-payments.mdx @@ -0,0 +1,1033 @@ +--- +title: "Track payments" +sidebarTitle: "Track payments" +description: "Record revenue with trackPayment, use payment status correctly, and send completed payments to your ad networks" +icon: "credit-card" +--- + +Use `trackPayment` whenever a user pays you: a purchase, a subscription charge or renewal, a wallet top-up. Linkrunner stores it as revenue, links it to the user and their install attribution, and forwards completed payments to the ad networks you mapped. + +`trackPayment` needs these SDK versions or later. On an older version, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). + +| Platform | Package | Minimum version | +| --- | --- | --- | +| Android | `io.linkrunner:android-sdk` | 5.0.0 | +| iOS | LinkrunnerKit | 5.0.0 | +| React Native | `rn-linkrunner` | 4.0.0 | +| Flutter | `linkrunner` | 5.0.0 | +| Expo | `expo-linkrunner` | 4.0.0, with `rn-linkrunner` 4.0.0 or later | +| Capacitor | `capacitor-linkrunner` | 3.0.0 | +| Cordova | `cordova-linkrunner` | 2.0.0 | +| Unity | [Unity bridges](/sdk/unity#track-payments) | Bridges built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 | + +Initialize the SDK before you track payments. See your platform's SDK guide for setup. + +## How it works + + + + You pass the event name, payment ID, user ID, amount, currency, and optionally a status and product details. + + + The SDK checks every required field before anything leaves the device. If a value is invalid, nothing is sent and your code gets an error (see [Handle invalid payments](#handle-invalid-payments)). + + + The SDK sends the payment to Linkrunner. An accepted request only means Linkrunner received it. Linkrunner then processes it: it stores the payment under your event name, links it to the user who paid, and attaches the campaign that user's install was attributed to. Payments from organic users are stored too, without a campaign. + + + The payment appears in the [Events Log](https://dashboard.linkrunner.io/dashboard/events) with its amount, currency, payment ID, and status. Completed payments count toward revenue and paying users in your reports. + + + If the event name is mapped for an ad network, such as Meta, Google, or TikTok, or for an affiliate, Linkrunner sends the payment there. Only `PAYMENT_COMPLETED` payments are sent. The value is included only when **Send Revenue** is on for that mapping. + + + Connected analytics integrations receive the payment with its status, whatever the status is. + + + +So a payment can be accepted but not yet in reports, in reports but not sent to an ad network (no mapping, or not completed), or sent without a value (**Send Revenue** off). [Verify it works](#verify-it-works) shows how to check each step. + +## Fields + +| Field | Required | What it is for | Why it matters | Example | +| --- | --- | --- | --- | --- | +| `eventName` | Yes | The name of this payment, such as `purchase` or `SUBSCRIPTION_RENEWED`. | It becomes the payment type in reports, and Linkrunner matches it exactly against your ad network and affiliate mappings. No mapping means the payment never reaches that network. | `"purchase"` | +| `paymentId` | Yes | Your order or transaction ID. Must not be blank. | Linkrunner uses it to recognize the same payment when it arrives again, so retries do not double your revenue. | `"order_8f2k1"` | +| `userId` | Yes | Your ID for the user who paid. | It links the payment to your user, so paying users, cohorts, and user-level reports are correct. If you pass an empty string, the SDK uses the user saved by `signup`, `setUserData`, or `setCustomerUserId`. | `"user_123"` | +| `amount` | Yes | The total the user paid. A finite number, 0 or greater. | This is the revenue in your reports and the value ad networks receive. Linkrunner does not add up item prices, so send the order total. | `499.0` | +| `currency` | Yes | The 3-letter ISO 4217 code of `amount`. Case-insensitive, sent in uppercase. | Linkrunner stores the payment in this currency, and ad networks receive it with the value. A wrong code makes the revenue wrong everywhere. | `"INR"` | +| `status` | No | Where the payment is in its lifecycle. Defaults to `PAYMENT_COMPLETED`. | Only completed payments count as revenue and reach ad networks. See [Payment status](#payment-status). | `PAYMENT_COMPLETED` | +| `eventData` | No | Extra details, such as the products in the order. | Meta and Google read product fields from it. An `amount` inside `eventData` is not revenue. | `{ "content_ids": ["sku_1"] }` | +| `eventId` | No | Your own identifier for the event. Android (Kotlin), iOS, and Capacitor only. | Use it when your own systems need an event-level reference. | `"evt_123"` | + +## Choose the event name + +Linkrunner shows the event name as the payment type and matches it exactly, letter case included, against your mappings. Use one name per kind of payment, such as `purchase` for one-time orders and `subscription_renewed` for renewals, and keep it the same across releases. + +The 14 old payment types, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, and `DEFAULT`, are matched in any letter case and stored as the uppercase type. If you already have mappings, reports, or affiliate templates for an old type, keep that name and nothing changes in the dashboard. Any other name is stored exactly as sent and needs its own mapping. See [Choose the event name](/sdk/migrate-to-track-event#choose-the-event-name) for the full list. + +## Payment status + +`status` tells Linkrunner whether money actually moved. Linkrunner stores every status, but only `PAYMENT_COMPLETED` counts as revenue and is sent to ad networks. This keeps an abandoned checkout or a declined card from showing up as revenue in your reports or training your ad campaigns on purchases that never happened. + +| Status | Use it when | Revenue and paying users | Ad networks | Analytics integrations | +| --- | --- | --- | --- | --- | +| `PAYMENT_INITIATED` | The user started paying, for example opened the checkout or payment sheet. | Stored, not added to revenue. | Not sent. | Sent. | +| `PAYMENT_COMPLETED` (default) | The charge succeeded and the money is yours. | Added to revenue and counts toward paying users. | Sent if the event name is mapped. | Sent. | +| `PAYMENT_FAILED` | The charge was declined or errored. | Not added to revenue. The amount is tracked separately as failed. | Not sent. | Sent. | +| `PAYMENT_CANCELLED` | The user backed out before paying. | Not added to revenue. The amount is tracked separately as cancelled. | Not sent. | Sent. | + +### Status is part of the payment's identity + +Linkrunner treats the same `paymentId`, event name, and status as one payment. So: + +- Sending `PAYMENT_COMPLETED` twice for `order_123` records it once. Retries are safe. +- Sending `PAYMENT_INITIATED` and then `PAYMENT_COMPLETED` for `order_123` records two entries. Only the completed one is revenue and reaches ad networks. +- Sending `order_123` under two different event names records two payments. Pick one name per payment. + +### A payment lifecycle + +Here is one checkout from start to finish, all with `paymentId` `order_123` and event name `purchase`: + +1. The user taps **Pay**. You send `PAYMENT_INITIATED`. It appears in the Events Log, revenue does not change, and ad networks get nothing. +2. The charge succeeds. You send `PAYMENT_COMPLETED`. Revenue goes up by the amount, the user counts as a paying user, and mapped ad networks receive the purchase. +3. If the charge fails instead, you send `PAYMENT_FAILED`. If the user closes the payment sheet, you send `PAYMENT_CANCELLED`. Either way, revenue does not change and ad networks get nothing. + +You only need the full lifecycle if you want to see drop-off between starting and finishing a payment. If your app only learns about the payment once it has succeeded, such as a store purchase callback, send one call and leave out `status`. It defaults to `PAYMENT_COMPLETED`. + +## Examples + +All examples use synthetic values. Replace them with your own order ID, user ID, amount, currency, and products. Each SDK reports an invalid payment differently. The examples handle it, and [Handle invalid payments](#handle-invalid-payments) summarizes the behavior. + +Every example sends product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id` for [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager#understanding-event_data), and Google receives `content_ids` as item IDs for [Google Commerce Manager](/ecommerce-manager/google-commerce-manager). `amount` stays the order total. + +In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by the package. Plain strings such as `"PAYMENT_COMPLETED"` also work. + +### One-step purchase with products + +Use this when your app learns about a payment only after it succeeds. `status` is left out, so the payment is `PAYMENT_COMPLETED`. + + + + Call it from your purchase success handler. `trackPayment` is a `suspend` function, so run it in a coroutine. + + ```kotlin + import io.linkrunner.sdk.LinkRunner + import kotlinx.coroutines.CoroutineScope + import kotlinx.coroutines.Dispatchers + import kotlinx.coroutines.launch + + fun onPurchaseSucceeded(orderId: String, userId: String) { + CoroutineScope(Dispatchers.IO).launch { + LinkRunner.getInstance().trackPayment( + eventName = "purchase", // becomes the payment type in reports and mappings + paymentId = orderId, // your order ID, used to deduplicate + userId = userId, // required, links the payment to your user + amount = 2298.0, // order total the user paid + currency = "INR", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData = mapOf( + "content_ids" to listOf("sku_blue_tshirt_m", "sku_black_jeans_32"), + "contents" to listOf( + mapOf("id" to "sku_blue_tshirt_m", "quantity" to 1, "item_price" to 799), + mapOf("id" to "sku_black_jeans_32", "quantity" to 1, "item_price" to 1499) + ), + "content_type" to "product", + "num_items" to 2, + "order_id" to orderId + ) + ).onFailure { error -> + println("Payment not tracked: ${error.message}") + } + } + } + ``` + + From Java, use `LinkrunnerJava.getInstance().trackPayment(...)` with the same arguments and a `LinkRunnerCallback` as the last argument. + + + Call it from your purchase success handler inside an `async` context. + + ```swift + import LinkrunnerKit + + func onPurchaseSucceeded(orderId: String, userId: String) async { + await LinkrunnerSDK.shared.trackPayment( + eventName: "purchase", // becomes the payment type in reports and mappings + paymentId: orderId, // your order ID, used to deduplicate + userId: userId, // required, links the payment to your user + amount: 2298.0, // order total the user paid + currency: "INR", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: [ + "content_ids": ["sku_blue_tshirt_m", "sku_black_jeans_32"], + "contents": [ + ["id": "sku_blue_tshirt_m", "quantity": 1, "item_price": 799], + ["id": "sku_black_jeans_32", "quantity": 1, "item_price": 1499] + ], + "content_type": "product", + "num_items": 2, + "order_id": orderId + ] + ) + } + ``` + + + ```javascript + import linkrunner from "rn-linkrunner"; + + export async function onPurchaseSucceeded(orderId, userId) { + try { + await linkrunner.trackPayment({ + eventName: "purchase", // becomes the payment type in reports and mappings + paymentId: orderId, // your order ID, used to deduplicate + userId, // required, links the payment to your user + amount: 2298, // order total the user paid + currency: "INR", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], + contents: [ + { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, + { id: "sku_black_jeans_32", quantity: 1, item_price: 1499 }, + ], + content_type: "product", + num_items: 2, + order_id: orderId, + }, + }); + } catch (error) { + console.error("Payment not tracked:", error); + } + } + ``` + + + ```dart + import 'package:linkrunner/main.dart'; + + Future onPurchaseSucceeded(String orderId, String userId) async { + try { + await LinkRunner().trackPayment( + eventName: 'purchase', // becomes the payment type in reports and mappings + paymentId: orderId, // your order ID, used to deduplicate + userId: userId, // required, links the payment to your user + amount: 2298.0, // order total the user paid + currency: 'INR', // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + 'content_ids': ['sku_blue_tshirt_m', 'sku_black_jeans_32'], + 'contents': [ + {'id': 'sku_blue_tshirt_m', 'quantity': 1, 'item_price': 799}, + {'id': 'sku_black_jeans_32', 'quantity': 1, 'item_price': 1499}, + ], + 'content_type': 'product', + 'num_items': 2, + 'order_id': orderId, + }, + ); + } on ArgumentError catch (e) { + print('Payment not tracked: ${e.message}'); + } + } + ``` + + + Expo apps call `rn-linkrunner`, so the code is the same as React Native. + + ```javascript + import linkrunner from "rn-linkrunner"; + + export async function onPurchaseSucceeded(orderId, userId) { + try { + await linkrunner.trackPayment({ + eventName: "purchase", // becomes the payment type in reports and mappings + paymentId: orderId, // your order ID, used to deduplicate + userId, // required, links the payment to your user + amount: 2298, // order total the user paid + currency: "INR", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], + contents: [ + { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, + { id: "sku_black_jeans_32", quantity: 1, item_price: 1499 }, + ], + content_type: "product", + num_items: 2, + order_id: orderId, + }, + }); + } catch (error) { + console.error("Payment not tracked:", error); + } + } + ``` + + + ```typescript + import linkrunner from "capacitor-linkrunner"; + + export async function onPurchaseSucceeded(orderId: string, userId: string) { + try { + await linkrunner.trackPayment({ + eventName: "purchase", // becomes the payment type in reports and mappings + paymentId: orderId, // your order ID, used to deduplicate + userId, // required, links the payment to your user + amount: 2298, // order total the user paid + currency: "INR", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], + contents: [ + { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, + { id: "sku_black_jeans_32", quantity: 1, item_price: 1499 }, + ], + content_type: "product", + num_items: 2, + order_id: orderId, + }, + }); + } catch (error) { + console.error("Payment not tracked:", error); + } + } + ``` + + + `linkrunner` is a global after the `deviceready` event, so no import is needed. + + ```javascript + function onPurchaseSucceeded(orderId, userId) { + return linkrunner.trackPayment({ + eventName: "purchase", // becomes the payment type in reports and mappings + paymentId: orderId, // your order ID, used to deduplicate + userId: userId, // required, links the payment to your user + amount: 2298, // order total the user paid + currency: "INR", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], + contents: [ + { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, + { id: "sku_black_jeans_32", quantity: 1, item_price: 1499 } + ], + content_type: "product", + num_items: 2, + order_id: orderId + } + }).catch(function (error) { + console.error("Payment not tracked:", error); + }); + } + ``` + + + Uses the `LinkrunnerSDK` wrapper from the [Unity guide](/sdk/unity#track-payments). Pass product details as a JSON string. + + ```csharp + using UnityEngine; + + public class Checkout : MonoBehaviour + { + public void OnPurchaseSucceeded(string orderId, string userId) + { + // Product details: Meta receives these fields, Google receives content_ids as item IDs + string eventDataJson = + "{\"content_ids\":[\"sku_blue_tshirt_m\",\"sku_black_jeans_32\"]," + + "\"contents\":[{\"id\":\"sku_blue_tshirt_m\",\"quantity\":1,\"item_price\":799}," + + "{\"id\":\"sku_black_jeans_32\",\"quantity\":1,\"item_price\":1499}]," + + "\"content_type\":\"product\",\"num_items\":2,\"order_id\":\"" + orderId + "\"}"; + + LinkrunnerSDK.TrackPayment( + eventName: "purchase", // becomes the payment type in reports and mappings + paymentId: orderId, // your order ID, used to deduplicate + userId: userId, // required, links the payment to your user + amount: 2298.0, // order total the user paid + currency: "INR", // required, 3-letter ISO 4217 code + eventDataJson: eventDataJson + ); + } + } + ``` + + + +### Payment lifecycle with status + +Use this when you want to see started, failed, and cancelled payments next to completed ones. Send `PAYMENT_INITIATED` when checkout starts, then one final status with the same `paymentId`, event name, and product details. Only the `PAYMENT_COMPLETED` call adds revenue and reaches ad networks. + + + + ```kotlin + import io.linkrunner.sdk.LinkRunner + import io.linkrunner.sdk.models.PaymentStatus + import kotlinx.coroutines.CoroutineScope + import kotlinx.coroutines.Dispatchers + import kotlinx.coroutines.launch + + enum class CheckoutResult { SUCCEEDED, FAILED, CANCELLED } + + fun trackCheckout(orderId: String, userId: String, status: PaymentStatus) { + CoroutineScope(Dispatchers.IO).launch { + LinkRunner.getInstance().trackPayment( + eventName = "purchase", // same name for every status of this payment + paymentId = orderId, // same ID for every status of this payment + userId = userId, // required, links the payment to your user + amount = 499.0, // order total the user pays + currency = "INR", // required, 3-letter ISO 4217 code + status = status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData = mapOf( + "content_ids" to listOf("sku_blue_tshirt_m"), + "contents" to listOf(mapOf("id" to "sku_blue_tshirt_m", "quantity" to 1, "item_price" to 499)), + "content_type" to "product", + "num_items" to 1, + "order_id" to orderId + ) + ).onFailure { error -> + println("Payment not tracked: ${error.message}") + } + } + } + + // When the user taps Pay + fun onCheckoutStarted(orderId: String, userId: String) = + trackCheckout(orderId, userId, PaymentStatus.PAYMENT_INITIATED) + + // When your payment provider returns a result + fun onCheckoutFinished(orderId: String, userId: String, result: CheckoutResult) = + trackCheckout( + orderId, + userId, + when (result) { + CheckoutResult.SUCCEEDED -> PaymentStatus.PAYMENT_COMPLETED + CheckoutResult.FAILED -> PaymentStatus.PAYMENT_FAILED + CheckoutResult.CANCELLED -> PaymentStatus.PAYMENT_CANCELLED + } + ) + ``` + + + ```swift + import LinkrunnerKit + + enum CheckoutResult { case succeeded, failed, cancelled } + + func trackCheckout(orderId: String, userId: String, status: PaymentStatus) async { + await LinkrunnerSDK.shared.trackPayment( + eventName: "purchase", // same name for every status of this payment + paymentId: orderId, // same ID for every status of this payment + userId: userId, // required, links the payment to your user + amount: 499.0, // order total the user pays + currency: "INR", // required, 3-letter ISO 4217 code + status: status, // only .completed counts as revenue and goes to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: [ + "content_ids": ["sku_blue_tshirt_m"], + "contents": [["id": "sku_blue_tshirt_m", "quantity": 1, "item_price": 499]], + "content_type": "product", + "num_items": 1, + "order_id": orderId + ] + ) + } + + // When the user taps Pay + func onCheckoutStarted(orderId: String, userId: String) async { + await trackCheckout(orderId: orderId, userId: userId, status: .initiated) + } + + // When your payment provider returns a result + func onCheckoutFinished(orderId: String, userId: String, result: CheckoutResult) async { + let status: PaymentStatus + switch result { + case .succeeded: status = .completed + case .failed: status = .failed + case .cancelled: status = .cancelled + } + await trackCheckout(orderId: orderId, userId: userId, status: status) + } + ``` + + + ```javascript + import linkrunner, { PaymentStatus } from "rn-linkrunner"; + + async function trackCheckout(orderId, userId, status) { + try { + await linkrunner.trackPayment({ + eventName: "purchase", // same name for every status of this payment + paymentId: orderId, // same ID for every status of this payment + userId, // required, links the payment to your user + amount: 499, // order total the user pays + currency: "INR", // required, 3-letter ISO 4217 code + status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["sku_blue_tshirt_m"], + contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], + content_type: "product", + num_items: 1, + order_id: orderId, + }, + }); + } catch (error) { + console.error("Payment not tracked:", error); + } + } + + // When the user taps Pay + export const onCheckoutStarted = (orderId, userId) => + trackCheckout(orderId, userId, PaymentStatus.PAYMENT_INITIATED); + + // When your payment provider returns "succeeded", "failed", or "cancelled" + export const onCheckoutFinished = (orderId, userId, result) => + trackCheckout( + orderId, + userId, + { + succeeded: PaymentStatus.PAYMENT_COMPLETED, + failed: PaymentStatus.PAYMENT_FAILED, + cancelled: PaymentStatus.PAYMENT_CANCELLED, + }[result], + ); + ``` + + + ```dart + import 'package:linkrunner/main.dart'; + + enum CheckoutResult { succeeded, failed, cancelled } + + Future trackCheckout(String orderId, String userId, PaymentStatus status) async { + try { + await LinkRunner().trackPayment( + eventName: 'purchase', // same name for every status of this payment + paymentId: orderId, // same ID for every status of this payment + userId: userId, // required, links the payment to your user + amount: 499.0, // order total the user pays + currency: 'INR', // required, 3-letter ISO 4217 code + status: status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + 'content_ids': ['sku_blue_tshirt_m'], + 'contents': [ + {'id': 'sku_blue_tshirt_m', 'quantity': 1, 'item_price': 499}, + ], + 'content_type': 'product', + 'num_items': 1, + 'order_id': orderId, + }, + ); + } on ArgumentError catch (e) { + print('Payment not tracked: ${e.message}'); + } + } + + // When the user taps Pay + Future onCheckoutStarted(String orderId, String userId) => + trackCheckout(orderId, userId, PaymentStatus.PAYMENT_INITIATED); + + // When your payment provider returns a result + Future onCheckoutFinished(String orderId, String userId, CheckoutResult result) => + trackCheckout(orderId, userId, const { + CheckoutResult.succeeded: PaymentStatus.PAYMENT_COMPLETED, + CheckoutResult.failed: PaymentStatus.PAYMENT_FAILED, + CheckoutResult.cancelled: PaymentStatus.PAYMENT_CANCELLED, + }[result]!); + ``` + + + Expo apps call `rn-linkrunner`, so the code is the same as React Native. + + ```javascript + import linkrunner, { PaymentStatus } from "rn-linkrunner"; + + async function trackCheckout(orderId, userId, status) { + try { + await linkrunner.trackPayment({ + eventName: "purchase", // same name for every status of this payment + paymentId: orderId, // same ID for every status of this payment + userId, // required, links the payment to your user + amount: 499, // order total the user pays + currency: "INR", // required, 3-letter ISO 4217 code + status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["sku_blue_tshirt_m"], + contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], + content_type: "product", + num_items: 1, + order_id: orderId, + }, + }); + } catch (error) { + console.error("Payment not tracked:", error); + } + } + + // When the user taps Pay + export const onCheckoutStarted = (orderId, userId) => + trackCheckout(orderId, userId, PaymentStatus.PAYMENT_INITIATED); + + // When your payment provider returns "succeeded", "failed", or "cancelled" + export const onCheckoutFinished = (orderId, userId, result) => + trackCheckout( + orderId, + userId, + { + succeeded: PaymentStatus.PAYMENT_COMPLETED, + failed: PaymentStatus.PAYMENT_FAILED, + cancelled: PaymentStatus.PAYMENT_CANCELLED, + }[result], + ); + ``` + + + ```typescript + import linkrunner, { PaymentStatus } from "capacitor-linkrunner"; + + type CheckoutResult = "succeeded" | "failed" | "cancelled"; + + async function trackCheckout(orderId: string, userId: string, status: PaymentStatus) { + try { + await linkrunner.trackPayment({ + eventName: "purchase", // same name for every status of this payment + paymentId: orderId, // same ID for every status of this payment + userId, // required, links the payment to your user + amount: 499, // order total the user pays + currency: "INR", // required, 3-letter ISO 4217 code + status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["sku_blue_tshirt_m"], + contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], + content_type: "product", + num_items: 1, + order_id: orderId, + }, + }); + } catch (error) { + console.error("Payment not tracked:", error); + } + } + + // When the user taps Pay + export const onCheckoutStarted = (orderId: string, userId: string) => + trackCheckout(orderId, userId, PaymentStatus.PAYMENT_INITIATED); + + // When your payment provider returns a result + export const onCheckoutFinished = (orderId: string, userId: string, result: CheckoutResult) => + trackCheckout(orderId, userId, { + succeeded: PaymentStatus.PAYMENT_COMPLETED, + failed: PaymentStatus.PAYMENT_FAILED, + cancelled: PaymentStatus.PAYMENT_CANCELLED, + }[result]); + ``` + + + ```javascript + function trackCheckout(orderId, userId, status) { + return linkrunner.trackPayment({ + eventName: "purchase", // same name for every status of this payment + paymentId: orderId, // same ID for every status of this payment + userId: userId, // required, links the payment to your user + amount: 499, // order total the user pays + currency: "INR", // required, 3-letter ISO 4217 code + status: status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["sku_blue_tshirt_m"], + contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], + content_type: "product", + num_items: 1, + order_id: orderId + } + }).catch(function (error) { + console.error("Payment not tracked:", error); + }); + } + + // When the user taps Pay + function onCheckoutStarted(orderId, userId) { + return trackCheckout(orderId, userId, linkrunner.PaymentStatus.PAYMENT_INITIATED); + } + + // When your payment provider returns "succeeded", "failed", or "cancelled" + function onCheckoutFinished(orderId, userId, result) { + var status = { + succeeded: linkrunner.PaymentStatus.PAYMENT_COMPLETED, + failed: linkrunner.PaymentStatus.PAYMENT_FAILED, + cancelled: linkrunner.PaymentStatus.PAYMENT_CANCELLED + }[result]; + return trackCheckout(orderId, userId, status); + } + ``` + + + ```csharp + using UnityEngine; + + public enum CheckoutResult { Succeeded, Failed, Cancelled } + + public class Checkout : MonoBehaviour + { + void TrackCheckout(string orderId, string userId, PaymentStatus status) + { + // Product details: Meta receives these fields, Google receives content_ids as item IDs + string eventDataJson = + "{\"content_ids\":[\"sku_blue_tshirt_m\"]," + + "\"contents\":[{\"id\":\"sku_blue_tshirt_m\",\"quantity\":1,\"item_price\":499}]," + + "\"content_type\":\"product\",\"num_items\":1,\"order_id\":\"" + orderId + "\"}"; + + LinkrunnerSDK.TrackPayment( + eventName: "purchase", // same name for every status of this payment + paymentId: orderId, // same ID for every status of this payment + userId: userId, // required, links the payment to your user + amount: 499.0, // order total the user pays + currency: "INR", // required, 3-letter ISO 4217 code + status: status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + eventDataJson: eventDataJson + ); + } + + // When the user taps Pay + public void OnCheckoutStarted(string orderId, string userId) => + TrackCheckout(orderId, userId, PaymentStatus.PAYMENT_INITIATED); + + // When your payment provider returns a result + public void OnCheckoutFinished(string orderId, string userId, CheckoutResult result) + { + PaymentStatus status = result switch + { + CheckoutResult.Succeeded => PaymentStatus.PAYMENT_COMPLETED, + CheckoutResult.Failed => PaymentStatus.PAYMENT_FAILED, + _ => PaymentStatus.PAYMENT_CANCELLED, + }; + TrackCheckout(orderId, userId, status); + } + } + ``` + + + +### Subscription renewal with an old payment type + +Use this when you already have mappings or affiliate templates for an old payment type. `SUBSCRIPTION_RENEWED` is an old type, so it keeps matching those mappings with no dashboard change. Use the store's transaction ID for each renewal as `paymentId`, so each renewal is a separate payment. The plan is sent as the product. + + + + ```kotlin + import io.linkrunner.sdk.LinkRunner + import kotlinx.coroutines.CoroutineScope + import kotlinx.coroutines.Dispatchers + import kotlinx.coroutines.launch + + fun onSubscriptionRenewed(transactionId: String, userId: String) { + CoroutineScope(Dispatchers.IO).launch { + LinkRunner.getInstance().trackPayment( + eventName = "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings + paymentId = transactionId, // a new ID for every renewal, used to deduplicate + userId = userId, // required, links the payment to your user + amount = 4.99, // total the user paid for this renewal + currency = "USD", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData = mapOf( + "content_ids" to listOf("pro_monthly"), + "contents" to listOf(mapOf("id" to "pro_monthly", "quantity" to 1, "item_price" to 4.99)), + "content_type" to "product", + "num_items" to 1, + "order_id" to transactionId + ) + ).onFailure { error -> + println("Payment not tracked: ${error.message}") + } + } + } + ``` + + + ```swift + import LinkrunnerKit + + func onSubscriptionRenewed(transactionId: String, userId: String) async { + await LinkrunnerSDK.shared.trackPayment( + eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings + paymentId: transactionId, // a new ID for every renewal, used to deduplicate + userId: userId, // required, links the payment to your user + amount: 4.99, // total the user paid for this renewal + currency: "USD", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: [ + "content_ids": ["pro_monthly"], + "contents": [["id": "pro_monthly", "quantity": 1, "item_price": 4.99]], + "content_type": "product", + "num_items": 1, + "order_id": transactionId + ] + ) + } + ``` + + + ```javascript + import linkrunner from "rn-linkrunner"; + + export async function onSubscriptionRenewed(transactionId, userId) { + try { + await linkrunner.trackPayment({ + eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings + paymentId: transactionId, // a new ID for every renewal, used to deduplicate + userId, // required, links the payment to your user + amount: 4.99, // total the user paid for this renewal + currency: "USD", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["pro_monthly"], + contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], + content_type: "product", + num_items: 1, + order_id: transactionId, + }, + }); + } catch (error) { + console.error("Payment not tracked:", error); + } + } + ``` + + + ```dart + import 'package:linkrunner/main.dart'; + + Future onSubscriptionRenewed(String transactionId, String userId) async { + try { + await LinkRunner().trackPayment( + eventName: 'SUBSCRIPTION_RENEWED', // old type name, keeps your existing mappings + paymentId: transactionId, // a new ID for every renewal, used to deduplicate + userId: userId, // required, links the payment to your user + amount: 4.99, // total the user paid for this renewal + currency: 'USD', // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + 'content_ids': ['pro_monthly'], + 'contents': [ + {'id': 'pro_monthly', 'quantity': 1, 'item_price': 4.99}, + ], + 'content_type': 'product', + 'num_items': 1, + 'order_id': transactionId, + }, + ); + } on ArgumentError catch (e) { + print('Payment not tracked: ${e.message}'); + } + } + ``` + + + Expo apps call `rn-linkrunner`, so the code is the same as React Native. + + ```javascript + import linkrunner from "rn-linkrunner"; + + export async function onSubscriptionRenewed(transactionId, userId) { + try { + await linkrunner.trackPayment({ + eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings + paymentId: transactionId, // a new ID for every renewal, used to deduplicate + userId, // required, links the payment to your user + amount: 4.99, // total the user paid for this renewal + currency: "USD", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["pro_monthly"], + contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], + content_type: "product", + num_items: 1, + order_id: transactionId, + }, + }); + } catch (error) { + console.error("Payment not tracked:", error); + } + } + ``` + + + ```typescript + import linkrunner from "capacitor-linkrunner"; + + export async function onSubscriptionRenewed(transactionId: string, userId: string) { + try { + await linkrunner.trackPayment({ + eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings + paymentId: transactionId, // a new ID for every renewal, used to deduplicate + userId, // required, links the payment to your user + amount: 4.99, // total the user paid for this renewal + currency: "USD", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["pro_monthly"], + contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], + content_type: "product", + num_items: 1, + order_id: transactionId, + }, + }); + } catch (error) { + console.error("Payment not tracked:", error); + } + } + ``` + + + ```javascript + function onSubscriptionRenewed(transactionId, userId) { + return linkrunner.trackPayment({ + eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings + paymentId: transactionId, // a new ID for every renewal, used to deduplicate + userId: userId, // required, links the payment to your user + amount: 4.99, // total the user paid for this renewal + currency: "USD", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + eventData: { + content_ids: ["pro_monthly"], + contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], + content_type: "product", + num_items: 1, + order_id: transactionId + } + }).catch(function (error) { + console.error("Payment not tracked:", error); + }); + } + ``` + + + ```csharp + using UnityEngine; + + public class Subscriptions : MonoBehaviour + { + public void OnSubscriptionRenewed(string transactionId, string userId) + { + // Product details: Meta receives these fields, Google receives content_ids as item IDs + string eventDataJson = + "{\"content_ids\":[\"pro_monthly\"]," + + "\"contents\":[{\"id\":\"pro_monthly\",\"quantity\":1,\"item_price\":4.99}]," + + "\"content_type\":\"product\",\"num_items\":1,\"order_id\":\"" + transactionId + "\"}"; + + LinkrunnerSDK.TrackPayment( + eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings + paymentId: transactionId, // a new ID for every renewal, used to deduplicate + userId: userId, // required, links the payment to your user + amount: 4.99, // total the user paid for this renewal + currency: "USD", // required, 3-letter ISO 4217 code + eventDataJson: eventDataJson + ); + } + } + ``` + + + +### Handle invalid payments + +The SDK validates every payment before sending it. A blank `eventName` or `paymentId`, a negative or non-finite `amount`, or a `currency` that is not a 3-letter code stops the payment on the device, and nothing reaches Linkrunner. How your code finds out depends on the SDK: + +| Platform | What happens on an invalid payment | +| --- | --- | +| Android (Kotlin) | `trackPayment` returns a failed `Result` holding an `IllegalArgumentException`. It does not throw. Use `onFailure`. | +| Android (Java) | `LinkrunnerJava.getInstance().trackPayment(...)` calls `onError` on the `LinkRunnerCallback` you pass. | +| iOS | `trackPayment` does not throw. The payment is dropped, and debug builds print the reason to the console. | +| React Native and Expo | `trackPayment` throws. Wrap it in `try`/`catch`. | +| Flutter | `trackPayment` throws `ArgumentError`. | +| Capacitor | The returned promise rejects. | +| Cordova | The returned promise rejects. | +| Unity | The native SDK rejects the payment, and `LinkrunnerSDK.OnEventTracked` reports `false` with the error. | + +Validation only proves the payment was well formed. Check the [Events Log](#verify-it-works) to confirm Linkrunner received it. + +## Send revenue to ad networks + +A payment reaches an ad network only when all of these are true: + +1. The status is `PAYMENT_COMPLETED`. +2. The event name has a mapping for that network in the dashboard, such as [Meta event mapping](/ad-networks/meta-ads#2-set-up-event-mapping) or [Google Ads event mapping](/ad-networks/google-ads#3-set-up-event-mapping). The name must match exactly, unless it is an old payment type. + +If a payment arrives before the user's install is attributed, Linkrunner holds it and sends it once the install is attributed. + +The value and currency are sent only when **Send Revenue** is turned on for that mapping. Otherwise the network gets the conversion without a value. + +What the networks receive: + +- **Value and currency** come from the top-level `amount` and `currency`. Send the order total as `amount`. Linkrunner does not add up item prices, and an `amount` inside `eventData` is not sent as revenue. On iOS, an `amount` inside `eventData` still counts toward SKAN conversion values. +- **Meta** receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id` from `eventData`. See [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager). +- **Google** receives `content_ids` from `eventData` as item IDs. See [Google Commerce Manager](/ecommerce-manager/google-commerce-manager). + +## Deduplication and retries + +Linkrunner keeps one payment per `paymentId`, event name, and status. Use this to make retries safe: + +- Use your real order or transaction ID as `paymentId`, not a random value generated on each call. Then a retry after a network error, an app restart, or a store callback that fires twice records one payment. +- Use a new `paymentId` for each real charge. Every subscription renewal is its own payment. +- Send each payment from one place. If both your app and your backend send the same purchase, use the same `paymentId` and event name from both, or pick one source. + +## Verify it works + + + + Use a debug build with an attributed test user. See [SDK Integration Testing](/testing/integration-testing) to get one. Use a new `paymentId`, such as `lr_test_payment_001`. + + + Open [**Events → Events Log**](https://dashboard.linkrunner.io/dashboard/events) and filter by your event name or test user. Expand the row. It should show **Event Type** as a payment, with the **Amount**, **Currency**, **Payment ID**, and **Payment Status** you sent. + + + For a `PAYMENT_COMPLETED` payment with a mapped event name, check the network. For Meta, look in **Events Manager → Test Events** or **Overview**. See [Test Custom Events and Payments](/testing/event-and-revenue-testing) for the full test flow. + + + +### Common problems + + + + The SDK may have rejected the payment. Check how your SDK reports it in [Handle invalid payments](#handle-invalid-payments). If the call succeeded, confirm the SDK is initialized with the right project token and the test device finished the [attribution test flow](/testing/integration-testing). + + + Check that the status is `PAYMENT_COMPLETED` and that the event name has a mapping for that network with the exact same name. If the install was attributed only after the payment arrived, the payment is sent once attribution completes. + + + Turn on **Send Revenue** for that event's mapping, and send the total as the top-level `amount`, not inside `eventData`. + + + Open the payment in the Events Log and compare **Amount** and **Currency** with what you meant to send. A wrong `currency` code, or an amount in the wrong unit, such as cents instead of dollars, makes the revenue wrong in reports and on ad networks. + + + Only `PAYMENT_COMPLETED` payments count as revenue. Check **Payment Status** on the missing rows. Also check that a second payment did not reuse the `paymentId`, event name, and status of an earlier one. + + + `amount, payment_id and currency must be sent together` means the request had only one or two of the three payment fields. Send all three. See [Payment fields](/api-reference/event-capture#payment-fields). + + + +## FAQ + + + + No. Removing or reversing a payment is not supported. `removePayment` was removed in the SDK versions above and has no replacement. + + + `capturePayment` keeps working on the older versions. When you upgrade, follow [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). + + + Yes. Send them to `POST /api/v1/capture-event` with `amount`, `payment_id`, and `currency`. The same naming, status, and deduplication rules apply. See the [Revenue Tracking API](/api-reference/revenue-tracking). + + + Yes. Pass a payment object to `trackEvent` with the same fields. It records the same payment as `trackPayment`. Each SDK guide shows it under **Tracking Payments**. + + + +**Need help?** Contact [support@linkrunner.io](mailto:support@linkrunner.io). diff --git a/sdk/unity.mdx b/sdk/unity.mdx index 9beacea7..fa290b34 100644 --- a/sdk/unity.mdx +++ b/sdk/unity.mdx @@ -1396,7 +1396,7 @@ An `amount` inside the event data is not sent to ad networks as revenue. To shar ### Track Payments -Use `TrackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires the bridges above, built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 or later. +Use `TrackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires the bridges above, built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. ```csharp LinkrunnerSDK.TrackPayment( diff --git a/testing/event-and-revenue-testing.mdx b/testing/event-and-revenue-testing.mdx index 56bfd8ed..3c8cae03 100644 --- a/testing/event-and-revenue-testing.mdx +++ b/testing/event-and-revenue-testing.mdx @@ -14,7 +14,7 @@ Use this guide after [SDK Integration Testing](/testing/integration-testing). It | A user action with no authoritative transaction | `trackEvent` | `POST /capture-event` | | A payment with an amount and unique transaction ID | `trackPayment` (`eventName`, `paymentId`, `userId`, `amount`, `currency`) | `POST /capture-event` with `amount`, `payment_id`, and `currency` | -When you attach a payment, the event name becomes the payment type in reports. See [Payment fields](/api-reference/event-capture#payment-fields) for the rules. +When you attach a payment, the event name becomes the payment type in reports. See [Payment fields](/api-reference/event-capture#payment-fields) for the rules. For SDK examples, payment status, and how payments reach ad networks, see [Track payments](/sdk/track-payments). The event name alone is not enough. If `purchase_completed` represents a real transaction, send it as a payment. If it only represents a funnel step with no authoritative transaction, send it as a custom event. @@ -175,7 +175,7 @@ See [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager#understandi - `status` is part of the deduplication key, so sending `PAYMENT_INITIATED` and then `PAYMENT_COMPLETED` with the same `payment_id` creates two records. Send the final payment state once. Only `PAYMENT_COMPLETED` payments are sent to ad networks. + `status` is part of the deduplication key, so sending `PAYMENT_INITIATED` and then `PAYMENT_COMPLETED` with the same `payment_id` creates two records. This is expected when you track the payment lifecycle. Only the `PAYMENT_COMPLETED` record counts as revenue and is sent to ad networks. If you do not need the lifecycle, send only the final state. See [Payment status](/sdk/track-payments#payment-status). From 705e5fb9cbe51ab22fbb9c3728058841791e6712 Mon Sep 17 00:00:00 2001 From: chetan Date: Mon, 12 Oct 2026 03:56:36 +0530 Subject: [PATCH 06/15] docs: show every trackPayment option and the PaymentStatus enum in each SDK guide The Tracking Payments example in each SDK guide now sets every parameter the SDK accepts, with a one-line comment for each: whether it is required, what it is for, and its default. eventId appears only where trackPayment takes it (Android Kotlin, iOS, Capacitor). Each guide also gets a PaymentStatus subsection that shows the enum as that SDK defines it, plus a table of when to use each status and what it does. The parameter table now has a Default column. Expo gets a compact full example that uses the rn-linkrunner API. Co-Authored-By: Claude Opus 5.5 (1M context) --- sdk/android.mdx | 79 ++++++++++++++++++++++++++------------- sdk/capacitor.mdx | 88 +++++++++++++++++++++++++++++--------------- sdk/cordova.mdx | 77 +++++++++++++++++++++++++------------- sdk/expo.mdx | 36 ++++++++++++++++++ sdk/flutter.mdx | 77 +++++++++++++++++++++++++------------- sdk/ios.mdx | 69 ++++++++++++++++++++++------------ sdk/react-native.mdx | 79 ++++++++++++++++++++++++++------------- sdk/unity.mdx | 75 ++++++++++++++++++++++++++++--------- 8 files changed, 408 insertions(+), 172 deletions(-) diff --git a/sdk/android.mdx b/sdk/android.mdx index f3e3eda8..b261e338 100644 --- a/sdk/android.mdx +++ b/sdk/android.mdx @@ -466,10 +466,13 @@ private fun setIntegrationData() { ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires Android SDK 5.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires Android SDK 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. + +This example sets every parameter. Only the first five are required. ```kotlin import io.linkrunner.sdk.LinkRunner +import io.linkrunner.sdk.models.PaymentStatus import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -477,47 +480,71 @@ import kotlinx.coroutines.launch private fun trackPurchase() { CoroutineScope(Dispatchers.IO).launch { val result = LinkRunner.getInstance().trackPayment( - eventName = "purchase", - paymentId = "payment_123", - userId = "user_123", - amount = 99.99, - currency = "USD" + eventName = "purchase", // Required: payment name, stored as the payment type in reports and mappings + paymentId = "order_123", // Required: your order or transaction ID, used to deduplicate + userId = "user_123", // Required: your user ID. "" uses the user saved by signup or setUserData + amount = 1499.0, // Required: total the user paid, a finite number >= 0 + currency = "INR", // Required: 3-letter ISO 4217 code + status = PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + eventData = mapOf( // Optional: extra details. Meta and Google read these product fields + "content_ids" to listOf("sku_1", "sku_2"), + "contents" to listOf( + mapOf("id" to "sku_1", "quantity" to 1, "item_price" to 999), + mapOf("id" to "sku_2", "quantity" to 1, "item_price" to 500) + ), + "content_type" to "product", + "num_items" to 2, + "order_id" to "order_123" + ), + eventId = "evt_order_123" // Optional: your own ID for this event, defaults to null ) result.onSuccess { println("Payment event queued") }.onFailure { error -> + // An invalid value returns IllegalArgumentException here, and nothing is sent println("Error tracking payment: ${error.message}") } } } ``` -#### `trackPayment` Parameters +#### `PaymentStatus` -| Parameter | Type | Required | Description | -| ----------- | ------------------ | -------- | ----------- | -| `eventName` | `String` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `String` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | -| `userId` | `String` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | -| `amount` | `Double` | Yes | Payment amount. Must be a finite number, 0 or greater. | -| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | Payment status. Defaults to `PAYMENT_COMPLETED`. | -| `eventData` | `Map` | No | Additional data, such as product details for [ecommerce events](#ecommerce-events). | -| `eventId` | `String` | No | Your own unique identifier for the event. | +`PaymentStatus` is an enum in `io.linkrunner.sdk.models`: -When a value breaks these rules, `trackPayment` returns a failed `Result` with an `IllegalArgumentException`, and nothing is sent. +```kotlin +import io.linkrunner.sdk.models.PaymentStatus + +PaymentStatus.PAYMENT_INITIATED +PaymentStatus.PAYMENT_COMPLETED // default +PaymentStatus.PAYMENT_FAILED +PaymentStatus.PAYMENT_CANCELLED +``` -#### Available Payment Statuses +| Status | Use it when | Effect | +| ------ | ----------- | ------ | +| `PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | +| `PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_CANCELLED` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | -| Status | Description | -| ------------------- | ------------------------------ | -| `PAYMENT_INITIATED` | Payment process started | -| `PAYMENT_COMPLETED` | Payment successfully completed | -| `PAYMENT_FAILED` | Payment failed | -| `PAYMENT_CANCELLED` | Payment was cancelled | +Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. -To set a status, import `io.linkrunner.sdk.models.PaymentStatus` and pass it, for example `status = PaymentStatus.PAYMENT_INITIATED`. Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. +#### `trackPayment` Parameters + +| Parameter | Type | Required | Default | Description | +| --------- | ---- | -------- | ------- | ----------- | +| `eventName` | `String` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | +| `paymentId` | `String` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `userId` | `String` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `amount` | `Double` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `currency` | `String` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | `PAYMENT_COMPLETED` | Where the payment is in its lifecycle. See the statuses above. | +| `eventData` | `Map?` | No | None | Extra details, such as product details for [ecommerce events](#ecommerce-events). An `amount` inside it is not revenue. | +| `eventId` | `String?` | No | None | Your own identifier for this event. | + +When a value breaks these rules, `trackPayment` returns a failed `Result` with an `IllegalArgumentException`, and nothing is sent. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. diff --git a/sdk/capacitor.mdx b/sdk/capacitor.mdx index 1cc1614f..191ef6f0 100644 --- a/sdk/capacitor.mdx +++ b/sdk/capacitor.mdx @@ -354,51 +354,79 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `capacitor-linkrunner` 3.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `capacitor-linkrunner` 3.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. + +This example sets every parameter. Only the first five are required. ```javascript -import linkrunner from "capacitor-linkrunner"; +import linkrunner, { PaymentStatus } from "capacitor-linkrunner"; const trackPurchase = async () => { try { await linkrunner.trackPayment({ - eventName: "purchase", - paymentId: "order_123", - userId: "user_123", - amount: 499, - currency: "INR", + eventName: "purchase", // Required: payment name, stored as the payment type in reports and mappings + paymentId: "order_123", // Required: your order or transaction ID, used to deduplicate + userId: "user_123", // Required: your user ID. "" uses the user saved by signup or setUserData + amount: 1499, // Required: total the user paid, a finite number >= 0 + currency: "INR", // Required: 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + eventData: { // Optional: extra details. Meta and Google read these product fields + content_ids: ["sku_1", "sku_2"], + contents: [ + { id: "sku_1", quantity: 1, item_price: 999 }, + { id: "sku_2", quantity: 1, item_price: 500 }, + ], + content_type: "product", + num_items: 2, + order_id: "order_123", + }, + eventId: "evt_order_123", // Optional: your own ID for this event, no default }); } catch (error) { + // An invalid value rejects here, and nothing is sent console.error("Error tracking payment:", error); } }; ``` +#### `PaymentStatus` + +`capacitor-linkrunner` exports `PaymentStatus` as an object of string values, and as a TypeScript type: + +```javascript +import { PaymentStatus } from "capacitor-linkrunner"; + +PaymentStatus.PAYMENT_INITIATED // "PAYMENT_INITIATED" +PaymentStatus.PAYMENT_COMPLETED // "PAYMENT_COMPLETED" (default) +PaymentStatus.PAYMENT_FAILED // "PAYMENT_FAILED" +PaymentStatus.PAYMENT_CANCELLED // "PAYMENT_CANCELLED" +``` + +Plain strings such as `"PAYMENT_COMPLETED"` also work. + +| Status | Use it when | Effect | +| ------ | ----------- | ------ | +| `PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | +| `PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_CANCELLED` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | + +Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. + #### `trackPayment` Parameters -| Parameter | Type | Required | Description | -| ----------- | -------- | -------- | ----------- | -| `eventName` | `string` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | -| `userId` | `string` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | -| `amount` | `number` | Yes | Payment amount. Must be a finite number, 0 or greater. | -| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `string` | No | Payment status. Defaults to `PAYMENT_COMPLETED`. | -| `eventData` | `object` | No | Additional data, such as product details. | -| `eventId` | `string` | No | Your own unique identifier for the event. | - -The promise returned by `trackPayment` rejects when a value breaks these rules, and nothing is sent. - -#### Available Payment Statuses - -| Status | Description | -| ------------------- | ------------------------------ | -| `PAYMENT_INITIATED` | Payment process started | -| `PAYMENT_COMPLETED` | Payment successfully completed | -| `PAYMENT_FAILED` | Payment failed | -| `PAYMENT_CANCELLED` | Payment was cancelled | - -Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. In TypeScript, you can import the `TrackPaymentOptions`, `EventPayment`, and `PaymentStatus` types from `capacitor-linkrunner`. +| Parameter | Type | Required | Default | Description | +| --------- | ---- | -------- | ------- | ----------- | +| `eventName` | `string` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | +| `paymentId` | `string` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `userId` | `string` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `amount` | `number` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `currency` | `string` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | `PAYMENT_COMPLETED` | Where the payment is in its lifecycle. See the statuses above. | +| `eventData` | `object` | No | None | Extra details, such as product details. An `amount` inside it is not revenue. | +| `eventId` | `string` | No | None | Your own identifier for this event. | + +The promise returned by `trackPayment` rejects when a value breaks these rules, and nothing is sent. In TypeScript, you can import the `TrackPaymentOptions`, `EventPayment`, and `PaymentStatus` types from `capacitor-linkrunner`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. diff --git a/sdk/cordova.mdx b/sdk/cordova.mdx index b16a97f8..b3a5e067 100644 --- a/sdk/cordova.mdx +++ b/sdk/cordova.mdx @@ -350,46 +350,73 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `cordova-linkrunner` 2.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `cordova-linkrunner` 2.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. + +This example sets every parameter. Only the first five are required. `linkrunner` is a global after the `deviceready` event, so no import is needed. ```javascript linkrunner.trackPayment({ - eventName: "purchase", - paymentId: "order_123", - userId: "user_123", - amount: 499, - currency: "INR" + eventName: "purchase", // Required: payment name, stored as the payment type in reports and mappings + paymentId: "order_123", // Required: your order or transaction ID, used to deduplicate + userId: "user_123", // Required: your user ID. "" uses the user saved by signup or setUserData + amount: 1499, // Required: total the user paid, a finite number >= 0 + currency: "INR", // Required: 3-letter ISO 4217 code + status: linkrunner.PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + eventData: { // Optional: extra details. Meta and Google read these product fields + content_ids: ["sku_1", "sku_2"], + contents: [ + { id: "sku_1", quantity: 1, item_price: 999 }, + { id: "sku_2", quantity: 1, item_price: 500 } + ], + content_type: "product", + num_items: 2, + order_id: "order_123" + } }).then(function () { console.log("Payment tracked"); }).catch(function (error) { + // An invalid value rejects here, and nothing is sent console.error("Error tracking payment:", error); }); ``` -#### `trackPayment` Parameters +#### `PaymentStatus` -| Parameter | Type | Required | Description | -| ----------- | -------- | -------- | ----------- | -| `eventName` | `string` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | -| `userId` | `string` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | -| `amount` | `number` | Yes | Payment amount. Must be a finite number, 0 or greater. | -| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `string` | No | Payment status. Defaults to `PAYMENT_COMPLETED`. | -| `eventData` | `object` | No | Additional data, such as product details for [ecommerce events](#ecommerce-events). | +The plugin exposes `PaymentStatus` on the `linkrunner` global as a frozen object of string values: -The promise returned by `trackPayment` rejects when a value breaks these rules, and nothing is sent. +```javascript +// No import needed, linkrunner is a global -#### Available Payment Statuses +linkrunner.PaymentStatus.PAYMENT_INITIATED // "PAYMENT_INITIATED" +linkrunner.PaymentStatus.PAYMENT_COMPLETED // "PAYMENT_COMPLETED" (default) +linkrunner.PaymentStatus.PAYMENT_FAILED // "PAYMENT_FAILED" +linkrunner.PaymentStatus.PAYMENT_CANCELLED // "PAYMENT_CANCELLED" +``` + +Plain strings such as `"PAYMENT_COMPLETED"` also work. -| Status | Description | -| ------------------- | ------------------------------ | -| `PAYMENT_INITIATED` | Payment process started | -| `PAYMENT_COMPLETED` | Payment successfully completed | -| `PAYMENT_FAILED` | Payment failed | -| `PAYMENT_CANCELLED` | Payment was cancelled | +| Status | Use it when | Effect | +| ------ | ----------- | ------ | +| `PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | +| `PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_CANCELLED` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | -Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. +Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. + +#### `trackPayment` Parameters + +| Parameter | Type | Required | Default | Description | +| --------- | ---- | -------- | ------- | ----------- | +| `eventName` | `string` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | +| `paymentId` | `string` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `userId` | `string` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `amount` | `number` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `currency` | `string` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `string` | No | `PAYMENT_COMPLETED` | Where the payment is in its lifecycle. See the statuses above. | +| `eventData` | `object` | No | None | Extra details, such as product details for [ecommerce events](#ecommerce-events). An `amount` inside it is not revenue. | + +The promise returned by `trackPayment` rejects when a value breaks these rules, and nothing is sent. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. diff --git a/sdk/expo.mdx b/sdk/expo.mdx index fde58ec3..aac2e302 100644 --- a/sdk/expo.mdx +++ b/sdk/expo.mdx @@ -186,6 +186,42 @@ Since `expo-linkrunner` is a config plugin that prepares your project, the actua `capturePayment` and `removePayment` are deprecated and removed in `rn-linkrunner` 4.0.0. With `expo-linkrunner` 4.0.0, track payments with `trackPayment`. It takes the same fields as `capturePayment`, including `userId`, with `type` renamed to `eventName` and a new required `currency`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, your existing mappings keep working. Any other name needs a new mapping in the dashboard. See [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). For status, examples, and how payments reach ad networks, see [Track payments](/sdk/track-payments). +### Tracking Payments + +Expo apps call `trackPayment` from `rn-linkrunner`. This example sets every parameter. Only the first five are required. For the status values and the full parameter table, see [Tracking Payments](/sdk/react-native#tracking-payments) in the React Native guide. + +```javascript +import linkrunner, { PaymentStatus } from "rn-linkrunner"; + +const trackPurchase = async () => { + try { + await linkrunner.trackPayment({ + eventName: "purchase", // Required: payment name, stored as the payment type in reports and mappings + paymentId: "order_123", // Required: your order or transaction ID, used to deduplicate + userId: "user_123", // Required: your user ID. "" uses the user saved by signup or setUserData + amount: 1499, // Required: total the user paid, a finite number >= 0 + currency: "INR", // Required: 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_COMPLETED, // Optional: PAYMENT_INITIATED, PAYMENT_COMPLETED (default), PAYMENT_FAILED, or PAYMENT_CANCELLED + eventData: { // Optional: extra details. Meta and Google read these product fields + content_ids: ["sku_1", "sku_2"], + contents: [ + { id: "sku_1", quantity: 1, item_price: 999 }, + { id: "sku_2", quantity: 1, item_price: 500 }, + ], + content_type: "product", + num_items: 2, + order_id: "order_123", + }, + }); + } catch (error) { + // An invalid value throws here, and nothing is sent + console.error("Error tracking payment:", error); + } +}; +``` + +Only `PAYMENT_COMPLETED` counts as revenue and is sent to ad networks. Plain strings such as `"PAYMENT_COMPLETED"` also work for `status`. + ## Function Placement Guide Expo uses the `rn-linkrunner` runtime. The function links below open the implementation details in the React Native guide. diff --git a/sdk/flutter.mdx b/sdk/flutter.mdx index 18fbe9dd..7ad5421f 100644 --- a/sdk/flutter.mdx +++ b/sdk/flutter.mdx @@ -578,7 +578,9 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `linkrunner` 5.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `linkrunner` 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. + +This example sets every parameter. Only the first five are required. ```dart import 'package:linkrunner/main.dart'; @@ -586,42 +588,67 @@ import 'package:linkrunner/main.dart'; Future trackPurchase() async { try { await LinkRunner().trackPayment( - eventName: 'purchase', - paymentId: 'order_123', - userId: 'user_123', - amount: 499.0, - currency: 'INR', + eventName: 'purchase', // Required: payment name, stored as the payment type in reports and mappings + paymentId: 'order_123', // Required: your order or transaction ID, used to deduplicate + userId: 'user_123', // Required: your user ID. '' uses the user saved by signup or setUserData + amount: 1499.0, // Required: total the user paid, a finite number >= 0 + currency: 'INR', // Required: 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + eventData: { // Optional: extra details. Meta and Google read these product fields + 'content_ids': ['sku_1', 'sku_2'], + 'contents': [ + {'id': 'sku_1', 'quantity': 1, 'item_price': 999}, + {'id': 'sku_2', 'quantity': 1, 'item_price': 500}, + ], + 'content_type': 'product', + 'num_items': 2, + 'order_id': 'order_123', + }, ); + } on ArgumentError catch (e) { + // An invalid value throws here, and nothing is sent + print('Invalid payment: ${e.message}'); } catch (e) { print('Error tracking payment: $e'); } } ``` -#### `trackPayment` Parameters +#### `PaymentStatus` -| Parameter | Type | Required | Description | -| ----------- | ---------------------- | -------- | ----------- | -| `eventName` | `String` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `String` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | -| `userId` | `String` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | -| `amount` | `double` | Yes | Payment amount. Must be a finite number, 0 or greater. | -| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | Payment status. Defaults to `PaymentStatus.PAYMENT_COMPLETED`. | -| `eventData` | `Map` | No | Additional data, such as product details for [ecommerce events](#ecommerce-events). | +`PaymentStatus` is a Dart enum exported by `package:linkrunner/main.dart`: -`trackPayment` throws `ArgumentError` when a value breaks these rules, and nothing is sent. +```dart +enum PaymentStatus { + PAYMENT_INITIATED, + PAYMENT_COMPLETED, // default + PAYMENT_FAILED, + PAYMENT_CANCELLED, +} +``` -#### Available Payment Statuses +| Status | Use it when | Effect | +| ------ | ----------- | ------ | +| `PaymentStatus.PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PaymentStatus.PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | +| `PaymentStatus.PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PaymentStatus.PAYMENT_CANCELLED` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | -| Status | Description | -| --------------------------------- | ------------------------------ | -| `PaymentStatus.PAYMENT_INITIATED` | Payment process started | -| `PaymentStatus.PAYMENT_COMPLETED` | Payment successfully completed | -| `PaymentStatus.PAYMENT_FAILED` | Payment failed | -| `PaymentStatus.PAYMENT_CANCELLED` | Payment was cancelled | +Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. -Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. +#### `trackPayment` Parameters + +| Parameter | Type | Required | Default | Description | +| --------- | ---- | -------- | ------- | ----------- | +| `eventName` | `String` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | +| `paymentId` | `String` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `userId` | `String` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `amount` | `double` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `currency` | `String` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | `PaymentStatus.PAYMENT_COMPLETED` | Where the payment is in its lifecycle. See the statuses above. | +| `eventData` | `Map?` | No | None | Extra details, such as product details for [ecommerce events](#ecommerce-events). An `amount` inside it is not revenue. | + +`trackPayment` throws `ArgumentError` when a value breaks these rules, and nothing is sent. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. If you called `capturePayment` with no type or with `PaymentType.DEFAULT_PAYMENT`, pass `'DEFAULT'`. diff --git a/sdk/ios.mdx b/sdk/ios.mdx index 1f51bfd0..a32e09ee 100644 --- a/sdk/ios.mdx +++ b/sdk/ios.mdx @@ -519,49 +519,72 @@ func setAdditionalData() async { ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires LinkrunnerKit 5.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires LinkrunnerKit 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. + +This example sets every parameter. Only the first five are required. ```swift import LinkrunnerKit func trackPurchase() async { await LinkrunnerSDK.shared.trackPayment( - eventName: "purchase", - paymentId: "payment_123", - userId: "user_123", - amount: 99.99, - currency: "USD" + eventName: "purchase", // Required: payment name, stored as the payment type in reports and mappings + paymentId: "order_123", // Required: your order or transaction ID, used to deduplicate + userId: "user_123", // Required: your user ID. "" uses the user saved by signup or setUserData + amount: 1499.0, // Required: total the user paid, a finite number >= 0 + currency: "INR", // Required: 3-letter ISO 4217 code + status: .completed, // Optional: defaults to .completed (see statuses below) + eventData: [ // Optional: extra details. Meta and Google read these product fields + "content_ids": ["sku_1", "sku_2"], + "contents": [ + ["id": "sku_1", "quantity": 1, "item_price": 999], + ["id": "sku_2", "quantity": 1, "item_price": 500] + ], + "content_type": "product", + "num_items": 2, + "order_id": "order_123" + ], + eventId: "evt_order_123" // Optional: your own ID for this event, defaults to nil ) } ``` -#### `trackPayment` Parameters - -| Parameter | Type | Required | Description | -| ----------- | -------------------- | -------- | ----------- | -| `eventName` | `String` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `String` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | -| `userId` | `String` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | -| `amount` | `Double` | Yes | Payment amount. Must be a finite number, 0 or greater. | -| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | Payment status. Defaults to `.completed` (`PAYMENT_COMPLETED`). | -| `eventData` | `SendableDictionary` | No | Additional data, such as product details for [ecommerce events](#ecommerce-events). | -| `eventId` | `String` | No | Your own unique identifier for the event. | - -`trackPayment` does not throw. When a value breaks these rules, the payment is not sent, and debug builds print the reason to the console. +#### `PaymentStatus` -#### Available Payment Statuses +`PaymentStatus` is a `String` enum in `LinkrunnerKit`. Pass a case, such as `status: .initiated`: ```swift public enum PaymentStatus: String, Sendable { case initiated = "PAYMENT_INITIATED" - case completed = "PAYMENT_COMPLETED" + case completed = "PAYMENT_COMPLETED" // default case failed = "PAYMENT_FAILED" case cancelled = "PAYMENT_CANCELLED" } ``` -To set a status, pass it to `trackPayment`, for example `status: .initiated`. Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. +| Status | Use it when | Effect | +| ------ | ----------- | ------ | +| `.initiated` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `.completed` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | +| `.failed` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `.cancelled` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | + +Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. + +#### `trackPayment` Parameters + +| Parameter | Type | Required | Default | Description | +| --------- | ---- | -------- | ------- | ----------- | +| `eventName` | `String` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | +| `paymentId` | `String` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `userId` | `String` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `amount` | `Double` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `currency` | `String` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | `.completed` | Where the payment is in its lifecycle. See the statuses above. | +| `eventData` | `SendableDictionary?` | No | None | Extra details, such as product details for [ecommerce events](#ecommerce-events). An `amount` inside it is not revenue. | +| `eventId` | `String?` | No | None | Your own identifier for this event. | + +`trackPayment` does not throw. When a value breaks these rules, the payment is not sent, and debug builds print the reason to the console. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. diff --git a/sdk/react-native.mdx b/sdk/react-native.mdx index 07a245ce..fedc460a 100644 --- a/sdk/react-native.mdx +++ b/sdk/react-native.mdx @@ -436,50 +436,77 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `rn-linkrunner` 4.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `rn-linkrunner` 4.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. + +This example sets every parameter. Only the first five are required. ```javascript -import linkrunner from "rn-linkrunner"; +import linkrunner, { PaymentStatus } from "rn-linkrunner"; const trackPurchase = async () => { try { await linkrunner.trackPayment({ - eventName: "purchase", - paymentId: "order_123", - userId: "user_123", - amount: 499, - currency: "INR", + eventName: "purchase", // Required: payment name, stored as the payment type in reports and mappings + paymentId: "order_123", // Required: your order or transaction ID, used to deduplicate + userId: "user_123", // Required: your user ID. "" uses the user saved by signup or setUserData + amount: 1499, // Required: total the user paid, a finite number >= 0 + currency: "INR", // Required: 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + eventData: { // Optional: extra details. Meta and Google read these product fields + content_ids: ["sku_1", "sku_2"], + contents: [ + { id: "sku_1", quantity: 1, item_price: 999 }, + { id: "sku_2", quantity: 1, item_price: 500 }, + ], + content_type: "product", + num_items: 2, + order_id: "order_123", + }, }); } catch (error) { + // An invalid value throws here, and nothing is sent console.error("Error tracking payment:", error); } }; ``` -#### `trackPayment` Parameters +#### `PaymentStatus` -| Parameter | Type | Required | Description | -| ----------- | -------- | -------- | ----------- | -| `eventName` | `string` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | -| `userId` | `string` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | -| `amount` | `number` | Yes | Payment amount. Must be a finite number, 0 or greater. | -| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `string` | No | Payment status. Defaults to `PAYMENT_COMPLETED`. | -| `eventData` | `object` | No | Additional data, such as product details for [ecommerce events](#ecommerce-events). | +`rn-linkrunner` exports `PaymentStatus` as an object of string values, and as a TypeScript type: -`trackPayment` throws when a value breaks these rules, and nothing is sent. +```javascript +import { PaymentStatus } from "rn-linkrunner"; + +PaymentStatus.PAYMENT_INITIATED // "PAYMENT_INITIATED" +PaymentStatus.PAYMENT_COMPLETED // "PAYMENT_COMPLETED" (default) +PaymentStatus.PAYMENT_FAILED // "PAYMENT_FAILED" +PaymentStatus.PAYMENT_CANCELLED // "PAYMENT_CANCELLED" +``` -#### Available Payment Statuses +Plain strings such as `"PAYMENT_COMPLETED"` also work. -| Status | Description | -| ------------------- | ------------------------------ | -| `PAYMENT_INITIATED` | Payment process started | -| `PAYMENT_COMPLETED` | Payment successfully completed | -| `PAYMENT_FAILED` | Payment failed | -| `PAYMENT_CANCELLED` | Payment was cancelled | +| Status | Use it when | Effect | +| ------ | ----------- | ------ | +| `PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | +| `PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_CANCELLED` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | + +Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. + +#### `trackPayment` Parameters -Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. In TypeScript, you can import the `TrackPaymentParams`, `EventPayment`, and `PaymentStatus` types from `rn-linkrunner`. +| Parameter | Type | Required | Default | Description | +| --------- | ---- | -------- | ------- | ----------- | +| `eventName` | `string` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | +| `paymentId` | `string` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `userId` | `string` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `amount` | `number` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `currency` | `string` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | `PAYMENT_COMPLETED` | Where the payment is in its lifecycle. See the statuses above. | +| `eventData` | `object` | No | None | Extra details, such as product details for [ecommerce events](#ecommerce-events). An `amount` inside it is not revenue. | + +`trackPayment` throws when a value breaks these rules, and nothing is sent. In TypeScript, you can import the `TrackPaymentParams`, `EventPayment`, and `PaymentStatus` types from `rn-linkrunner`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. diff --git a/sdk/unity.mdx b/sdk/unity.mdx index fa290b34..32840b74 100644 --- a/sdk/unity.mdx +++ b/sdk/unity.mdx @@ -1396,31 +1396,72 @@ An `amount` inside the event data is not sent to ad networks as revenue. To shar ### Track Payments -Use `TrackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires the bridges above, built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 or later. For payment status, examples for every case, and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `TrackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires the bridges above, built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. + +This example sets every parameter. Only the first five are required. ```csharp +// Product details: Meta and Google read these fields +string eventDataJson = + "{\"content_ids\":[\"sku_1\",\"sku_2\"]," + + "\"contents\":[{\"id\":\"sku_1\",\"quantity\":1,\"item_price\":999}," + + "{\"id\":\"sku_2\",\"quantity\":1,\"item_price\":500}]," + + "\"content_type\":\"product\",\"num_items\":2,\"order_id\":\"order_123\"}"; + +// Reports the result of every TrackEvent and TrackPayment call. Subscribe once, for example in Awake +LinkrunnerSDK.OnEventTracked += (success, message) => +{ + // An invalid value arrives here as success == false, and nothing is sent + if (!success) Debug.LogError($"Error tracking payment: {message}"); +}; + LinkrunnerSDK.TrackPayment( - eventName: "purchase", - paymentId: "pay_abc123", - userId: "user_123", - amount: 9.99, - currency: "USD" + eventName: "purchase", // Required: payment name, stored as the payment type in reports and mappings + paymentId: "order_123", // Required: your order or transaction ID, used to deduplicate + userId: "user_123", // Required: your user ID. "" uses the user saved by Signup or SetUserData + amount: 1499.0, // Required: total the user paid, a finite number >= 0 + currency: "INR", // Required: 3-letter ISO 4217 code + status: PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + eventDataJson: eventDataJson // Optional: extra details as a JSON string, defaults to "" ); ``` -The result arrives through `OnEventTracked`, like any other event. +#### `PaymentStatus` + +`PaymentStatus` is the C# enum defined in the `LinkrunnerSDK` wrapper above. The bridges pass its name to the native SDK: + +```csharp +public enum PaymentStatus +{ + PAYMENT_INITIATED, + PAYMENT_COMPLETED, // default + PAYMENT_FAILED, + PAYMENT_CANCELLED +} +``` + +| Status | Use it when | Effect | +| ------ | ----------- | ------ | +| `PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | +| `PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_CANCELLED` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | + +Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. + +#### `TrackPayment` Parameters -| Parameter | Type | Required | Description | -| --------------- | --------------- | -------- | ----------- | -| `eventName` | `string` | Yes | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `string` | Yes | Unique identifier for this payment, such as your order or transaction ID. Must not be blank. | -| `userId` | `string` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `Signup` or `SetUserData`. | -| `amount` | `double` | Yes | Payment amount. Must be a finite number, 0 or greater. | -| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. Defaults to `PAYMENT_COMPLETED`. | -| `eventDataJson` | `string` | No | Additional data as a JSON string, such as product details. | +| Parameter | Type | Required | Default | Description | +| --------- | ---- | -------- | ------- | ----------- | +| `eventName` | `string` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | +| `paymentId` | `string` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `userId` | `string` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `Signup` or `SetUserData`. | +| `amount` | `double` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `currency` | `string` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `status` | `PaymentStatus` | No | `PAYMENT_COMPLETED` | Where the payment is in its lifecycle. See the statuses above. | +| `eventDataJson` | `string` | No | `""` | Extra details as a JSON string, such as product details. An `amount` inside it is not revenue. | -If a value breaks these rules, the native SDK rejects the payment, nothing is sent, and `OnEventTracked` reports `false` with the error. Only `PAYMENT_COMPLETED` payments are sent to ad network postbacks. +If a value breaks these rules, the native SDK rejects the payment, nothing is sent, and `OnEventTracked` reports `false` with the error. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. From 1983adba47a661b9a0b19b9bb75dfd88e19764ef Mon Sep 17 00:00:00 2001 From: chetan Date: Fri, 23 Oct 2026 14:09:47 +0530 Subject: [PATCH 07/15] docs: payment event names are stored exactly as sent Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/event-capture.mdx | 2 +- api-reference/revenue-tracking.mdx | 4 ++-- sdk/android.mdx | 4 ++-- sdk/capacitor.mdx | 4 ++-- sdk/cordova.mdx | 4 ++-- sdk/flutter.mdx | 4 ++-- sdk/ios.mdx | 4 ++-- sdk/migrate-to-track-event.mdx | 8 ++++---- sdk/react-native.mdx | 4 ++-- sdk/track-payments.mdx | 4 ++-- sdk/unity.mdx | 4 ++-- testing/event-and-revenue-testing.mdx | 4 ++-- 12 files changed, 25 insertions(+), 25 deletions(-) diff --git a/api-reference/event-capture.mdx b/api-reference/event-capture.mdx index 36dbf40d..e939438a 100644 --- a/api-reference/event-capture.mdx +++ b/api-reference/event-capture.mdx @@ -80,7 +80,7 @@ POST: /capture-event Send `amount`, `payment_id`, and `currency` at the top level of the request, next to `event_name`, when the event is a payment. Linkrunner then stores the event as revenue, and `event_name` becomes the payment type you see in reports. Without these fields, the request is a custom event. -If `event_name` is one of the old payment types, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so existing event mappings, reports, and affiliate templates keep working. Any other name is stored exactly as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. See [Choose the event name](/sdk/migrate-to-track-event#choose-the-event-name) for the full list. +`event_name` is stored exactly as you send it. To keep existing event mappings, reports, and affiliate templates working, send an old payment type in uppercase, exactly as before, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, or `DEFAULT`. A different spelling such as `first_payment` is a new payment type. Any other name is also stored exactly as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. See [Choose the event name](/sdk/migrate-to-track-event#choose-the-event-name) for the full list. ```json { diff --git a/api-reference/revenue-tracking.mdx b/api-reference/revenue-tracking.mdx index 7fecf978..5c71c6d9 100644 --- a/api-reference/revenue-tracking.mdx +++ b/api-reference/revenue-tracking.mdx @@ -30,9 +30,9 @@ linkrunner-key: YOUR-SERVER-KEY ## Capture a payment -Send the payment to the [capture-event](/api-reference/event-capture) endpoint with `amount`, `payment_id`, and `currency` at the top level. `event_name` becomes the payment type you see in reports, so use a name that describes the payment, such as `purchase_completed` or `subscription_renewed`. +Send the payment to the [capture-event](/api-reference/event-capture) endpoint with `amount`, `payment_id`, and `currency` at the top level. `event_name` becomes the payment type you see in reports, so use a name that describes the payment, such as `purchase_completed` or `SUBSCRIPTION_RENEWED`. -If `event_name` is one of the old payment types, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so existing event mappings, reports, and affiliate templates keep working. Any other name is stored exactly as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. See [Choose the event name](/sdk/migrate-to-track-event#choose-the-event-name) for the full list. +`event_name` is stored exactly as you send it. To keep existing event mappings, reports, and affiliate templates working, send an old payment type in uppercase, exactly as before, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, or `DEFAULT`. A different spelling such as `first_payment` is a new payment type. Any other name is also stored exactly as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. See [Choose the event name](/sdk/migrate-to-track-event#choose-the-event-name) for the full list. ``` POST: /capture-event diff --git a/sdk/android.mdx b/sdk/android.mdx index 60a11ca1..f93434d2 100644 --- a/sdk/android.mdx +++ b/sdk/android.mdx @@ -491,7 +491,7 @@ private fun setIntegrationData() { ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires Android SDK 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires Android SDK 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. This example sets every parameter. Only the first five are required. @@ -572,7 +572,7 @@ Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry When a value breaks these rules, `trackPayment` returns a failed `Result` with an `IllegalArgumentException`, and nothing is sent. - If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. #### Track a payment with `trackEvent` diff --git a/sdk/capacitor.mdx b/sdk/capacitor.mdx index d6b617a7..35b49e7d 100644 --- a/sdk/capacitor.mdx +++ b/sdk/capacitor.mdx @@ -382,7 +382,7 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `capacitor-linkrunner` 3.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires `capacitor-linkrunner` 3.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. This example sets every parameter. Only the first five are required. @@ -457,7 +457,7 @@ Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry The promise returned by `trackPayment` rejects when a value breaks these rules, and nothing is sent. In TypeScript, you can import the `TrackPaymentOptions`, `EventPayment`, and `PaymentStatus` types from `capacitor-linkrunner`. - If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. #### Track a payment with `trackEvent` diff --git a/sdk/cordova.mdx b/sdk/cordova.mdx index 2a8ff55a..d4e155df 100644 --- a/sdk/cordova.mdx +++ b/sdk/cordova.mdx @@ -377,7 +377,7 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `cordova-linkrunner` 2.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires `cordova-linkrunner` 2.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. This example sets every parameter. Only the first five are required. `linkrunner` is a global after the `deviceready` event, so no import is needed. @@ -446,7 +446,7 @@ Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry The promise returned by `trackPayment` rejects when a value breaks these rules, and nothing is sent. - If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. #### Track a payment with `trackEvent` diff --git a/sdk/flutter.mdx b/sdk/flutter.mdx index 25339bcd..27a004c9 100644 --- a/sdk/flutter.mdx +++ b/sdk/flutter.mdx @@ -599,7 +599,7 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `linkrunner` 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires `linkrunner` 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. This example sets every parameter. Only the first five are required. @@ -672,7 +672,7 @@ Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry `trackPayment` throws `ArgumentError` when a value breaks these rules, and nothing is sent. - If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. If you called `capturePayment` with no type or with `PaymentType.DEFAULT_PAYMENT`, pass `'DEFAULT'`. + The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. If you called `capturePayment` with no type or with `PaymentType.DEFAULT_PAYMENT`, pass `'DEFAULT'`. #### Track a payment with `trackEvent` diff --git a/sdk/ios.mdx b/sdk/ios.mdx index 3d52cce9..ee840d43 100644 --- a/sdk/ios.mdx +++ b/sdk/ios.mdx @@ -547,7 +547,7 @@ func setAdditionalData() async { ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires LinkrunnerKit 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires LinkrunnerKit 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. This example sets every parameter. Only the first five are required. @@ -615,7 +615,7 @@ Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry `trackPayment` does not throw. When a value breaks these rules, the payment is not sent, and debug builds print the reason to the console. - If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. #### Track a payment with `trackEvent` diff --git a/sdk/migrate-to-track-event.mdx b/sdk/migrate-to-track-event.mdx index b1df370a..2b836d06 100644 --- a/sdk/migrate-to-track-event.mdx +++ b/sdk/migrate-to-track-event.mdx @@ -33,14 +33,14 @@ This page covers the code change. For how payments work, what each status does, Linkrunner shows the event name as the payment type in reports and matches it exactly against your event mappings to ad networks and affiliate templates. Choose the name before you change any code. -If the name is one of the old payment types, in any letter case, Linkrunner stores it as that uppercase type. Your existing mappings, reports, and affiliate templates keep working with no dashboard change. The old types are `FIRST_PAYMENT`, `SECOND_PAYMENT`, `TRIAL_STARTED`, `TRIAL_CANCELLED`, `WALLET_TOPUP`, `FUNDS_WITHDRAWAL`, `SUBSCRIPTION_CREATED`, `SUBSCRIPTION_RENEWED`, `SUBSCRIPTION_CANCELLED`, `SUBSCRIPTION_REFUNDED`, `SUBSCRIPTION_BILLING_ISSUE`, `DEFAULT`, `ONE_TIME`, and `RECURRING`. +Linkrunner stores the name exactly as you send it, letter case included. If you used one of the old payment types, pass it in uppercase exactly as before, and your existing mappings, reports, and affiliate templates keep working with no dashboard change. A different spelling such as `first_payment` is a new payment type that matches none of them. The old types are `FIRST_PAYMENT`, `SECOND_PAYMENT`, `TRIAL_STARTED`, `TRIAL_CANCELLED`, `WALLET_TOPUP`, `FUNDS_WITHDRAWAL`, `SUBSCRIPTION_CREATED`, `SUBSCRIPTION_RENEWED`, `SUBSCRIPTION_CANCELLED`, `SUBSCRIPTION_REFUNDED`, `SUBSCRIPTION_BILLING_ISSUE`, `DEFAULT`, `ONE_TIME`, and `RECURRING`. -Any other name is stored exactly as you send it, letter case included. Add a mapping for it in the dashboard before it can reach ad networks. +Any other name needs its own mapping in the dashboard before it can reach ad networks. | You called `capturePayment` with | Pass as `eventName` | Stored as | Mapping change | | --- | --- | --- | --- | -| `type: FIRST_PAYMENT` (or any old type) | `"FIRST_PAYMENT"` or `"first_payment"` | `FIRST_PAYMENT` | None | -| No `type` (the default) | `"DEFAULT"` or `"default"` | `DEFAULT` | None | +| `type: FIRST_PAYMENT` (or any old type) | `"FIRST_PAYMENT"` | `FIRST_PAYMENT` | None | +| No `type` (the default) | `"DEFAULT"` | `DEFAULT` | None | | Flutter `PaymentType.DEFAULT_PAYMENT` (the Flutter default) | `"DEFAULT"` | `DEFAULT` | None | | Any type, and you want a new name | `"purchase"` | `purchase` | Add a mapping for `purchase` | diff --git a/sdk/react-native.mdx b/sdk/react-native.mdx index e3098809..0931207a 100644 --- a/sdk/react-native.mdx +++ b/sdk/react-native.mdx @@ -462,7 +462,7 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires `rn-linkrunner` 4.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires `rn-linkrunner` 4.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. This example sets every parameter. Only the first five are required. @@ -535,7 +535,7 @@ Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry `trackPayment` throws when a value breaks these rules, and nothing is sent. In TypeScript, you can import the `TrackPaymentParams`, `EventPayment`, and `PaymentStatus` types from `rn-linkrunner`. - If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. + The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. #### Track a payment with `trackEvent` diff --git a/sdk/track-payments.mdx b/sdk/track-payments.mdx index 11429e15..dcb0bdf3 100644 --- a/sdk/track-payments.mdx +++ b/sdk/track-payments.mdx @@ -62,9 +62,9 @@ So a payment can be accepted but not yet in reports, in reports but not sent to ## Choose the event name -Linkrunner shows the event name as the payment type and matches it exactly, letter case included, against your mappings. Use one name per kind of payment, such as `purchase` for one-time orders and `subscription_renewed` for renewals, and keep it the same across releases. +Linkrunner shows the event name as the payment type and matches it exactly, letter case included, against your mappings. Use one name per kind of payment, such as `purchase` for one-time orders and `SUBSCRIPTION_RENEWED` for renewals, and keep it the same across releases. -The 14 old payment types, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, and `DEFAULT`, are matched in any letter case and stored as the uppercase type. If you already have mappings, reports, or affiliate templates for an old type, keep that name and nothing changes in the dashboard. Any other name is stored exactly as sent and needs its own mapping. See [Choose the event name](/sdk/migrate-to-track-event#choose-the-event-name) for the full list. +The 14 old payment types, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, and `DEFAULT`, match your existing mappings only when sent in uppercase, exactly as before. If you already have mappings, reports, or affiliate templates for an old type, keep that name and nothing changes in the dashboard. Every name is stored exactly as sent, so `first_payment` is a new payment type, and any new name needs its own mapping. See [Choose the event name](/sdk/migrate-to-track-event#choose-the-event-name) for the full list. ## Payment status diff --git a/sdk/unity.mdx b/sdk/unity.mdx index ebf12530..d1d2002e 100644 --- a/sdk/unity.mdx +++ b/sdk/unity.mdx @@ -1438,7 +1438,7 @@ An `amount` inside the event data is not sent to ad networks as revenue. To shar ### Track Payments -Use `TrackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `subscription_renewed`. Requires the bridges above, built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +Use `TrackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires the bridges above, built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. This example sets every parameter. Only the first five are required. @@ -1506,7 +1506,7 @@ Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry If a value breaks these rules, the native SDK rejects the payment, nothing is sent, and `OnEventTracked` reports `false` with the error. -If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, in any letter case, Linkrunner stores it as that uppercase type, so your existing mappings and reports keep working. Any other name, such as `purchase`, is stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. +The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. #### Track a payment with `TrackEvent` diff --git a/testing/event-and-revenue-testing.mdx b/testing/event-and-revenue-testing.mdx index 3c8cae03..67b4da15 100644 --- a/testing/event-and-revenue-testing.mdx +++ b/testing/event-and-revenue-testing.mdx @@ -137,8 +137,8 @@ If you record a failed or cancelled payment, send its final state once. Linkrunn | `checkout_started` | Custom event | `event_name`, `user_id` | | `purchase_initiated` as a funnel step | Custom event | `event_name`, `user_id` | | Completed purchase | Payment | `event_name`, `user_id`, `payment_id`, `amount`, `currency` | -| Subscription renewal with a charge | Payment | `event_name: subscription_renewed` | -| Wallet top-up | Payment | `event_name: wallet_topup` | +| Subscription renewal with a charge | Payment | `event_name: SUBSCRIPTION_RENEWED` | +| Wallet top-up | Payment | `event_name: WALLET_TOPUP` | | Failed payment that must be recorded | Payment | `status: PAYMENT_FAILED` | | Cancelled payment that must be recorded | Payment | `status: PAYMENT_CANCELLED` | From 689d2917145231ad4bdfa0e138369bc5eeec8bf4 Mon Sep 17 00:00:00 2001 From: chetan Date: Sat, 24 Oct 2026 15:36:11 +0530 Subject: [PATCH 08/15] docs: simplify the payment migration guide and add a same-name warning Co-Authored-By: Claude Opus 5.5 (1M context) --- api-reference/event-capture.mdx | 4 +- api-reference/revenue-tracking.mdx | 4 +- sdk/android.mdx | 2 +- sdk/capacitor.mdx | 2 +- sdk/cordova.mdx | 2 +- sdk/expo.mdx | 2 +- sdk/flutter.mdx | 2 +- sdk/ios.mdx | 2 +- sdk/migrate-to-track-event.mdx | 225 ++++++----------------------- sdk/react-native.mdx | 2 +- sdk/track-payments.mdx | 6 +- sdk/unity.mdx | 2 +- 12 files changed, 59 insertions(+), 196 deletions(-) diff --git a/api-reference/event-capture.mdx b/api-reference/event-capture.mdx index e939438a..b9011cf1 100644 --- a/api-reference/event-capture.mdx +++ b/api-reference/event-capture.mdx @@ -80,7 +80,7 @@ POST: /capture-event Send `amount`, `payment_id`, and `currency` at the top level of the request, next to `event_name`, when the event is a payment. Linkrunner then stores the event as revenue, and `event_name` becomes the payment type you see in reports. Without these fields, the request is a custom event. -`event_name` is stored exactly as you send it. To keep existing event mappings, reports, and affiliate templates working, send an old payment type in uppercase, exactly as before, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, or `DEFAULT`. A different spelling such as `first_payment` is a new payment type. Any other name is also stored exactly as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. See [Choose the event name](/sdk/migrate-to-track-event#choose-the-event-name) for the full list. +`event_name` is stored exactly as you send it. To keep existing event mappings, reports, and affiliate templates working, send an old payment type in uppercase, exactly as before, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, or `DEFAULT`. A different spelling such as `first_payment` is a new payment type. Any other name is also stored exactly as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. See [Old payment types](/sdk/migrate-to-track-event#old-payment-types) for the full list. ```json { @@ -183,7 +183,7 @@ Put product details in `event_data`. Meta receives `content_ids`, `contents`, `c An `amount` inside `event_data` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). - `POST /capture-payment` is deprecated. It keeps working for existing integrations, but new integrations should send payments through `/capture-event`. See [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). + `POST /capture-payment` is deprecated. It keeps working for existing integrations, but new integrations should send payments through `/capture-event`. See [Migrate payments to in-app events](/sdk/migrate-to-track-event). ## Best Practices diff --git a/api-reference/revenue-tracking.mdx b/api-reference/revenue-tracking.mdx index 5c71c6d9..7860c1e0 100644 --- a/api-reference/revenue-tracking.mdx +++ b/api-reference/revenue-tracking.mdx @@ -32,7 +32,7 @@ linkrunner-key: YOUR-SERVER-KEY Send the payment to the [capture-event](/api-reference/event-capture) endpoint with `amount`, `payment_id`, and `currency` at the top level. `event_name` becomes the payment type you see in reports, so use a name that describes the payment, such as `purchase_completed` or `SUBSCRIPTION_RENEWED`. -`event_name` is stored exactly as you send it. To keep existing event mappings, reports, and affiliate templates working, send an old payment type in uppercase, exactly as before, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, or `DEFAULT`. A different spelling such as `first_payment` is a new payment type. Any other name is also stored exactly as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. See [Choose the event name](/sdk/migrate-to-track-event#choose-the-event-name) for the full list. +`event_name` is stored exactly as you send it. To keep existing event mappings, reports, and affiliate templates working, send an old payment type in uppercase, exactly as before, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, or `DEFAULT`. A different spelling such as `first_payment` is a new payment type. Any other name is also stored exactly as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. See [Old payment types](/sdk/migrate-to-track-event#old-payment-types) for the full list. ``` POST: /capture-event @@ -89,7 +89,7 @@ These calls come from your server, so Linkrunner never sends your server's IP to 3. **401** Invalid server key - `POST /capture-payment` is deprecated for app payments. It keeps working for existing integrations, but new integrations should send app payments through `/capture-event`. Website payments still use `/capture-payment`, see [Website payments](#website-payments). See [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). + `POST /capture-payment` is deprecated for app payments. It keeps working for existing integrations, but new integrations should send app payments through `/capture-event`. Website payments still use `/capture-payment`, see [Website payments](#website-payments). See [Migrate payments to in-app events](/sdk/migrate-to-track-event). ## Website payments diff --git a/sdk/android.mdx b/sdk/android.mdx index f93434d2..84f27fbe 100644 --- a/sdk/android.mdx +++ b/sdk/android.mdx @@ -600,7 +600,7 @@ CoroutineScope(Dispatchers.IO).launch { ``` - `capturePayment` is deprecated. Apps on Android SDK 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). + `capturePayment` is deprecated. Apps on Android SDK 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). ## Tracking Custom Events diff --git a/sdk/capacitor.mdx b/sdk/capacitor.mdx index 35b49e7d..04ed524b 100644 --- a/sdk/capacitor.mdx +++ b/sdk/capacitor.mdx @@ -479,7 +479,7 @@ await linkrunner.trackEvent( ``` - `capturePayment` is deprecated. Apps on `capacitor-linkrunner` 2.x keep using it and continue to work unchanged. To move to 3.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). + `capturePayment` is deprecated. Apps on `capacitor-linkrunner` 2.x keep using it and continue to work unchanged. To move to 3.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). ## Tracking Custom Events diff --git a/sdk/cordova.mdx b/sdk/cordova.mdx index d4e155df..4a55e63e 100644 --- a/sdk/cordova.mdx +++ b/sdk/cordova.mdx @@ -470,7 +470,7 @@ linkrunner.trackEvent( ``` - `capturePayment` is deprecated. Apps on `cordova-linkrunner` 1.x keep using it and continue to work unchanged. To move to 2.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). + `capturePayment` is deprecated. Apps on `cordova-linkrunner` 1.x keep using it and continue to work unchanged. To move to 2.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). ## Ecommerce Events diff --git a/sdk/expo.mdx b/sdk/expo.mdx index 0dc0b758..0179871f 100644 --- a/sdk/expo.mdx +++ b/sdk/expo.mdx @@ -183,7 +183,7 @@ Since `expo-linkrunner` is a config plugin that prepares your project, the actua - Enhanced privacy controls - `capturePayment` and `removePayment` are deprecated and removed in `rn-linkrunner` 4.0.0. With `expo-linkrunner` 4.0.0, track payments with `trackPayment`. It takes the same fields as `capturePayment`, including `userId`, with `type` renamed to `eventName` and a new required `currency`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, your existing mappings keep working. Any other name needs a new mapping in the dashboard. See [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). For status, examples, and how payments reach ad networks, see [Track payments](/sdk/track-payments). + `capturePayment` and `removePayment` are deprecated and removed in `rn-linkrunner` 4.0.0. With `expo-linkrunner` 4.0.0, track payments with `trackPayment`. It takes the same fields as `capturePayment`, including `userId`, with `type` renamed to `eventName` and a new required `currency`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, your existing mappings keep working. Any other name needs a new mapping in the dashboard. See [Migrate payments to in-app events](/sdk/migrate-to-track-event). For status, examples, and how payments reach ad networks, see [Track payments](/sdk/track-payments). ### Tracking Payments diff --git a/sdk/flutter.mdx b/sdk/flutter.mdx index 27a004c9..8726c55a 100644 --- a/sdk/flutter.mdx +++ b/sdk/flutter.mdx @@ -692,7 +692,7 @@ await LinkRunner().trackEvent( ``` - `capturePayment` is deprecated. Apps on `linkrunner` 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). + `capturePayment` is deprecated. Apps on `linkrunner` 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). ## Ecommerce Events diff --git a/sdk/ios.mdx b/sdk/ios.mdx index ee840d43..bc4099c9 100644 --- a/sdk/ios.mdx +++ b/sdk/ios.mdx @@ -641,7 +641,7 @@ do { ``` - `capturePayment` is deprecated. Apps on LinkrunnerKit 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). + `capturePayment` is deprecated. Apps on LinkrunnerKit 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). ## Tracking Custom Events diff --git a/sdk/migrate-to-track-event.mdx b/sdk/migrate-to-track-event.mdx index 2b836d06..1aaaff9f 100644 --- a/sdk/migrate-to-track-event.mdx +++ b/sdk/migrate-to-track-event.mdx @@ -1,15 +1,15 @@ --- -title: "Migrate from capturePayment to trackPayment" -sidebarTitle: "Migrate to trackPayment" -description: "Move payment tracking from capturePayment to trackPayment, and keep your reports and postback mappings working" +title: "Migrate payments to in-app events" +sidebarTitle: "Migrate to in-app events" +description: "Move from capturePayment to trackPayment, which sends each payment as an in-app event with revenue" icon: "arrow-right-arrow-left" --- - - `capturePayment` and `removePayment` are deprecated and removed in the SDK versions below. Apps on older versions keep working unchanged. `removePayment` has no replacement: removing a payment is not supported in the new versions. - +In the new SDK versions, a payment is an in-app event with revenue. You send it with `trackPayment`, and it goes to the same pipeline as your other in-app events. `capturePayment` and `removePayment` are removed. + +Apps on older SDK versions keep working with no change. Migrate when you upgrade. -| Platform | Package | Last versions with `capturePayment` | `trackPayment` from | +| Platform | Package | Old versions (`capturePayment`) | New versions (`trackPayment`) | | --- | --- | --- | --- | | Android | `io.linkrunner:android-sdk` | 4.x | 5.0.0 | | iOS | LinkrunnerKit | 4.x | 5.0.0 | @@ -18,45 +18,49 @@ icon: "arrow-right-arrow-left" | Expo | `expo-linkrunner` | 3.x | 4.0.0, with `rn-linkrunner` 4.0.0 or later | | Capacitor | `capacitor-linkrunner` | 2.x | 3.0.0 | | Cordova | `cordova-linkrunner` | 1.x | 2.0.0 | -| Unity | [Unity bridges](/sdk/unity#track-payments) | Bridges built on Android SDK 4.x or LinkrunnerKit 4.x | Bridges built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 | +| Unity | [Unity bridges](/sdk/unity#track-payments) | Bridges on native 4.x | Bridges on native 5.0.0 | -This page covers the code change. For how payments work, what each status does, and examples for every case, see [Track payments](/sdk/track-payments). + + **Use the exact same event name, with the same spelling and letter case.** -## What changes + Your old payment `type` becomes the `eventName`. If you sent `FIRST_PAYMENT` before, send `"FIRST_PAYMENT"` now. Do not change it to `first_payment`, `First_Payment`, or `firstPayment`. -- **`trackPayment` replaces `capturePayment`.** It takes the same fields, so the change is small. -- **`type` becomes `eventName`.** The event name becomes the payment type. You are no longer limited to a fixed list of types. -- **Currency is sent.** `currency` is now required with every payment. Older SDKs never sent a currency. -- **`userId` stays.** Pass the same user ID you passed to `capturePayment`. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`, as before. + Linkrunner matches the name exactly. A different spelling is treated as a new in-app event, so your existing Meta and Google mappings will not match it, and those payments will not be sent to Meta or Google. + -## Choose the event name +## How to migrate -Linkrunner shows the event name as the payment type in reports and matches it exactly against your event mappings to ad networks and affiliate templates. Choose the name before you change any code. +1. **Upgrade the SDK** to the new version in the table above. +2. **Replace `capturePayment` with `trackPayment`.** Rename `type` to `eventName` and keep the same value. +3. **Add `currency`.** It is required now, for example `"USD"` or `"INR"`. +4. **Remove `removePayment` calls.** There is no replacement. +5. **Test once** with a debug build and check the payment on the [Events](https://dashboard.linkrunner.io/dashboard/events) page under the same name as before. -Linkrunner stores the name exactly as you send it, letter case included. If you used one of the old payment types, pass it in uppercase exactly as before, and your existing mappings, reports, and affiliate templates keep working with no dashboard change. A different spelling such as `first_payment` is a new payment type that matches none of them. The old types are `FIRST_PAYMENT`, `SECOND_PAYMENT`, `TRIAL_STARTED`, `TRIAL_CANCELLED`, `WALLET_TOPUP`, `FUNDS_WITHDRAWAL`, `SUBSCRIPTION_CREATED`, `SUBSCRIPTION_RENEWED`, `SUBSCRIPTION_CANCELLED`, `SUBSCRIPTION_REFUNDED`, `SUBSCRIPTION_BILLING_ISSUE`, `DEFAULT`, `ONE_TIME`, and `RECURRING`. +## Old payment types -Any other name needs its own mapping in the dashboard before it can reach ad networks. +These are the old payment types. Send them exactly like this, in uppercase: -| You called `capturePayment` with | Pass as `eventName` | Stored as | Mapping change | -| --- | --- | --- | --- | -| `type: FIRST_PAYMENT` (or any old type) | `"FIRST_PAYMENT"` | `FIRST_PAYMENT` | None | -| No `type` (the default) | `"DEFAULT"` | `DEFAULT` | None | -| Flutter `PaymentType.DEFAULT_PAYMENT` (the Flutter default) | `"DEFAULT"` | `DEFAULT` | None | -| Any type, and you want a new name | `"purchase"` | `purchase` | Add a mapping for `purchase` | +`FIRST_PAYMENT`, `SECOND_PAYMENT`, `TRIAL_STARTED`, `TRIAL_CANCELLED`, `WALLET_TOPUP`, `FUNDS_WITHDRAWAL`, `SUBSCRIPTION_CREATED`, `SUBSCRIPTION_RENEWED`, `SUBSCRIPTION_CANCELLED`, `SUBSCRIPTION_REFUNDED`, `SUBSCRIPTION_BILLING_ISSUE`, `DEFAULT`, `ONE_TIME`, `RECURRING` - - In Flutter, `DEFAULT_PAYMENT` was always stored as `DEFAULT`. Pass `"DEFAULT"`, not `"DEFAULT_PAYMENT"`. `DEFAULT_PAYMENT` is not an old payment type, so it would be stored as a new name and would not match your existing mappings. - +If you did not pass a `type` before, your payments were saved as `DEFAULT`. Use `"DEFAULT"` as the event name. In Flutter, `PaymentType.DEFAULT_PAYMENT` was also saved as `DEFAULT`, so use `"DEFAULT"`, not `"DEFAULT_PAYMENT"`. -A payment with the same `paymentId`, name, and status counts as one payment, so retries are safe. The same `paymentId` sent under two different names counts as two payments. Pick one name per payment and keep it. +## What changes in the call -## Update your code +| `capturePayment` | `trackPayment` | +| --- | --- | +| `type` | `eventName`, same value and spelling | +| `paymentId` | `paymentId` | +| `userId` | `userId`, still required | +| `amount` | `amount`, the order total | +| `status` | `status`, optional, defaults to `PAYMENT_COMPLETED` | +| `eventData` | `eventData` | +| Not available | `currency`, new and required | -The examples are fragments. Replace your `capturePayment` call with the `trackPayment` call in the same function, with the same coroutine or async context. They keep the old `FIRST_PAYMENT` type, so no mapping changes. Use your own amount, payment ID, user ID, currency, and products. +Only `PAYMENT_COMPLETED` payments count as revenue and are sent to ad networks. See [Payment status](/sdk/track-payments#payment-status) for the other statuses. -The **After** examples send product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`, and Google receives `content_ids` as item IDs. `amount` stays the order total. If you do not use [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager) or [Google Commerce Manager](/ecommerce-manager/google-commerce-manager), you can leave `eventData` out. +## Code examples -In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by the package. Plain strings such as `"PAYMENT_COMPLETED"` also work. +Each example keeps the old `FIRST_PAYMENT` name, so your mappings keep working. `eventData` with product details is optional. Send it only if you use [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager) or [Google Commerce Manager](/ecommerce-manager/google-commerce-manager). @@ -98,20 +102,8 @@ In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by th } ``` - With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: - ```kotlin - LinkRunner.getInstance().trackPayment( - eventName = "FIRST_PAYMENT", // same name as the rest of this payment - paymentId = "payment_123", // same order ID as the rest of this payment - userId = "user123", // required, links the payment to your user - amount = 99.99, // order total the user tried to pay - currency = "USD", // required, 3-letter ISO 4217 code - status = PaymentStatus.PAYMENT_FAILED // stored, not revenue, not sent to ad networks - ) - ``` - - An invalid value returns a failed `Result` instead of throwing. See [Tracking Payments](/sdk/android#tracking-payments) for the full reference. + See [Tracking Payments](/sdk/android#tracking-payments) for the full reference. Before (LinkrunnerKit 4.x): @@ -147,18 +139,6 @@ In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by th ) ``` - With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: - - ```swift - await LinkrunnerSDK.shared.trackPayment( - eventName: "FIRST_PAYMENT", // same name as the rest of this payment - paymentId: "payment456", // same order ID as the rest of this payment - userId: "user123", // required, links the payment to your user - amount: 99.99, // order total the user tried to pay - currency: "USD", // required, 3-letter ISO 4217 code - status: .failed // stored, not revenue, not sent to ad networks - ) - ``` See [Tracking Payments](/sdk/ios#tracking-payments) for the full reference. @@ -198,18 +178,6 @@ In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by th }); ``` - With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: - - ```javascript - await linkrunner.trackPayment({ - eventName: "FIRST_PAYMENT", // same name as the rest of this payment - paymentId: "payment456", // same order ID as the rest of this payment - userId: "user123", // required, links the payment to your user - amount: 100, // order total the user tried to pay - currency: "USD", // required, 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_FAILED, // stored, not revenue, not sent to ad networks - }); - ``` See [Tracking Payments](/sdk/react-native#tracking-payments) for the full reference. @@ -251,18 +219,6 @@ In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by th ); ``` - With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: - - ```dart - await LinkRunner().trackPayment( - eventName: 'FIRST_PAYMENT', // same name as the rest of this payment - paymentId: 'payment456', // same order ID as the rest of this payment - userId: 'user123', // required, links the payment to your user - amount: 99.99, // order total the user tried to pay - currency: 'USD', // required, 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_FAILED, // stored, not revenue, not sent to ad networks - ); - ``` See [Tracking Payments](/sdk/flutter#tracking-payments) for the full reference. @@ -304,18 +260,6 @@ In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by th }); ``` - With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: - - ```javascript - await linkrunner.trackPayment({ - eventName: "FIRST_PAYMENT", // same name as the rest of this payment - paymentId: "payment456", // same order ID as the rest of this payment - userId: "user123", // required, links the payment to your user - amount: 100, // order total the user tried to pay - currency: "USD", // required, 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_FAILED, // stored, not revenue, not sent to ad networks - }); - ``` See [Tracking Payments](/sdk/react-native#tracking-payments) for the full reference. @@ -355,18 +299,6 @@ In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by th }); ``` - With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: - - ```typescript - await linkrunner.trackPayment({ - eventName: "FIRST_PAYMENT", // same name as the rest of this payment - paymentId: "payment456", // same order ID as the rest of this payment - userId: "user123", // required, links the payment to your user - amount: 100, // order total the user tried to pay - currency: "USD", // required, 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_FAILED, // stored, not revenue, not sent to ad networks - }); - ``` See [Tracking Payments](/sdk/capacitor#tracking-payments) for the full reference. @@ -406,20 +338,6 @@ In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by th }); ``` - With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: - - ```javascript - linkrunner.trackPayment({ - eventName: "FIRST_PAYMENT", // same name as the rest of this payment - paymentId: "payment456", // same order ID as the rest of this payment - userId: "user123", // required, links the payment to your user - amount: 100, // order total the user tried to pay - currency: "USD", // required, 3-letter ISO 4217 code - status: linkrunner.PaymentStatus.PAYMENT_FAILED // stored, not revenue, not sent to ad networks - }).catch(function (error) { - console.error("Error tracking payment:", error); - }); - ``` See [Tracking Payments](/sdk/cordova#tracking-payments) for the full reference. @@ -456,78 +374,23 @@ In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by th ); ``` - With a status. To record a payment that did not complete, pass another status with the same `paymentId`. Only `PAYMENT_COMPLETED` counts as revenue and reaches ad networks: - - ```csharp - LinkrunnerSDK.TrackPayment( - eventName: "FIRST_PAYMENT", // same name as the rest of this payment - paymentId: "pay_abc123", // same order ID as the rest of this payment - userId: "user_123", // required, links the payment to your user - amount: 9.99, // order total the user tried to pay - currency: "USD", // required, 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_FAILED // stored, not revenue, not sent to ad networks - ); - ``` Update your Android and iOS bridge code and C# wrapper as shown in [Track Payments](/sdk/unity#track-payments). -### Field mapping - -| `capturePayment` field | `trackPayment` field | -| --- | --- | -| `type` | `eventName`. See [Choose the event name](#choose-the-event-name) | -| `paymentId` | `paymentId` | -| `userId` | `userId`, still required. An empty string falls back to the user saved by `signup` or `setUserData` | -| `amount` | `amount` | -| `status` | `status`, optional. Defaults to `PAYMENT_COMPLETED` | -| `eventData` | `eventData` | -| Not available | `currency`, new and required | - -### Alternative: `trackEvent` with a payment - -If you already call `trackEvent` for the same action, you can attach the payment to it instead. Pass a payment object (`EventPayment` on Android, iOS, Flutter, and Unity) with `amount`, `paymentId`, `currency`, `userId`, and an optional `status`. It records the same payment as `trackPayment`. Each SDK page shows the call under **Tracking Payments**. - -## Payment rules - -Every field has a job. Leaving one out or getting it wrong breaks a specific part of your reporting. - -- **`eventName`** is required because it is the payment type in reports and the key Linkrunner matches against your ad network and affiliate mappings. -- **`paymentId`** is required because Linkrunner uses it to recognize a retried payment, so it must not be blank. Use your order or transaction ID. -- **`userId`** is required because it links the payment to your user for paying users, cohorts, and user-level reports. An empty string uses the user saved by `signup` or `setUserData`. -- **`amount`** is required because it is the revenue in reports and the value ad networks receive. It must be a finite number, 0 or greater. Send the order total. -- **`currency`** is required because Linkrunner stores the payment in it and ad networks receive it with the value. It must be a 3-letter ISO 4217 code such as `USD` or `INR`. -- **`status`** is optional and defaults to `PAYMENT_COMPLETED`. The values are `PAYMENT_INITIATED`, `PAYMENT_COMPLETED`, `PAYMENT_FAILED`, and `PAYMENT_CANCELLED`. Only `PAYMENT_COMPLETED` counts as revenue and is sent to ad networks, and only for event names that have a mapping. See [Payment status](/sdk/track-payments#payment-status). -- **Validation.** The SDK rejects an invalid payment before sending it, so nothing reaches Linkrunner. See [Handle invalid payments](/sdk/track-payments#handle-invalid-payments) for how each SDK reports it. - -## Revenue sent to ad networks - -Meta and Google receive the value and currency from the payment `amount` and `currency`. Send the order total as `amount`. Linkrunner does not add up item prices. - -Product details come from `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. See [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager) and [Google Commerce Manager](/ecommerce-manager/google-commerce-manager) for the fields. - -An `amount` inside `eventData` is not sent to ad networks as revenue. On iOS it still counts toward SKAN conversion values. - -Revenue is shared only when **Send Revenue** is turned on for the event's mapping. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). - -## removePayment +## Using a new event name -`removePayment` is removed in the new SDK versions and has no replacement. Removing a payment is not supported going forward. +You can use a new name such as `purchase` instead of an old type. Before you release, add a mapping for that name in the dashboard for Meta, Google, and any other partner that should get it. Without a mapping, the payment is saved in Linkrunner but not sent to ad networks. -## Server-side API +Keep one name per payment. The same `paymentId` sent under two names counts as two payments. -If you send payments from your backend, use `POST /api/v1/capture-event`. It accepts `amount`, `payment_id`, `currency`, and `status` next to `event_name` and `user_id`, and the same naming rule applies. See [Events Capture API](/api-reference/event-capture#payment-fields). +## Already calling trackEvent? -`POST /api/v1/capture-payment` is deprecated. It keeps working for existing integrations, but new integrations should use `/capture-event`. +You can attach the payment to an existing `trackEvent` call instead of calling `trackPayment`. Pass a payment object with `amount`, `paymentId`, `currency`, `userId`, and an optional `status`. The same naming rule applies. Each SDK page shows this under **Tracking Payments**. -## Checklist before upgrading +## Sending payments from your server -1. Pick an event name for each payment type you send today. Keep the old type name if you want your mappings, reports, and affiliate templates to work with no change. -2. For every new name, add a mapping in the dashboard for each ad network or affiliate that should receive it, and turn on **Send Revenue** where you share revenue. -3. Send `currency` with every payment, and send the order total as `amount`. -4. Remove `removePayment` calls. -5. Decide which statuses you send. Leave `status` out for a one-step purchase, or follow the lifecycle in [Payment status](/sdk/track-payments#payment-status). -6. Test with a debug build, then confirm the payment on the [Events](https://dashboard.linkrunner.io/dashboard/events) page under the expected name. Use [Verify it works](/sdk/track-payments#verify-it-works) and [Test Custom Events and Payments](/testing/event-and-revenue-testing) for the full check. +Use `POST /api/v1/capture-event` with `amount`, `payment_id`, and `currency` next to `event_name` and `user_id`. The same naming rule applies. See [Events Capture API](/api-reference/event-capture#payment-fields). `POST /api/v1/capture-payment` is deprecated but keeps working. **Need help?** Contact [support@linkrunner.io](mailto:support@linkrunner.io). diff --git a/sdk/react-native.mdx b/sdk/react-native.mdx index 0931207a..6d2e3399 100644 --- a/sdk/react-native.mdx +++ b/sdk/react-native.mdx @@ -557,7 +557,7 @@ await linkrunner.trackEvent( ``` - `capturePayment` is deprecated. Apps on `rn-linkrunner` 3.x keep using it and continue to work unchanged. To move to 4.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). + `capturePayment` is deprecated. Apps on `rn-linkrunner` 3.x keep using it and continue to work unchanged. To move to 4.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). ## Ecommerce Events diff --git a/sdk/track-payments.mdx b/sdk/track-payments.mdx index dcb0bdf3..fe9aab22 100644 --- a/sdk/track-payments.mdx +++ b/sdk/track-payments.mdx @@ -7,7 +7,7 @@ icon: "credit-card" Use `trackPayment` whenever a user pays you: a purchase, a subscription charge or renewal, a wallet top-up. Linkrunner stores it as revenue, links it to the user and their install attribution, and forwards completed payments to the ad networks you mapped. -`trackPayment` needs these SDK versions or later. On an older version, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). +`trackPayment` needs these SDK versions or later. On an older version, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). | Platform | Package | Minimum version | | --- | --- | --- | @@ -64,7 +64,7 @@ So a payment can be accepted but not yet in reports, in reports but not sent to Linkrunner shows the event name as the payment type and matches it exactly, letter case included, against your mappings. Use one name per kind of payment, such as `purchase` for one-time orders and `SUBSCRIPTION_RENEWED` for renewals, and keep it the same across releases. -The 14 old payment types, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, and `DEFAULT`, match your existing mappings only when sent in uppercase, exactly as before. If you already have mappings, reports, or affiliate templates for an old type, keep that name and nothing changes in the dashboard. Every name is stored exactly as sent, so `first_payment` is a new payment type, and any new name needs its own mapping. See [Choose the event name](/sdk/migrate-to-track-event#choose-the-event-name) for the full list. +The 14 old payment types, such as `FIRST_PAYMENT`, `SUBSCRIPTION_RENEWED`, and `DEFAULT`, match your existing mappings only when sent in uppercase, exactly as before. If you already have mappings, reports, or affiliate templates for an old type, keep that name and nothing changes in the dashboard. Every name is stored exactly as sent, so `first_payment` is a new payment type, and any new name needs its own mapping. See [Old payment types](/sdk/migrate-to-track-event#old-payment-types) for the full list. ## Payment status @@ -1020,7 +1020,7 @@ Linkrunner keeps one payment per `paymentId`, event name, and status. Use this t No. Removing or reversing a payment is not supported. `removePayment` was removed in the SDK versions above and has no replacement. - `capturePayment` keeps working on the older versions. When you upgrade, follow [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). + `capturePayment` keeps working on the older versions. When you upgrade, follow [Migrate payments to in-app events](/sdk/migrate-to-track-event). Yes. Send them to `POST /api/v1/capture-event` with `amount`, `payment_id`, and `currency`. The same naming, status, and deduplication rules apply. See the [Revenue Tracking API](/api-reference/revenue-tracking). diff --git a/sdk/unity.mdx b/sdk/unity.mdx index d1d2002e..46972087 100644 --- a/sdk/unity.mdx +++ b/sdk/unity.mdx @@ -1519,7 +1519,7 @@ LinkrunnerSDK.TrackEvent("purchase", payment: payment); ``` -`capturePayment` is deprecated. Bridges built against Android SDK 4.x or LinkrunnerKit 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate from capturePayment to trackPayment](/sdk/migrate-to-track-event). +`capturePayment` is deprecated. Bridges built against Android SDK 4.x or LinkrunnerKit 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). ### Push Token From c9a7abe79f7f87bc50df5d2f260e68dabe8ebfb9 Mon Sep 17 00:00:00 2001 From: chetan Date: Sat, 24 Oct 2026 17:05:31 +0530 Subject: [PATCH 09/15] docs: payments are trackEvent with payment fields in eventData Co-Authored-By: Claude Opus 5.5 (1M context) --- ecommerce-manager/google-commerce-manager.mdx | 15 +- features/events-tab.mdx | 2 +- sdk-less/android-minimal.mdx | 26 +- sdk-less/ios-minimal.mdx | 22 +- sdk/android.mdx | 175 +++---- sdk/capacitor.mdx | 131 ++---- sdk/cordova.mdx | 177 +++---- sdk/expo.mdx | 27 +- sdk/flutter.mdx | 149 +++--- sdk/ios.mdx | 186 +++----- sdk/migrate-to-track-event.mdx | 394 ++++++++-------- sdk/react-native.mdx | 165 +++---- sdk/track-payments.mdx | 436 ++++++++---------- sdk/unity.mdx | 242 ++-------- testing/event-and-revenue-testing.mdx | 4 +- 15 files changed, 835 insertions(+), 1316 deletions(-) diff --git a/ecommerce-manager/google-commerce-manager.mdx b/ecommerce-manager/google-commerce-manager.mdx index cd003cc8..a528a834 100644 --- a/ecommerce-manager/google-commerce-manager.mdx +++ b/ecommerce-manager/google-commerce-manager.mdx @@ -169,7 +169,7 @@ await linkrunner.trackEvent( ### ecommerce_purchase -Fired when a user completes a purchase. Send it as a payment: pass `amount`, `paymentId`, `currency`, and `userId` with the event so Linkrunner records the revenue. +Fired when a user completes a purchase. Send it as a payment: put `amount`, `paymentId`, `currency`, and `userId` in the event data so Linkrunner records the revenue. @@ -177,19 +177,14 @@ Fired when a user completes a purchase. Send it as a payment: pass `amount`, `pa await linkrunner.trackEvent( "purchase_completed", // Map this event to "ecommerce_purchase" in the Linkrunner Dashboard { - content_ids: ["sku_blue_tshirt", "sku_red_shoes"], // Must match Merchant Center ID (offer_id) - value: 2298, // Required + amount: 2298.0, // Required: order total, sent as the value + paymentId: "order_98765", // Required for a payment currency: "INR", // Required + userId: "user_456", // Required for a payment + content_ids: ["sku_blue_tshirt", "sku_red_shoes"], // Must match Merchant Center ID (offer_id) num_items: 2, // Required order_id: "order_98765", // Required for purchase events }, - undefined, // eventId - { - amount: 2298.0, - paymentId: "order_98765", - currency: "INR", - userId: "user_456", - }, ); ``` diff --git a/features/events-tab.mdx b/features/events-tab.mdx index da08a513..451f9d62 100644 --- a/features/events-tab.mdx +++ b/features/events-tab.mdx @@ -152,7 +152,7 @@ If this sub-tab is empty, nothing has arrived yet. The empty state links to the empties every step below it. - The amount, currency, and payment ID come from the payment your app sends with `trackPayment`. Confirm your app + The amount, currency, and payment ID come from the payment keys your app sends in `trackEvent` event data. Confirm your app sends all three. See your platform's SDK guide, for example [React Native](/sdk/react-native). diff --git a/sdk-less/android-minimal.mdx b/sdk-less/android-minimal.mdx index 5d05c251..f0f8c650 100644 --- a/sdk-less/android-minimal.mdx +++ b/sdk-less/android-minimal.mdx @@ -132,17 +132,23 @@ object LinkrunnerMinimal { .put("data", JSONObject().put("device_data", deviceData(context))) ) - suspend fun trackEvent(context: Context, name: String, data: JSONObject? = null) = + // Send amount, paymentId, and currency together to record a payment; leave them out for a custom event + suspend fun trackEvent( + context: Context, + name: String, + data: JSONObject? = null, + amount: Double? = null, + paymentId: String? = null, + currency: String? = null + ) = post( "/api/client/capture-event", - base(context).put("event_name", name).apply { if (data != null) put("event_data", data) } - ) - - // A payment is a capture-event with amount, payment_id, and currency sent together - suspend fun trackPayment(context: Context, name: String, amount: Double, paymentId: String, currency: String) = - post( - "/api/client/capture-event", - base(context).put("event_name", name).put("amount", amount).put("payment_id", paymentId).put("currency", currency) + base(context).put("event_name", name).apply { + if (data != null) put("event_data", data) + if (amount != null && paymentId != null && currency != null) { + put("amount", amount).put("payment_id", paymentId).put("currency", currency) + } + } ) } ``` @@ -188,7 +194,7 @@ CoroutineScope(Dispatchers.IO).launch { // 5. Track a payment CoroutineScope(Dispatchers.IO).launch { - LinkrunnerMinimal.trackPayment(context, "purchase_completed", amount = 99.99, paymentId = "payment_123", currency = "USD") + LinkrunnerMinimal.trackEvent(context, "purchase_completed", amount = 99.99, paymentId = "payment_123", currency = "USD") } ``` diff --git a/sdk-less/ios-minimal.mdx b/sdk-less/ios-minimal.mdx index 480c3648..7c063d8d 100644 --- a/sdk-less/ios-minimal.mdx +++ b/sdk-less/ios-minimal.mdx @@ -90,22 +90,18 @@ enum LinkrunnerMinimal { return try await post("/api/client/trigger", body) } + // Send amount, paymentId, and currency together to record a payment; leave them out for a custom event @discardableResult - static func trackEvent(_ name: String, data: [String: Any]? = nil) async throws -> [String: Any] { + static func trackEvent(_ name: String, data: [String: Any]? = nil, + amount: Double? = nil, paymentId: String? = nil, currency: String? = nil) async throws -> [String: Any] { var body = base() body["event_name"] = name if let data { body["event_data"] = data } - return try await post("/api/client/capture-event", body) - } - - // A payment is a capture-event with amount, payment_id, and currency sent together - @discardableResult - static func trackPayment(_ name: String, amount: Double, paymentId: String, currency: String) async throws -> [String: Any] { - var body = base() - body["event_name"] = name - body["amount"] = amount - body["payment_id"] = paymentId - body["currency"] = currency + if let amount, let paymentId, let currency { + body["amount"] = amount + body["payment_id"] = paymentId + body["currency"] = currency + } return try await post("/api/client/capture-event", body) } } @@ -153,7 +149,7 @@ Task { try? await LinkrunnerMinimal.signup(userId: "user_123") } Task { try? await LinkrunnerMinimal.trackEvent("purchase_initiated", data: ["amount": 99.99]) } // 5. Track a payment -Task { try? await LinkrunnerMinimal.trackPayment("purchase_completed", amount: 99.99, paymentId: "payment_123", currency: "USD") } +Task { try? await LinkrunnerMinimal.trackEvent("purchase_completed", amount: 99.99, paymentId: "payment_123", currency: "USD") } ``` diff --git a/sdk/android.mdx b/sdk/android.mdx index 84f27fbe..cc55d0ef 100644 --- a/sdk/android.mdx +++ b/sdk/android.mdx @@ -491,27 +491,27 @@ private fun setIntegrationData() { ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires Android SDK 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +A payment is an in-app event with revenue. Send it with `trackEvent`, and put `amount`, `paymentId`, and `currency` in `eventData`. The event name becomes the payment type in reports and mappings, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires Android SDK 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. -This example sets every parameter. Only the first five are required. +This example sends every payment key. `amount`, `paymentId`, `currency`, and `userId` are required for a payment. ```kotlin import io.linkrunner.sdk.LinkRunner -import io.linkrunner.sdk.models.PaymentStatus import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch private fun trackPurchase() { CoroutineScope(Dispatchers.IO).launch { - val result = LinkRunner.getInstance().trackPayment( - eventName = "purchase", // Required: payment name, stored as the payment type in reports and mappings - paymentId = "order_123", // Required: your order or transaction ID, used to deduplicate - userId = "user_123", // Required: your user ID. "" uses the user saved by signup or setUserData - amount = 1499.0, // Required: total the user paid, a finite number >= 0 - currency = "INR", // Required: 3-letter ISO 4217 code - status = PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) - eventData = mapOf( // Optional: extra details. Meta and Google read these product fields + val result = LinkRunner.getInstance().trackEvent( + eventName = "purchase", // Payment name, stored as the payment type in reports and mappings + eventData = mapOf( + "amount" to 1499.0, // Required for a payment: total the user paid, a finite number >= 0 + "paymentId" to "order_123", // Required for a payment: your order or transaction ID, used to deduplicate + "currency" to "INR", // Required for a payment: 3-letter ISO 4217 code + "userId" to "user_123", // Required for a payment: your user ID. "" uses the user saved by signup or setUserData + "status" to "PAYMENT_COMPLETED", // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + // Optional: any other keys stay as event data. Meta and Google read these product fields "content_ids" to listOf("sku_1", "sku_2"), "contents" to listOf( mapOf("id" to "sku_1", "quantity" to 1, "item_price" to 999), @@ -521,84 +521,54 @@ private fun trackPurchase() { "num_items" to 2, "order_id" to "order_123" ), - eventId = "evt_order_123" // Optional: your own ID for this event, defaults to null + eventId = "evt_order_123" // Optional: your own ID for this event ) result.onSuccess { println("Payment event queued") }.onFailure { error -> - // An invalid value returns IllegalArgumentException here, and nothing is sent + // An invalid payment returns IllegalArgumentException here, and nothing is sent println("Error tracking payment: ${error.message}") } } } ``` -#### `PaymentStatus` +#### Payment keys in `eventData` -`PaymentStatus` is an enum in `io.linkrunner.sdk.models`: +| Key | Type | Required | Description | +| --- | ---- | -------- | ----------- | +| `amount` | `Double` or `Int` | Yes | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `paymentId` | `String` | Yes | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `userId` | `String` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `status` | `String` | No | `PAYMENT_INITIATED`, `PAYMENT_COMPLETED` (default), `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. | +| Any other key | Any | No | Stays in the event data, such as product details for [ecommerce events](#ecommerce-events). | -```kotlin -import io.linkrunner.sdk.models.PaymentStatus +How `trackEvent` decides: -PaymentStatus.PAYMENT_INITIATED -PaymentStatus.PAYMENT_COMPLETED // default -PaymentStatus.PAYMENT_FAILED -PaymentStatus.PAYMENT_CANCELLED -``` +- **`amount`, `paymentId`, and `currency` are all in `eventData`:** the event is a payment. The SDK checks the payment keys and sends them as payment fields. +- **None of them, or `amount` or `currency` without `paymentId`:** the event is a normal custom event, exactly as before. An `amount` or `currency` key on its own is ordinary event data. +- **`paymentId` with `amount` or `currency` missing:** `trackEvent` returns a failed `Result` with an `IllegalArgumentException` with `amount, paymentId and currency must be sent together`, and nothing is sent. +- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** `trackEvent` returns a failed `Result` with an `IllegalArgumentException`, and nothing is sent. + +#### Payment status + +`status` is a string. `io.linkrunner.sdk.models.PaymentStatus` lists the same four values if you prefer an enum. | Status | Use it when | Effect | | ------ | ----------- | ------ | | `PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | | `PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | | `PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | -| `PAYMENT_CANCELLED` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_CANCELLED` | The user backed out before paying, or you refunded it. Send it with the same `paymentId` as the completed payment. | Stored for reports and analytics. The payment no longer counts as revenue and nothing is sent to ad networks. | Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. -#### `trackPayment` Parameters - -| Parameter | Type | Required | Default | Description | -| --------- | ---- | -------- | ------- | ----------- | -| `eventName` | `String` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `String` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | -| `userId` | `String` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | -| `amount` | `Double` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | -| `currency` | `String` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | `PAYMENT_COMPLETED` | Where the payment is in its lifecycle. See the statuses above. | -| `eventData` | `Map?` | No | None | Extra details, such as product details for [ecommerce events](#ecommerce-events). An `amount` inside it is not revenue. | -| `eventId` | `String?` | No | None | Your own identifier for this event. | - -When a value breaks these rules, `trackPayment` returns a failed `Result` with an `IllegalArgumentException`, and nothing is sent. - The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. -#### Track a payment with `trackEvent` - -You can also pass an `EventPayment` to `trackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields and records the same payment. The `EventPayment` constructor throws `IllegalArgumentException` for an invalid value. - -```kotlin -import io.linkrunner.sdk.models.EventPayment - -CoroutineScope(Dispatchers.IO).launch { - try { - LinkRunner.getInstance().trackEvent( - eventName = "purchase", - payment = EventPayment( - amount = 99.99, - paymentId = "payment_123", - currency = "USD", - userId = "user_123" - ) - ) - } catch (e: IllegalArgumentException) { - println("Invalid payment: ${e.message}") - } -} -``` - `capturePayment` is deprecated. Apps on Android SDK 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). @@ -610,7 +580,7 @@ CoroutineScope(Dispatchers.IO).launch { - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, use [`trackPayment`](#tracking-payments). + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, add the [payment keys](#tracking-payments) to `eventData`. Track custom events in your app: @@ -643,41 +613,37 @@ private fun trackEvent() { #### Parameters for `LinkRunner.getInstance().trackEvent` - `eventName`: `String` (required) - Name of the event to track -- `eventData`: `Map` (optional) - Key-value pairs for additional event data, including Meta ecommerce properties +- `eventData`: `Map` (optional) - Key-value pairs for additional event data, including Meta ecommerce properties. Add `amount`, `paymentId`, `currency`, and `userId` to make it a payment. See [Tracking Payments](#tracking-payments). - `eventId`: `String` (optional) - Your own unique identifier for the event, useful for deduplication and correlating with your backend ### Revenue Sharing with Ad Networks Send revenue to ad networks such as Google Ads and Meta as a [payment](#tracking-payments). Meta and Google receive the value and currency from the payment `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. -Put product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. +Put the payment keys and the product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. ```kotlin private fun trackPurchase() { CoroutineScope(Dispatchers.IO).launch { - try { - LinkRunner.getInstance().trackEvent( - eventName = "purchase", - eventData = mapOf( - "content_ids" to listOf("product_123"), - "num_items" to 1, - "order_id" to "order_12345" - ), - payment = EventPayment( - amount = 149.99, // Order total, sent to ad networks as the value - paymentId = "order_12345", - currency = "USD", - userId = "user_123" - ) + LinkRunner.getInstance().trackEvent( + eventName = "purchase", + eventData = mapOf( + "amount" to 149.99, // Order total, sent to ad networks as the value + "paymentId" to "order_12345", + "currency" to "USD", + "userId" to "user_123", + "content_ids" to listOf("product_123"), + "num_items" to 1, + "order_id" to "order_12345" ) - } catch (e: IllegalArgumentException) { - println("Invalid payment: ${e.message}") + ).onFailure { error -> + println("Invalid payment: ${error.message}") } } } ``` -An `amount` inside `eventData` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). +An `amount` in `eventData` is revenue only when `paymentId` and `currency` are there too. On its own it is ordinary event data. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). ## Ecommerce Events @@ -745,38 +711,27 @@ private fun trackViewContent() { ### Payment / Purchase Example -Use `trackEvent` with an `EventPayment` to send a `Purchase` event containing the ecommerce payload: +Use `trackEvent` with the [payment keys](#tracking-payments) and the ecommerce fields in `eventData` to send a `Purchase` event: ```kotlin private fun trackPurchase() { CoroutineScope(Dispatchers.IO).launch { - try { - LinkRunner.getInstance().trackEvent( - eventName = "purchase", // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard. See: /ecommerce-manager/meta-commerce-manager, /ecommerce-manager/google-commerce-manager - eventData = mapOf( - "content_ids" to listOf("product_123"), - "contents" to listOf( - mapOf( - "id" to "product_123", // Matches content_ids - "quantity" to 1, - "item_price" to 49.99 - ) - ), - "content_type" to "product", - "currency" to "USD", - "value" to 49.99, - "num_items" to 1, - "order_id" to "order_abc123" // Required for Purchase events - ), - payment = EventPayment( - amount = 49.99, - paymentId = "payment_456", - currency = "USD", - userId = "user_123" - ) + LinkRunner.getInstance().trackEvent( + eventName = "purchase", // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard + eventData = mapOf( + "amount" to 49.99, // Order total, sent to ad networks as the value + "paymentId" to "payment_456", + "currency" to "USD", + "userId" to "user_123", + "status" to "PAYMENT_COMPLETED", // Optional, the default + "content_ids" to listOf("product_123"), + "contents" to listOf(mapOf("id" to "product_123", "quantity" to 1, "item_price" to 49.99)), + "content_type" to "product", + "num_items" to 1, + "order_id" to "order_abc123" // Required for Purchase events ) - } catch (e: IllegalArgumentException) { - println("Invalid payment: ${e.message}") + ).onFailure { error -> + println("Invalid payment: ${error.message}") } } } @@ -1024,7 +979,7 @@ Connect Firebase Cloud Messaging (FCM) with Linkrunner | [`LinkRunner.getInstance().setAdditionalData`](/sdk/android#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`LinkRunner.getInstance().setUserData`](/sdk/android#setting-user-data) | Optional | App start, authentication or profile update logic | Right after `init` with your analytics IDs, and when user details become available | | [`LinkRunner.getInstance().trackEvent`](/sdk/android#tracking-custom-events) | Optional | Throughout app | When specific user actions occur, including [ecommerce events](/sdk/android#ecommerce-events) | -| [`LinkRunner.getInstance().trackPayment`](/sdk/android#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/android#ecommerce-events) | +| [`LinkRunner.getInstance().trackEvent` with payment keys](/sdk/android#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/android#ecommerce-events) | | [`LinkRunner.getInstance().setPushToken`](/sdk/android#uninstall-tracking) | Optional | Push notification setup | When FCM token is available | | [`LinkRunner.getInstance().setConsent`](/sdk/android#google-ads-consent) | Optional | App initialization or consent flow | Before `init`, and again when consent changes | | [`LinkRunner.getInstance().enablePIIHashing`](/sdk/android#enhanced-privacy-controls) | Optional | Privacy settings | When you want to enable PII hashing | diff --git a/sdk/capacitor.mdx b/sdk/capacitor.mdx index 04ed524b..7560dfcb 100644 --- a/sdk/capacitor.mdx +++ b/sdk/capacitor.mdx @@ -382,102 +382,77 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires `capacitor-linkrunner` 3.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +A payment is an in-app event with revenue. Send it with `trackEvent`, and put `amount`, `paymentId`, and `currency` in `eventData`. The event name becomes the payment type in reports and mappings, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires `capacitor-linkrunner` 3.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. -This example sets every parameter. Only the first five are required. +This example sends every payment key. `amount`, `paymentId`, `currency`, and `userId` are required for a payment. ```javascript import linkrunner, { PaymentStatus } from "capacitor-linkrunner"; const trackPurchase = async () => { try { - await linkrunner.trackPayment({ - eventName: "purchase", // Required: payment name, stored as the payment type in reports and mappings - paymentId: "order_123", // Required: your order or transaction ID, used to deduplicate - userId: "user_123", // Required: your user ID. "" uses the user saved by signup or setUserData - amount: 1499, // Required: total the user paid, a finite number >= 0 - currency: "INR", // Required: 3-letter ISO 4217 code + await linkrunner.trackEvent({ + eventName: "purchase", // Payment name, stored as the payment type in reports and mappings + eventData: { + amount: 1499, // Required for a payment: total the user paid, a finite number >= 0 + paymentId: "order_123", // Required for a payment: your order or transaction ID, used to deduplicate + currency: "INR", // Required for a payment: 3-letter ISO 4217 code + userId: "user_123", // Required for a payment: your user ID. "" uses the user saved by signup or setUserData status: PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) - eventData: { // Optional: extra details. Meta and Google read these product fields - content_ids: ["sku_1", "sku_2"], - contents: [ - { id: "sku_1", quantity: 1, item_price: 999 }, - { id: "sku_2", quantity: 1, item_price: 500 }, - ], - content_type: "product", - num_items: 2, - order_id: "order_123", + // Optional: any other keys stay as event data. Meta and Google read these product fields + content_ids: ["sku_1", "sku_2"], + contents: [ + { id: "sku_1", quantity: 1, item_price: 999 }, + { id: "sku_2", quantity: 1, item_price: 500 }, + ], + content_type: "product", + num_items: 2, + order_id: "order_123", }, - eventId: "evt_order_123", // Optional: your own ID for this event, no default + eventId: "evt_order_123", // Optional: your own ID for this event }); } catch (error) { - // An invalid value rejects here, and nothing is sent + // An invalid payment rejects here, and nothing is sent console.error("Error tracking payment:", error); } }; ``` -#### `PaymentStatus` +#### Payment keys in `eventData` -`capacitor-linkrunner` exports `PaymentStatus` as an object of string values, and as a TypeScript type: +| Key | Type | Required | Description | +| --- | ---- | -------- | ----------- | +| `amount` | `number` | Yes | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `paymentId` | `string` | Yes | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `userId` | `string` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `status` | `string` | No | `PAYMENT_INITIATED`, `PAYMENT_COMPLETED` (default), `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. | +| Any other key | Any | No | Stays in the event data, such as product details for [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager). | -```javascript -import { PaymentStatus } from "capacitor-linkrunner"; +How `trackEvent` decides: -PaymentStatus.PAYMENT_INITIATED // "PAYMENT_INITIATED" -PaymentStatus.PAYMENT_COMPLETED // "PAYMENT_COMPLETED" (default) -PaymentStatus.PAYMENT_FAILED // "PAYMENT_FAILED" -PaymentStatus.PAYMENT_CANCELLED // "PAYMENT_CANCELLED" -``` +- **`amount`, `paymentId`, and `currency` are all in `eventData`:** the event is a payment. The SDK checks the payment keys and sends them as payment fields. +- **None of them, or `amount` or `currency` without `paymentId`:** the event is a normal custom event, exactly as before. An `amount` or `currency` key on its own is ordinary event data. +- **`paymentId` with `amount` or `currency` missing:** The promise returned by `trackEvent` rejects with `amount, paymentId and currency must be sent together`, and nothing is sent. +- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** The promise returned by `trackEvent` rejects, and nothing is sent. + +#### Payment status -Plain strings such as `"PAYMENT_COMPLETED"` also work. +`capacitor-linkrunner` exports `PaymentStatus` as an object of string values. Plain strings such as `"PAYMENT_COMPLETED"` also work. | Status | Use it when | Effect | | ------ | ----------- | ------ | | `PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | | `PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | | `PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | -| `PAYMENT_CANCELLED` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_CANCELLED` | The user backed out before paying, or you refunded it. Send it with the same `paymentId` as the completed payment. | Stored for reports and analytics. The payment no longer counts as revenue and nothing is sent to ad networks. | Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. -#### `trackPayment` Parameters - -| Parameter | Type | Required | Default | Description | -| --------- | ---- | -------- | ------- | ----------- | -| `eventName` | `string` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `string` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | -| `userId` | `string` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | -| `amount` | `number` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | -| `currency` | `string` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | `PAYMENT_COMPLETED` | Where the payment is in its lifecycle. See the statuses above. | -| `eventData` | `object` | No | None | Extra details, such as product details. An `amount` inside it is not revenue. | -| `eventId` | `string` | No | None | Your own identifier for this event. | - -The promise returned by `trackPayment` rejects when a value breaks these rules, and nothing is sent. In TypeScript, you can import the `TrackPaymentOptions`, `EventPayment`, and `PaymentStatus` types from `capacitor-linkrunner`. - The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. -#### Track a payment with `trackEvent` - -You can also pass a `payment` as the fourth argument to `trackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields, follows the same rules, and records the same payment. - -```javascript -await linkrunner.trackEvent( - "purchase", // Event name, used as the payment type - undefined, // Optional: Event data - undefined, // Optional: Event ID - { - amount: 499, - paymentId: "order_123", - currency: "INR", - userId: "user_123", - } -); -``` - `capturePayment` is deprecated. Apps on `capacitor-linkrunner` 2.x keep using it and continue to work unchanged. To move to 3.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). @@ -489,7 +464,7 @@ await linkrunner.trackEvent( - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, use [`trackPayment`](#tracking-payments). + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, add the [payment keys](#tracking-payments) to `eventData`. Track custom events in your app: @@ -509,41 +484,37 @@ const trackEvent = async () => { #### Parameters for `linkrunner.trackEvent` - `eventName`: string (required) - Name of the event to track -- `eventData`: object (optional) - Key-value pairs for additional event data, including Meta ecommerce properties +- `eventData`: object (optional) - Key-value pairs for additional event data, including Meta ecommerce properties. Add `amount`, `paymentId`, `currency`, and `userId` to make it a payment. See [Tracking Payments](#tracking-payments). - `eventId`: string | number (optional) - Your own unique identifier for the event, useful for deduplication and correlating with your backend -- `payment`: object (optional). Records revenue with the event. See [Tracking Payments](#tracking-payments). ### Revenue Sharing with Ad Networks Send revenue to ad networks such as Google Ads and Meta as a [payment](#tracking-payments). Meta and Google receive the value and currency from the payment `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. -Put product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. +Put the payment keys and the product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. ```typescript const trackPurchase = async () => { try { - await linkrunner.trackEvent( - "purchase", - { - content_ids: ["product_123"], - num_items: 1, - order_id: "order_12345", - }, - undefined, // Optional: Event ID - { + await linkrunner.trackEvent({ + eventName: "purchase", + eventData: { amount: 149.99, // Order total, sent to ad networks as the value paymentId: "order_12345", currency: "USD", userId: "user_123", - } - ); + content_ids: ["product_123"], + num_items: 1, + order_id: "order_12345", + }, + }); } catch (error) { console.error("Error tracking payment:", error); } }; ``` -An `amount` inside `eventData` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). +An `amount` in `eventData` is revenue only when `paymentId` and `currency` are there too. On its own it is ordinary event data. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). ## Enhanced Privacy Controls @@ -727,7 +698,7 @@ Connect APNs with Linkrunner | [`linkrunner.setAdditionalData`](/sdk/capacitor#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`linkrunner.setUserData`](/sdk/capacitor#setting-user-data) | Optional | App start, authentication or profile update logic | Right after `init` with your analytics IDs, and when user details become available | | [`linkrunner.trackEvent`](/sdk/capacitor#tracking-custom-events) | Optional | Throughout app | When specific user actions occur | -| [`linkrunner.trackPayment`](/sdk/capacitor#tracking-payments) | Optional | Payment processing | When user makes a payment | +| [`linkrunner.trackEvent` with payment keys](/sdk/capacitor#tracking-payments) | Optional | Payment processing | When user makes a payment | | [`linkrunner.setPushToken`](/sdk/capacitor#uninstall-tracking) | Optional | Push notification setup | When push token is available | | [`linkrunner.enablePIIHashing`](/sdk/capacitor#enhanced-privacy-controls) | Optional | Privacy configuration | When enabling PII hashing | diff --git a/sdk/cordova.mdx b/sdk/cordova.mdx index 4a55e63e..6acaa5db 100644 --- a/sdk/cordova.mdx +++ b/sdk/cordova.mdx @@ -377,98 +377,72 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires `cordova-linkrunner` 2.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +A payment is an in-app event with revenue. Send it with `trackEvent`, and put `amount`, `paymentId`, and `currency` in `eventData`. The event name becomes the payment type in reports and mappings, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires `cordova-linkrunner` 2.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. -This example sets every parameter. Only the first five are required. `linkrunner` is a global after the `deviceready` event, so no import is needed. +This example sends every payment key. `amount`, `paymentId`, `currency`, and `userId` are required for a payment. ```javascript -linkrunner.trackPayment({ - eventName: "purchase", // Required: payment name, stored as the payment type in reports and mappings - paymentId: "order_123", // Required: your order or transaction ID, used to deduplicate - userId: "user_123", // Required: your user ID. "" uses the user saved by signup or setUserData - amount: 1499, // Required: total the user paid, a finite number >= 0 - currency: "INR", // Required: 3-letter ISO 4217 code - status: linkrunner.PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) - eventData: { // Optional: extra details. Meta and Google read these product fields +// linkrunner is a global after the deviceready event, so no import is needed +linkrunner.trackEvent( + "purchase", // Payment name, stored as the payment type in reports and mappings + { + amount: 1499, // Required for a payment: total the user paid, a finite number >= 0 + paymentId: "order_123", // Required for a payment: your order or transaction ID, used to deduplicate + currency: "INR", // Required for a payment: 3-letter ISO 4217 code + userId: "user_123", // Required for a payment: your user ID. "" uses the user saved by signup or setUserData + status: linkrunner.PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + // Optional: any other keys stay as event data. Meta and Google read these product fields content_ids: ["sku_1", "sku_2"], contents: [ - { id: "sku_1", quantity: 1, item_price: 999 }, - { id: "sku_2", quantity: 1, item_price: 500 } + { id: "sku_1", quantity: 1, item_price: 999 }, + { id: "sku_2", quantity: 1, item_price: 500 }, ], content_type: "product", num_items: 2, - order_id: "order_123" - } -}).then(function () { - console.log("Payment tracked"); -}).catch(function (error) { - // An invalid value rejects here, and nothing is sent + order_id: "order_123", + }, + "evt_order_123" // Optional: your own ID for this event +).catch(function (error) { + // An invalid payment rejects here, and nothing is sent console.error("Error tracking payment:", error); }); ``` -#### `PaymentStatus` +#### Payment keys in `eventData` -The plugin exposes `PaymentStatus` on the `linkrunner` global as a frozen object of string values: +| Key | Type | Required | Description | +| --- | ---- | -------- | ----------- | +| `amount` | `number` | Yes | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `paymentId` | `string` | Yes | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `userId` | `string` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `status` | `string` | No | `PAYMENT_INITIATED`, `PAYMENT_COMPLETED` (default), `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. | +| Any other key | Any | No | Stays in the event data, such as product details for [ecommerce events](#ecommerce-events). | -```javascript -// No import needed, linkrunner is a global +How `trackEvent` decides: -linkrunner.PaymentStatus.PAYMENT_INITIATED // "PAYMENT_INITIATED" -linkrunner.PaymentStatus.PAYMENT_COMPLETED // "PAYMENT_COMPLETED" (default) -linkrunner.PaymentStatus.PAYMENT_FAILED // "PAYMENT_FAILED" -linkrunner.PaymentStatus.PAYMENT_CANCELLED // "PAYMENT_CANCELLED" -``` +- **`amount`, `paymentId`, and `currency` are all in `eventData`:** the event is a payment. The SDK checks the payment keys and sends them as payment fields. +- **None of them, or `amount` or `currency` without `paymentId`:** the event is a normal custom event, exactly as before. An `amount` or `currency` key on its own is ordinary event data. +- **`paymentId` with `amount` or `currency` missing:** The promise returned by `trackEvent` rejects with `amount, paymentId and currency must be sent together`, and nothing is sent. +- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** The promise returned by `trackEvent` rejects, and nothing is sent. + +#### Payment status -Plain strings such as `"PAYMENT_COMPLETED"` also work. +The plugin exposes `PaymentStatus` on the `linkrunner` global as an object of string values, such as `linkrunner.PaymentStatus.PAYMENT_CANCELLED`. Plain strings also work. | Status | Use it when | Effect | | ------ | ----------- | ------ | | `PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | | `PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | | `PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | -| `PAYMENT_CANCELLED` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_CANCELLED` | The user backed out before paying, or you refunded it. Send it with the same `paymentId` as the completed payment. | Stored for reports and analytics. The payment no longer counts as revenue and nothing is sent to ad networks. | Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. -#### `trackPayment` Parameters - -| Parameter | Type | Required | Default | Description | -| --------- | ---- | -------- | ------- | ----------- | -| `eventName` | `string` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `string` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | -| `userId` | `string` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | -| `amount` | `number` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | -| `currency` | `string` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `string` | No | `PAYMENT_COMPLETED` | Where the payment is in its lifecycle. See the statuses above. | -| `eventData` | `object` | No | None | Extra details, such as product details for [ecommerce events](#ecommerce-events). An `amount` inside it is not revenue. | - -The promise returned by `trackPayment` rejects when a value breaks these rules, and nothing is sent. - The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. -#### Track a payment with `trackEvent` - -You can also pass a `payment` as the fourth argument to `trackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields, follows the same rules, and records the same payment. - -```javascript -linkrunner.trackEvent( - "purchase", // Event name, used as the payment type - null, // Optional: Event data - null, // Optional: Event ID - { - amount: 499, - paymentId: "order_123", - currency: "INR", - userId: "user_123" - } -).catch(function (error) { - console.error("Error tracking payment:", error); -}); -``` - `capturePayment` is deprecated. Apps on `cordova-linkrunner` 1.x keep using it and continue to work unchanged. To move to 2.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). @@ -537,38 +511,22 @@ linkrunner.trackEvent( ### Payment / Purchase Example -Use `trackEvent` with a [`payment`](#tracking-payments) to send a `Purchase` event containing the ecommerce payload: +Use `trackEvent` with the [payment keys](#tracking-payments) and the ecommerce fields in `eventData` to send a `Purchase` event: ```javascript -linkrunner.trackEvent( - "purchase", // Map this event to "Purchase" in the Linkrunner Dashboard - { - content_ids: ["product_123"], - contents: [ - { - id: "product_123", - quantity: 1, - item_price: 49.99 - } - ], - content_type: "product", - currency: "USD", - value: 49.99, - num_items: 1, - order_id: "order_abc123" // Required for Purchase events - }, - null, - { - amount: 49.99, - paymentId: "payment_456", - currency: "USD", - userId: "user_123", - status: "PAYMENT_COMPLETED" - } -).then(function () { - console.log("Purchase tracked successfully"); +linkrunner.trackEvent("purchase", { // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard + amount: 49.99, // Order total, sent to ad networks as the value + paymentId: "payment_456", + currency: "USD", + userId: "user_123", + status: "PAYMENT_COMPLETED", // Optional, the default + content_ids: ["product_123"], + contents: [{ id: "product_123", quantity: 1, item_price: 49.99 }], + content_type: "product", + num_items: 1, + order_id: "order_abc123", // Required for Purchase events }).catch(function (error) { - console.error("Error tracking purchase:", error); + console.error("Error tracking payment:", error); }); ``` @@ -581,7 +539,7 @@ linkrunner.trackEvent( - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, use [`trackPayment`](#tracking-payments). + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, add the [payment keys](#tracking-payments) to `eventData`. Track custom events in your app: @@ -599,37 +557,30 @@ linkrunner.trackEvent( #### Parameters for `linkrunner.trackEvent` - `eventName`: string (required) - Name of the event to track -- `eventData`: object (optional) - Key-value pairs for additional event data, including Meta ecommerce properties +- `eventData`: object (optional) - Key-value pairs for additional event data, including Meta ecommerce properties. Add `amount`, `paymentId`, `currency`, and `userId` to make it a payment. See [Tracking Payments](#tracking-payments). - `eventId`: string | number (optional) - Your own unique identifier for the event, useful for deduplication and correlating with your backend -- `payment`: object (optional). Records revenue with the event. See [Tracking Payments](#tracking-payments). ### Revenue Sharing with Ad Networks Send revenue to ad networks such as Google Ads and Meta as a [payment](#tracking-payments). Meta and Google receive the value and currency from the payment `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. -Put product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. +Put the payment keys and the product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. ```javascript -linkrunner.trackEvent( - "purchase", - { - content_ids: ["product_123"], - num_items: 1, - order_id: "order_12345" - }, - null, // Optional: Event ID - { - amount: 149.99, // Order total, sent to ad networks as the value - paymentId: "order_12345", - currency: "USD", - userId: "user_123" - } -).catch(function (error) { +linkrunner.trackEvent("purchase", { + amount: 149.99, // Order total, sent to ad networks as the value + paymentId: "order_12345", + currency: "USD", + userId: "user_123", + content_ids: ["product_123"], + num_items: 1, + order_id: "order_12345", +}).catch(function (error) { console.error("Error tracking payment:", error); }); ``` -An `amount` inside `eventData` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). +An `amount` in `eventData` is revenue only when `paymentId` and `currency` are there too. On its own it is ordinary event data. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). ## Enhanced Privacy Controls @@ -816,7 +767,7 @@ Connect APNs with Linkrunner | [`linkrunner.setAdditionalData`](/sdk/cordova#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`linkrunner.setUserData`](/sdk/cordova#setting-user-data) | Optional | App start, authentication or profile update logic | Right after `init` with your analytics IDs, and when user details become available | | [`linkrunner.trackEvent`](/sdk/cordova#tracking-custom-events) | Optional | Throughout app, including ecommerce flows | When specific user actions occur, including AddToCart or ViewContent events | -| [`linkrunner.trackPayment`](/sdk/cordova#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | +| [`linkrunner.trackEvent` with payment keys](/sdk/cordova#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | | [`linkrunner.setPushToken`](/sdk/cordova#uninstall-tracking) | Optional | Push notification setup | When push token is available | | [`linkrunner.enablePIIHashing`](/sdk/cordova#enhanced-privacy-controls) | Optional | Privacy configuration | When enabling PII hashing | diff --git a/sdk/expo.mdx b/sdk/expo.mdx index 0179871f..cc3c0d40 100644 --- a/sdk/expo.mdx +++ b/sdk/expo.mdx @@ -183,26 +183,27 @@ Since `expo-linkrunner` is a config plugin that prepares your project, the actua - Enhanced privacy controls - `capturePayment` and `removePayment` are deprecated and removed in `rn-linkrunner` 4.0.0. With `expo-linkrunner` 4.0.0, track payments with `trackPayment`. It takes the same fields as `capturePayment`, including `userId`, with `type` renamed to `eventName` and a new required `currency`. If the event name is one of the old payment types, such as `FIRST_PAYMENT` or `DEFAULT`, your existing mappings keep working. Any other name needs a new mapping in the dashboard. See [Migrate payments to in-app events](/sdk/migrate-to-track-event). For status, examples, and how payments reach ad networks, see [Track payments](/sdk/track-payments). + `capturePayment` and `removePayment` are deprecated and removed in `rn-linkrunner` 4.0.0. With `expo-linkrunner` 4.0.0, a payment is an in-app event: call `trackEvent` with the old payment type as the event name, and put `amount`, `paymentId`, `currency`, and `userId` in the event data. Keep the old type spelling exactly, such as `FIRST_PAYMENT`, so your existing mappings keep working. Any other name needs a new mapping in the dashboard. See [Migrate payments to in-app events](/sdk/migrate-to-track-event). For status, examples, and how payments reach ad networks, see [Track payments](/sdk/track-payments). ### Tracking Payments -Expo apps call `trackPayment` from `rn-linkrunner`. This example sets every parameter. Only the first five are required. For the status values and the full parameter table, see [Tracking Payments](/sdk/react-native#tracking-payments) in the React Native guide. +Expo apps call `trackEvent` from `rn-linkrunner`. When the event data has `amount`, `paymentId`, and `currency`, the event is a payment. For the full key table and status values, see [Tracking Payments](/sdk/react-native#tracking-payments) in the React Native guide. ```javascript import linkrunner, { PaymentStatus } from "rn-linkrunner"; const trackPurchase = async () => { try { - await linkrunner.trackPayment({ - eventName: "purchase", // Required: payment name, stored as the payment type in reports and mappings - paymentId: "order_123", // Required: your order or transaction ID, used to deduplicate - userId: "user_123", // Required: your user ID. "" uses the user saved by signup or setUserData - amount: 1499, // Required: total the user paid, a finite number >= 0 - currency: "INR", // Required: 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_COMPLETED, // Optional: PAYMENT_INITIATED, PAYMENT_COMPLETED (default), PAYMENT_FAILED, or PAYMENT_CANCELLED - eventData: { // Optional: extra details. Meta and Google read these product fields + await linkrunner.trackEvent( + "purchase", // Payment name, stored as the payment type in reports and mappings + { + amount: 1499, // Required for a payment: total the user paid, a finite number >= 0 + paymentId: "order_123", // Required for a payment: your order or transaction ID, used to deduplicate + currency: "INR", // Required for a payment: 3-letter ISO 4217 code + userId: "user_123", // Required for a payment: your user ID. "" uses the user saved by signup or setUserData + status: PaymentStatus.PAYMENT_COMPLETED, // Optional: PAYMENT_INITIATED, PAYMENT_COMPLETED (default), PAYMENT_FAILED, or PAYMENT_CANCELLED + // Optional: any other keys stay as event data. Meta and Google read these product fields content_ids: ["sku_1", "sku_2"], contents: [ { id: "sku_1", quantity: 1, item_price: 999 }, @@ -212,9 +213,9 @@ const trackPurchase = async () => { num_items: 2, order_id: "order_123", }, - }); + ); } catch (error) { - // An invalid value throws here, and nothing is sent + // An invalid payment throws here, and nothing is sent console.error("Error tracking payment:", error); } }; @@ -236,7 +237,7 @@ Expo uses the `rn-linkrunner` runtime. The function links below open the impleme | [`linkrunner.setAdditionalData`](/sdk/react-native#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`linkrunner.setUserData`](/sdk/react-native#setting-user-data) | Optional | App start, authentication or profile update logic | Right after `init` with your analytics IDs, and when user details become available | | [`linkrunner.trackEvent`](/sdk/react-native#tracking-custom-events) | Optional | Throughout app, including ecommerce flows | When specific user actions occur, including AddToCart or ViewContent events | -| [`linkrunner.trackPayment`](/sdk/react-native#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | +| [`linkrunner.trackEvent` with payment keys](/sdk/react-native#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | | [`linkrunner.setPushToken`](/sdk/react-native#uninstall-tracking) | Optional | Push notification setup | When FCM/APNs token is available | | [`linkrunner.setConsent`](/sdk/react-native#google-integrated-conversion-measurement-optional) | Optional | App initialization or consent flow | Before `init`, and again when consent changes | | [`linkrunner.enablePIIHashing`](/sdk/react-native#enhanced-privacy-controls) | Optional | Privacy configuration | When enabling PII hashing | diff --git a/sdk/flutter.mdx b/sdk/flutter.mdx index 8726c55a..37956968 100644 --- a/sdk/flutter.mdx +++ b/sdk/flutter.mdx @@ -357,7 +357,7 @@ If the platform's identifier function is not called, you must provide a user ide - posthogDistinctId for PostHog - The model classes (`LRUserData`, `LRConsent`, `EventPayment`, `PaymentStatus`) are not re-exported by `linkrunner.dart`. Import `package:linkrunner/main.dart`, which exports `linkrunner.dart` and all models, or import each model directly, for example `import 'package:linkrunner/models/lr_user_data.dart';`. + The model classes (`LRUserData`, `LRConsent`, `PaymentStatus`) are not re-exported by `linkrunner.dart`. Import `package:linkrunner/main.dart`, which exports `linkrunner.dart` and all models, or import each model directly, for example `import 'package:linkrunner/models/lr_user_data.dart';`. ```dart @@ -599,23 +599,24 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires `linkrunner` 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +A payment is an in-app event with revenue. Send it with `trackEvent`, and put `amount`, `paymentId`, and `currency` in `eventData`. The event name becomes the payment type in reports and mappings, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires `linkrunner` 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. -This example sets every parameter. Only the first five are required. +This example sends every payment key. `amount`, `paymentId`, `currency`, and `userId` are required for a payment. ```dart import 'package:linkrunner/main.dart'; Future trackPurchase() async { try { - await LinkRunner().trackPayment( - eventName: 'purchase', // Required: payment name, stored as the payment type in reports and mappings - paymentId: 'order_123', // Required: your order or transaction ID, used to deduplicate - userId: 'user_123', // Required: your user ID. '' uses the user saved by signup or setUserData - amount: 1499.0, // Required: total the user paid, a finite number >= 0 - currency: 'INR', // Required: 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) - eventData: { // Optional: extra details. Meta and Google read these product fields + await LinkRunner().trackEvent( + eventName: 'purchase', // Payment name, stored as the payment type in reports and mappings + eventData: { + 'amount': 1499.0, // Required for a payment: total the user paid, a finite number >= 0 + 'paymentId': 'order_123', // Required for a payment: your order or transaction ID, used to deduplicate + 'currency': 'INR', // Required for a payment: 3-letter ISO 4217 code + 'userId': 'user_123', // Required for a payment: your user ID. '' uses the user saved by signup or setUserData + 'status': 'PAYMENT_COMPLETED', // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + // Optional: any other keys stay as event data. Meta and Google read these product fields 'content_ids': ['sku_1', 'sku_2'], 'contents': [ {'id': 'sku_1', 'quantity': 1, 'item_price': 999}, @@ -625,9 +626,10 @@ Future trackPurchase() async { 'num_items': 2, 'order_id': 'order_123', }, + eventId: 'evt_order_123', // Optional: your own ID for this event ); } on ArgumentError catch (e) { - // An invalid value throws here, and nothing is sent + // An invalid payment throws here, and nothing is sent print('Invalid payment: ${e.message}'); } catch (e) { print('Error tracking payment: $e'); @@ -635,62 +637,41 @@ Future trackPurchase() async { } ``` -#### `PaymentStatus` +#### Payment keys in `eventData` -`PaymentStatus` is a Dart enum exported by `package:linkrunner/main.dart`: +| Key | Type | Required | Description | +| --- | ---- | -------- | ----------- | +| `amount` | `double` or `int` | Yes | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `paymentId` | `String` | Yes | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `userId` | `String` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `status` | `String` | No | `PAYMENT_INITIATED`, `PAYMENT_COMPLETED` (default), `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. | +| Any other key | Any | No | Stays in the event data, such as product details for [ecommerce events](#ecommerce-events). | -```dart -enum PaymentStatus { - PAYMENT_INITIATED, - PAYMENT_COMPLETED, // default - PAYMENT_FAILED, - PAYMENT_CANCELLED, -} -``` +How `trackEvent` decides: -| Status | Use it when | Effect | -| ------ | ----------- | ------ | -| `PaymentStatus.PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | -| `PaymentStatus.PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | -| `PaymentStatus.PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | -| `PaymentStatus.PAYMENT_CANCELLED` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +- **`amount`, `paymentId`, and `currency` are all in `eventData`:** the event is a payment. The SDK checks the payment keys and sends them as payment fields. +- **None of them, or `amount` or `currency` without `paymentId`:** the event is a normal custom event, exactly as before. An `amount` or `currency` key on its own is ordinary event data. +- **`paymentId` with `amount` or `currency` missing:** `trackEvent` throws `ArgumentError` with `amount, paymentId and currency must be sent together`, and nothing is sent. +- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** `trackEvent` throws `ArgumentError`, and nothing is sent. -Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. +#### Payment status -#### `trackPayment` Parameters +`status` is a string. The `PaymentStatus` enum exported by `package:linkrunner/main.dart` has the same values, so `PaymentStatus.PAYMENT_CANCELLED.name` also works. -| Parameter | Type | Required | Default | Description | -| --------- | ---- | -------- | ------- | ----------- | -| `eventName` | `String` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `String` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | -| `userId` | `String` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | -| `amount` | `double` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | -| `currency` | `String` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | `PaymentStatus.PAYMENT_COMPLETED` | Where the payment is in its lifecycle. See the statuses above. | -| `eventData` | `Map?` | No | None | Extra details, such as product details for [ecommerce events](#ecommerce-events). An `amount` inside it is not revenue. | +| Status | Use it when | Effect | +| ------ | ----------- | ------ | +| `PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | +| `PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_CANCELLED` | The user backed out before paying, or you refunded it. Send it with the same `paymentId` as the completed payment. | Stored for reports and analytics. The payment no longer counts as revenue and nothing is sent to ad networks. | -`trackPayment` throws `ArgumentError` when a value breaks these rules, and nothing is sent. +Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. - The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. If you called `capturePayment` with no type or with `PaymentType.DEFAULT_PAYMENT`, pass `'DEFAULT'`. + The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. -#### Track a payment with `trackEvent` - -You can also pass an `EventPayment` to `trackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields and records the same payment. The `EventPayment` constructor throws `ArgumentError` for an invalid value. - -```dart -await LinkRunner().trackEvent( - eventName: 'purchase', // Event name, used as the payment type - payment: EventPayment( - amount: 499.0, - paymentId: 'order_123', - currency: 'INR', - userId: 'user_123', - ), -); -``` - `capturePayment` is deprecated. Apps on `linkrunner` 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). @@ -767,41 +748,28 @@ Future trackViewContent() async { ### Payment / Purchase Example -Use `trackEvent` with an [`EventPayment`](#tracking-payments) to send a `Purchase` event containing the ecommerce payload: +Use `trackEvent` with the [payment keys](#tracking-payments) and the ecommerce fields in `eventData` to send a `Purchase` event: ```dart -import 'package:linkrunner/main.dart'; - Future trackPurchase() async { try { await LinkRunner().trackEvent( - eventName: 'purchase', // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard. See: /ecommerce-manager/meta-commerce-manager, /ecommerce-manager/google-commerce-manager + eventName: 'purchase', // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard eventData: { + 'amount': 49.99, // Order total, sent to ad networks as the value + 'paymentId': 'payment_456', + 'currency': 'USD', + 'userId': 'user_123', + 'status': 'PAYMENT_COMPLETED', // Optional, the default 'content_ids': ['product_123'], - 'contents': [ - { - 'id': 'product_123', // Matches content_ids - 'quantity': 1, - 'item_price': 49.99 - } - ], + 'contents': [{'id': 'product_123', 'quantity': 1, 'item_price': 49.99}], 'content_type': 'product', - 'currency': 'USD', - 'value': 49.99, 'num_items': 1, - 'order_id': 'order_abc123' // Required for Purchase events + 'order_id': 'order_abc123', // Required for Purchase events }, - payment: EventPayment( - amount: 49.99, - paymentId: 'payment_456', - currency: 'USD', - userId: 'user_123', - status: PaymentStatus.PAYMENT_COMPLETED, - ), ); - print('Purchase tracked successfully'); } catch (e) { - print('Error tracking purchase: $e'); + print('Error tracking payment: $e'); } } ``` @@ -815,7 +783,7 @@ Future trackPurchase() async { - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, use [`trackPayment`](#tracking-payments). + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, add the [payment keys](#tracking-payments) to `eventData`. Track custom events in your app: @@ -841,15 +809,14 @@ Future trackEvent() async { #### Parameters for `LinkRunner().trackEvent` - `eventName`: `String` (required) - Name of the event to track -- `eventData`: `Map` (optional) - Key-value pairs for additional event data, including Meta ecommerce properties +- `eventData`: `Map` (optional) - Key-value pairs for additional event data, including Meta ecommerce properties. Add `amount`, `paymentId`, `currency`, and `userId` to make it a payment. See [Tracking Payments](#tracking-payments). - `eventId`: `Object` (optional) - Your own unique identifier for the event (`String` or `num`), useful for deduplication and correlating with your backend -- `payment`: `EventPayment` (optional). Records revenue with the event. See [Tracking Payments](#tracking-payments). ### Revenue Sharing with Ad Networks Send revenue to ad networks such as Google Ads and Meta as a [payment](#tracking-payments). Meta and Google receive the value and currency from the payment `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. -Put product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. +Put the payment keys and the product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. ```dart Future trackPurchase() async { @@ -857,16 +824,14 @@ Future trackPurchase() async { await LinkRunner().trackEvent( eventName: 'purchase', eventData: { + 'amount': 149.99, // Order total, sent to ad networks as the value + 'paymentId': 'order_12345', + 'currency': 'USD', + 'userId': 'user_123', 'content_ids': ['product_123'], 'num_items': 1, 'order_id': 'order_12345', }, - payment: EventPayment( - amount: 149.99, // Order total, sent to ad networks as the value - paymentId: 'order_12345', - currency: 'USD', - userId: 'user_123', - ), ); } catch (e) { print('Error tracking payment: $e'); @@ -874,7 +839,7 @@ Future trackPurchase() async { } ``` -An `amount` inside `eventData` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). +An `amount` in `eventData` is revenue only when `paymentId` and `currency` are there too. On its own it is ordinary event data. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). ## Enhanced Privacy Controls @@ -1096,7 +1061,7 @@ Connect APNs with Linkrunner | [`LinkRunner().setAdditionalData`](/sdk/flutter#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`LinkRunner().setUserData`](/sdk/flutter#setting-user-data) | Optional | App start, authentication or profile update logic | Right after `init` with your analytics IDs, and when user details become available | | [`LinkRunner().trackEvent`](/sdk/flutter#tracking-custom-events) | Optional | Throughout app | When specific user actions occur, including [ecommerce events](/sdk/flutter#ecommerce-events) | -| [`LinkRunner().trackPayment`](/sdk/flutter#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/flutter#ecommerce-events) | +| [`LinkRunner().trackEvent` with payment keys](/sdk/flutter#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/flutter#ecommerce-events) | | [`LinkRunner().setPushToken`](/sdk/flutter#uninstall-tracking) | Optional | Push notification setup | When FCM (Android) or APNs (iOS) token is available | | [`LinkRunner().setConsent`](/sdk/flutter#google-integrated-conversion-measurement-optional) | Optional | App initialization or consent flow | Before `init`, and again when consent changes | | [`LinkRunner().enablePIIHashing`](/sdk/flutter#enhanced-privacy-controls) | Optional | Privacy settings | When you want to enable PII hashing | diff --git a/sdk/ios.mdx b/sdk/ios.mdx index bc4099c9..c0e6139e 100644 --- a/sdk/ios.mdx +++ b/sdk/ios.mdx @@ -547,22 +547,23 @@ func setAdditionalData() async { ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires LinkrunnerKit 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +A payment is an in-app event with revenue. Send it with `trackEvent`, and put `amount`, `paymentId`, and `currency` in `eventData`. The event name becomes the payment type in reports and mappings, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires LinkrunnerKit 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. -This example sets every parameter. Only the first five are required. +This example sends every payment key. `amount`, `paymentId`, `currency`, and `userId` are required for a payment. ```swift import LinkrunnerKit func trackPurchase() async { - await LinkrunnerSDK.shared.trackPayment( - eventName: "purchase", // Required: payment name, stored as the payment type in reports and mappings - paymentId: "order_123", // Required: your order or transaction ID, used to deduplicate - userId: "user_123", // Required: your user ID. "" uses the user saved by signup or setUserData - amount: 1499.0, // Required: total the user paid, a finite number >= 0 - currency: "INR", // Required: 3-letter ISO 4217 code - status: .completed, // Optional: defaults to .completed (see statuses below) - eventData: [ // Optional: extra details. Meta and Google read these product fields + await LinkrunnerSDK.shared.trackEvent( + eventName: "purchase", // Payment name, stored as the payment type in reports and mappings + eventData: [ + "amount": 1499.0, // Required for a payment: total the user paid, a finite number >= 0 + "paymentId": "order_123", // Required for a payment: your order or transaction ID, used to deduplicate + "currency": "INR", // Required for a payment: 3-letter ISO 4217 code + "userId": "user_123", // Required for a payment: your user ID. "" uses the user saved by signup or setUserData + "status": "PAYMENT_COMPLETED", // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + // Optional: any other keys stay as event data. Meta and Google read these product fields "content_ids": ["sku_1", "sku_2"], "contents": [ ["id": "sku_1", "quantity": 1, "item_price": 999], @@ -572,74 +573,46 @@ func trackPurchase() async { "num_items": 2, "order_id": "order_123" ], - eventId: "evt_order_123" // Optional: your own ID for this event, defaults to nil + eventId: "evt_order_123" // Optional: your own ID for this event ) } ``` -#### `PaymentStatus` +#### Payment keys in `eventData` -`PaymentStatus` is a `String` enum in `LinkrunnerKit`. Pass a case, such as `status: .initiated`: +| Key | Type | Required | Description | +| --- | ---- | -------- | ----------- | +| `amount` | `Double` or `Int` | Yes | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `paymentId` | `String` | Yes | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `currency` | `String` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `userId` | `String` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `status` | `String` | No | `PAYMENT_INITIATED`, `PAYMENT_COMPLETED` (default), `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. | +| Any other key | Any | No | Stays in the event data, such as product details for [ecommerce events](#ecommerce-events). | -```swift -public enum PaymentStatus: String, Sendable { - case initiated = "PAYMENT_INITIATED" - case completed = "PAYMENT_COMPLETED" // default - case failed = "PAYMENT_FAILED" - case cancelled = "PAYMENT_CANCELLED" -} -``` +How `trackEvent` decides: -| Status | Use it when | Effect | -| ------ | ----------- | ------ | -| `.initiated` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | -| `.completed` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | -| `.failed` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | -| `.cancelled` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +- **`amount`, `paymentId`, and `currency` are all in `eventData`:** the event is a payment. The SDK checks the payment keys and sends them as payment fields. +- **None of them, or `amount` or `currency` without `paymentId`:** the event is a normal custom event, exactly as before. An `amount` or `currency` key on its own is ordinary event data. +- **`paymentId` with `amount` or `currency` missing:** `trackEvent` does not send the event, and debug builds print with `amount, paymentId and currency must be sent together`, and nothing is sent. +- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** `trackEvent` does not send the event, and debug builds print the reason to the console, and nothing is sent. -Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. +#### Payment status -#### `trackPayment` Parameters +`status` is a string. `LinkrunnerKit` also has a `PaymentStatus` enum whose `rawValue` is the same string, for example `PaymentStatus.cancelled.rawValue`. -| Parameter | Type | Required | Default | Description | -| --------- | ---- | -------- | ------- | ----------- | -| `eventName` | `String` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `String` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | -| `userId` | `String` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | -| `amount` | `Double` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | -| `currency` | `String` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | `.completed` | Where the payment is in its lifecycle. See the statuses above. | -| `eventData` | `SendableDictionary?` | No | None | Extra details, such as product details for [ecommerce events](#ecommerce-events). An `amount` inside it is not revenue. | -| `eventId` | `String?` | No | None | Your own identifier for this event. | +| Status | Use it when | Effect | +| ------ | ----------- | ------ | +| `PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | +| `PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_CANCELLED` | The user backed out before paying, or you refunded it. Send it with the same `paymentId` as the completed payment. | Stored for reports and analytics. The payment no longer counts as revenue and nothing is sent to ad networks. | -`trackPayment` does not throw. When a value breaks these rules, the payment is not sent, and debug builds print the reason to the console. +Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. -#### Track a payment with `trackEvent` - -You can also pass an `EventPayment` to `trackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields and records the same payment. The `EventPayment` initializer throws `EventPaymentError.invalidAmount`, `.invalidPaymentId`, or `.invalidCurrency` for an invalid value. - -```swift -do { - let payment = try EventPayment( - amount: 99.99, - paymentId: "payment_123", - currency: "USD", - userId: "user_123" - ) - - await LinkrunnerSDK.shared.trackEvent( - eventName: "purchase", - payment: payment - ) -} catch { - print("Invalid payment: \(error)") -} -``` - `capturePayment` is deprecated. Apps on LinkrunnerKit 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). @@ -651,7 +624,7 @@ do { - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, use [`trackPayment`](#tracking-payments). + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-registration) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, add the [payment keys](#tracking-payments) to `eventData`. Track custom events in your app: @@ -673,41 +646,33 @@ func trackEvent() async { #### Parameters for `LinkrunnerSDK.shared.trackEvent` - `eventName`: `String` (required) - Name of the event to track -- `eventData`: `[String: Any]` (optional) - Key-value pairs for additional event data, including Meta ecommerce properties +- `eventData`: `[String: Any]` (optional) - Key-value pairs for additional event data, including Meta ecommerce properties. Add `amount`, `paymentId`, `currency`, and `userId` to make it a payment. See [Tracking Payments](#tracking-payments). - `eventId`: `String` (optional) - Your own unique identifier for the event, useful for deduplication and correlating with your backend ### Revenue Sharing with Ad Networks Send revenue to ad networks such as Google Ads and Meta as a [payment](#tracking-payments). Meta and Google receive the value and currency from the payment `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. -Put product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. +Put the payment keys and the product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. ```swift func trackPurchase() async { - do { - let payment = try EventPayment( - amount: 149.99, // Order total, sent to ad networks as the value - paymentId: "order_12345", - currency: "USD", - userId: "user_123" - ) - - await LinkrunnerSDK.shared.trackEvent( - eventName: "purchase", - eventData: [ - "content_ids": ["product_123"], - "num_items": 1, - "order_id": "order_12345" - ], - payment: payment - ) - } catch { - print("Invalid payment: \(error)") - } + await LinkrunnerSDK.shared.trackEvent( + eventName: "purchase", + eventData: [ + "amount": 149.99, // Order total, sent to ad networks as the value + "paymentId": "order_12345", + "currency": "USD", + "userId": "user_123", + "content_ids": ["product_123"], + "num_items": 1, + "order_id": "order_12345" + ] + ) } ``` -An `amount` inside `eventData` is not sent to ad networks as revenue, but it still counts toward SKAN conversion values. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). +An `amount` in `eventData` is revenue only when `paymentId` and `currency` are there too. On its own it is ordinary event data, and on iOS it still counts toward SKAN conversion values. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). ## Ecommerce Events @@ -773,40 +738,25 @@ func trackViewContent() async { ### Payment / Purchase Example -Use `trackEvent` with an `EventPayment` to send a `Purchase` event containing the ecommerce payload: +Use `trackEvent` with the [payment keys](#tracking-payments) and the ecommerce fields in `eventData` to send a `Purchase` event: ```swift func trackPurchase() async { - do { - let payment = try EventPayment( - amount: 49.99, - paymentId: "payment_456", - currency: "USD", - userId: "user_123" - ) - - await LinkrunnerSDK.shared.trackEvent( - eventName: "purchase", // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard - eventData: [ - "content_ids": ["product_123"], - "contents": [ - [ - "id": "product_123", // Matches content_ids - "quantity": 1, - "item_price": 49.99 - ] - ], - "content_type": "product", - "currency": "USD", - "value": 49.99, - "num_items": 1, - "order_id": "order_abc123" // Required for Purchase events - ], - payment: payment - ) - } catch { - print("Invalid payment: \(error)") - } + await LinkrunnerSDK.shared.trackEvent( + eventName: "purchase", // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard + eventData: [ + "amount": 49.99, // Order total, sent to ad networks as the value + "paymentId": "payment_456", + "currency": "USD", + "userId": "user_123", + "status": "PAYMENT_COMPLETED", // Optional, the default + "content_ids": ["product_123"], + "contents": [["id": "product_123", "quantity": 1, "item_price": 49.99]], + "content_type": "product", + "num_items": 1, + "order_id": "order_abc123" // Required for Purchase events + ] + ) } ``` @@ -956,7 +906,7 @@ Connect APNs with Linkrunner | [`LinkrunnerSDK.shared.setAdditionalData`](/sdk/ios#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`LinkrunnerSDK.shared.setUserData`](/sdk/ios#setting-user-data) | Optional | App start, authentication or profile update logic | Right after `init` with your analytics IDs, and when user details become available | | [`LinkrunnerSDK.shared.trackEvent`](/sdk/ios#tracking-custom-events) | Optional | Throughout app | When specific user actions occur, including [ecommerce events](/sdk/ios#ecommerce-events) | -| [`LinkrunnerSDK.shared.trackPayment`](/sdk/ios#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/ios#ecommerce-events) | +| [`LinkrunnerSDK.shared.trackEvent` with payment keys](/sdk/ios#tracking-payments) | Optional | Payment processing | When user makes a payment, including [ecommerce purchases](/sdk/ios#ecommerce-events) | | [`LinkrunnerSDK.shared.setPushToken`](/sdk/ios#uninstall-tracking) | Optional | Push notification setup | When APNs token is available | | [`LinkrunnerSDK.shared.setConsent`](/sdk/ios#consent) | Optional | App initialization or consent flow | Before `initialize`, and again when consent changes | | [`LinkrunnerSDK.shared.enablePIIHashing`](/sdk/ios#enhanced-privacy-controls) | Optional | Privacy settings | When you want to enable PII hashing | diff --git a/sdk/migrate-to-track-event.mdx b/sdk/migrate-to-track-event.mdx index 1aaaff9f..6a2c38f4 100644 --- a/sdk/migrate-to-track-event.mdx +++ b/sdk/migrate-to-track-event.mdx @@ -1,15 +1,15 @@ --- title: "Migrate payments to in-app events" sidebarTitle: "Migrate to in-app events" -description: "Move from capturePayment to trackPayment, which sends each payment as an in-app event with revenue" +description: "Move from capturePayment to trackEvent, which sends each payment as an in-app event with revenue" icon: "arrow-right-arrow-left" --- -In the new SDK versions, a payment is an in-app event with revenue. You send it with `trackPayment`, and it goes to the same pipeline as your other in-app events. `capturePayment` and `removePayment` are removed. +In the new SDK versions, a payment is an in-app event with revenue. You send it with `trackEvent`, the same method you use for custom events. `capturePayment` and `removePayment` are removed. Apps on older SDK versions keep working with no change. Migrate when you upgrade. -| Platform | Package | Old versions (`capturePayment`) | New versions (`trackPayment`) | +| Platform | Package | Old versions (`capturePayment`) | New versions (`trackEvent` for payments) | | --- | --- | --- | --- | | Android | `io.linkrunner:android-sdk` | 4.x | 5.0.0 | | iOS | LinkrunnerKit | 4.x | 5.0.0 | @@ -23,18 +23,37 @@ Apps on older SDK versions keep working with no change. Migrate when you upgrade **Use the exact same event name, with the same spelling and letter case.** - Your old payment `type` becomes the `eventName`. If you sent `FIRST_PAYMENT` before, send `"FIRST_PAYMENT"` now. Do not change it to `first_payment`, `First_Payment`, or `firstPayment`. + Your old payment `type` becomes the event name. If you sent `FIRST_PAYMENT` before, send `"FIRST_PAYMENT"` now. Do not change it to `first_payment`, `First_Payment`, or `firstPayment`. Linkrunner matches the name exactly. A different spelling is treated as a new in-app event, so your existing Meta and Google mappings will not match it, and those payments will not be sent to Meta or Google. ## How to migrate -1. **Upgrade the SDK** to the new version in the table above. -2. **Replace `capturePayment` with `trackPayment`.** Rename `type` to `eventName` and keep the same value. -3. **Add `currency`.** It is required now, for example `"USD"` or `"INR"`. -4. **Remove `removePayment` calls.** There is no replacement. -5. **Test once** with a debug build and check the payment on the [Events](https://dashboard.linkrunner.io/dashboard/events) page under the same name as before. +For each `capturePayment` call, make three edits: + +1. **Rename `capturePayment` to `trackEvent`.** +2. **Pass your old `type` as the event name,** the first argument, with exactly the same spelling. +3. **Move the payment fields into the event data:** `amount`, `paymentId`, `userId`, and `status` keep their names. Add `currency`, which is new and required, for example `"USD"` or `"INR"`. + +Then remove every `removePayment` call, and test once with a debug build. The payment should show on the [Events](https://dashboard.linkrunner.io/dashboard/events) page under the same name as before. + +| `capturePayment` | `trackEvent` | +| --- | --- | +| `type` | the event name (first argument), same value and spelling | +| `amount` | `amount` in the event data, the order total | +| `paymentId` | `paymentId` in the event data, required | +| `userId` | `userId` in the event data, still required. An empty string uses the user saved by `signup` or `setUserData` | +| `status` | `status` in the event data, optional, defaults to `PAYMENT_COMPLETED` | +| `eventData` | the other keys in the event data, such as product details | +| Not available | `currency` in the event data, new and required | + +The event is a payment only when the event data has `amount`, `paymentId`, and `currency` together. If any of the three is missing, it is not a payment: + +- `amount` or `currency` without `paymentId` is a normal custom event, exactly as before. +- `paymentId` with `amount` or `currency` missing is an error, and nothing is sent. + +Only `PAYMENT_COMPLETED` payments count as revenue and are sent to ad networks. See [Payment status](/sdk/track-payments#payment-status) for the other statuses. ## Old payment types @@ -44,142 +63,120 @@ These are the old payment types. Send them exactly like this, in uppercase: If you did not pass a `type` before, your payments were saved as `DEFAULT`. Use `"DEFAULT"` as the event name. In Flutter, `PaymentType.DEFAULT_PAYMENT` was also saved as `DEFAULT`, so use `"DEFAULT"`, not `"DEFAULT_PAYMENT"`. -## What changes in the call - -| `capturePayment` | `trackPayment` | -| --- | --- | -| `type` | `eventName`, same value and spelling | -| `paymentId` | `paymentId` | -| `userId` | `userId`, still required | -| `amount` | `amount`, the order total | -| `status` | `status`, optional, defaults to `PAYMENT_COMPLETED` | -| `eventData` | `eventData` | -| Not available | `currency`, new and required | - -Only `PAYMENT_COMPLETED` payments count as revenue and are sent to ad networks. See [Payment status](/sdk/track-payments#payment-status) for the other statuses. - ## Code examples -Each example keeps the old `FIRST_PAYMENT` name, so your mappings keep working. `eventData` with product details is optional. Send it only if you use [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager) or [Google Commerce Manager](/ecommerce-manager/google-commerce-manager). +Each example is from a sports booking app. It shows the old payment call, then the new custom event and payment with `trackEvent`. The payment keeps the old `FIRST_PAYMENT` name, so the existing mappings keep working. Use your own values. Before (Android SDK 4.x): ```kotlin - val paymentData = CapturePaymentRequest( - paymentId = "payment_123", // your order ID - userId = "user123", // your user ID - amount = 99.99, // total the user paid - type = PaymentType.FIRST_PAYMENT, // becomes eventName in trackPayment - status = PaymentStatus.PAYMENT_COMPLETED // same values in trackPayment + LinkRunner.getInstance().capturePayment( + CapturePaymentRequest( + paymentId = "bk_10231", + userId = "playo_u_8842", + amount = 1200.0, + type = PaymentType.FIRST_PAYMENT, + status = PaymentStatus.PAYMENT_COMPLETED + ) ) - LinkRunner.getInstance().capturePayment(paymentData) ``` After (Android SDK 5.0.0): ```kotlin - import io.linkrunner.sdk.models.PaymentStatus - - LinkRunner.getInstance().trackPayment( - eventName = "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings - paymentId = "payment_123", // your order ID, used to deduplicate - userId = "user123", // required, links the payment to your user - amount = 99.99, // order total the user paid - currency = "USD", // new, required: 3-letter ISO 4217 code - status = PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks - // Product details: Meta receives these fields, Google receives content_ids as item IDs + // Custom event, no change + LinkRunner.getInstance().trackEvent( + eventName = "venue_viewed", + eventData = mapOf("venue_id" to "v_221", "sport" to "badminton") + ) + + // Payment: same trackEvent, payment fields in eventData + LinkRunner.getInstance().trackEvent( + eventName = "FIRST_PAYMENT", // was type, same spelling eventData = mapOf( - "content_ids" to listOf("sku_123"), - "contents" to listOf(mapOf("id" to "sku_123", "quantity" to 1, "item_price" to 99.99)), - "content_type" to "product", - "num_items" to 1, - "order_id" to "payment_123" + "amount" to 1200.0, + "paymentId" to "bk_10231", + "currency" to "INR", // new, required + "userId" to "playo_u_8842", + "status" to "PAYMENT_COMPLETED", // optional, the default + "venue_id" to "v_221" // other keys stay as event data ) - ).onFailure { error -> - println("Error tracking payment: ${error.message}") - } + ) ``` - - See [Tracking Payments](/sdk/android#tracking-payments) for the full reference. + From Java, call `LinkrunnerJava.getInstance().trackEvent("FIRST_PAYMENT", eventData, null, callback)` with the same keys. See [Tracking Payments](/sdk/android#tracking-payments). Before (LinkrunnerKit 4.x): ```swift await LinkrunnerSDK.shared.capturePayment( - amount: 99.99, // total the user paid - userId: "user123", // your user ID - paymentId: "payment456", // your order ID - type: .firstPayment, // becomes eventName in trackPayment - status: .completed // same values in trackPayment + amount: 1200, + userId: "playo_u_8842", + paymentId: "bk_10231", + type: .firstPayment, + status: .completed ) ``` After (LinkrunnerKit 5.0.0): ```swift - await LinkrunnerSDK.shared.trackPayment( - eventName: "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings - paymentId: "payment456", // your order ID, used to deduplicate - userId: "user123", // required, links the payment to your user - amount: 99.99, // order total the user paid - currency: "USD", // new, required: 3-letter ISO 4217 code - status: .completed, // optional, default; only completed payments go to ad networks - // Product details: Meta receives these fields, Google receives content_ids as item IDs + // Custom event, no change + await LinkrunnerSDK.shared.trackEvent( + eventName: "venue_viewed", + eventData: ["venue_id": "v_221", "sport": "badminton"] + ) + + // Payment: same trackEvent, payment fields in eventData + await LinkrunnerSDK.shared.trackEvent( + eventName: "FIRST_PAYMENT", // was type, same spelling eventData: [ - "content_ids": ["sku_123"], - "contents": [["id": "sku_123", "quantity": 1, "item_price": 99.99]], - "content_type": "product", - "num_items": 1, - "order_id": "payment456" + "amount": 1200, + "paymentId": "bk_10231", + "currency": "INR", // new, required + "userId": "playo_u_8842", + "status": "PAYMENT_COMPLETED", // optional, the default + "venue_id": "v_221" // other keys stay as event data ] ) ``` - - See [Tracking Payments](/sdk/ios#tracking-payments) for the full reference. + See [Tracking Payments](/sdk/ios#tracking-payments). Before (`rn-linkrunner` 3.x): ```javascript await linkrunner.capturePayment({ - amount: 100, // total the user paid - userId: "user123", // your user ID - paymentId: "payment456", // your order ID - type: "FIRST_PAYMENT", // becomes eventName in trackPayment - status: "PAYMENT_COMPLETED", // same values in trackPayment + amount: 1200, + userId: "playo_u_8842", + paymentId: "bk_10231", + type: "FIRST_PAYMENT", + status: "PAYMENT_COMPLETED", }); ``` After (`rn-linkrunner` 4.0.0): ```javascript - import linkrunner, { PaymentStatus } from "rn-linkrunner"; - - await linkrunner.trackPayment({ - eventName: "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings - paymentId: "payment456", // your order ID, used to deduplicate - userId: "user123", // required, links the payment to your user - amount: 100, // order total the user paid - currency: "USD", // new, required: 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks - // Product details: Meta receives these fields, Google receives content_ids as item IDs - eventData: { - content_ids: ["sku_123"], - contents: [{ id: "sku_123", quantity: 1, item_price: 100 }], - content_type: "product", - num_items: 1, - order_id: "payment456", - }, + // Custom event, no change + await linkrunner.trackEvent("venue_viewed", { venue_id: "v_221", sport: "badminton" }); + + // Payment: same trackEvent, payment fields in eventData + await linkrunner.trackEvent("FIRST_PAYMENT", { // was type, same spelling + amount: 1200, + paymentId: "bk_10231", + currency: "INR", // new, required + userId: "playo_u_8842", + status: "PAYMENT_COMPLETED", // optional, the default + venue_id: "v_221", // other keys stay as event data }); ``` - - See [Tracking Payments](/sdk/react-native#tracking-payments) for the full reference. + See [Tracking Payments](/sdk/react-native#tracking-payments). Before (`linkrunner` 4.x): @@ -187,11 +184,11 @@ Each example keeps the old `FIRST_PAYMENT` name, so your mappings keep working. ```dart await LinkRunner().capturePayment( capturePayment: LRCapturePayment( - amount: 99.99, // total the user paid - userId: 'user123', // your user ID - paymentId: 'payment456', // your order ID - type: PaymentType.FIRST_PAYMENT, // becomes eventName in trackPayment - status: PaymentStatus.PAYMENT_COMPLETED, // same values in trackPayment + amount: 1200, + userId: 'playo_u_8842', + paymentId: 'bk_10231', + type: PaymentType.FIRST_PAYMENT, + status: PaymentStatus.PAYMENT_COMPLETED, ), ); ``` @@ -199,28 +196,27 @@ Each example keeps the old `FIRST_PAYMENT` name, so your mappings keep working. After (`linkrunner` 5.0.0): ```dart - await LinkRunner().trackPayment( - eventName: 'FIRST_PAYMENT', // was "type"; becomes the payment type in reports and mappings - paymentId: 'payment456', // your order ID, used to deduplicate - userId: 'user123', // required, links the payment to your user - amount: 99.99, // order total the user paid - currency: 'USD', // new, required: 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks - // Product details: Meta receives these fields, Google receives content_ids as item IDs + // Custom event, no change + await LinkRunner().trackEvent( + eventName: 'venue_viewed', + eventData: {'venue_id': 'v_221', 'sport': 'badminton'}, + ); + + // Payment: same trackEvent, payment fields in eventData + await LinkRunner().trackEvent( + eventName: 'FIRST_PAYMENT', // was type, same spelling eventData: { - 'content_ids': ['sku_123'], - 'contents': [ - {'id': 'sku_123', 'quantity': 1, 'item_price': 99.99}, - ], - 'content_type': 'product', - 'num_items': 1, - 'order_id': 'payment456', + 'amount': 1200, + 'paymentId': 'bk_10231', + 'currency': 'INR', // new, required + 'userId': 'playo_u_8842', + 'status': 'PAYMENT_COMPLETED', // optional, the default + 'venue_id': 'v_221', // other keys stay as event data }, ); ``` - - See [Tracking Payments](/sdk/flutter#tracking-payments) for the full reference. + See [Tracking Payments](/sdk/flutter#tracking-payments). Expo apps call `rn-linkrunner`, so the code change is the same as React Native. Upgrade `expo-linkrunner` to 4.0.0 and `rn-linkrunner` to 4.0.0 or later together. @@ -229,166 +225,146 @@ Each example keeps the old `FIRST_PAYMENT` name, so your mappings keep working. ```javascript await linkrunner.capturePayment({ - amount: 100, // total the user paid - userId: "user123", // your user ID - paymentId: "payment456", // your order ID - type: "FIRST_PAYMENT", // becomes eventName in trackPayment - status: "PAYMENT_COMPLETED", // same values in trackPayment + amount: 1200, + userId: "playo_u_8842", + paymentId: "bk_10231", + type: "FIRST_PAYMENT", }); ``` After (`rn-linkrunner` 4.0.0): ```javascript - import linkrunner, { PaymentStatus } from "rn-linkrunner"; - - await linkrunner.trackPayment({ - eventName: "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings - paymentId: "payment456", // your order ID, used to deduplicate - userId: "user123", // required, links the payment to your user - amount: 100, // order total the user paid - currency: "USD", // new, required: 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks - // Product details: Meta receives these fields, Google receives content_ids as item IDs - eventData: { - content_ids: ["sku_123"], - contents: [{ id: "sku_123", quantity: 1, item_price: 100 }], - content_type: "product", - num_items: 1, - order_id: "payment456", - }, + // Custom event, no change + await linkrunner.trackEvent("venue_viewed", { venue_id: "v_221", sport: "badminton" }); + + // Payment: same trackEvent, payment fields in eventData + await linkrunner.trackEvent("FIRST_PAYMENT", { // was type, same spelling + amount: 1200, + paymentId: "bk_10231", + currency: "INR", // new, required + userId: "playo_u_8842", }); ``` - - See [Tracking Payments](/sdk/react-native#tracking-payments) for the full reference. + See [Tracking Payments](/sdk/expo#tracking-payments). Before (`capacitor-linkrunner` 2.x): ```typescript await linkrunner.capturePayment({ - amount: 100, // total the user paid - userId: "user123", // your user ID - paymentId: "payment456", // your order ID - type: "FIRST_PAYMENT", // becomes eventName in trackPayment - status: "PAYMENT_COMPLETED", // same values in trackPayment + amount: 1200, + userId: "playo_u_8842", + paymentId: "bk_10231", + type: "FIRST_PAYMENT", + status: "PAYMENT_COMPLETED", }); ``` After (`capacitor-linkrunner` 3.0.0): ```typescript - import linkrunner, { PaymentStatus } from "capacitor-linkrunner"; - - await linkrunner.trackPayment({ - eventName: "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings - paymentId: "payment456", // your order ID, used to deduplicate - userId: "user123", // required, links the payment to your user - amount: 100, // order total the user paid - currency: "USD", // new, required: 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks - // Product details: Meta receives these fields, Google receives content_ids as item IDs + // Custom event, no change + await linkrunner.trackEvent({ + eventName: "venue_viewed", + eventData: { venue_id: "v_221", sport: "badminton" }, + }); + + // Payment: same trackEvent, payment fields in eventData + await linkrunner.trackEvent({ + eventName: "FIRST_PAYMENT", // was type, same spelling eventData: { - content_ids: ["sku_123"], - contents: [{ id: "sku_123", quantity: 1, item_price: 100 }], - content_type: "product", - num_items: 1, - order_id: "payment456", + amount: 1200, + paymentId: "bk_10231", + currency: "INR", // new, required + userId: "playo_u_8842", + status: "PAYMENT_COMPLETED", // optional, the default + venue_id: "v_221", // other keys stay as event data }, }); ``` - - See [Tracking Payments](/sdk/capacitor#tracking-payments) for the full reference. + See [Tracking Payments](/sdk/capacitor#tracking-payments). Before (`cordova-linkrunner` 1.x): ```javascript linkrunner.capturePayment({ - amount: 100, // total the user paid - userId: "user123", // your user ID - paymentId: "payment456", // your order ID - type: "FIRST_PAYMENT", // becomes eventName in trackPayment - status: "PAYMENT_COMPLETED" // same values in trackPayment + amount: 1200, + userId: "playo_u_8842", + paymentId: "bk_10231", + type: "FIRST_PAYMENT", + status: "PAYMENT_COMPLETED" }); ``` After (`cordova-linkrunner` 2.0.0): ```javascript - linkrunner.trackPayment({ - eventName: "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings - paymentId: "payment456", // your order ID, used to deduplicate - userId: "user123", // required, links the payment to your user - amount: 100, // order total the user paid - currency: "USD", // new, required: 3-letter ISO 4217 code - status: linkrunner.PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks - // Product details: Meta receives these fields, Google receives content_ids as item IDs - eventData: { - content_ids: ["sku_123"], - contents: [{ id: "sku_123", quantity: 1, item_price: 100 }], - content_type: "product", - num_items: 1, - order_id: "payment456" - } + // Custom event, no change + linkrunner.trackEvent("venue_viewed", { venue_id: "v_221", sport: "badminton" }); + + // Payment: same trackEvent, payment fields in eventData + linkrunner.trackEvent("FIRST_PAYMENT", { // was type, same spelling + amount: 1200, + paymentId: "bk_10231", + currency: "INR", // new, required + userId: "playo_u_8842", + status: "PAYMENT_COMPLETED", // optional, the default + venue_id: "v_221" // other keys stay as event data }).catch(function (error) { console.error("Error tracking payment:", error); }); ``` - - See [Tracking Payments](/sdk/cordova#tracking-payments) for the full reference. + See [Tracking Payments](/sdk/cordova#tracking-payments). Before (bridges built on native 4.x): ```csharp LinkrunnerSDK.CapturePayment( - userId: "user_123", // your user ID - amount: 9.99, // total the user paid - paymentId: "pay_abc123", // your order ID - type: "FIRST_PAYMENT", // becomes eventName in TrackPayment - status: "PAYMENT_COMPLETED" // same values in TrackPayment + userId: "playo_u_8842", + amount: 1200.0, + paymentId: "bk_10231", + type: "FIRST_PAYMENT", + status: "PAYMENT_COMPLETED" ); ``` After (bridges built on native 5.0.0): ```csharp - // Product details: Meta receives these fields, Google receives content_ids as item IDs + // Custom event, no change + LinkrunnerSDK.TrackEvent("venue_viewed", "{\"venue_id\":\"v_221\",\"sport\":\"badminton\"}"); + + // Payment: same TrackEvent, payment fields in the event data JSON string eventDataJson = - "{\"content_ids\":[\"sku_123\"]," + - "\"contents\":[{\"id\":\"sku_123\",\"quantity\":1,\"item_price\":9.99}]," + - "\"content_type\":\"product\",\"num_items\":1,\"order_id\":\"pay_abc123\"}"; - - LinkrunnerSDK.TrackPayment( - eventName: "FIRST_PAYMENT", // was "type"; becomes the payment type in reports and mappings - paymentId: "pay_abc123", // your order ID, used to deduplicate - userId: "user_123", // required, links the payment to your user - amount: 9.99, // order total the user paid - currency: "USD", // new, required: 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_COMPLETED, // optional, default; only completed payments go to ad networks - eventDataJson: eventDataJson - ); + "{\"amount\":1200.0," + + "\"paymentId\":\"bk_10231\"," + + "\"currency\":\"INR\"," + // new, required + "\"userId\":\"playo_u_8842\"," + + "\"status\":\"PAYMENT_COMPLETED\"," + // optional, the default + "\"venue_id\":\"v_221\"}"; // other keys stay as event data + LinkrunnerSDK.TrackEvent("FIRST_PAYMENT", eventDataJson); // was type, same spelling ``` - Update your Android and iOS bridge code and C# wrapper as shown in [Track Payments](/sdk/unity#track-payments). +## Refunds and removePayment + +`removePayment` is removed and has no replacement. To record a refund or a cancelled booking, send the same event name and `paymentId` again with `status` set to `PAYMENT_CANCELLED`. That payment no longer counts as revenue, and nothing is sent to ad networks. + ## Using a new event name You can use a new name such as `purchase` instead of an old type. Before you release, add a mapping for that name in the dashboard for Meta, Google, and any other partner that should get it. Without a mapping, the payment is saved in Linkrunner but not sent to ad networks. Keep one name per payment. The same `paymentId` sent under two names counts as two payments. -## Already calling trackEvent? - -You can attach the payment to an existing `trackEvent` call instead of calling `trackPayment`. Pass a payment object with `amount`, `paymentId`, `currency`, `userId`, and an optional `status`. The same naming rule applies. Each SDK page shows this under **Tracking Payments**. - ## Sending payments from your server Use `POST /api/v1/capture-event` with `amount`, `payment_id`, and `currency` next to `event_name` and `user_id`. The same naming rule applies. See [Events Capture API](/api-reference/event-capture#payment-fields). `POST /api/v1/capture-payment` is deprecated but keeps working. diff --git a/sdk/react-native.mdx b/sdk/react-native.mdx index 6d2e3399..394e7d63 100644 --- a/sdk/react-native.mdx +++ b/sdk/react-native.mdx @@ -462,23 +462,24 @@ This method allows you to connect user identities across different analytics and ### Tracking Payments -Use `trackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires `rn-linkrunner` 4.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +A payment is an in-app event with revenue. Send it with `trackEvent`, and put `amount`, `paymentId`, and `currency` in `eventData`. The event name becomes the payment type in reports and mappings, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires `rn-linkrunner` 4.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. -This example sets every parameter. Only the first five are required. +This example sends every payment key. `amount`, `paymentId`, `currency`, and `userId` are required for a payment. ```javascript import linkrunner, { PaymentStatus } from "rn-linkrunner"; const trackPurchase = async () => { try { - await linkrunner.trackPayment({ - eventName: "purchase", // Required: payment name, stored as the payment type in reports and mappings - paymentId: "order_123", // Required: your order or transaction ID, used to deduplicate - userId: "user_123", // Required: your user ID. "" uses the user saved by signup or setUserData - amount: 1499, // Required: total the user paid, a finite number >= 0 - currency: "INR", // Required: 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) - eventData: { // Optional: extra details. Meta and Google read these product fields + await linkrunner.trackEvent( + "purchase", // Payment name, stored as the payment type in reports and mappings + { + amount: 1499, // Required for a payment: total the user paid, a finite number >= 0 + paymentId: "order_123", // Required for a payment: your order or transaction ID, used to deduplicate + currency: "INR", // Required for a payment: 3-letter ISO 4217 code + userId: "user_123", // Required for a payment: your user ID. "" uses the user saved by signup or setUserData + status: PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + // Optional: any other keys stay as event data. Meta and Google read these product fields content_ids: ["sku_1", "sku_2"], contents: [ { id: "sku_1", quantity: 1, item_price: 999 }, @@ -488,74 +489,50 @@ const trackPurchase = async () => { num_items: 2, order_id: "order_123", }, - }); + "evt_order_123", // Optional: your own ID for this event + ); } catch (error) { - // An invalid value throws here, and nothing is sent + // An invalid payment throws here, and nothing is sent console.error("Error tracking payment:", error); } }; ``` -#### `PaymentStatus` +#### Payment keys in `eventData` -`rn-linkrunner` exports `PaymentStatus` as an object of string values, and as a TypeScript type: +| Key | Type | Required | Description | +| --- | ---- | -------- | ----------- | +| `amount` | `number` | Yes | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `paymentId` | `string` | Yes | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `userId` | `string` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `status` | `string` | No | `PAYMENT_INITIATED`, `PAYMENT_COMPLETED` (default), `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. | +| Any other key | Any | No | Stays in the event data, such as product details for [ecommerce events](#ecommerce-events). | -```javascript -import { PaymentStatus } from "rn-linkrunner"; +How `trackEvent` decides: -PaymentStatus.PAYMENT_INITIATED // "PAYMENT_INITIATED" -PaymentStatus.PAYMENT_COMPLETED // "PAYMENT_COMPLETED" (default) -PaymentStatus.PAYMENT_FAILED // "PAYMENT_FAILED" -PaymentStatus.PAYMENT_CANCELLED // "PAYMENT_CANCELLED" -``` +- **`amount`, `paymentId`, and `currency` are all in `eventData`:** the event is a payment. The SDK checks the payment keys and sends them as payment fields. +- **None of them, or `amount` or `currency` without `paymentId`:** the event is a normal custom event, exactly as before. An `amount` or `currency` key on its own is ordinary event data. +- **`paymentId` with `amount` or `currency` missing:** `trackEvent` throws with `amount, paymentId and currency must be sent together`, and nothing is sent. +- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** `trackEvent` throws, and nothing is sent. -Plain strings such as `"PAYMENT_COMPLETED"` also work. +#### Payment status + +`rn-linkrunner` exports `PaymentStatus` as an object of string values. Plain strings such as `"PAYMENT_COMPLETED"` also work. | Status | Use it when | Effect | | ------ | ----------- | ------ | | `PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | | `PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | | `PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | -| `PAYMENT_CANCELLED` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_CANCELLED` | The user backed out before paying, or you refunded it. Send it with the same `paymentId` as the completed payment. | Stored for reports and analytics. The payment no longer counts as revenue and nothing is sent to ad networks. | Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. -#### `trackPayment` Parameters - -| Parameter | Type | Required | Default | Description | -| --------- | ---- | -------- | ------- | ----------- | -| `eventName` | `string` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `string` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | -| `userId` | `string` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | -| `amount` | `number` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | -| `currency` | `string` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | `PAYMENT_COMPLETED` | Where the payment is in its lifecycle. See the statuses above. | -| `eventData` | `object` | No | None | Extra details, such as product details for [ecommerce events](#ecommerce-events). An `amount` inside it is not revenue. | - -`trackPayment` throws when a value breaks these rules, and nothing is sent. In TypeScript, you can import the `TrackPaymentParams`, `EventPayment`, and `PaymentStatus` types from `rn-linkrunner`. - The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. -#### Track a payment with `trackEvent` - -You can also pass a `payment` as the fourth argument to `trackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields, follows the same rules, and records the same payment. - -```javascript -await linkrunner.trackEvent( - "purchase", // Event name, used as the payment type - undefined, // Optional: Event data - undefined, // Optional: Event ID - { - amount: 499, - paymentId: "order_123", - currency: "INR", - userId: "user_123", - }, -); -``` - `capturePayment` is deprecated. Apps on `rn-linkrunner` 3.x keep using it and continue to work unchanged. To move to 4.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). @@ -632,40 +609,25 @@ const trackViewContent = async () => { ### Payment / Purchase Example -Use `trackEvent` with a [`payment`](#tracking-payments) to send a `Purchase` event containing the ecommerce payload: +Use `trackEvent` with the [payment keys](#tracking-payments) and the ecommerce fields in `eventData` to send a `Purchase` event: ```javascript const trackPurchase = async () => { try { - await linkrunner.trackEvent( - "purchase", // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard. See: /ecommerce-manager/meta-commerce-manager, /ecommerce-manager/google-commerce-manager - { - content_ids: ["product_123"], - contents: [ - { - id: "product_123", // Matches content_ids - quantity: 1, - item_price: 49.99, - }, - ], - content_type: "product", - currency: "USD", - value: 49.99, - num_items: 1, - order_id: "order_abc123", // Required for Purchase events - }, - undefined, - { - amount: 49.99, - paymentId: "payment_456", - currency: "USD", - userId: "user_123", - status: "PAYMENT_COMPLETED", - }, - ); - console.log("Purchase tracked successfully"); + await linkrunner.trackEvent("purchase", { // Map this event to "Purchase" (Meta) or "ecommerce_purchase" (Google) in the Linkrunner Dashboard + amount: 49.99, // Order total, sent to ad networks as the value + paymentId: "payment_456", + currency: "USD", + userId: "user_123", + status: "PAYMENT_COMPLETED", // Optional, the default + content_ids: ["product_123"], + contents: [{ id: "product_123", quantity: 1, item_price: 49.99 }], + content_type: "product", + num_items: 1, + order_id: "order_abc123", // Required for Purchase events + }); } catch (error) { - console.error("Error tracking purchase:", error); + console.error("Error tracking payment:", error); } }; ``` @@ -679,7 +641,7 @@ const trackPurchase = async () => { - Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, use [`trackPayment`](#tracking-payments). + Events are stored for all users, including organic ones. Events from users with no matching click are stored without campaign attribution. Call [`.signup`](#user-identification-required) so events are linked to a user. You can verify your events are being captured on the [Events](https://dashboard.linkrunner.io/dashboard/events) page. To record revenue, add the [payment keys](#tracking-payments) to `eventData`. Track custom events in your app: @@ -697,41 +659,34 @@ const trackEvent = async () => { #### Parameters for `linkrunner.trackEvent` - `eventName`: string (required) - Name of the event to track -- `eventData`: object (optional) - Key-value pairs for additional event data, including Meta ecommerce properties +- `eventData`: object (optional) - Key-value pairs for additional event data, including Meta ecommerce properties. Add `amount`, `paymentId`, `currency`, and `userId` to make it a payment. See [Tracking Payments](#tracking-payments). - `eventId`: string | number (optional) - Your own unique identifier for the event, useful for deduplication and correlating with your backend -- `payment`: object (optional). Records revenue with the event. See [Tracking Payments](#tracking-payments). ### Revenue Sharing with Ad Networks Send revenue to ad networks such as Google Ads and Meta as a [payment](#tracking-payments). Meta and Google receive the value and currency from the payment `amount` and `currency`, so send the order total as `amount`. Linkrunner does not add up item prices. -Put product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. +Put the payment keys and the product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`. Google receives `content_ids` as item IDs. ```javascript const trackPurchase = async () => { try { - await linkrunner.trackEvent( - "purchase", - { - content_ids: ["product_123"], - num_items: 1, - order_id: "order_12345", - }, - undefined, // Optional: Event ID - { - amount: 149.99, // Order total, sent to ad networks as the value - paymentId: "order_12345", - currency: "USD", - userId: "user_123", - }, - ); + await linkrunner.trackEvent("purchase", { + amount: 149.99, // Order total, sent to ad networks as the value + paymentId: "order_12345", + currency: "USD", + userId: "user_123", + content_ids: ["product_123"], + num_items: 1, + order_id: "order_12345", + }); } catch (error) { console.error("Error tracking payment:", error); } }; ``` -An `amount` inside `eventData` is not sent to ad networks as revenue. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). +An `amount` in `eventData` is revenue only when `paymentId` and `currency` are there too. On its own it is ordinary event data. Revenue is shared only when **Send Revenue** is turned on for the event's mapping in the dashboard. See [Meta Ads](/ad-networks/meta-ads) and [Google Ads](/ad-networks/google-ads). ## SDK Package Version @@ -924,7 +879,7 @@ Connect APNs with Linkrunner | [`linkrunner.setAdditionalData`](/sdk/react-native#setting-clevertap-id) | Optional | Integration code | When third-party integration IDs are available | | [`linkrunner.setUserData`](/sdk/react-native#setting-user-data) | Optional | App start, authentication or profile update logic | Right after `init` with your analytics IDs, and when user details become available | | [`linkrunner.trackEvent`](/sdk/react-native#tracking-custom-events) | Optional | Throughout app, including ecommerce flows | When specific user actions occur, including AddToCart or ViewContent events | -| [`linkrunner.trackPayment`](/sdk/react-native#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | +| [`linkrunner.trackEvent` with payment keys](/sdk/react-native#tracking-payments) | Optional | Payment processing, including ecommerce purchases | When user makes a payment, including Purchase events with ecommerce data | | [`linkrunner.setPushToken`](/sdk/react-native#uninstall-tracking) | Optional | Push notification setup | When FCM/APNs token is available | | [`linkrunner.setConsent`](/sdk/react-native#google-integrated-conversion-measurement-optional) | Optional | App initialization or consent flow | Before `init`, and again when consent changes | | [`linkrunner.enablePIIHashing`](/sdk/react-native#enhanced-privacy-controls) | Optional | Privacy configuration | When enabling PII hashing | diff --git a/sdk/track-payments.mdx b/sdk/track-payments.mdx index fe9aab22..f64aa79c 100644 --- a/sdk/track-payments.mdx +++ b/sdk/track-payments.mdx @@ -1,13 +1,13 @@ --- -title: "Track payments" +title: "Track payments with trackEvent" sidebarTitle: "Track payments" -description: "Record revenue with trackPayment, use payment status correctly, and send completed payments to your ad networks" +description: "Record revenue as an in-app event with trackEvent, use payment status correctly, and send completed payments to your ad networks" icon: "credit-card" --- -Use `trackPayment` whenever a user pays you: a purchase, a subscription charge or renewal, a wallet top-up. Linkrunner stores it as revenue, links it to the user and their install attribution, and forwards completed payments to the ad networks you mapped. +A payment is an in-app event with revenue. Send it with `trackEvent` whenever a user pays you: a purchase, a subscription charge or renewal, a wallet top-up. Put `amount`, `paymentId`, `currency`, and `userId` in `eventData`, and the SDK sends the event as a payment. Linkrunner stores it as revenue, links it to the user and their install attribution, and forwards completed payments to the ad networks you mapped. -`trackPayment` needs these SDK versions or later. On an older version, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). +Payment keys in `trackEvent` need these SDK versions or later. On an older version, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). | Platform | Package | Minimum version | | --- | --- | --- | @@ -25,8 +25,8 @@ Initialize the SDK before you track payments. See your platform's SDK guide for ## How it works - - You pass the event name, payment ID, user ID, amount, currency, and optionally a status and product details. + + You pass the event name, and in `eventData` the amount, payment ID, currency, user ID, and optionally a status and product details. Without `amount`, `paymentId`, and `currency` together, the call stays a normal custom event. The SDK checks every required field before anything leaves the device. If a value is invalid, nothing is sent and your code gets an error (see [Handle invalid payments](#handle-invalid-payments)). @@ -47,18 +47,20 @@ Initialize the SDK before you track payments. See your platform's SDK guide for So a payment can be accepted but not yet in reports, in reports but not sent to an ad network (no mapping, or not completed), or sent without a value (**Send Revenue** off). [Verify it works](#verify-it-works) shows how to check each step. -## Fields +## Payment keys + +`eventName` and `eventId` are arguments of `trackEvent`. The other fields are keys in `eventData`. | Field | Required | What it is for | Why it matters | Example | | --- | --- | --- | --- | --- | -| `eventName` | Yes | The name of this payment, such as `purchase` or `SUBSCRIPTION_RENEWED`. | It becomes the payment type in reports, and Linkrunner matches it exactly against your ad network and affiliate mappings. No mapping means the payment never reaches that network. | `"purchase"` | +| `eventName` | Yes | The first argument of `trackEvent`: the name of this payment, such as `purchase` or `SUBSCRIPTION_RENEWED`. | It becomes the payment type in reports, and Linkrunner matches it exactly against your ad network and affiliate mappings. No mapping means the payment never reaches that network. | `"purchase"` | | `paymentId` | Yes | Your order or transaction ID. Must not be blank. | Linkrunner uses it to recognize the same payment when it arrives again, so retries do not double your revenue. | `"order_8f2k1"` | | `userId` | Yes | Your ID for the user who paid. | It links the payment to your user, so paying users, cohorts, and user-level reports are correct. If you pass an empty string, the SDK uses the user saved by `signup`, `setUserData`, or `setCustomerUserId`. | `"user_123"` | | `amount` | Yes | The total the user paid. A finite number, 0 or greater. | This is the revenue in your reports and the value ad networks receive. Linkrunner does not add up item prices, so send the order total. | `499.0` | | `currency` | Yes | The 3-letter ISO 4217 code of `amount`. Case-insensitive, sent in uppercase. | Linkrunner stores the payment in this currency, and ad networks receive it with the value. A wrong code makes the revenue wrong everywhere. | `"INR"` | | `status` | No | Where the payment is in its lifecycle. Defaults to `PAYMENT_COMPLETED`. | Only completed payments count as revenue and reach ad networks. See [Payment status](#payment-status). | `PAYMENT_COMPLETED` | -| `eventData` | No | Extra details, such as the products in the order. | Meta and Google read product fields from it. An `amount` inside `eventData` is not revenue. | `{ "content_ids": ["sku_1"] }` | -| `eventId` | No | Your own identifier for the event. Android (Kotlin), iOS, and Capacitor only. | Use it when your own systems need an event-level reference. | `"evt_123"` | +| Other `eventData` keys | No | Extra details, such as the products in the order. | Meta and Google read product fields from them. They stay in the event data. | `"content_ids": ["sku_1"]` | +| `eventId` | No | Your own identifier for the event. This is the third argument of `trackEvent`, not a key in `eventData`. | Use it when your own systems need an event-level reference. | `"evt_123"` | ## Choose the event name @@ -101,7 +103,7 @@ All examples use synthetic values. Replace them with your own order ID, user ID, Every example sends product details in `eventData`. Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id` for [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager#understanding-event_data), and Google receives `content_ids` as item IDs for [Google Commerce Manager](/ecommerce-manager/google-commerce-manager). `amount` stays the order total. -In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by the package. Plain strings such as `"PAYMENT_COMPLETED"` also work. +`status` is a string such as `"PAYMENT_COMPLETED"`. In React Native, Expo, Capacitor, and Cordova, `PaymentStatus` is exported by the package with the same strings. On Android and Flutter, `PaymentStatus.X.name` gives the string, and on iOS `PaymentStatus.x.rawValue` does. ### One-step purchase with products @@ -109,7 +111,7 @@ Use this when your app learns about a payment only after it succeeds. `status` i - Call it from your purchase success handler. `trackPayment` is a `suspend` function, so run it in a coroutine. + Call it from your purchase success handler. `trackEvent` is a `suspend` function, so run it in a coroutine. ```kotlin import io.linkrunner.sdk.LinkRunner @@ -119,14 +121,14 @@ Use this when your app learns about a payment only after it succeeds. `status` i fun onPurchaseSucceeded(orderId: String, userId: String) { CoroutineScope(Dispatchers.IO).launch { - LinkRunner.getInstance().trackPayment( + LinkRunner.getInstance().trackEvent( eventName = "purchase", // becomes the payment type in reports and mappings - paymentId = orderId, // your order ID, used to deduplicate - userId = userId, // required, links the payment to your user - amount = 2298.0, // order total the user paid - currency = "INR", // required, 3-letter ISO 4217 code - // Product details: Meta receives these fields, Google receives content_ids as item IDs eventData = mapOf( + "paymentId" to orderId, // your order ID, used to deduplicate + "userId" to userId, // required, links the payment to your user + "amount" to 2298.0, // order total the user paid + "currency" to "INR", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs "content_ids" to listOf("sku_blue_tshirt_m", "sku_black_jeans_32"), "contents" to listOf( mapOf("id" to "sku_blue_tshirt_m", "quantity" to 1, "item_price" to 799), @@ -143,7 +145,7 @@ Use this when your app learns about a payment only after it succeeds. `status` i } ``` - From Java, use `LinkrunnerJava.getInstance().trackPayment(...)` with the same arguments and a `LinkRunnerCallback` as the last argument. + From Java, use `LinkrunnerJava.getInstance().trackEvent(eventName, eventData, eventId, callback)` with the same `eventData` keys and a `LinkRunnerCallback`. Call it from your purchase success handler inside an `async` context. @@ -152,14 +154,14 @@ Use this when your app learns about a payment only after it succeeds. `status` i import LinkrunnerKit func onPurchaseSucceeded(orderId: String, userId: String) async { - await LinkrunnerSDK.shared.trackPayment( + await LinkrunnerSDK.shared.trackEvent( eventName: "purchase", // becomes the payment type in reports and mappings - paymentId: orderId, // your order ID, used to deduplicate - userId: userId, // required, links the payment to your user - amount: 2298.0, // order total the user paid - currency: "INR", // required, 3-letter ISO 4217 code - // Product details: Meta receives these fields, Google receives content_ids as item IDs eventData: [ + "paymentId": orderId, // your order ID, used to deduplicate + "userId": userId, // required, links the payment to your user + "amount": 2298.0, // order total the user paid + "currency": "INR", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs "content_ids": ["sku_blue_tshirt_m", "sku_black_jeans_32"], "contents": [ ["id": "sku_blue_tshirt_m", "quantity": 1, "item_price": 799], @@ -179,23 +181,20 @@ Use this when your app learns about a payment only after it succeeds. `status` i export async function onPurchaseSucceeded(orderId, userId) { try { - await linkrunner.trackPayment({ - eventName: "purchase", // becomes the payment type in reports and mappings + await linkrunner.trackEvent("purchase", { // becomes the payment type in reports and mappings paymentId: orderId, // your order ID, used to deduplicate - userId, // required, links the payment to your user + userId: userId, // required, links the payment to your user amount: 2298, // order total the user paid currency: "INR", // required, 3-letter ISO 4217 code // Product details: Meta receives these fields, Google receives content_ids as item IDs - eventData: { - content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], - contents: [ - { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, - { id: "sku_black_jeans_32", quantity: 1, item_price: 1499 }, - ], - content_type: "product", - num_items: 2, - order_id: orderId, - }, + content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], + contents: [ + { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, + { id: "sku_black_jeans_32", quantity: 1, item_price: 1499 }, + ], + content_type: "product", + num_items: 2, + order_id: orderId, }); } catch (error) { console.error("Payment not tracked:", error); @@ -209,14 +208,14 @@ Use this when your app learns about a payment only after it succeeds. `status` i Future onPurchaseSucceeded(String orderId, String userId) async { try { - await LinkRunner().trackPayment( + await LinkRunner().trackEvent( eventName: 'purchase', // becomes the payment type in reports and mappings - paymentId: orderId, // your order ID, used to deduplicate - userId: userId, // required, links the payment to your user - amount: 2298.0, // order total the user paid - currency: 'INR', // required, 3-letter ISO 4217 code - // Product details: Meta receives these fields, Google receives content_ids as item IDs eventData: { + 'paymentId': orderId, // your order ID, used to deduplicate + 'userId': userId, // required, links the payment to your user + 'amount': 2298.0, // order total the user paid + 'currency': 'INR', // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs 'content_ids': ['sku_blue_tshirt_m', 'sku_black_jeans_32'], 'contents': [ {'id': 'sku_blue_tshirt_m', 'quantity': 1, 'item_price': 799}, @@ -241,23 +240,20 @@ Use this when your app learns about a payment only after it succeeds. `status` i export async function onPurchaseSucceeded(orderId, userId) { try { - await linkrunner.trackPayment({ - eventName: "purchase", // becomes the payment type in reports and mappings + await linkrunner.trackEvent("purchase", { // becomes the payment type in reports and mappings paymentId: orderId, // your order ID, used to deduplicate - userId, // required, links the payment to your user + userId: userId, // required, links the payment to your user amount: 2298, // order total the user paid currency: "INR", // required, 3-letter ISO 4217 code // Product details: Meta receives these fields, Google receives content_ids as item IDs - eventData: { - content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], - contents: [ - { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, - { id: "sku_black_jeans_32", quantity: 1, item_price: 1499 }, - ], - content_type: "product", - num_items: 2, - order_id: orderId, - }, + content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], + contents: [ + { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, + { id: "sku_black_jeans_32", quantity: 1, item_price: 1499 }, + ], + content_type: "product", + num_items: 2, + order_id: orderId, }); } catch (error) { console.error("Payment not tracked:", error); @@ -271,14 +267,14 @@ Use this when your app learns about a payment only after it succeeds. `status` i export async function onPurchaseSucceeded(orderId: string, userId: string) { try { - await linkrunner.trackPayment({ + await linkrunner.trackEvent({ eventName: "purchase", // becomes the payment type in reports and mappings - paymentId: orderId, // your order ID, used to deduplicate - userId, // required, links the payment to your user - amount: 2298, // order total the user paid - currency: "INR", // required, 3-letter ISO 4217 code - // Product details: Meta receives these fields, Google receives content_ids as item IDs eventData: { + paymentId: orderId, // your order ID, used to deduplicate + userId: userId, // required, links the payment to your user + amount: 2298, // order total the user paid + currency: "INR", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], contents: [ { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, @@ -300,23 +296,20 @@ Use this when your app learns about a payment only after it succeeds. `status` i ```javascript function onPurchaseSucceeded(orderId, userId) { - return linkrunner.trackPayment({ - eventName: "purchase", // becomes the payment type in reports and mappings + return linkrunner.trackEvent("purchase", { // becomes the payment type in reports and mappings paymentId: orderId, // your order ID, used to deduplicate userId: userId, // required, links the payment to your user amount: 2298, // order total the user paid currency: "INR", // required, 3-letter ISO 4217 code // Product details: Meta receives these fields, Google receives content_ids as item IDs - eventData: { - content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], - contents: [ - { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, - { id: "sku_black_jeans_32", quantity: 1, item_price: 1499 } - ], - content_type: "product", - num_items: 2, - order_id: orderId - } + content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], + contents: [ + { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, + { id: "sku_black_jeans_32", quantity: 1, item_price: 1499 } + ], + content_type: "product", + num_items: 2, + order_id: orderId }).catch(function (error) { console.error("Payment not tracked:", error); }); @@ -333,21 +326,18 @@ Use this when your app learns about a payment only after it succeeds. `status` i { public void OnPurchaseSucceeded(string orderId, string userId) { - // Product details: Meta receives these fields, Google receives content_ids as item IDs + // Payment keys, then product details: Meta receives these fields, Google receives content_ids as item IDs string eventDataJson = - "{\"content_ids\":[\"sku_blue_tshirt_m\",\"sku_black_jeans_32\"]," + + "{\"amount\":2298.0," + // order total the user paid + "\"paymentId\":\"" + orderId + "\"," + // your order ID, used to deduplicate + "\"currency\":\"INR\"," + // required, 3-letter ISO 4217 code + "\"userId\":\"" + userId + "\"," + // required, links the payment to your user + "\"content_ids\":[\"sku_blue_tshirt_m\",\"sku_black_jeans_32\"]," + "\"contents\":[{\"id\":\"sku_blue_tshirt_m\",\"quantity\":1,\"item_price\":799}," + "{\"id\":\"sku_black_jeans_32\",\"quantity\":1,\"item_price\":1499}]," + "\"content_type\":\"product\",\"num_items\":2,\"order_id\":\"" + orderId + "\"}"; - LinkrunnerSDK.TrackPayment( - eventName: "purchase", // becomes the payment type in reports and mappings - paymentId: orderId, // your order ID, used to deduplicate - userId: userId, // required, links the payment to your user - amount: 2298.0, // order total the user paid - currency: "INR", // required, 3-letter ISO 4217 code - eventDataJson: eventDataJson - ); + LinkrunnerSDK.TrackEvent("purchase", eventDataJson); // becomes the payment type in reports and mappings } } ``` @@ -371,15 +361,15 @@ Use this when you want to see started, failed, and cancelled payments next to co fun trackCheckout(orderId: String, userId: String, status: PaymentStatus) { CoroutineScope(Dispatchers.IO).launch { - LinkRunner.getInstance().trackPayment( + LinkRunner.getInstance().trackEvent( eventName = "purchase", // same name for every status of this payment - paymentId = orderId, // same ID for every status of this payment - userId = userId, // required, links the payment to your user - amount = 499.0, // order total the user pays - currency = "INR", // required, 3-letter ISO 4217 code - status = status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks - // Product details: Meta receives these fields, Google receives content_ids as item IDs eventData = mapOf( + "paymentId" to orderId, // same ID for every status of this payment + "userId" to userId, // required, links the payment to your user + "amount" to 499.0, // order total the user pays + "currency" to "INR", // required, 3-letter ISO 4217 code + "status" to status.name, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs "content_ids" to listOf("sku_blue_tshirt_m"), "contents" to listOf(mapOf("id" to "sku_blue_tshirt_m", "quantity" to 1, "item_price" to 499)), "content_type" to "product", @@ -416,15 +406,15 @@ Use this when you want to see started, failed, and cancelled payments next to co enum CheckoutResult { case succeeded, failed, cancelled } func trackCheckout(orderId: String, userId: String, status: PaymentStatus) async { - await LinkrunnerSDK.shared.trackPayment( + await LinkrunnerSDK.shared.trackEvent( eventName: "purchase", // same name for every status of this payment - paymentId: orderId, // same ID for every status of this payment - userId: userId, // required, links the payment to your user - amount: 499.0, // order total the user pays - currency: "INR", // required, 3-letter ISO 4217 code - status: status, // only .completed counts as revenue and goes to ad networks - // Product details: Meta receives these fields, Google receives content_ids as item IDs eventData: [ + "paymentId": orderId, // same ID for every status of this payment + "userId": userId, // required, links the payment to your user + "amount": 499.0, // order total the user pays + "currency": "INR", // required, 3-letter ISO 4217 code + "status": status.rawValue, // only .completed counts as revenue and goes to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs "content_ids": ["sku_blue_tshirt_m"], "contents": [["id": "sku_blue_tshirt_m", "quantity": 1, "item_price": 499]], "content_type": "product", @@ -457,21 +447,18 @@ Use this when you want to see started, failed, and cancelled payments next to co async function trackCheckout(orderId, userId, status) { try { - await linkrunner.trackPayment({ - eventName: "purchase", // same name for every status of this payment + await linkrunner.trackEvent("purchase", { // same name for every status of this payment paymentId: orderId, // same ID for every status of this payment - userId, // required, links the payment to your user + userId: userId, // required, links the payment to your user amount: 499, // order total the user pays currency: "INR", // required, 3-letter ISO 4217 code - status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + status: status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks // Product details: Meta receives these fields, Google receives content_ids as item IDs - eventData: { - content_ids: ["sku_blue_tshirt_m"], - contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], - content_type: "product", - num_items: 1, - order_id: orderId, - }, + content_ids: ["sku_blue_tshirt_m"], + contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], + content_type: "product", + num_items: 1, + order_id: orderId, }); } catch (error) { console.error("Payment not tracked:", error); @@ -503,15 +490,15 @@ Use this when you want to see started, failed, and cancelled payments next to co Future trackCheckout(String orderId, String userId, PaymentStatus status) async { try { - await LinkRunner().trackPayment( + await LinkRunner().trackEvent( eventName: 'purchase', // same name for every status of this payment - paymentId: orderId, // same ID for every status of this payment - userId: userId, // required, links the payment to your user - amount: 499.0, // order total the user pays - currency: 'INR', // required, 3-letter ISO 4217 code - status: status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks - // Product details: Meta receives these fields, Google receives content_ids as item IDs eventData: { + 'paymentId': orderId, // same ID for every status of this payment + 'userId': userId, // required, links the payment to your user + 'amount': 499.0, // order total the user pays + 'currency': 'INR', // required, 3-letter ISO 4217 code + 'status': status.name, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs 'content_ids': ['sku_blue_tshirt_m'], 'contents': [ {'id': 'sku_blue_tshirt_m', 'quantity': 1, 'item_price': 499}, @@ -547,21 +534,18 @@ Use this when you want to see started, failed, and cancelled payments next to co async function trackCheckout(orderId, userId, status) { try { - await linkrunner.trackPayment({ - eventName: "purchase", // same name for every status of this payment + await linkrunner.trackEvent("purchase", { // same name for every status of this payment paymentId: orderId, // same ID for every status of this payment - userId, // required, links the payment to your user + userId: userId, // required, links the payment to your user amount: 499, // order total the user pays currency: "INR", // required, 3-letter ISO 4217 code - status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + status: status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks // Product details: Meta receives these fields, Google receives content_ids as item IDs - eventData: { - content_ids: ["sku_blue_tshirt_m"], - contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], - content_type: "product", - num_items: 1, - order_id: orderId, - }, + content_ids: ["sku_blue_tshirt_m"], + contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], + content_type: "product", + num_items: 1, + order_id: orderId, }); } catch (error) { console.error("Payment not tracked:", error); @@ -593,15 +577,15 @@ Use this when you want to see started, failed, and cancelled payments next to co async function trackCheckout(orderId: string, userId: string, status: PaymentStatus) { try { - await linkrunner.trackPayment({ + await linkrunner.trackEvent({ eventName: "purchase", // same name for every status of this payment - paymentId: orderId, // same ID for every status of this payment - userId, // required, links the payment to your user - amount: 499, // order total the user pays - currency: "INR", // required, 3-letter ISO 4217 code - status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks - // Product details: Meta receives these fields, Google receives content_ids as item IDs eventData: { + paymentId: orderId, // same ID for every status of this payment + userId: userId, // required, links the payment to your user + amount: 499, // order total the user pays + currency: "INR", // required, 3-letter ISO 4217 code + status: status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs content_ids: ["sku_blue_tshirt_m"], contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], content_type: "product", @@ -630,21 +614,18 @@ Use this when you want to see started, failed, and cancelled payments next to co ```javascript function trackCheckout(orderId, userId, status) { - return linkrunner.trackPayment({ - eventName: "purchase", // same name for every status of this payment + return linkrunner.trackEvent("purchase", { // same name for every status of this payment paymentId: orderId, // same ID for every status of this payment userId: userId, // required, links the payment to your user amount: 499, // order total the user pays currency: "INR", // required, 3-letter ISO 4217 code status: status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks // Product details: Meta receives these fields, Google receives content_ids as item IDs - eventData: { - content_ids: ["sku_blue_tshirt_m"], - contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], - content_type: "product", - num_items: 1, - order_id: orderId - } + content_ids: ["sku_blue_tshirt_m"], + contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], + content_type: "product", + num_items: 1, + order_id: orderId }).catch(function (error) { console.error("Payment not tracked:", error); }); @@ -674,37 +655,34 @@ Use this when you want to see started, failed, and cancelled payments next to co public class Checkout : MonoBehaviour { - void TrackCheckout(string orderId, string userId, PaymentStatus status) + void TrackCheckout(string orderId, string userId, string status) { - // Product details: Meta receives these fields, Google receives content_ids as item IDs + // Payment keys, then product details: Meta receives these fields, Google receives content_ids as item IDs string eventDataJson = - "{\"content_ids\":[\"sku_blue_tshirt_m\"]," + + "{\"amount\":499.0," + // order total the user pays + "\"paymentId\":\"" + orderId + "\"," + // same ID for every status of this payment + "\"currency\":\"INR\"," + // required, 3-letter ISO 4217 code + "\"userId\":\"" + userId + "\"," + // required, links the payment to your user + "\"status\":\"" + status + "\"," + // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + "\"content_ids\":[\"sku_blue_tshirt_m\"]," + "\"contents\":[{\"id\":\"sku_blue_tshirt_m\",\"quantity\":1,\"item_price\":499}]," + "\"content_type\":\"product\",\"num_items\":1,\"order_id\":\"" + orderId + "\"}"; - LinkrunnerSDK.TrackPayment( - eventName: "purchase", // same name for every status of this payment - paymentId: orderId, // same ID for every status of this payment - userId: userId, // required, links the payment to your user - amount: 499.0, // order total the user pays - currency: "INR", // required, 3-letter ISO 4217 code - status: status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks - eventDataJson: eventDataJson - ); + LinkrunnerSDK.TrackEvent("purchase", eventDataJson); // same name for every status of this payment } // When the user taps Pay public void OnCheckoutStarted(string orderId, string userId) => - TrackCheckout(orderId, userId, PaymentStatus.PAYMENT_INITIATED); + TrackCheckout(orderId, userId, "PAYMENT_INITIATED"); // When your payment provider returns a result public void OnCheckoutFinished(string orderId, string userId, CheckoutResult result) { - PaymentStatus status = result switch + string status = result switch { - CheckoutResult.Succeeded => PaymentStatus.PAYMENT_COMPLETED, - CheckoutResult.Failed => PaymentStatus.PAYMENT_FAILED, - _ => PaymentStatus.PAYMENT_CANCELLED, + CheckoutResult.Succeeded => "PAYMENT_COMPLETED", + CheckoutResult.Failed => "PAYMENT_FAILED", + _ => "PAYMENT_CANCELLED", }; TrackCheckout(orderId, userId, status); } @@ -727,14 +705,14 @@ Use this when you already have mappings or affiliate templates for an old paymen fun onSubscriptionRenewed(transactionId: String, userId: String) { CoroutineScope(Dispatchers.IO).launch { - LinkRunner.getInstance().trackPayment( + LinkRunner.getInstance().trackEvent( eventName = "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings - paymentId = transactionId, // a new ID for every renewal, used to deduplicate - userId = userId, // required, links the payment to your user - amount = 4.99, // total the user paid for this renewal - currency = "USD", // required, 3-letter ISO 4217 code - // Product details: Meta receives these fields, Google receives content_ids as item IDs eventData = mapOf( + "paymentId" to transactionId, // a new ID for every renewal, used to deduplicate + "userId" to userId, // required, links the payment to your user + "amount" to 4.99, // total the user paid for this renewal + "currency" to "USD", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs "content_ids" to listOf("pro_monthly"), "contents" to listOf(mapOf("id" to "pro_monthly", "quantity" to 1, "item_price" to 4.99)), "content_type" to "product", @@ -753,14 +731,14 @@ Use this when you already have mappings or affiliate templates for an old paymen import LinkrunnerKit func onSubscriptionRenewed(transactionId: String, userId: String) async { - await LinkrunnerSDK.shared.trackPayment( + await LinkrunnerSDK.shared.trackEvent( eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings - paymentId: transactionId, // a new ID for every renewal, used to deduplicate - userId: userId, // required, links the payment to your user - amount: 4.99, // total the user paid for this renewal - currency: "USD", // required, 3-letter ISO 4217 code - // Product details: Meta receives these fields, Google receives content_ids as item IDs eventData: [ + "paymentId": transactionId, // a new ID for every renewal, used to deduplicate + "userId": userId, // required, links the payment to your user + "amount": 4.99, // total the user paid for this renewal + "currency": "USD", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs "content_ids": ["pro_monthly"], "contents": [["id": "pro_monthly", "quantity": 1, "item_price": 4.99]], "content_type": "product", @@ -777,20 +755,17 @@ Use this when you already have mappings or affiliate templates for an old paymen export async function onSubscriptionRenewed(transactionId, userId) { try { - await linkrunner.trackPayment({ - eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings + await linkrunner.trackEvent("SUBSCRIPTION_RENEWED", { // old type name, keeps your existing mappings paymentId: transactionId, // a new ID for every renewal, used to deduplicate - userId, // required, links the payment to your user + userId: userId, // required, links the payment to your user amount: 4.99, // total the user paid for this renewal currency: "USD", // required, 3-letter ISO 4217 code // Product details: Meta receives these fields, Google receives content_ids as item IDs - eventData: { - content_ids: ["pro_monthly"], - contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], - content_type: "product", - num_items: 1, - order_id: transactionId, - }, + content_ids: ["pro_monthly"], + contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], + content_type: "product", + num_items: 1, + order_id: transactionId, }); } catch (error) { console.error("Payment not tracked:", error); @@ -804,14 +779,14 @@ Use this when you already have mappings or affiliate templates for an old paymen Future onSubscriptionRenewed(String transactionId, String userId) async { try { - await LinkRunner().trackPayment( + await LinkRunner().trackEvent( eventName: 'SUBSCRIPTION_RENEWED', // old type name, keeps your existing mappings - paymentId: transactionId, // a new ID for every renewal, used to deduplicate - userId: userId, // required, links the payment to your user - amount: 4.99, // total the user paid for this renewal - currency: 'USD', // required, 3-letter ISO 4217 code - // Product details: Meta receives these fields, Google receives content_ids as item IDs eventData: { + 'paymentId': transactionId, // a new ID for every renewal, used to deduplicate + 'userId': userId, // required, links the payment to your user + 'amount': 4.99, // total the user paid for this renewal + 'currency': 'USD', // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs 'content_ids': ['pro_monthly'], 'contents': [ {'id': 'pro_monthly', 'quantity': 1, 'item_price': 4.99}, @@ -835,20 +810,17 @@ Use this when you already have mappings or affiliate templates for an old paymen export async function onSubscriptionRenewed(transactionId, userId) { try { - await linkrunner.trackPayment({ - eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings + await linkrunner.trackEvent("SUBSCRIPTION_RENEWED", { // old type name, keeps your existing mappings paymentId: transactionId, // a new ID for every renewal, used to deduplicate - userId, // required, links the payment to your user + userId: userId, // required, links the payment to your user amount: 4.99, // total the user paid for this renewal currency: "USD", // required, 3-letter ISO 4217 code // Product details: Meta receives these fields, Google receives content_ids as item IDs - eventData: { - content_ids: ["pro_monthly"], - contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], - content_type: "product", - num_items: 1, - order_id: transactionId, - }, + content_ids: ["pro_monthly"], + contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], + content_type: "product", + num_items: 1, + order_id: transactionId, }); } catch (error) { console.error("Payment not tracked:", error); @@ -862,14 +834,14 @@ Use this when you already have mappings or affiliate templates for an old paymen export async function onSubscriptionRenewed(transactionId: string, userId: string) { try { - await linkrunner.trackPayment({ + await linkrunner.trackEvent({ eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings - paymentId: transactionId, // a new ID for every renewal, used to deduplicate - userId, // required, links the payment to your user - amount: 4.99, // total the user paid for this renewal - currency: "USD", // required, 3-letter ISO 4217 code - // Product details: Meta receives these fields, Google receives content_ids as item IDs eventData: { + paymentId: transactionId, // a new ID for every renewal, used to deduplicate + userId: userId, // required, links the payment to your user + amount: 4.99, // total the user paid for this renewal + currency: "USD", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs content_ids: ["pro_monthly"], contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], content_type: "product", @@ -886,20 +858,17 @@ Use this when you already have mappings or affiliate templates for an old paymen ```javascript function onSubscriptionRenewed(transactionId, userId) { - return linkrunner.trackPayment({ - eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings + return linkrunner.trackEvent("SUBSCRIPTION_RENEWED", { // old type name, keeps your existing mappings paymentId: transactionId, // a new ID for every renewal, used to deduplicate userId: userId, // required, links the payment to your user amount: 4.99, // total the user paid for this renewal currency: "USD", // required, 3-letter ISO 4217 code // Product details: Meta receives these fields, Google receives content_ids as item IDs - eventData: { - content_ids: ["pro_monthly"], - contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], - content_type: "product", - num_items: 1, - order_id: transactionId - } + content_ids: ["pro_monthly"], + contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], + content_type: "product", + num_items: 1, + order_id: transactionId }).catch(function (error) { console.error("Payment not tracked:", error); }); @@ -914,20 +883,17 @@ Use this when you already have mappings or affiliate templates for an old paymen { public void OnSubscriptionRenewed(string transactionId, string userId) { - // Product details: Meta receives these fields, Google receives content_ids as item IDs + // Payment keys, then product details: Meta receives these fields, Google receives content_ids as item IDs string eventDataJson = - "{\"content_ids\":[\"pro_monthly\"]," + + "{\"amount\":4.99," + // total the user paid for this renewal + "\"paymentId\":\"" + transactionId + "\"," + // a new ID for every renewal, used to deduplicate + "\"currency\":\"USD\"," + // required, 3-letter ISO 4217 code + "\"userId\":\"" + userId + "\"," + // required, links the payment to your user + "\"content_ids\":[\"pro_monthly\"]," + "\"contents\":[{\"id\":\"pro_monthly\",\"quantity\":1,\"item_price\":4.99}]," + "\"content_type\":\"product\",\"num_items\":1,\"order_id\":\"" + transactionId + "\"}"; - LinkrunnerSDK.TrackPayment( - eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings - paymentId: transactionId, // a new ID for every renewal, used to deduplicate - userId: userId, // required, links the payment to your user - amount: 4.99, // total the user paid for this renewal - currency: "USD", // required, 3-letter ISO 4217 code - eventDataJson: eventDataJson - ); + LinkrunnerSDK.TrackEvent("SUBSCRIPTION_RENEWED", eventDataJson); // old type name, keeps your existing mappings } } ``` @@ -936,15 +902,15 @@ Use this when you already have mappings or affiliate templates for an old paymen ### Handle invalid payments -The SDK validates every payment before sending it. A blank `eventName` or `paymentId`, a negative or non-finite `amount`, or a `currency` that is not a 3-letter code stops the payment on the device, and nothing reaches Linkrunner. How your code finds out depends on the SDK: +The SDK validates every payment before sending it. A `paymentId` sent without `amount` or `currency`, a blank `eventName` or `paymentId`, a negative or non-finite `amount`, a `currency` that is not a 3-letter code, or an unknown `status` stops the payment on the device, and nothing reaches Linkrunner. An `amount` or `currency` without `paymentId` is not an error: the event is sent as a normal custom event. How your code finds out depends on the SDK: | Platform | What happens on an invalid payment | | --- | --- | -| Android (Kotlin) | `trackPayment` returns a failed `Result` holding an `IllegalArgumentException`. It does not throw. Use `onFailure`. | -| Android (Java) | `LinkrunnerJava.getInstance().trackPayment(...)` calls `onError` on the `LinkRunnerCallback` you pass. | -| iOS | `trackPayment` does not throw. The payment is dropped, and debug builds print the reason to the console. | -| React Native and Expo | `trackPayment` throws. Wrap it in `try`/`catch`. | -| Flutter | `trackPayment` throws `ArgumentError`. | +| Android (Kotlin) | `trackEvent` returns a failed `Result` holding an `IllegalArgumentException`. It does not throw. Use `onFailure`. | +| Android (Java) | `LinkrunnerJava.getInstance().trackEvent(...)` calls `onError` on the `LinkRunnerCallback` you pass. | +| iOS | `trackEvent` does not throw. The payment is dropped, and debug builds print the reason to the console. | +| React Native and Expo | `trackEvent` throws. Wrap it in `try`/`catch`. | +| Flutter | `trackEvent` throws `ArgumentError`. | | Capacitor | The returned promise rejects. | | Cordova | The returned promise rejects. | | Unity | The native SDK rejects the payment, and `LinkrunnerSDK.OnEventTracked` reports `false` with the error. | @@ -964,7 +930,7 @@ The value and currency are sent only when **Send Revenue** is turned on for that What the networks receive: -- **Value and currency** come from the top-level `amount` and `currency`. Send the order total as `amount`. Linkrunner does not add up item prices, and an `amount` inside `eventData` is not sent as revenue. On iOS, an `amount` inside `eventData` still counts toward SKAN conversion values. +- **Value and currency** come from the payment `amount` and `currency`. Send the order total as `amount`. Linkrunner does not add up item prices. An `amount` without `paymentId` and `currency` is ordinary event data and is not sent as revenue. On iOS, it still counts toward SKAN conversion values. - **Meta** receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id` from `eventData`. See [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager). - **Google** receives `content_ids` from `eventData` as item IDs. See [Google Commerce Manager](/ecommerce-manager/google-commerce-manager). @@ -1000,7 +966,7 @@ Linkrunner keeps one payment per `paymentId`, event name, and status. Use this t Check that the status is `PAYMENT_COMPLETED` and that the event name has a mapping for that network with the exact same name. If the install was attributed only after the payment arrived, the payment is sent once attribution completes. - Turn on **Send Revenue** for that event's mapping, and send the total as the top-level `amount`, not inside `eventData`. + Turn on **Send Revenue** for that event's mapping, and send the order total as `amount` together with `paymentId` and `currency`. Open the payment in the Events Log and compare **Amount** and **Currency** with what you meant to send. A wrong `currency` code, or an amount in the wrong unit, such as cents instead of dollars, makes the revenue wrong in reports and on ad networks. @@ -1017,7 +983,7 @@ Linkrunner keeps one payment per `paymentId`, event name, and status. Use this t - No. Removing or reversing a payment is not supported. `removePayment` was removed in the SDK versions above and has no replacement. + `removePayment` was removed in the SDK versions above. To record a refund or a cancellation, send the same event name and `paymentId` again with `status` set to `PAYMENT_CANCELLED`. That payment no longer counts as revenue. `capturePayment` keeps working on the older versions. When you upgrade, follow [Migrate payments to in-app events](/sdk/migrate-to-track-event). @@ -1025,8 +991,8 @@ Linkrunner keeps one payment per `paymentId`, event name, and status. Use this t Yes. Send them to `POST /api/v1/capture-event` with `amount`, `payment_id`, and `currency`. The same naming, status, and deduplication rules apply. See the [Revenue Tracking API](/api-reference/revenue-tracking). - - Yes. Pass a payment object to `trackEvent` with the same fields. It records the same payment as `trackPayment`. Each SDK guide shows it under **Tracking Payments**. + + No. `trackEvent` is the only method. Add `amount`, `paymentId`, `currency`, and `userId` to `eventData` and the event is a payment. Leave them out and it is a custom event. diff --git a/sdk/unity.mdx b/sdk/unity.mdx index 46972087..8ffaeafe 100644 --- a/sdk/unity.mdx +++ b/sdk/unity.mdx @@ -117,9 +117,7 @@ import io.linkrunner.sdk.LinkRunnerCallback; import io.linkrunner.sdk.models.ConsentStatus; import io.linkrunner.sdk.models.LinkrunnerConsent; import io.linkrunner.sdk.models.request.UserDataRequest; -import io.linkrunner.sdk.models.EventPayment; import io.linkrunner.sdk.models.IntegrationData; -import io.linkrunner.sdk.models.PaymentStatus; import io.linkrunner.sdk.models.response.AttributionData; import org.json.JSONObject; @@ -155,17 +153,6 @@ public class LinkrunnerBridge { return map; } - // Falls back to PAYMENT_COMPLETED for an empty or unknown status - private static PaymentStatus parsePaymentStatus(String status) { - if (status == null) return PaymentStatus.PAYMENT_COMPLETED; - switch (status) { - case "PAYMENT_INITIATED": return PaymentStatus.PAYMENT_INITIATED; - case "PAYMENT_FAILED": return PaymentStatus.PAYMENT_FAILED; - case "PAYMENT_CANCELLED": return PaymentStatus.PAYMENT_CANCELLED; - default: return PaymentStatus.PAYMENT_COMPLETED; - } - } - // ---- Public API called from Unity C# ---- public static void initialize(String token, String secretKey, String keyId, boolean debug) { @@ -310,38 +297,6 @@ public class LinkrunnerBridge { ); } - public static void trackEventWithPayment(String eventName, String eventDataJson, String eventId, - double amount, String paymentId, String currency, - String userId, String status) { - EventPayment payment; - try { - payment = new EventPayment(amount, paymentId, currency, - userId == null ? "" : userId, parsePaymentStatus(status)); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Invalid payment: " + e.getMessage()); - sendToUnity("OnTrackEventComplete", "error:" + e.getMessage()); - return; - } - - Map eventData = jsonToMap(eventDataJson); - LinkrunnerJava lr = LinkrunnerJava.getInstance(); - lr.trackEvent( - eventName, - eventData.isEmpty() ? null : eventData, - eventId.isEmpty() ? null : eventId, - payment, - new LinkRunnerCallback() { - public void onSuccess(Void result) { - sendToUnity("OnTrackEventComplete", "success"); - } - public void onError(String error) { - Log.e(TAG, "TrackEvent with payment failed: " + error); - sendToUnity("OnTrackEventComplete", "error:" + error); - } - } - ); - } - public static void setPushToken(String pushToken) { LinkrunnerJava lr = LinkrunnerJava.getInstance(); lr.setPushToken(pushToken, null); @@ -529,12 +484,6 @@ private func parseUserData(_ dict: [String: Any]) -> UserData { ) } -// Falls back to PAYMENT_COMPLETED for an empty or unknown status -private func parsePaymentStatus(_ str: String?) -> PaymentStatus { - guard let str = str else { return .completed } - return PaymentStatus(rawValue: str) ?? .completed -} - // MARK: - C-callable functions exposed to Unity @_cdecl("_LinkrunnerInitialize") @@ -633,44 +582,6 @@ func linkrunnerTrackEvent(_ eventName: UnsafePointer, } } -@_cdecl("_LinkrunnerTrackEventWithPayment") -func linkrunnerTrackEventWithPayment(_ eventName: UnsafePointer, - _ eventDataJson: UnsafePointer?, - _ eventId: UnsafePointer?, - _ amount: Double, - _ paymentId: UnsafePointer?, - _ currency: UnsafePointer?, - _ userId: UnsafePointer?, - _ status: UnsafePointer?) { - let name = String(cString: eventName) - let eventData = toDict(eventDataJson) - let eid = toOptionalString(eventId) - - let payment: EventPayment - do { - payment = try EventPayment( - amount: amount, - paymentId: toOptionalString(paymentId) ?? "", - currency: toOptionalString(currency) ?? "", - userId: toOptionalString(userId) ?? "", - status: parsePaymentStatus(toOptionalString(status)) - ) - } catch { - sendToUnity("OnTrackEventComplete", "error:\(error)") - return - } - - Task { - await LinkrunnerSDK.shared.trackEvent( - eventName: name, - eventData: eventData.isEmpty ? nil : eventData, - eventId: eid, - payment: payment - ) - sendToUnity("OnTrackEventComplete", "success") - } -} - @_cdecl("_LinkrunnerSetPushToken") func linkrunnerSetPushToken(_ pushToken: UnsafePointer) { let token = String(cString: pushToken) @@ -860,10 +771,6 @@ public class LinkrunnerSDK : MonoBehaviour private static extern void _LinkrunnerTrackEvent(string eventName, string eventDataJson, string eventId); [DllImport("__Internal")] - private static extern void _LinkrunnerTrackEventWithPayment(string eventName, - string eventDataJson, string eventId, double amount, string paymentId, - string currency, string userId, string status); - [DllImport("__Internal")] private static extern void _LinkrunnerSetPushToken(string pushToken); [DllImport("__Internal")] private static extern void _LinkrunnerSetAdditionalData(string clevertapId); @@ -965,26 +872,10 @@ public class LinkrunnerSDK : MonoBehaviour } /// - /// Track a custom event. Pass a payment to store the event as revenue under the event name. + /// Track an in-app event. Event data with amount, paymentId and currency is tracked as a payment. /// - public static void TrackEvent(string eventName, string eventDataJson = "", string eventId = "", - EventPayment payment = null) + public static void TrackEvent(string eventName, string eventDataJson = "", string eventId = "") { - if (payment != null) - { - string status = payment.status.ToString(); -#if UNITY_ANDROID && !UNITY_EDITOR - Bridge.CallStatic("trackEventWithPayment", eventName, eventDataJson ?? "", eventId ?? "", - payment.amount, payment.paymentId ?? "", payment.currency ?? "", payment.userId ?? "", status); -#elif UNITY_IOS && !UNITY_EDITOR - _LinkrunnerTrackEventWithPayment(eventName, eventDataJson, eventId, - payment.amount, payment.paymentId, payment.currency, payment.userId ?? "", status); -#else - Debug.Log($"[Linkrunner] TrackEvent with payment: {eventName}, {payment.amount} {payment.currency}"); -#endif - return; - } - #if UNITY_ANDROID && !UNITY_EDITOR Bridge.CallStatic("trackEvent", eventName, eventDataJson ?? "", eventId ?? ""); #elif UNITY_IOS && !UNITY_EDITOR @@ -994,16 +885,6 @@ public class LinkrunnerSDK : MonoBehaviour #endif } - /// - /// Track a payment. The event name becomes the payment type in reports and mappings. - /// An empty userId falls back to the user saved by Signup or SetUserData. - /// - public static void TrackPayment(string eventName, string paymentId, string userId, double amount, - string currency, PaymentStatus status = PaymentStatus.PAYMENT_COMPLETED, string eventDataJson = "") - { - TrackEvent(eventName, eventDataJson, "", new EventPayment(amount, paymentId, currency, userId, status)); - } - /// /// Set the push notification token (Firebase/APNs). /// @@ -1164,38 +1045,6 @@ public class LinkrunnerUserData public bool isFirstTimeUser; } -/// -/// Payment status. Defaults to PAYMENT_COMPLETED. -/// -public enum PaymentStatus -{ - PAYMENT_INITIATED, - PAYMENT_COMPLETED, - PAYMENT_FAILED, - PAYMENT_CANCELLED -} - -/// -/// Revenue attached to a tracked event. The event name becomes the payment type. -/// -public class EventPayment -{ - public double amount; - public string paymentId; - public string currency; - public string userId; - public PaymentStatus status; - - public EventPayment(double amount, string paymentId, string currency, string userId, - PaymentStatus status = PaymentStatus.PAYMENT_COMPLETED) - { - this.amount = amount; - this.paymentId = paymentId; - this.currency = currency; - this.userId = userId; - this.status = status; - } -} ``` --- @@ -1433,91 +1282,74 @@ LinkrunnerSDK.TrackEvent("purchase", eventData, eventId: "purchase_abc123"); ``` -An `amount` inside the event data is not sent to ad networks as revenue. To share revenue, send it as a payment. See [Track Payments](#track-payments). +An `amount` or `currency` in the event data on its own is ordinary event data. To record revenue, send the payment keys described in [Track Payments](#track-payments). ### Track Payments -Use `TrackPayment` to record revenue. Linkrunner stores the payment under the event name you pass, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires the bridges above, built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. +A payment is an in-app event with revenue. Send it with `trackEvent`, and put `amount`, `paymentId`, and `currency` in the event data JSON. The event name becomes the payment type in reports and mappings, so you can use any name, such as `purchase` or `SUBSCRIPTION_RENEWED`. Requires the bridges above, built on Android SDK 5.0.0 and LinkrunnerKit 5.0.0 or later. For examples for every case and how payments reach ad networks, see the full [Track payments](/sdk/track-payments) guide. -This example sets every parameter. Only the first five are required. +This example sends every payment key. `amount`, `paymentId`, `currency`, and `userId` are required for a payment. ```csharp -// Product details: Meta and Google read these fields +// Payment keys and product details in one JSON object string eventDataJson = - "{\"content_ids\":[\"sku_1\",\"sku_2\"]," + + "{\"amount\":1499.0," + // Required for a payment: total the user paid, a finite number >= 0 + "\"paymentId\":\"order_123\"," + // Required for a payment: your order or transaction ID, used to deduplicate + "\"currency\":\"INR\"," + // Required for a payment: 3-letter ISO 4217 code + "\"userId\":\"user_123\"," + // Required for a payment: your user ID. "" uses the user saved by Signup or SetUserData + "\"status\":\"PAYMENT_COMPLETED\"," + // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + // Optional: any other keys stay as event data. Meta and Google read these product fields + "\"content_ids\":[\"sku_1\",\"sku_2\"]," + "\"contents\":[{\"id\":\"sku_1\",\"quantity\":1,\"item_price\":999}," + "{\"id\":\"sku_2\",\"quantity\":1,\"item_price\":500}]," + "\"content_type\":\"product\",\"num_items\":2,\"order_id\":\"order_123\"}"; -// Reports the result of every TrackEvent and TrackPayment call. Subscribe once, for example in Awake +// Reports the result of every TrackEvent call. Subscribe once, for example in Awake LinkrunnerSDK.OnEventTracked += (success, message) => { - // An invalid value arrives here as success == false, and nothing is sent + // An invalid payment arrives here as success == false, and nothing is sent if (!success) Debug.LogError($"Error tracking payment: {message}"); }; -LinkrunnerSDK.TrackPayment( - eventName: "purchase", // Required: payment name, stored as the payment type in reports and mappings - paymentId: "order_123", // Required: your order or transaction ID, used to deduplicate - userId: "user_123", // Required: your user ID. "" uses the user saved by Signup or SetUserData - amount: 1499.0, // Required: total the user paid, a finite number >= 0 - currency: "INR", // Required: 3-letter ISO 4217 code - status: PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) - eventDataJson: eventDataJson // Optional: extra details as a JSON string, defaults to "" -); +LinkrunnerSDK.TrackEvent("purchase", eventDataJson, eventId: "evt_order_123"); ``` -#### `PaymentStatus` +#### Payment keys in the event data -`PaymentStatus` is the C# enum defined in the `LinkrunnerSDK` wrapper above. The bridges pass its name to the native SDK: +| Key | Type | Required | Description | +| --- | ---- | -------- | ----------- | +| `amount` | `number` | Yes | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | +| `paymentId` | `string` | Yes | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | +| `currency` | `string` | Yes | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | +| `userId` | `string` | Yes | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `signup` or `setUserData`. | +| `status` | `string` | No | `PAYMENT_INITIATED`, `PAYMENT_COMPLETED` (default), `PAYMENT_FAILED`, or `PAYMENT_CANCELLED`. | +| Any other key | Any | No | Stays in the event data, such as product details for [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager). | -```csharp -public enum PaymentStatus -{ - PAYMENT_INITIATED, - PAYMENT_COMPLETED, // default - PAYMENT_FAILED, - PAYMENT_CANCELLED -} -``` +How `trackEvent` decides: + +- **`amount`, `paymentId`, and `currency` are all in `eventData`:** the event is a payment. The SDK checks the payment keys and sends them as payment fields. +- **None of them, or `amount` or `currency` without `paymentId`:** the event is a normal custom event, exactly as before. An `amount` or `currency` key on its own is ordinary event data. +- **`paymentId` with `amount` or `currency` missing:** The native SDK rejects the event, and `OnEventTracked` reports `false` with `amount, paymentId and currency must be sent together`, and nothing is sent. +- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** The native SDK rejects the event, and `OnEventTracked` reports `false`, and nothing is sent. + +#### Payment status + +`status` is a string in the event data JSON. | Status | Use it when | Effect | | ------ | ----------- | ------ | | `PAYMENT_INITIATED` | The user started paying, such as opening the checkout or payment sheet. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | | `PAYMENT_COMPLETED` (default) | The charge succeeded. | Added to revenue and paying users. Sent to ad networks when the event name is mapped. | | `PAYMENT_FAILED` | The charge was declined or errored. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | -| `PAYMENT_CANCELLED` | The user backed out before paying. | Stored for reports and analytics. Not added to revenue and not sent to ad networks. | +| `PAYMENT_CANCELLED` | The user backed out before paying, or you refunded it. Send it with the same `paymentId` as the completed payment. | Stored for reports and analytics. The payment no longer counts as revenue and nothing is sent to ad networks. | Linkrunner keeps one payment per `paymentId`, event name, and status, so a retry with the same values is recorded once. See [Payment status](/sdk/track-payments#payment-status) for a full lifecycle example. -#### `TrackPayment` Parameters - -| Parameter | Type | Required | Default | Description | -| --------- | ---- | -------- | ------- | ----------- | -| `eventName` | `string` | Yes | | Payment name, shown as the payment type in reports and matched against your event mappings. | -| `paymentId` | `string` | Yes | | Your order or transaction ID. Must not be blank. Used to recognize the same payment when it arrives again. | -| `userId` | `string` | Yes | | Your ID for the user who paid. If you pass an empty string, the SDK uses the user saved by `Signup` or `SetUserData`. | -| `amount` | `double` | Yes | | Total the user paid. Must be a finite number, 0 or greater. This is the revenue value. | -| `currency` | `string` | Yes | | 3-letter ISO 4217 currency code, such as `INR` or `USD`. Case-insensitive, sent in uppercase. | -| `status` | `PaymentStatus` | No | `PAYMENT_COMPLETED` | Where the payment is in its lifecycle. See the statuses above. | -| `eventDataJson` | `string` | No | `""` | Extra details as a JSON string, such as product details. An `amount` inside it is not revenue. | - -If a value breaks these rules, the native SDK rejects the payment, nothing is sent, and `OnEventTracked` reports `false` with the error. - The event name is stored exactly as you send it. If you used one of the old payment types, pass it in uppercase exactly as before, such as `FIRST_PAYMENT` or `DEFAULT`, so your existing mappings and reports keep working. A different spelling such as `first_payment` is a new payment type. Any other name, such as `purchase`, is also stored as sent, and you need to add a mapping for it in the dashboard before it reaches ad networks. -#### Track a payment with `TrackEvent` - -You can also pass an `EventPayment` to `TrackEvent`. It takes the same `amount`, `paymentId`, `currency`, `userId`, and `status` fields and records the same payment. Use it when you also need an `eventId`. - -```csharp -var payment = new EventPayment(amount: 9.99, paymentId: "pay_abc123", currency: "USD", userId: "user_123"); -LinkrunnerSDK.TrackEvent("purchase", payment: payment); -``` - `capturePayment` is deprecated. Bridges built against Android SDK 4.x or LinkrunnerKit 4.x keep using it and continue to work unchanged. To move to 5.0.0, see [Migrate payments to in-app events](/sdk/migrate-to-track-event). @@ -1559,7 +1391,7 @@ LinkrunnerSDK.SetDisableAaidCollection(true); | [`GetAttributionData`](/sdk/unity#get-attribution-data) | Optional | After init completes | When you need campaign/deeplink info | | [`SetUserData`](/sdk/unity#set-user-data-on-app-open) | Optional | App start, authentication or profile update logic | Right after `Initialize` with your analytics IDs, and when user details become available | | [`TrackEvent`](/sdk/unity#track-events) | Optional | Throughout the app | On user actions | -| [`TrackPayment`](/sdk/unity#track-payments) | Optional | Payment flow | When payment succeeds | +| [`TrackEvent` with payment keys](/sdk/unity#track-payments) | Optional | Payment flow | When payment succeeds | | [`SetPushToken`](/sdk/unity#push-token) | Optional | After token refresh | When push token changes | | [`SetConsent`](/sdk/unity#set-consent) | Optional | Before `Initialize` | At launch and whenever consent changes | | [`SetAdditionalData`](/sdk/unity#set-additional-data) | Optional | Integration setup | When the CleverTap ID is available | diff --git a/testing/event-and-revenue-testing.mdx b/testing/event-and-revenue-testing.mdx index 67b4da15..ec1c04b0 100644 --- a/testing/event-and-revenue-testing.mdx +++ b/testing/event-and-revenue-testing.mdx @@ -12,7 +12,7 @@ Use this guide after [SDK Integration Testing](/testing/integration-testing). It | What happened | App SDK | Server-side API | | --- | --- | --- | | A user action with no authoritative transaction | `trackEvent` | `POST /capture-event` | -| A payment with an amount and unique transaction ID | `trackPayment` (`eventName`, `paymentId`, `userId`, `amount`, `currency`) | `POST /capture-event` with `amount`, `payment_id`, and `currency` | +| A payment with an amount and unique transaction ID | `trackEvent` with `amount`, `paymentId`, `currency`, and `userId` in the event data | `POST /capture-event` with `amount`, `payment_id`, and `currency` | When you attach a payment, the event name becomes the payment type in reports. See [Payment fields](/api-reference/event-capture#payment-fields) for the rules. For SDK examples, payment status, and how payments reach ad networks, see [Track payments](/sdk/track-payments). @@ -163,7 +163,7 @@ See [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager#understandi - The app or backend sent it without the payment fields. Send `amount`, `payment_id`, and `currency` together with the event. An `amount` inside `event_data` does not make the event a payment. + The app or backend sent it without all three payment fields. From the SDK, put `amount`, `paymentId`, and `currency` in the event data. From your backend, send `amount`, `payment_id`, and `currency` at the top level of the request. An `amount` without the other two is a custom event property. From a73574932c0e317bc2953820334fdce316d6e3b1 Mon Sep 17 00:00:00 2001 From: chetan Date: Sat, 24 Oct 2026 17:15:16 +0530 Subject: [PATCH 10/15] docs: show only the payment call in migration examples Co-Authored-By: Claude Opus 5.5 (1M context) --- sdk/migrate-to-track-event.mdx | 38 +--------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/sdk/migrate-to-track-event.mdx b/sdk/migrate-to-track-event.mdx index 6a2c38f4..7c039f35 100644 --- a/sdk/migrate-to-track-event.mdx +++ b/sdk/migrate-to-track-event.mdx @@ -65,7 +65,7 @@ If you did not pass a `type` before, your payments were saved as `DEFAULT`. Use ## Code examples -Each example is from a sports booking app. It shows the old payment call, then the new custom event and payment with `trackEvent`. The payment keeps the old `FIRST_PAYMENT` name, so the existing mappings keep working. Use your own values. +Each example is from a sports booking app. It shows the old `capturePayment` call, then the same payment with `trackEvent`. The payment keeps the old `FIRST_PAYMENT` name, so the existing mappings keep working. Use your own values. @@ -86,12 +86,6 @@ Each example is from a sports booking app. It shows the old payment call, then t After (Android SDK 5.0.0): ```kotlin - // Custom event, no change - LinkRunner.getInstance().trackEvent( - eventName = "venue_viewed", - eventData = mapOf("venue_id" to "v_221", "sport" to "badminton") - ) - // Payment: same trackEvent, payment fields in eventData LinkRunner.getInstance().trackEvent( eventName = "FIRST_PAYMENT", // was type, same spelling @@ -124,12 +118,6 @@ Each example is from a sports booking app. It shows the old payment call, then t After (LinkrunnerKit 5.0.0): ```swift - // Custom event, no change - await LinkrunnerSDK.shared.trackEvent( - eventName: "venue_viewed", - eventData: ["venue_id": "v_221", "sport": "badminton"] - ) - // Payment: same trackEvent, payment fields in eventData await LinkrunnerSDK.shared.trackEvent( eventName: "FIRST_PAYMENT", // was type, same spelling @@ -162,9 +150,6 @@ Each example is from a sports booking app. It shows the old payment call, then t After (`rn-linkrunner` 4.0.0): ```javascript - // Custom event, no change - await linkrunner.trackEvent("venue_viewed", { venue_id: "v_221", sport: "badminton" }); - // Payment: same trackEvent, payment fields in eventData await linkrunner.trackEvent("FIRST_PAYMENT", { // was type, same spelling amount: 1200, @@ -196,12 +181,6 @@ Each example is from a sports booking app. It shows the old payment call, then t After (`linkrunner` 5.0.0): ```dart - // Custom event, no change - await LinkRunner().trackEvent( - eventName: 'venue_viewed', - eventData: {'venue_id': 'v_221', 'sport': 'badminton'}, - ); - // Payment: same trackEvent, payment fields in eventData await LinkRunner().trackEvent( eventName: 'FIRST_PAYMENT', // was type, same spelling @@ -235,9 +214,6 @@ Each example is from a sports booking app. It shows the old payment call, then t After (`rn-linkrunner` 4.0.0): ```javascript - // Custom event, no change - await linkrunner.trackEvent("venue_viewed", { venue_id: "v_221", sport: "badminton" }); - // Payment: same trackEvent, payment fields in eventData await linkrunner.trackEvent("FIRST_PAYMENT", { // was type, same spelling amount: 1200, @@ -265,12 +241,6 @@ Each example is from a sports booking app. It shows the old payment call, then t After (`capacitor-linkrunner` 3.0.0): ```typescript - // Custom event, no change - await linkrunner.trackEvent({ - eventName: "venue_viewed", - eventData: { venue_id: "v_221", sport: "badminton" }, - }); - // Payment: same trackEvent, payment fields in eventData await linkrunner.trackEvent({ eventName: "FIRST_PAYMENT", // was type, same spelling @@ -303,9 +273,6 @@ Each example is from a sports booking app. It shows the old payment call, then t After (`cordova-linkrunner` 2.0.0): ```javascript - // Custom event, no change - linkrunner.trackEvent("venue_viewed", { venue_id: "v_221", sport: "badminton" }); - // Payment: same trackEvent, payment fields in eventData linkrunner.trackEvent("FIRST_PAYMENT", { // was type, same spelling amount: 1200, @@ -337,9 +304,6 @@ Each example is from a sports booking app. It shows the old payment call, then t After (bridges built on native 5.0.0): ```csharp - // Custom event, no change - LinkrunnerSDK.TrackEvent("venue_viewed", "{\"venue_id\":\"v_221\",\"sport\":\"badminton\"}"); - // Payment: same TrackEvent, payment fields in the event data JSON string eventDataJson = "{\"amount\":1200.0," + From 85213f6f780277301fb2e3dff981d43057cded3a Mon Sep 17 00:00:00 2001 From: chetan Date: Sat, 24 Oct 2026 17:16:50 +0530 Subject: [PATCH 11/15] docs: add product details to the payment migration examples Co-Authored-By: Claude Opus 5.5 (1M context) --- sdk/migrate-to-track-event.mdx | 57 +++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/sdk/migrate-to-track-event.mdx b/sdk/migrate-to-track-event.mdx index 7c039f35..8586b9c2 100644 --- a/sdk/migrate-to-track-event.mdx +++ b/sdk/migrate-to-track-event.mdx @@ -65,7 +65,7 @@ If you did not pass a `type` before, your payments were saved as `DEFAULT`. Use ## Code examples -Each example is from a sports booking app. It shows the old `capturePayment` call, then the same payment with `trackEvent`. The payment keeps the old `FIRST_PAYMENT` name, so the existing mappings keep working. Use your own values. +Each example is from a sports booking app. It shows the old `capturePayment` call, then the same payment with `trackEvent`. The product keys in `eventData` are optional: Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`, and Google receives `content_ids` as item IDs. `amount` stays the order total. Leave them out if you do not use [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager) or [Google Commerce Manager](/ecommerce-manager/google-commerce-manager). The payment keeps the old `FIRST_PAYMENT` name, so the existing mappings keep working. Use your own values. @@ -95,7 +95,12 @@ Each example is from a sports booking app. It shows the old `capturePayment` cal "currency" to "INR", // new, required "userId" to "playo_u_8842", "status" to "PAYMENT_COMPLETED", // optional, the default - "venue_id" to "v_221" // other keys stay as event data + // product details for Meta and Google + "content_ids" to listOf("court_c2_1800"), + "contents" to listOf(mapOf("id" to "court_c2_1800", "quantity" to 1, "item_price" to 1200.0)), + "content_type" to "product", + "num_items" to 1, + "order_id" to "bk_10231" ) ) ``` @@ -127,7 +132,12 @@ Each example is from a sports booking app. It shows the old `capturePayment` cal "currency": "INR", // new, required "userId": "playo_u_8842", "status": "PAYMENT_COMPLETED", // optional, the default - "venue_id": "v_221" // other keys stay as event data + // product details for Meta and Google + "content_ids": ["court_c2_1800"], + "contents": [["id": "court_c2_1800", "quantity": 1, "item_price": 1200]], + "content_type": "product", + "num_items": 1, + "order_id": "bk_10231" ] ) ``` @@ -157,7 +167,12 @@ Each example is from a sports booking app. It shows the old `capturePayment` cal currency: "INR", // new, required userId: "playo_u_8842", status: "PAYMENT_COMPLETED", // optional, the default - venue_id: "v_221", // other keys stay as event data + // product details for Meta and Google + content_ids: ["court_c2_1800"], + contents: [{ id: "court_c2_1800", quantity: 1, item_price: 1200 }], + content_type: "product", + num_items: 1, + order_id: "bk_10231", }); ``` @@ -190,7 +205,14 @@ Each example is from a sports booking app. It shows the old `capturePayment` cal 'currency': 'INR', // new, required 'userId': 'playo_u_8842', 'status': 'PAYMENT_COMPLETED', // optional, the default - 'venue_id': 'v_221', // other keys stay as event data + // product details for Meta and Google + 'content_ids': ['court_c2_1800'], + 'contents': [ + {'id': 'court_c2_1800', 'quantity': 1, 'item_price': 1200}, + ], + 'content_type': 'product', + 'num_items': 1, + 'order_id': 'bk_10231', }, ); ``` @@ -220,6 +242,12 @@ Each example is from a sports booking app. It shows the old `capturePayment` cal paymentId: "bk_10231", currency: "INR", // new, required userId: "playo_u_8842", + // product details for Meta and Google + content_ids: ["court_c2_1800"], + contents: [{ id: "court_c2_1800", quantity: 1, item_price: 1200 }], + content_type: "product", + num_items: 1, + order_id: "bk_10231", }); ``` @@ -250,7 +278,12 @@ Each example is from a sports booking app. It shows the old `capturePayment` cal currency: "INR", // new, required userId: "playo_u_8842", status: "PAYMENT_COMPLETED", // optional, the default - venue_id: "v_221", // other keys stay as event data + // product details for Meta and Google + content_ids: ["court_c2_1800"], + contents: [{ id: "court_c2_1800", quantity: 1, item_price: 1200 }], + content_type: "product", + num_items: 1, + order_id: "bk_10231", }, }); ``` @@ -280,7 +313,12 @@ Each example is from a sports booking app. It shows the old `capturePayment` cal currency: "INR", // new, required userId: "playo_u_8842", status: "PAYMENT_COMPLETED", // optional, the default - venue_id: "v_221" // other keys stay as event data + // product details for Meta and Google + content_ids: ["court_c2_1800"], + contents: [{ id: "court_c2_1800", quantity: 1, item_price: 1200 }], + content_type: "product", + num_items: 1, + order_id: "bk_10231" }).catch(function (error) { console.error("Error tracking payment:", error); }); @@ -311,7 +349,10 @@ Each example is from a sports booking app. It shows the old `capturePayment` cal "\"currency\":\"INR\"," + // new, required "\"userId\":\"playo_u_8842\"," + "\"status\":\"PAYMENT_COMPLETED\"," + // optional, the default - "\"venue_id\":\"v_221\"}"; // other keys stay as event data + // product details for Meta and Google + "\"content_ids\":[\"court_c2_1800\"]," + + "\"contents\":[{\"id\":\"court_c2_1800\",\"quantity\":1,\"item_price\":1200}]," + + "\"content_type\":\"product\",\"num_items\":1,\"order_id\":\"bk_10231\"}"; LinkrunnerSDK.TrackEvent("FIRST_PAYMENT", eventDataJson); // was type, same spelling ``` From abf03b656a2ad3cea302c30fb23faa5240312651 Mon Sep 17 00:00:00 2001 From: chetan Date: Sat, 24 Oct 2026 17:20:18 +0530 Subject: [PATCH 12/15] docs: move the migration examples to the top of the page Co-Authored-By: Claude Opus 5.5 (1M context) --- sdk/migrate-to-track-event.mdx | 100 +++++++++++++++++---------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/sdk/migrate-to-track-event.mdx b/sdk/migrate-to-track-event.mdx index 8586b9c2..852b7a91 100644 --- a/sdk/migrate-to-track-event.mdx +++ b/sdk/migrate-to-track-event.mdx @@ -7,18 +7,7 @@ icon: "arrow-right-arrow-left" In the new SDK versions, a payment is an in-app event with revenue. You send it with `trackEvent`, the same method you use for custom events. `capturePayment` and `removePayment` are removed. -Apps on older SDK versions keep working with no change. Migrate when you upgrade. - -| Platform | Package | Old versions (`capturePayment`) | New versions (`trackEvent` for payments) | -| --- | --- | --- | --- | -| Android | `io.linkrunner:android-sdk` | 4.x | 5.0.0 | -| iOS | LinkrunnerKit | 4.x | 5.0.0 | -| React Native | `rn-linkrunner` | 3.x | 4.0.0 | -| Flutter | `linkrunner` | 4.x | 5.0.0 | -| Expo | `expo-linkrunner` | 3.x | 4.0.0, with `rn-linkrunner` 4.0.0 or later | -| Capacitor | `capacitor-linkrunner` | 2.x | 3.0.0 | -| Cordova | `cordova-linkrunner` | 1.x | 2.0.0 | -| Unity | [Unity bridges](/sdk/unity#track-payments) | Bridges on native 4.x | Bridges on native 5.0.0 | +Apps on older SDK versions keep working with no change. Migrate when you upgrade to the [versions below](#sdk-versions). **Use the exact same event name, with the same spelling and letter case.** @@ -28,44 +17,9 @@ Apps on older SDK versions keep working with no change. Migrate when you upgrade Linkrunner matches the name exactly. A different spelling is treated as a new in-app event, so your existing Meta and Google mappings will not match it, and those payments will not be sent to Meta or Google. -## How to migrate - -For each `capturePayment` call, make three edits: - -1. **Rename `capturePayment` to `trackEvent`.** -2. **Pass your old `type` as the event name,** the first argument, with exactly the same spelling. -3. **Move the payment fields into the event data:** `amount`, `paymentId`, `userId`, and `status` keep their names. Add `currency`, which is new and required, for example `"USD"` or `"INR"`. +## Before and after -Then remove every `removePayment` call, and test once with a debug build. The payment should show on the [Events](https://dashboard.linkrunner.io/dashboard/events) page under the same name as before. - -| `capturePayment` | `trackEvent` | -| --- | --- | -| `type` | the event name (first argument), same value and spelling | -| `amount` | `amount` in the event data, the order total | -| `paymentId` | `paymentId` in the event data, required | -| `userId` | `userId` in the event data, still required. An empty string uses the user saved by `signup` or `setUserData` | -| `status` | `status` in the event data, optional, defaults to `PAYMENT_COMPLETED` | -| `eventData` | the other keys in the event data, such as product details | -| Not available | `currency` in the event data, new and required | - -The event is a payment only when the event data has `amount`, `paymentId`, and `currency` together. If any of the three is missing, it is not a payment: - -- `amount` or `currency` without `paymentId` is a normal custom event, exactly as before. -- `paymentId` with `amount` or `currency` missing is an error, and nothing is sent. - -Only `PAYMENT_COMPLETED` payments count as revenue and are sent to ad networks. See [Payment status](/sdk/track-payments#payment-status) for the other statuses. - -## Old payment types - -These are the old payment types. Send them exactly like this, in uppercase: - -`FIRST_PAYMENT`, `SECOND_PAYMENT`, `TRIAL_STARTED`, `TRIAL_CANCELLED`, `WALLET_TOPUP`, `FUNDS_WITHDRAWAL`, `SUBSCRIPTION_CREATED`, `SUBSCRIPTION_RENEWED`, `SUBSCRIPTION_CANCELLED`, `SUBSCRIPTION_REFUNDED`, `SUBSCRIPTION_BILLING_ISSUE`, `DEFAULT`, `ONE_TIME`, `RECURRING` - -If you did not pass a `type` before, your payments were saved as `DEFAULT`. Use `"DEFAULT"` as the event name. In Flutter, `PaymentType.DEFAULT_PAYMENT` was also saved as `DEFAULT`, so use `"DEFAULT"`, not `"DEFAULT_PAYMENT"`. - -## Code examples - -Each example is from a sports booking app. It shows the old `capturePayment` call, then the same payment with `trackEvent`. The product keys in `eventData` are optional: Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`, and Google receives `content_ids` as item IDs. `amount` stays the order total. Leave them out if you do not use [Meta Commerce Manager](/ecommerce-manager/meta-commerce-manager) or [Google Commerce Manager](/ecommerce-manager/google-commerce-manager). The payment keeps the old `FIRST_PAYMENT` name, so the existing mappings keep working. Use your own values. +The old `capturePayment` call, then the same payment with `trackEvent`. The product keys are optional: Meta receives `content_ids`, `contents`, `content_type`, `num_items`, and `order_id`, and Google receives `content_ids` as item IDs. `amount` stays the order total. @@ -360,6 +314,54 @@ Each example is from a sports booking app. It shows the old `capturePayment` cal +## How to migrate + +For each `capturePayment` call, make three edits: + +1. **Rename `capturePayment` to `trackEvent`.** +2. **Pass your old `type` as the event name,** the first argument, with exactly the same spelling. +3. **Move the payment fields into the event data:** `amount`, `paymentId`, `userId`, and `status` keep their names. Add `currency`, which is new and required, for example `"USD"` or `"INR"`. + +Then remove every `removePayment` call, and test once with a debug build. The payment should show on the [Events](https://dashboard.linkrunner.io/dashboard/events) page under the same name as before. + +| `capturePayment` | `trackEvent` | +| --- | --- | +| `type` | the event name (first argument), same value and spelling | +| `amount` | `amount` in the event data, the order total | +| `paymentId` | `paymentId` in the event data, required | +| `userId` | `userId` in the event data, still required. An empty string uses the user saved by `signup` or `setUserData` | +| `status` | `status` in the event data, optional, defaults to `PAYMENT_COMPLETED` | +| `eventData` | the other keys in the event data, such as product details | +| Not available | `currency` in the event data, new and required | + +The event is a payment only when the event data has `amount`, `paymentId`, and `currency` together. If any of the three is missing, it is not a payment: + +- `amount` or `currency` without `paymentId` is a normal custom event, exactly as before. +- `paymentId` with `amount` or `currency` missing is an error, and nothing is sent. + +Only `PAYMENT_COMPLETED` payments count as revenue and are sent to ad networks. See [Payment status](/sdk/track-payments#payment-status) for the other statuses. + +## Old payment types + +These are the old payment types. Send them exactly like this, in uppercase: + +`FIRST_PAYMENT`, `SECOND_PAYMENT`, `TRIAL_STARTED`, `TRIAL_CANCELLED`, `WALLET_TOPUP`, `FUNDS_WITHDRAWAL`, `SUBSCRIPTION_CREATED`, `SUBSCRIPTION_RENEWED`, `SUBSCRIPTION_CANCELLED`, `SUBSCRIPTION_REFUNDED`, `SUBSCRIPTION_BILLING_ISSUE`, `DEFAULT`, `ONE_TIME`, `RECURRING` + +If you did not pass a `type` before, your payments were saved as `DEFAULT`. Use `"DEFAULT"` as the event name. In Flutter, `PaymentType.DEFAULT_PAYMENT` was also saved as `DEFAULT`, so use `"DEFAULT"`, not `"DEFAULT_PAYMENT"`. + +## SDK versions + +| Platform | Package | Old versions (`capturePayment`) | New versions (`trackEvent` for payments) | +| --- | --- | --- | --- | +| Android | `io.linkrunner:android-sdk` | 4.x | 5.0.0 | +| iOS | LinkrunnerKit | 4.x | 5.0.0 | +| React Native | `rn-linkrunner` | 3.x | 4.0.0 | +| Flutter | `linkrunner` | 4.x | 5.0.0 | +| Expo | `expo-linkrunner` | 3.x | 4.0.0, with `rn-linkrunner` 4.0.0 or later | +| Capacitor | `capacitor-linkrunner` | 2.x | 3.0.0 | +| Cordova | `cordova-linkrunner` | 1.x | 2.0.0 | +| Unity | [Unity bridges](/sdk/unity#track-payments) | Bridges on native 4.x | Bridges on native 5.0.0 | + ## Refunds and removePayment `removePayment` is removed and has no replacement. To record a refund or a cancelled booking, send the same event name and `paymentId` again with `status` set to `PAYMENT_CANCELLED`. That payment no longer counts as revenue, and nothing is sent to ad networks. From ccd6ea13f16a077d6908f7285c7cf40e53130298 Mon Sep 17 00:00:00 2001 From: chetan Date: Sat, 24 Oct 2026 17:55:15 +0530 Subject: [PATCH 13/15] docs: Capacitor trackEvent takes the event name and data as arguments Co-Authored-By: Claude Opus 5.5 (1M context) --- sdk/capacitor.mdx | 57 +++++++++++------------ sdk/migrate-to-track-event.mdx | 27 +++++------ sdk/track-payments.mdx | 83 +++++++++++++++------------------- 3 files changed, 76 insertions(+), 91 deletions(-) diff --git a/sdk/capacitor.mdx b/sdk/capacitor.mdx index 7560dfcb..05f9e4d8 100644 --- a/sdk/capacitor.mdx +++ b/sdk/capacitor.mdx @@ -391,26 +391,26 @@ import linkrunner, { PaymentStatus } from "capacitor-linkrunner"; const trackPurchase = async () => { try { - await linkrunner.trackEvent({ - eventName: "purchase", // Payment name, stored as the payment type in reports and mappings - eventData: { - amount: 1499, // Required for a payment: total the user paid, a finite number >= 0 - paymentId: "order_123", // Required for a payment: your order or transaction ID, used to deduplicate - currency: "INR", // Required for a payment: 3-letter ISO 4217 code - userId: "user_123", // Required for a payment: your user ID. "" uses the user saved by signup or setUserData - status: PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) - // Optional: any other keys stay as event data. Meta and Google read these product fields - content_ids: ["sku_1", "sku_2"], - contents: [ - { id: "sku_1", quantity: 1, item_price: 999 }, - { id: "sku_2", quantity: 1, item_price: 500 }, - ], - content_type: "product", - num_items: 2, - order_id: "order_123", + await linkrunner.trackEvent( + "purchase", // Payment name, stored as the payment type in reports and mappings + { + amount: 1499, // Required for a payment: total the user paid, a finite number >= 0 + paymentId: "order_123", // Required for a payment: your order or transaction ID, used to deduplicate + currency: "INR", // Required for a payment: 3-letter ISO 4217 code + userId: "user_123", // Required for a payment: your user ID. "" uses the user saved by signup or setUserData + status: PaymentStatus.PAYMENT_COMPLETED, // Optional: defaults to PAYMENT_COMPLETED (see statuses below) + // Optional: any other keys stay as event data. Meta and Google read these product fields + content_ids: ["sku_1", "sku_2"], + contents: [ + { id: "sku_1", quantity: 1, item_price: 999 }, + { id: "sku_2", quantity: 1, item_price: 500 }, + ], + content_type: "product", + num_items: 2, + order_id: "order_123", }, - eventId: "evt_order_123", // Optional: your own ID for this event - }); + "evt_order_123" // Optional: your own ID for this event + ); } catch (error) { // An invalid payment rejects here, and nothing is sent console.error("Error tracking payment:", error); @@ -496,17 +496,14 @@ Put the payment keys and the product details in `eventData`. Meta receives `cont ```typescript const trackPurchase = async () => { try { - await linkrunner.trackEvent({ - eventName: "purchase", - eventData: { - amount: 149.99, // Order total, sent to ad networks as the value - paymentId: "order_12345", - currency: "USD", - userId: "user_123", - content_ids: ["product_123"], - num_items: 1, - order_id: "order_12345", - }, + await linkrunner.trackEvent("purchase", { + amount: 149.99, // Order total, sent to ad networks as the value + paymentId: "order_12345", + currency: "USD", + userId: "user_123", + content_ids: ["product_123"], + num_items: 1, + order_id: "order_12345", }); } catch (error) { console.error("Error tracking payment:", error); diff --git a/sdk/migrate-to-track-event.mdx b/sdk/migrate-to-track-event.mdx index 852b7a91..08519c50 100644 --- a/sdk/migrate-to-track-event.mdx +++ b/sdk/migrate-to-track-event.mdx @@ -224,21 +224,18 @@ The old `capturePayment` call, then the same payment with `trackEvent`. The prod ```typescript // Payment: same trackEvent, payment fields in eventData - await linkrunner.trackEvent({ - eventName: "FIRST_PAYMENT", // was type, same spelling - eventData: { - amount: 1200, - paymentId: "bk_10231", - currency: "INR", // new, required - userId: "playo_u_8842", - status: "PAYMENT_COMPLETED", // optional, the default - // product details for Meta and Google - content_ids: ["court_c2_1800"], - contents: [{ id: "court_c2_1800", quantity: 1, item_price: 1200 }], - content_type: "product", - num_items: 1, - order_id: "bk_10231", - }, + await linkrunner.trackEvent("FIRST_PAYMENT", { // was type, same spelling + amount: 1200, + paymentId: "bk_10231", + currency: "INR", // new, required + userId: "playo_u_8842", + status: "PAYMENT_COMPLETED", // optional, the default + // product details for Meta and Google + content_ids: ["court_c2_1800"], + contents: [{ id: "court_c2_1800", quantity: 1, item_price: 1200 }], + content_type: "product", + num_items: 1, + order_id: "bk_10231", }); ``` diff --git a/sdk/track-payments.mdx b/sdk/track-payments.mdx index f64aa79c..58c05ab9 100644 --- a/sdk/track-payments.mdx +++ b/sdk/track-payments.mdx @@ -267,23 +267,20 @@ Use this when your app learns about a payment only after it succeeds. `status` i export async function onPurchaseSucceeded(orderId: string, userId: string) { try { - await linkrunner.trackEvent({ - eventName: "purchase", // becomes the payment type in reports and mappings - eventData: { - paymentId: orderId, // your order ID, used to deduplicate - userId: userId, // required, links the payment to your user - amount: 2298, // order total the user paid - currency: "INR", // required, 3-letter ISO 4217 code - // Product details: Meta receives these fields, Google receives content_ids as item IDs - content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], - contents: [ - { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, - { id: "sku_black_jeans_32", quantity: 1, item_price: 1499 }, - ], - content_type: "product", - num_items: 2, - order_id: orderId, - }, + await linkrunner.trackEvent("purchase", { // becomes the payment type in reports and mappings + paymentId: orderId, // your order ID, used to deduplicate + userId: userId, // required, links the payment to your user + amount: 2298, // order total the user paid + currency: "INR", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + content_ids: ["sku_blue_tshirt_m", "sku_black_jeans_32"], + contents: [ + { id: "sku_blue_tshirt_m", quantity: 1, item_price: 799 }, + { id: "sku_black_jeans_32", quantity: 1, item_price: 1499 }, + ], + content_type: "product", + num_items: 2, + order_id: orderId, }); } catch (error) { console.error("Payment not tracked:", error); @@ -577,21 +574,18 @@ Use this when you want to see started, failed, and cancelled payments next to co async function trackCheckout(orderId: string, userId: string, status: PaymentStatus) { try { - await linkrunner.trackEvent({ - eventName: "purchase", // same name for every status of this payment - eventData: { - paymentId: orderId, // same ID for every status of this payment - userId: userId, // required, links the payment to your user - amount: 499, // order total the user pays - currency: "INR", // required, 3-letter ISO 4217 code - status: status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks - // Product details: Meta receives these fields, Google receives content_ids as item IDs - content_ids: ["sku_blue_tshirt_m"], - contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], - content_type: "product", - num_items: 1, - order_id: orderId, - }, + await linkrunner.trackEvent("purchase", { // same name for every status of this payment + paymentId: orderId, // same ID for every status of this payment + userId: userId, // required, links the payment to your user + amount: 499, // order total the user pays + currency: "INR", // required, 3-letter ISO 4217 code + status: status, // only PAYMENT_COMPLETED counts as revenue and goes to ad networks + // Product details: Meta receives these fields, Google receives content_ids as item IDs + content_ids: ["sku_blue_tshirt_m"], + contents: [{ id: "sku_blue_tshirt_m", quantity: 1, item_price: 499 }], + content_type: "product", + num_items: 1, + order_id: orderId, }); } catch (error) { console.error("Payment not tracked:", error); @@ -834,20 +828,17 @@ Use this when you already have mappings or affiliate templates for an old paymen export async function onSubscriptionRenewed(transactionId: string, userId: string) { try { - await linkrunner.trackEvent({ - eventName: "SUBSCRIPTION_RENEWED", // old type name, keeps your existing mappings - eventData: { - paymentId: transactionId, // a new ID for every renewal, used to deduplicate - userId: userId, // required, links the payment to your user - amount: 4.99, // total the user paid for this renewal - currency: "USD", // required, 3-letter ISO 4217 code - // Product details: Meta receives these fields, Google receives content_ids as item IDs - content_ids: ["pro_monthly"], - contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], - content_type: "product", - num_items: 1, - order_id: transactionId, - }, + await linkrunner.trackEvent("SUBSCRIPTION_RENEWED", { // old type name, keeps your existing mappings + paymentId: transactionId, // a new ID for every renewal, used to deduplicate + userId: userId, // required, links the payment to your user + amount: 4.99, // total the user paid for this renewal + currency: "USD", // required, 3-letter ISO 4217 code + // Product details: Meta receives these fields, Google receives content_ids as item IDs + content_ids: ["pro_monthly"], + contents: [{ id: "pro_monthly", quantity: 1, item_price: 4.99 }], + content_type: "product", + num_items: 1, + order_id: transactionId, }); } catch (error) { console.error("Payment not tracked:", error); From 4e9be4d44c5b46563a0036e64ec04d37450992ce Mon Sep 17 00:00:00 2001 From: chetan Date: Sat, 24 Oct 2026 17:59:38 +0530 Subject: [PATCH 14/15] docs: describe invalid payment errors per platform Co-Authored-By: Claude Opus 5.5 (1M context) --- sdk/flutter.mdx | 9 +++++---- sdk/track-payments.mdx | 19 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/sdk/flutter.mdx b/sdk/flutter.mdx index 37956968..4d149307 100644 --- a/sdk/flutter.mdx +++ b/sdk/flutter.mdx @@ -604,6 +604,7 @@ A payment is an in-app event with revenue. Send it with `trackEvent`, and put `a This example sends every payment key. `amount`, `paymentId`, `currency`, and `userId` are required for a payment. ```dart +import 'package:flutter/services.dart'; import 'package:linkrunner/main.dart'; Future trackPurchase() async { @@ -628,8 +629,8 @@ Future trackPurchase() async { }, eventId: 'evt_order_123', // Optional: your own ID for this event ); - } on ArgumentError catch (e) { - // An invalid payment throws here, and nothing is sent + } on PlatformException catch (e) { + // On Android an invalid payment throws here (code TRACK_EVENT_FAILED), and nothing is sent print('Invalid payment: ${e.message}'); } catch (e) { print('Error tracking payment: $e'); @@ -652,8 +653,8 @@ How `trackEvent` decides: - **`amount`, `paymentId`, and `currency` are all in `eventData`:** the event is a payment. The SDK checks the payment keys and sends them as payment fields. - **None of them, or `amount` or `currency` without `paymentId`:** the event is a normal custom event, exactly as before. An `amount` or `currency` key on its own is ordinary event data. -- **`paymentId` with `amount` or `currency` missing:** `trackEvent` throws `ArgumentError` with `amount, paymentId and currency must be sent together`, and nothing is sent. -- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** `trackEvent` throws `ArgumentError`, and nothing is sent. +- **`paymentId` with `amount` or `currency` missing:** on Android `trackEvent` throws `PlatformException` (code `TRACK_EVENT_FAILED`) with `amount, paymentId and currency must be sent together`; on iOS the payment is dropped and debug builds log the reason. Nothing is sent. +- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** on Android `trackEvent` throws `PlatformException` with the reason; on iOS the payment is dropped and debug builds log it. Nothing is sent. #### Payment status diff --git a/sdk/track-payments.mdx b/sdk/track-payments.mdx index 58c05ab9..4926b270 100644 --- a/sdk/track-payments.mdx +++ b/sdk/track-payments.mdx @@ -204,6 +204,7 @@ Use this when your app learns about a payment only after it succeeds. `status` i ```dart + import 'package:flutter/services.dart'; import 'package:linkrunner/main.dart'; Future onPurchaseSucceeded(String orderId, String userId) async { @@ -226,7 +227,7 @@ Use this when your app learns about a payment only after it succeeds. `status` i 'order_id': orderId, }, ); - } on ArgumentError catch (e) { + } on PlatformException catch (e) { print('Payment not tracked: ${e.message}'); } } @@ -481,6 +482,7 @@ Use this when you want to see started, failed, and cancelled payments next to co ```dart + import 'package:flutter/services.dart'; import 'package:linkrunner/main.dart'; enum CheckoutResult { succeeded, failed, cancelled } @@ -505,7 +507,7 @@ Use this when you want to see started, failed, and cancelled payments next to co 'order_id': orderId, }, ); - } on ArgumentError catch (e) { + } on PlatformException catch (e) { print('Payment not tracked: ${e.message}'); } } @@ -769,6 +771,7 @@ Use this when you already have mappings or affiliate templates for an old paymen ```dart + import 'package:flutter/services.dart'; import 'package:linkrunner/main.dart'; Future onSubscriptionRenewed(String transactionId, String userId) async { @@ -790,7 +793,7 @@ Use this when you already have mappings or affiliate templates for an old paymen 'order_id': transactionId, }, ); - } on ArgumentError catch (e) { + } on PlatformException catch (e) { print('Payment not tracked: ${e.message}'); } } @@ -900,11 +903,11 @@ The SDK validates every payment before sending it. A `paymentId` sent without `a | Android (Kotlin) | `trackEvent` returns a failed `Result` holding an `IllegalArgumentException`. It does not throw. Use `onFailure`. | | Android (Java) | `LinkrunnerJava.getInstance().trackEvent(...)` calls `onError` on the `LinkRunnerCallback` you pass. | | iOS | `trackEvent` does not throw. The payment is dropped, and debug builds print the reason to the console. | -| React Native and Expo | `trackEvent` throws. Wrap it in `try`/`catch`. | -| Flutter | `trackEvent` throws `ArgumentError`. | -| Capacitor | The returned promise rejects. | -| Cordova | The returned promise rejects. | -| Unity | The native SDK rejects the payment, and `LinkrunnerSDK.OnEventTracked` reports `false` with the error. | +| React Native and Expo | On Android, `trackEvent` rejects with the reason. Wrap it in `try`/`catch`. On iOS it does not throw: the payment is dropped and debug builds log the reason. | +| Flutter | On Android, `trackEvent` throws `PlatformException` with code `TRACK_EVENT_FAILED` and the reason as the message. On iOS it does not throw: the payment is dropped and debug builds log the reason. | +| Capacitor | On Android, the returned promise rejects with code `TRACK_EVENT_FAILED`. On iOS it resolves: the payment is dropped and debug builds log the reason. | +| Cordova | On Android, the returned promise rejects. On iOS it resolves: the payment is dropped and debug builds log the reason. | +| Unity | On Android, `LinkrunnerSDK.OnEventTracked` reports `false` with the error. On iOS the payment is dropped and debug builds log the reason. | Validation only proves the payment was well formed. Check the [Events Log](#verify-it-works) to confirm Linkrunner received it. From aca53253da0ebe72faf7675c77e263d95a806bc2 Mon Sep 17 00:00:00 2001 From: chetan Date: Sat, 24 Oct 2026 18:00:04 +0530 Subject: [PATCH 15/15] docs: note that iOS drops an invalid payment without an error Co-Authored-By: Claude Opus 5.5 (1M context) --- sdk/capacitor.mdx | 6 +++--- sdk/cordova.mdx | 6 +++--- sdk/expo.mdx | 2 +- sdk/react-native.mdx | 6 +++--- sdk/unity.mdx | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/sdk/capacitor.mdx b/sdk/capacitor.mdx index 05f9e4d8..00e4dced 100644 --- a/sdk/capacitor.mdx +++ b/sdk/capacitor.mdx @@ -412,7 +412,7 @@ const trackPurchase = async () => { "evt_order_123" // Optional: your own ID for this event ); } catch (error) { - // An invalid payment rejects here, and nothing is sent + // On Android an invalid payment rejects here, and nothing is sent console.error("Error tracking payment:", error); } }; @@ -433,8 +433,8 @@ How `trackEvent` decides: - **`amount`, `paymentId`, and `currency` are all in `eventData`:** the event is a payment. The SDK checks the payment keys and sends them as payment fields. - **None of them, or `amount` or `currency` without `paymentId`:** the event is a normal custom event, exactly as before. An `amount` or `currency` key on its own is ordinary event data. -- **`paymentId` with `amount` or `currency` missing:** The promise returned by `trackEvent` rejects with `amount, paymentId and currency must be sent together`, and nothing is sent. -- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** The promise returned by `trackEvent` rejects, and nothing is sent. +- **`paymentId` with `amount` or `currency` missing:** The promise returned by `trackEvent` rejects with `amount, paymentId and currency must be sent together`, and nothing is sent. On iOS the payment is dropped instead and debug builds log the reason. +- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** The promise returned by `trackEvent` rejects, and nothing is sent. On iOS the payment is dropped instead and debug builds log the reason. #### Payment status diff --git a/sdk/cordova.mdx b/sdk/cordova.mdx index 6acaa5db..c4060fa0 100644 --- a/sdk/cordova.mdx +++ b/sdk/cordova.mdx @@ -403,7 +403,7 @@ linkrunner.trackEvent( }, "evt_order_123" // Optional: your own ID for this event ).catch(function (error) { - // An invalid payment rejects here, and nothing is sent + // On Android an invalid payment rejects here, and nothing is sent console.error("Error tracking payment:", error); }); ``` @@ -423,8 +423,8 @@ How `trackEvent` decides: - **`amount`, `paymentId`, and `currency` are all in `eventData`:** the event is a payment. The SDK checks the payment keys and sends them as payment fields. - **None of them, or `amount` or `currency` without `paymentId`:** the event is a normal custom event, exactly as before. An `amount` or `currency` key on its own is ordinary event data. -- **`paymentId` with `amount` or `currency` missing:** The promise returned by `trackEvent` rejects with `amount, paymentId and currency must be sent together`, and nothing is sent. -- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** The promise returned by `trackEvent` rejects, and nothing is sent. +- **`paymentId` with `amount` or `currency` missing:** The promise returned by `trackEvent` rejects with `amount, paymentId and currency must be sent together`, and nothing is sent. On iOS the payment is dropped instead and debug builds log the reason. +- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** The promise returned by `trackEvent` rejects, and nothing is sent. On iOS the payment is dropped instead and debug builds log the reason. #### Payment status diff --git a/sdk/expo.mdx b/sdk/expo.mdx index cc3c0d40..0be8d941 100644 --- a/sdk/expo.mdx +++ b/sdk/expo.mdx @@ -215,7 +215,7 @@ const trackPurchase = async () => { }, ); } catch (error) { - // An invalid payment throws here, and nothing is sent + // On Android an invalid payment throws here, and nothing is sent console.error("Error tracking payment:", error); } }; diff --git a/sdk/react-native.mdx b/sdk/react-native.mdx index 394e7d63..5e3951dc 100644 --- a/sdk/react-native.mdx +++ b/sdk/react-native.mdx @@ -492,7 +492,7 @@ const trackPurchase = async () => { "evt_order_123", // Optional: your own ID for this event ); } catch (error) { - // An invalid payment throws here, and nothing is sent + // On Android an invalid payment throws here, and nothing is sent console.error("Error tracking payment:", error); } }; @@ -513,8 +513,8 @@ How `trackEvent` decides: - **`amount`, `paymentId`, and `currency` are all in `eventData`:** the event is a payment. The SDK checks the payment keys and sends them as payment fields. - **None of them, or `amount` or `currency` without `paymentId`:** the event is a normal custom event, exactly as before. An `amount` or `currency` key on its own is ordinary event data. -- **`paymentId` with `amount` or `currency` missing:** `trackEvent` throws with `amount, paymentId and currency must be sent together`, and nothing is sent. -- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** `trackEvent` throws, and nothing is sent. +- **`paymentId` with `amount` or `currency` missing:** `trackEvent` throws with `amount, paymentId and currency must be sent together`, and nothing is sent. On iOS the payment is dropped instead and debug builds log the reason. +- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** `trackEvent` throws, and nothing is sent. On iOS the payment is dropped instead and debug builds log the reason. #### Payment status diff --git a/sdk/unity.mdx b/sdk/unity.mdx index 8ffaeafe..4035f2b8 100644 --- a/sdk/unity.mdx +++ b/sdk/unity.mdx @@ -1330,8 +1330,8 @@ How `trackEvent` decides: - **`amount`, `paymentId`, and `currency` are all in `eventData`:** the event is a payment. The SDK checks the payment keys and sends them as payment fields. - **None of them, or `amount` or `currency` without `paymentId`:** the event is a normal custom event, exactly as before. An `amount` or `currency` key on its own is ordinary event data. -- **`paymentId` with `amount` or `currency` missing:** The native SDK rejects the event, and `OnEventTracked` reports `false` with `amount, paymentId and currency must be sent together`, and nothing is sent. -- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** The native SDK rejects the event, and `OnEventTracked` reports `false`, and nothing is sent. +- **`paymentId` with `amount` or `currency` missing:** The native SDK rejects the event, and `OnEventTracked` reports `false` with `amount, paymentId and currency must be sent together`, and nothing is sent. On iOS the payment is dropped instead and debug builds log the reason. +- **A negative or non-numeric `amount`, a blank `paymentId`, a currency that is not 3 letters, or an unknown `status`:** The native SDK rejects the event, and `OnEventTracked` reports `false`, and nothing is sent. On iOS the payment is dropped instead and debug builds log the reason. #### Payment status