InAppify

Discount codes and payment links

Use native getDiscountedPaymentLinks and Web validateDiscountCode with exact offering selection and correct output handling.

Validation alone doesn't purchase, redeem a code or alter cached prices. The server revalidates eligibility and amounts when payment starts.

import com.inappify.sdk.*

suspend fun discountedLinks(client: InappifyV2Client, code: String) {
    when (val result = client.getDiscountedPaymentLinks(code)) {
        is InappifyResult.Failure -> { /* Network/server failure, not invalid code. */ }
        is InappifyResult.Success -> {
            if (result.data.isValid != true) return
            val links = result.data.paymentLinks
            // Open only a destination accepted by client.isPaymentUrlAllowed(url).
        }
    }
}

The only input is the code string. Invalid codes are successful business results with isValid == false; network/database errors are failures. Output includes validity, code, errorCode, percent and eligible paymentLinks. Opening an allowed link continues existing Web payment; it doesn't discount the offering cache.

Flutter: convenience and Result

import 'package:inappify_sdk/inappify_sdk.dart';

Future<Map<String, Object?>> discountAlternatives(
  String code, String selectedOfferingId,
) async {
  // Native links, or Web current offering:
  final current = await InappifySdk.getDiscountedPaymentLinksResult(code);
  // Alternative for a selected Web offering:
  final selected = await InappifySdk.getDiscountedPaymentLinksResult(
    code, offeringIdentifier: selectedOfferingId,
  );
  if (!selected.isSuccess || selected.data?.isValid != true) return {};
  return {
    'current': current.data,
    'links': selected.data?.paymentLinks,
    'discountedOffering': selected.data?.offering,
  };
}

Convenience getDiscountedPaymentLinks(code, offeringIdentifier: ...) returns a nullable model. The optional selector defaults to null and selects an offering only on Web. Android/iOS retain code-only links; passing a selector does not retarget them.

ValidateDiscountCodeData preserves offering, code, isValid, errorCode, paymentLinks. Inspect fields according to platform; success/non-null alone does not mean the code is valid.

Web: discount the placement's selected offering

import type { InAppify } from '@inappifylabs/web-sdk';

export async function discountedPayment(
  client: InAppify,
  code: string,
  savedAttemptId: string,
) {
  const selected = await client.getCurrentOffering('upgrade');
  if (!selected)
    return null;
  const validated = await client.validateDiscountCode(code, selected.identifier);
  if (!validated.discount.isValid)
    return null;
  const packageItem = validated.offering.packages[0];
  if (!packageItem)
    return null;
  return client.purchase({
    package: packageItem,
    discountCode: code,
    idempotencyKey: savedAttemptId,
  }, { redirect: false });
}

Omitted identifier in validateDiscountCode(code) selects current. An explicit identifier validates that offering. Never convert empty/invalid input to omission or retry against default. JS returns discount plus a separately priced offering; don't replace SDK current/base catalog with it.

On Flutter Web, pass selectedOffering.identifier to getDiscountedPaymentLinksResult and use its returned offering/code with createCheckout. Flutter's InappifyPurchaseParams has no discountCode input; displaying a discount doesn't apply it to payment. See the Flutter Web checkout example.

Avoid these mistakes

Don't submit a client-computed discount as server price authority, report server failure as invalid code, substitute another offering, or rebuild the checkout independently from its original package context.

Purchases · Web