npm package: @tagadapay/core-js
Description: Core JavaScript SDK for Tagada Pay with provider-agnostic tokenization, 3DS, and React hooks
Author: Tagada Pay
Latest version: 2.5.4

# @tagadapay/core-js

Provider-agnostic payment tokenization and 3DS authentication SDK for TagadaPay.

## Features

- ✅ **Provider-Agnostic**: Works with BasisTheory, Stripe, Adyen, or any tokenization provider
- ✅ **TagadaToken**: Automatic creation of TagadaPay's standard token format
- ✅ **3DS Authentication**: Complete 3DS flow with session management and challenge handling
- ✅ **SCA Detection**: Automatic detection of Strong Customer Authentication requirements
- ✅ **Airwallex Radar**: Device fingerprint collection and payment resumption for Airwallex
- ✅ **React Hooks**: Optional React integration with `useCardTokenization`, `useThreeds`, and `useAirwallexRadar`
- ✅ **Framework Agnostic**: Core functionality works in any JavaScript environment
- ✅ **TypeScript**: Full type safety and IntelliSense support

## Installation

```bash
npm install @tagadapay/core-js
# or
pnpm install @tagadapay/core-js
```

## Get Your API Keys

Get your TagadaPay API keys from: **[https://app.tagadapay.com/settings/apiKeys](https://app.tagadapay.com/settings/apiKeys)**

You'll need these for backend operations (payment processing, payment instrument creation, etc.). Card tokenization happens client-side and doesn't require API keys.

## Quick Start

### React (Recommended)

```typescript
import { useCardTokenization } from '@tagadapay/core-js/react';

function CheckoutForm() {
  const { tokenizeCard, isLoading, error } = useCardTokenization({
    environment: 'production',
    autoInitialize: true,
  });

  const handleSubmit = async (cardData) => {
    // Get TagadaToken (ready for backend) and rawToken (for UI)
    const { tagadaToken, rawToken } = await tokenizeCard(cardData);

    // Check if 3DS is required (provider-agnostic!)
    if (rawToken.metadata?.auth?.scaRequired) {
      console.log('3DS authentication required');
    }

    // Send to your backend
    await fetch('/api/payment-instruments/create', {
      method: 'POST',
      body: JSON.stringify({ tagadaToken, storeId, customerData }),
    });
  };

  return <form onSubmit={handleSubmit}>{/* Your form */}</form>;
}
```

### Vanilla JavaScript

```typescript
import { Tokenizer, createTagadaToken } from '@tagadapay/core-js';

const tokenizer = new Tokenizer({ environment: 'production' });
await tokenizer.initialize();

// Returns TagadaToken string
const tagadaToken = await tokenizer.tokenizeCard(cardData);

// Or get normalized response
const rawToken = await tokenizer.tokenizeCardRaw(cardData);
const tagadaToken = createTagadaToken(rawToken);
```

## Core Concepts

### TagadaToken

TagadaPay's standard token format that wraps provider-specific tokens:

```json
{
  "type": "card",
  "token": "provider_token_id",
  "provider": "basistheory",
  "nonSensitiveMetadata": {
    "authentication": "sca_required",
    "last4": "4242",
    "bin": "424242",
    "brand": "visa",
    "expiryMonth": 12,
    "expiryYear": 2026
  }
}
```

### SCA Detection (Provider-Agnostic)

```typescript
const { rawToken } = await tokenizeCard(cardData);

// Works with ANY provider (BasisTheory, Stripe, Adyen, etc.)
if (rawToken.metadata?.auth?.scaRequired) {
  // Handle 3DS authentication
}
```

### Provider Pattern

Use the default provider (BasisTheory):

```typescript
const tokenizer = new Tokenizer();
```

Or use a custom provider:

```typescript
import { Tokenizer, StripeProvider } from '@tagadapay/core-js';

const tokenizer = new Tokenizer({
  provider: new StripeProvider({ apiKey: 'pk_...' }),
});
```

## API Reference

### useCardTokenization (React Hook)

```typescript
interface TokenizeCardResult {
  tagadaToken: string; // Base64-encoded TagadaToken (send to backend)
  rawToken: CardTokenResponse; // Normalized token (for UI/SCA checking)
}

const {
  tokenizeCard: (cardData: CardPaymentMethod) => Promise<TokenizeCardResult>,
  tokenizeApplePay: (applePayToken: ApplePayToken) => Promise<ApplePayTokenResponse>,
  tokenizeGooglePay: (googlePayToken: GooglePayToken) => Promise<GooglePayTokenResponse>,
  initialize: () => Promise<void>,
  isLoading: boolean,
  isInitialized: boolean,
  error: string | null,
  clearError: () => void,
} = useCardTokenization(config);
```

### Tokenizer (Core Class)

```typescript
class Tokenizer {
  constructor(config: PaymentSDKConfig & { provider?: ITokenizationProvider });

  // Returns TagadaToken string (ready for backend)
  async tokenizeCard(cardData: CardPaymentMethod): Promise<string>;

  // Returns normalized CardTokenResponse (for advanced usage)
  async tokenizeCardRaw(cardData: CardPaymentMethod): Promise<CardTokenResponse>;

  async tokenizeApplePay(applePayToken: ApplePayToken): Promise<ApplePayTokenResponse>;
  async tokenizeGooglePay(googlePayToken: GooglePayToken): Promise<GooglePayTokenResponse>;
  async initialize(): Promise<void>;
  isReady(): boolean;
  getProvider(): ITokenizationProvider;
}
```

### createTagadaToken / decodeTagadaToken

```typescript
// Create TagadaToken from normalized response
function createTagadaToken(cardTokenResponse: CardTokenResponse, providerName?: string): string;

// Decode TagadaToken
function decodeTagadaToken(tagadaTokenString: string): TagadaToken;
```

### Configuration Utilities

```typescript
// Get tenant ID for Google Pay configuration
import { getGoogleTenantId } from '@tagadapay/core-js';

const tenantId = getGoogleTenantId('development'); // or 'production', 'local'

// Use in Google Pay configuration
const paymentRequest = {
  // ... other config
  allowedPaymentMethods: [{
    tokenizationSpecification: {
      type: 'PAYMENT_GATEWAY',
      parameters: {
        gateway: 'basistheory',
        gatewayMerchantId: tenantId,
      },
    },
  }],
};
```

### useThreeds (React Hook)

```typescript
const {
  createSession: (instrument, options) => Promise<ThreedsSession>,
  startChallenge: (challenge, options?) => Promise<ChallengeCompletion>,
  isLoading: boolean,
  error: ThreedsError | null,
} = useThreeds({
  environment: 'production',
  autoInitialize: true,
});
```

### ThreedsManager (Core Class)

```typescript
import { ThreedsManager } from '@tagadapay/core-js/threeds';

const manager = new ThreedsManager({
  defaultProvider: 'basis_theory',
  providers: {
    basis_theory: { apiKey: 'your-api-key' },
  },
});

const session = await manager.createSession(paymentInstrument, options);
const completion = await manager.startChallenge(challenge);
```

## Complete Example

```typescript
import { useCardTokenization, useThreeds } from '@tagadapay/core-js/react';

function PaymentFlow() {
  // 1. Tokenization
  const { tokenizeCard } = useCardTokenization({
    environment: 'production',
  });

  // 2. 3DS
  const { createSession, startChallenge } = useThreeds({
    environment: 'production',
  });

  const handlePayment = async (cardData) => {
    // Step 1: Tokenize card
    const { tagadaToken, rawToken } = await tokenizeCard(cardData);

    // Step 2: Create payment instrument (server-side)
    const { paymentInstrument } = await fetch('/api/payment-instruments/create', {
      method: 'POST',
      body: JSON.stringify({ tagadaToken, storeId, customerData }),
    }).then((r) => r.json());

    // Step 3: Create 3DS session (if SCA required)
    if (rawToken.metadata?.auth?.scaRequired) {
      const session = await createSession(
        {
          id: paymentInstrument.id,
          token: rawToken.id,
          type: 'card',
          card: {
            expirationMonth: rawToken.data.expiration_month,
            expirationYear: rawToken.data.expiration_year,
            last4: rawToken.data.number?.slice(-4),
          },
        },
        {
          amount: 2999,
          currency: 'USD',
          customerInfo: { name: 'John Doe', email: 'john@example.com' },
        },
      );

      // Persist session to backend
      await fetch('/api/threeds/sessions', {
        method: 'POST',
        body: JSON.stringify({
          provider: session.provider,
          storeId,
          paymentInstrumentId: paymentInstrument.id,
          sessionData: session.metadata?.raw,
        }),
      });
    }

    // Step 4: Process payment (server-side)
    const { payment } = await fetch('/api/payments/process', {
      method: 'POST',
      body: JSON.stringify({
        amount: 2999,
        currency: 'USD',
        storeId,
        paymentInstrumentId: paymentInstrument.id,
      }),
    }).then((r) => r.json());

    // Step 5: Handle 3DS challenge if required (client-side)
    if (payment.requireAction === 'threeds_auth') {
      const { threedsSession } = payment.requireActionData.metadata;

      await startChallenge({
        sessionId: threedsSession.externalSessionId,
        acsChallengeUrl: threedsSession.acsChallengeUrl,
        acsTransactionId: threedsSession.acsTransID,
        threeDSVersion: threedsSession.messageVersion,
      });

      // Poll for final payment status
      const finalPayment = await pollPaymentStatus(payment.id);
      console.log('Payment status:', finalPayment.status);
    }
  };
}
```

## Exports

### Core

```typescript
import {
  // Classes
  Tokenizer,

  // Types
  CardTokenResponse,
  CardPaymentMethod,
  ApplePayToken,
  GooglePayToken,
  PaymentSDKConfig,

  // TagadaToken
  createTagadaToken,
  decodeTagadaToken,
  TagadaToken,

  // Providers
  ITokenizationProvider,
  BasisTheoryProvider,

  // Configuration
  getGoogleTenantId,
} from '@tagadapay/core-js';
```

### React

```typescript
import { useCardTokenization, useThreeds, useAirwallexRadar, TokenizeCardResult } from '@tagadapay/core-js/react';
```

### 3DS

```typescript
import {
  ThreedsManager,
  ThreedsModal,
  ThreedsSession,
  ThreedsChallenge,
  ChallengeCompletion,
  ThreedsError,
  ThreedsErrorCode,
} from '@tagadapay/core-js/threeds';
```

### Airwallex Radar + Payments

```typescript
import {
  // Radar
  AirwallexRadarManager,
  AirwallexRadarError,
  AirwallexRadarErrorCode,

  // General payments
  PaymentsClient,
  PaymentsClientError,
  PaymentsClientErrorCode,

  // Types
  type IAirwallexRadarApiClient,
  type AirwallexRadarManagerConfig,
  type AirwallexRadarActionParams,
  type PaymentContinueResult,
} from '@tagadapay/core-js';
```

## Adding Custom Providers

```typescript
import { ITokenizationProvider, RawTokenResponse } from '@tagadapay/core-js';

export class StripeProvider implements ITokenizationProvider {
  private stripe: any;
  private config: { apiKey: string };

  constructor(config: { apiKey: string }) {
    this.config = config;
  }

  getProviderName(): string {
    return 'Stripe';
  }

  async initialize(): Promise<void> {
    this.stripe = await loadStripe(this.config.apiKey);
  }

  isInitialized(): boolean {
    return !!this.stripe;
  }

  async tokenizeCard(cardData: CardPaymentMethod): Promise<RawTokenResponse> {
    const token = await this.stripe.createToken('card', {
      number: cardData.cardNumber,
      exp_month: /* ... */,
      exp_year: /* ... */,
      cvc: cardData.cvc,
    });
    return token as RawTokenResponse;
  }

  async tokenizeApplePay(applePayToken: ApplePayToken): Promise<RawTokenResponse> {
    // Stripe Apple Pay implementation
  }

  detectScaRequirement(rawResponse: RawTokenResponse): boolean {
    // Stripe-specific SCA detection
    return rawResponse.card?.three_d_secure_usage?.supported === true;
  }
}

// Use it
const tokenizer = new Tokenizer({
  provider: new StripeProvider({ apiKey: 'pk_...' }),
});
```

## Environment Configuration

The SDK automatically selects the correct BasisTheory API key based on environment:

```typescript
// Production
useCardTokenization({ environment: 'production' });
// Uses: process.env.NEXT_PUBLIC_BASIS_THEORY_PUBLIC_API_KEY or embedded prod key

// Development/Local
useCardTokenization({ environment: 'development' });
// Uses: Embedded test key (safe for local dev)
```

## Examples

See the complete working examples:

### `examples/core-js-tokenization`
- Card tokenization with TagadaToken creation
- SCA requirement detection
- Payment instrument creation
- 3DS session management
- Payment processing with 3DS
- Retry 3DS flow
- Backend/frontend separation best practices

### `examples/apple-google-tokenization`
- Apple Pay tokenization
- Google Pay tokenization with tenant ID configuration
- Real TagadaPay API integration
- Step-by-step payment flow

## Guides

- [Airwallex Payments](docs/airwallex-payments.md) — Radar device fingerprint, payment resumption, and full Airwallex flow

## TypeScript

The SDK is written in TypeScript and provides full type definitions:

```typescript
import type {
  CardTokenResponse,
  TagadaToken,
  ThreedsSession,
  ThreedsChallenge,
  TokenizeCardResult,
} from '@tagadapay/core-js';
```

## Browser Support

- Modern browsers (ES2020+)
- React 18+
- TypeScript 5+

## License

MIT

## Support

- GitHub: [tagadapay/tagadapay](https://github.com/tagadapay/tagadapay)
- Documentation: [docs.tagadapay.com](https://docs.tagadapay.com)
- Issues: [GitHub Issues](https://github.com/tagadapay/tagadapay/issues)