@tagadapay/plugin-sdk 1.0.29 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,16 +6,110 @@ A comprehensive SDK for building checkout plugins on the TagadaPay platform. Cre
6
6
 
7
7
  ### Core APIs
8
8
 
9
- - **[useCheckout](./README-useCheckout.md)** - Checkout state management and flow control
10
- - **[setCheckoutInfo](./README-setCheckoutInfo.md)** - Customer information and validation
11
- - **[useOffers](./README-useOffers.md)** - Dynamic pricing and promotional offers
12
- - **[Money utilities](./README-money.md)** - Currency formatting and calculations
13
- - **[URL utilities](./README-urlUtils.md)** - Navigation and routing helpers
9
+ - **[useCheckout](./docs/README-useCheckout.md)** - Checkout state management and flow control
10
+ - **[setCheckoutInfo](./docs/README-setCheckoutInfo.md)** - Customer information and validation
11
+ - **[useOffers](./docs/README-useOffers.md)** - Dynamic pricing and promotional offers
12
+ - **[Money utilities](./docs/README-money.md)** - Currency formatting and calculations
13
+ - **[URL utilities](./docs/README-urlUtils.md)** - Navigation and routing helpers
14
+
15
+ ### Plugin Development
16
+
17
+ - **[Plugin Configuration](./docs/PLUGIN_CONFIG.md)** - How to access store context, config, and branding
18
+ - **[Google Autocomplete](./docs/README-google-autocomplete.md)** - Address autocomplete with Google Places API
19
+ - **[ISO Data](./docs/README-iso-data.md)** - Country and region data with Google integration
14
20
 
15
21
  ### Examples
16
22
 
17
23
  - **[Vite Checkout Demo](../checkout-vite)** - Complete checkout implementation
18
24
 
25
+ ## 🏗️ Building a Plugin
26
+
27
+ ### Plugin Structure
28
+
29
+ Every TagadaPay plugin follows this simple structure:
30
+
31
+ ```
32
+ my-plugin/
33
+ ├── plugin.manifest.json # Plugin metadata & routing
34
+ ├── .local.json # Local dev config (auto-injected in production)
35
+ ├── config/ # Optional deployment configs
36
+ │ ├── theme-green.json # Config variant A
37
+ │ └── theme-blue.json # Config variant B
38
+ ├── src/
39
+ │ └── App.tsx # Your plugin code
40
+ └── dist/ # Built plugin files
41
+ ```
42
+
43
+ ### Configuration Flow
44
+
45
+ ```mermaid
46
+ graph TD
47
+ A[🛠️ Local Development] --> B[.local.json]
48
+ A --> C[config/*.json]
49
+ B --> D[usePluginConfig()]
50
+ C --> D
51
+
52
+ E[🚀 Production] --> F[Platform Injection]
53
+ F --> G[HTTP Headers & Meta Tags]
54
+ G --> D
55
+
56
+ D --> H[Your Plugin Component]
57
+
58
+ style A fill:#e1f5fe
59
+ style E fill:#f3e5f5
60
+ style D fill:#fff3e0
61
+ style H fill:#e8f5e8
62
+ ```
63
+
64
+ ### Essential Files
65
+
66
+ #### 1. **`plugin.manifest.json`** - Plugin Metadata
67
+
68
+ ```json
69
+ {
70
+ "pluginId": "my-awesome-plugin",
71
+ "name": "My Awesome Plugin",
72
+ "version": "1.0.0",
73
+ "mode": "direct-mode",
74
+ "router": {
75
+ "basePath": "/",
76
+ "matcher": ".*",
77
+ "excluder": "/checkout"
78
+ }
79
+ }
80
+ ```
81
+
82
+ #### 2. **`.local.json`** - Local Development Context
83
+
84
+ ```json
85
+ {
86
+ "storeId": "store_abc123",
87
+ "accountId": "acc_xyz789",
88
+ "basePath": "/"
89
+ }
90
+ ```
91
+
92
+ > ⚠️ **Auto-managed**: This file is only for local dev. In production, the platform injects this data automatically.
93
+
94
+ #### 3. **`config/my-theme.json`** - Optional Deployment Config
95
+
96
+ ```json
97
+ {
98
+ "configName": "green-theme",
99
+ "branding": {
100
+ "primaryColor": "#059669",
101
+ "companyName": "My Store",
102
+ "logoUrl": "https://example.com/logo.png"
103
+ },
104
+ "features": {
105
+ "enableChat": true,
106
+ "maxItems": 10
107
+ }
108
+ }
109
+ ```
110
+
111
+ > 📝 **Note**: Config can contain any keys you need - the SDK doesn't enforce a specific structure.
112
+
19
113
  ## 🚀 Quick Start
20
114
 
21
115
  ### Installation
@@ -24,27 +118,65 @@ A comprehensive SDK for building checkout plugins on the TagadaPay platform. Cre
24
118
  npm install @tagadapay/plugin-sdk
25
119
  ```
26
120
 
27
- ### Basic Usage
121
+ ### Basic Plugin Setup
28
122
 
29
- ```typescript
30
- import { useCheckout, setCheckoutInfo } from '@tagadapay/plugin-sdk';
123
+ ```tsx
124
+ import React from 'react';
125
+ import {
126
+ TagadaProvider,
127
+ usePluginConfig,
128
+ useGoogleAutocomplete,
129
+ useISOData
130
+ } from '@tagadapay/plugin-sdk/react';
131
+
132
+ function MyPlugin() {
133
+ const { storeId, accountId, basePath, config, loading } = usePluginConfig();
31
134
 
32
- function CheckoutPage() {
33
- const { customer, cart, payment } = useCheckout();
135
+ // Optional: Add address autocomplete
136
+ const { searchPlaces, predictions } = useGoogleAutocomplete({
137
+ apiKey: config?.googleMapsApiKey || 'YOUR_API_KEY'
138
+ });
34
139
 
35
- const handleSubmit = async (data) => {
36
- await setCheckoutInfo({
37
- customer: data.customer,
38
- payment: data.payment
39
- });
40
- };
140
+ // Optional: Add country/region data
141
+ const { countries } = useISOData('en');
142
+
143
+ if (loading) return <div>Loading...</div>;
41
144
 
42
145
  return (
43
- <div>
44
- {/* Your checkout UI */}
146
+ <div style={{ '--primary': config?.branding?.primaryColor }}>
147
+ <h1>Welcome to {config?.branding?.companyName}</h1>
148
+ <p>Store: {storeId}</p>
149
+ <p>Base Path: {basePath}</p>
150
+ <p>Available Countries: {Object.keys(countries).length}</p>
45
151
  </div>
46
152
  );
47
153
  }
154
+
155
+ // Wrap your plugin with TagadaProvider
156
+ function App() {
157
+ return (
158
+ <TagadaProvider>
159
+ <MyPlugin />
160
+ </TagadaProvider>
161
+ );
162
+ }
163
+
164
+ export default App;
165
+ ```
166
+
167
+ ### Development vs Production
168
+
169
+ | Environment | Store/Account ID | Deployment Config | How it Works |
170
+ | -------------- | ---------------- | ----------------- | ------------------ |
171
+ | **Local Dev** | `.local.json` | `config/*.json` | Files on disk |
172
+ | **Production** | HTTP Headers | Meta Tags | Platform injection |
173
+
174
+ ```tsx
175
+ // ✅ ALWAYS use hooks - works in both environments
176
+ const { storeId, accountId, basePath, config } = usePluginConfig();
177
+
178
+ // ❌ NEVER access directly
179
+ // const config = window.__PLUGIN_CONFIG__; // Doesn't exist!
48
180
  ```
49
181
 
50
182
  ## 🎯 Key Features
@@ -0,0 +1,30 @@
1
+ export interface Country {
2
+ code: string;
3
+ name: string;
4
+ iso3?: string;
5
+ numeric?: number;
6
+ uniqueKey?: string;
7
+ }
8
+ export interface State {
9
+ code: string;
10
+ name: string;
11
+ countryCode: string;
12
+ uniqueKey?: string;
13
+ }
14
+ export declare const getCountries: () => Country[];
15
+ export declare const getAllStates: () => State[];
16
+ export declare const getStatesForCountry: (countryCode: string) => State[];
17
+ export declare const findCountryByName: (countryName: string) => Country | null;
18
+ export declare const findRegionByCode: (regionCode: string) => State | null;
19
+ export declare const isValidCountryCode: (countryCode: string) => boolean;
20
+ export declare const isValidStateCode: (countryCode: string, stateCode: string) => boolean;
21
+ export declare const getCountryWithRegions: (countryCode: string) => {
22
+ country: {
23
+ code: any;
24
+ name: any;
25
+ iso3: any;
26
+ numeric: any;
27
+ uniqueKey: string;
28
+ };
29
+ regions: State[];
30
+ } | null;
@@ -0,0 +1,102 @@
1
+ // Import the ISO3166 library API functions
2
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
3
+ // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
4
+ import { getDataSet, reduce } from 'iso3166-2-db';
5
+ // Get the full dataset with default English language
6
+ const worldDatabase = reduce(getDataSet(), 'en');
7
+ // Transform the ISO3166 data into our expected format
8
+ export const getCountries = () => {
9
+ const countries = [];
10
+ Object.keys(worldDatabase).forEach((countryCode) => {
11
+ const countryData = worldDatabase[countryCode];
12
+ countries.push({
13
+ code: countryData.iso, // iso3166-1 alpha-2 code
14
+ name: countryData.name,
15
+ iso3: countryData.iso3, // iso3166-1 alpha-3 code
16
+ numeric: countryData.numeric,
17
+ uniqueKey: `country-${countryData.iso}`,
18
+ });
19
+ });
20
+ // Sort countries alphabetically by name
21
+ return countries.sort((a, b) => a.name.localeCompare(b.name));
22
+ };
23
+ // Get all states/regions for all countries
24
+ export const getAllStates = () => {
25
+ const states = [];
26
+ Object.keys(worldDatabase).forEach((countryCode) => {
27
+ const countryData = worldDatabase[countryCode];
28
+ if (countryData.regions && Array.isArray(countryData.regions)) {
29
+ countryData.regions.forEach((region, index) => {
30
+ states.push({
31
+ code: region.iso, // iso3166-2 code (the part after the dash)
32
+ name: region.name,
33
+ countryCode: countryData.iso,
34
+ uniqueKey: `state-${countryData.iso}-${region.iso}-${index}`,
35
+ });
36
+ });
37
+ }
38
+ });
39
+ // Sort states alphabetically by name
40
+ return states.sort((a, b) => a.name.localeCompare(b.name));
41
+ };
42
+ // Get states for a specific country
43
+ export const getStatesForCountry = (countryCode) => {
44
+ const countryData = worldDatabase[countryCode];
45
+ if (!countryData?.regions || !Array.isArray(countryData.regions)) {
46
+ return [];
47
+ }
48
+ return countryData.regions
49
+ .map((region, index) => ({
50
+ code: region.iso,
51
+ name: region.name,
52
+ countryCode: countryData.iso,
53
+ uniqueKey: `state-${countryData.iso}-${region.iso}-${index}`,
54
+ }))
55
+ .sort((a, b) => a.name.localeCompare(b.name));
56
+ };
57
+ // Find country by name (fuzzy search)
58
+ export const findCountryByName = (countryName) => {
59
+ const countries = getCountries();
60
+ const normalizedSearchName = countryName.toLowerCase().trim();
61
+ // Exact match first
62
+ const exactMatch = countries.find((country) => country.name.toLowerCase() === normalizedSearchName);
63
+ if (exactMatch)
64
+ return exactMatch;
65
+ // Partial match
66
+ const partialMatch = countries.find((country) => country.name.toLowerCase().includes(normalizedSearchName) ||
67
+ normalizedSearchName.includes(country.name.toLowerCase()));
68
+ return partialMatch ?? null;
69
+ };
70
+ // Find region by ISO code (e.g., "US-CA" for California)
71
+ export const findRegionByCode = (regionCode) => {
72
+ const [countryCode, stateCode] = regionCode.split('-');
73
+ if (!countryCode || !stateCode)
74
+ return null;
75
+ const states = getStatesForCountry(countryCode);
76
+ return states.find((state) => state.code === stateCode) ?? null;
77
+ };
78
+ // Validate if a country code exists
79
+ export const isValidCountryCode = (countryCode) => {
80
+ return Object.prototype.hasOwnProperty.call(worldDatabase, countryCode);
81
+ };
82
+ // Validate if a state code exists for a given country
83
+ export const isValidStateCode = (countryCode, stateCode) => {
84
+ const states = getStatesForCountry(countryCode);
85
+ return states.some((state) => state.code === stateCode);
86
+ };
87
+ // Get country info including regions
88
+ export const getCountryWithRegions = (countryCode) => {
89
+ const countryData = worldDatabase[countryCode];
90
+ if (!countryData)
91
+ return null;
92
+ return {
93
+ country: {
94
+ code: countryData.iso,
95
+ name: countryData.name,
96
+ iso3: countryData.iso3,
97
+ numeric: countryData.numeric,
98
+ uniqueKey: `country-${countryData.iso}`,
99
+ },
100
+ regions: getStatesForCountry(countryCode),
101
+ };
102
+ };
@@ -0,0 +1,53 @@
1
+ import { type Country as ISO3166Country, type State as ISO3166State } from '../../data/iso3166';
2
+ export type Country = ISO3166Country;
3
+ export type State = ISO3166State;
4
+ export interface AddressField {
5
+ value: string;
6
+ isValid: boolean;
7
+ error?: string;
8
+ touched?: boolean;
9
+ }
10
+ export interface AddressData {
11
+ firstName: string;
12
+ lastName: string;
13
+ email: string;
14
+ phone: string;
15
+ country: string;
16
+ address1: string;
17
+ address2: string;
18
+ city: string;
19
+ state: string;
20
+ postal: string;
21
+ }
22
+ export interface UseAddressV2Config {
23
+ autoValidate?: boolean;
24
+ enableGooglePlaces?: boolean;
25
+ googlePlacesApiKey?: string;
26
+ countryRestrictions?: string[];
27
+ onFieldsChange?: (data: AddressData) => void;
28
+ debounceConfig?: {
29
+ autoSaveDelay?: number;
30
+ enabled?: boolean;
31
+ };
32
+ initialValues?: Partial<AddressData>;
33
+ }
34
+ export interface UseAddressV2Return {
35
+ fields: Record<keyof AddressData, AddressField>;
36
+ setValue: (field: keyof AddressData, value: string) => void;
37
+ setValues: (values: Partial<AddressData>) => void;
38
+ getValue: (field: keyof AddressData) => string;
39
+ getValues: () => AddressData;
40
+ validateField: (field: keyof AddressData) => boolean;
41
+ validateAll: () => boolean;
42
+ isValid: boolean;
43
+ reset: () => void;
44
+ countries: Country[];
45
+ states: State[];
46
+ getStatesForCountry: (countryCode: string) => State[];
47
+ addressRef: React.RefObject<HTMLInputElement | null>;
48
+ addressInputValue: string;
49
+ setAddressInputValue: (value: string) => void;
50
+ handleFieldChange: (field: keyof AddressData) => (value: string) => void;
51
+ handleFieldBlur: (field: keyof AddressData) => () => void;
52
+ }
53
+ export declare const useAddressV2: (config?: UseAddressV2Config) => UseAddressV2Return;