diff --git a/README.md b/README.md index 909a3ea..c96dd09 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,12 @@ Official SDK for Internet Computer payments with account-based authentication an ## Version History -### v1.3.2 (Latest) +### v1.3.37 (Latest) +- Added debug configuration option for development and troubleshooting +- All console.log statements now respect the debug setting +- Improved logging for transaction flow and API operations + +### v1.3.2 - Initial release with user-based authentication ## Installation @@ -283,11 +288,49 @@ interface IcpayConfig { plugNPlayConfig?: Record; connectedWallet?: ConnectedWallet; actorProvider?: (canisterId: string, idl: any) => ActorSubclass; + + // Debug configuration + debug?: boolean; } ``` +``` **Note:** Either `publishableKey` (public mode) or `secretKey` (private mode) must be provided. +### Debug Configuration + +The SDK includes a debug mode that can be enabled to output detailed console logs for development and troubleshooting: + +```typescript +const icpay = new Icpay({ + publishableKey: 'pk_live_your_key_here', + debug: true // Enable debug logging +}); +``` + +**Debug Mode Features:** +- Detailed logging of all SDK operations +- Transaction flow tracking +- API request/response logging +- Wallet connection debugging +- Balance checking logs + +**Usage:** +- **Development**: Set `debug: true` to see detailed operation logs +- **Production**: Leave `debug` unset or set to `false` (default) to avoid console clutter +- **Troubleshooting**: Enable debug mode to diagnose issues with transactions or wallet connections + +**Example Output:** +``` +[ICPay SDK] constructor { config: {...} } +[ICPay SDK] sendFunds start { request: {...} } +[ICPay SDK] checking balance { ledgerCanisterId: "...", requiredAmount: "..." } +[ICPay SDK] balance ok { actualBalance: "..." } +[ICPay SDK] creating payment intent +[ICPay SDK] payment intent created { paymentIntentId: "...", paymentIntentCode: ... } +[ICPay SDK] sendFunds done { transactionId: ..., status: "completed" } +``` + ## API Reference ### Account Methods diff --git a/examples/debug-example.ts b/examples/debug-example.ts new file mode 100644 index 0000000..6606da7 --- /dev/null +++ b/examples/debug-example.ts @@ -0,0 +1,37 @@ +import { Icpay } from '../src'; + +async function debugExample() { + console.log('=== ICPay SDK Debug Configuration Example ===\n'); + + // Example 1: Debug disabled (default behavior) + console.log('1. Creating SDK with debug: false (default)'); + const sdkNoDebug = new Icpay({ + publishableKey: 'pk_test_example', + debug: false + }); + console.log(' SDK created - no debug messages should appear above\n'); + + // Example 2: Debug enabled + console.log('2. Creating SDK with debug: true'); + const sdkWithDebug = new Icpay({ + publishableKey: 'pk_test_example', + debug: true + }); + console.log(' SDK created - debug messages should appear above\n'); + + // Example 3: Debug not specified (should default to false) + console.log('3. Creating SDK without debug specified'); + const sdkDefault = new Icpay({ + publishableKey: 'pk_test_example' + }); + console.log(' SDK created - no debug messages should appear above\n'); + + console.log('=== Debug Configuration Example Completed ==='); + console.log('\nKey points:'); + console.log('- When debug: false (or not set), no console.log messages are output'); + console.log('- When debug: true, all SDK operations will log detailed information'); + console.log('- This helps with development and troubleshooting without cluttering production logs'); +} + +// Run the example +debugExample().catch(console.error); diff --git a/package.json b/package.json index 9e18e34..650d6ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@icpay/sdk", - "version": "1.3.37", + "version": "1.3.38", "description": "Official icpay SDK for Internet Computer payments", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/index.ts b/src/index.ts index 5b2093e..5e22d21 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,7 +23,7 @@ import { HttpAgent, Actor } from '@dfinity/agent'; import { idlFactory as icpayIdl } from './declarations/icpay_canister_backend/icpay_canister_backend.did.js'; import { idlFactory as ledgerIdl } from './declarations/icrc-ledger/ledger.did.js'; import { Principal } from '@dfinity/principal'; -import { toAccountIdentifier } from './utils'; // We'll add this helper +import { toAccountIdentifier, debugLog } from './utils'; export class Icpay { private config: IcpayConfig; @@ -38,13 +38,15 @@ export class Icpay { private verifiedLedgersCache: { data: VerifiedLedger[] | null; timestamp: number } = { data: null, timestamp: 0 }; constructor(config: IcpayConfig) { - console.log('[ICPay SDK] constructor', { config }); this.config = { environment: 'production', apiUrl: 'https://api.icpay.org', + debug: false, ...config }; + debugLog(this.config.debug || false, 'constructor', { config: this.config }); + // Validate authentication configuration if (!this.config.publishableKey && !this.config.secretKey) { throw new Error('Either publishableKey or secretKey must be provided'); @@ -53,12 +55,12 @@ export class Icpay { this.icHost = config.icHost || 'https://ic0.app'; this.connectedWallet = config.connectedWallet || null; this.actorProvider = config.actorProvider; - console.log('[ICPay SDK] constructor', { connectedWallet: this.connectedWallet, actorProvider: this.actorProvider }); + debugLog(this.config.debug || false, 'constructor', { connectedWallet: this.connectedWallet, actorProvider: this.actorProvider }); // Initialize wallet with connected wallet if provided this.wallet = new IcpayWallet({ connectedWallet: this.connectedWallet }); - console.log('[ICPay SDK] constructor', { connectedWallet: this.connectedWallet }); + debugLog(this.config.debug || false, 'constructor', { connectedWallet: this.connectedWallet }); // Create public API client (always available) this.publicApiClient = axios.create({ @@ -69,7 +71,7 @@ export class Icpay { } }); - console.log('[ICPay SDK] publicApiClient', this.publicApiClient); + debugLog(this.config.debug || false, 'publicApiClient created', this.publicApiClient); // Create private API client (only if secret key is provided) if (this.config.secretKey) { @@ -84,7 +86,7 @@ export class Icpay { }); } - console.log('[ICPay SDK] privateApiClient', this.privateApiClient); + debugLog(this.config.debug || false, 'privateApiClient created', this.privateApiClient); } /** @@ -419,14 +421,14 @@ export class Icpay { */ async sendFunds(request: CreateTransactionRequest): Promise { try { - console.log('[ICPay SDK] sendFunds start', { request }); + debugLog(this.config.debug || false, 'sendFunds start', { request }); // Fetch account info to get accountCanisterId if not provided let accountCanisterId = request.accountCanisterId; if (!accountCanisterId) { - console.log('[ICPay SDK] fetching account info for accountCanisterId'); + debugLog(this.config.debug || false, 'fetching account info for accountCanisterId'); const accountInfo = await this.getAccountInfo(); accountCanisterId = accountInfo.accountCanisterId.toString(); - console.log('[ICPay SDK] accountCanisterId resolved', { accountCanisterId }); + debugLog(this.config.debug || false, 'accountCanisterId resolved', { accountCanisterId }); } // Always use icpayCanisterId as toPrincipal @@ -444,7 +446,7 @@ export class Icpay { // Check balance before sending const requiredAmount = amount; - console.log('[ICPay SDK] checking balance', { ledgerCanisterId, requiredAmount: requiredAmount.toString() }); + debugLog(this.config.debug || false, 'checking balance', { ledgerCanisterId, requiredAmount: requiredAmount.toString() }); // Helper function to make amounts human-readable const formatAmount = (amount: bigint, decimals: number = 8, symbol: string = '') => { @@ -469,7 +471,7 @@ export class Icpay { ledgerCanisterId }); } - console.log('[ICPay SDK] balance ok', { actualBalance: actualBalance.toString() }); + debugLog(this.config.debug || false, 'balance ok', { actualBalance: actualBalance.toString() }); } catch (balanceError) { // If we can't fetch the specific ledger balance, fall back to the old logic throw new IcpayError({ @@ -483,7 +485,7 @@ export class Icpay { let paymentIntentId: string | null = null; let paymentIntentCode: number | null = null; try { - console.log('[ICPay SDK] creating payment intent'); + debugLog(this.config.debug || false, 'creating payment intent'); const intentResp = await this.publicApiClient.post('/sdk/public/payments/intents', { amount: request.amount, ledgerCanisterId, @@ -491,7 +493,7 @@ export class Icpay { }); paymentIntentId = intentResp.data?.paymentIntent?.id || null; paymentIntentCode = intentResp.data?.paymentIntent?.intentCode ?? null; - console.log('[ICPay SDK] payment intent created', { paymentIntentId, paymentIntentCode }); + debugLog(this.config.debug || false, 'payment intent created', { paymentIntentId, paymentIntentCode }); } catch (e) { // Do not proceed without a payment intent // Throw a standardized error so integrators can handle it consistently @@ -509,17 +511,17 @@ export class Icpay { const acctIdNum = parseInt(accountCanisterId); if (!isNaN(acctIdNum) && paymentIntentCode != null) { memo = this.createPackedMemo(acctIdNum, Number(paymentIntentCode)); - console.log('[ICPay SDK] built packed memo', { accountCanisterId: acctIdNum, paymentIntentCode }); + debugLog(this.config.debug || false, 'built packed memo', { accountCanisterId: acctIdNum, paymentIntentCode }); } else if (!isNaN(acctIdNum)) { memo = this.createMemoWithAccountCanisterId(acctIdNum); - console.log('[ICPay SDK] built legacy memo', { accountCanisterId: acctIdNum }); + debugLog(this.config.debug || false, 'built legacy memo', { accountCanisterId: acctIdNum }); } } catch {} let transferResult; if (ledgerCanisterId === 'ryjl3-tyaaa-aaaaa-aaaba-cai') { // ICP Ledger: use ICRC-1 transfer (ICP ledger supports ICRC-1) - console.log('[ICPay SDK] sending ICRC-1 transfer (ICP)'); + debugLog(this.config.debug || false, 'sending ICRC-1 transfer (ICP)'); transferResult = await this.sendFundsToLedger( ledgerCanisterId, toPrincipal, @@ -529,7 +531,7 @@ export class Icpay { ); } else { // ICRC-1 ledgers: use principal directly - console.log('[ICPay SDK] sending ICRC-1 transfer'); + debugLog(this.config.debug || false, 'sending ICRC-1 transfer'); transferResult = await this.sendFundsToLedger( ledgerCanisterId, toPrincipal, @@ -541,13 +543,13 @@ export class Icpay { // Assume transferResult returns a block index or transaction id const blockIndex = transferResult?.Ok?.toString() || transferResult?.blockIndex?.toString() || `temp-${Date.now()}`; - console.log('[ICPay SDK] transfer result', { blockIndex }); + debugLog(this.config.debug || false, 'transfer result', { blockIndex }); // First, notify the canister about the ledger transaction let canisterTransactionId: number; let notifyStatus: any = null; try { - console.log('[ICPay SDK] notifying canister about ledger tx'); + debugLog(this.config.debug || false, 'notifying canister about ledger tx'); const notifyRes: any = await this.notifyLedgerTransaction( this.icpayCanisterId!, ledgerCanisterId, @@ -560,10 +562,10 @@ export class Icpay { canisterTransactionId = parseInt(notifyRes.id, 10); notifyStatus = notifyRes; } - console.log('[ICPay SDK] canister notified', { canisterTransactionId }); + debugLog(this.config.debug || false, 'canister notified', { canisterTransactionId }); } catch (notifyError) { canisterTransactionId = parseInt(blockIndex, 10); - console.log('[ICPay SDK] notify failed, using blockIndex as tx id', { canisterTransactionId }); + debugLog(this.config.debug || false, 'notify failed, using blockIndex as tx id', { canisterTransactionId }); } // Poll for transaction status until completed @@ -573,12 +575,12 @@ export class Icpay { status = { status: notifyStatus.status }; } else { try { - console.log('[ICPay SDK] polling transaction status (public)', { canisterTransactionId }); + debugLog(this.config.debug || false, 'polling transaction status (public)', { canisterTransactionId }); status = await this.pollTransactionStatus(this.icpayCanisterId!, canisterTransactionId, accountCanisterId as string, Number(blockIndex), 2000, 30); - console.log('[ICPay SDK] poll done', { status }); + debugLog(this.config.debug || false, 'poll done', { status }); } catch (e) { status = { status: 'pending' }; - console.log('[ICPay SDK] poll failed, falling back to pending'); + debugLog(this.config.debug || false, 'poll failed, falling back to pending'); } } @@ -614,7 +616,7 @@ export class Icpay { const notifyDelayMs = 1000; for (let attempt = 1; attempt <= maxNotifyAttempts; attempt++) { try { - console.log('[ICPay SDK] notifying API about completion', { attempt, notifyPath, paymentIntentId, canisterTransactionId }); + debugLog(this.config.debug || false, 'notifying API about completion', { attempt, notifyPath, paymentIntentId, canisterTransactionId }); const resp = await notifyClient.post(notifyPath, { paymentIntentId, canisterTxId: canisterTransactionId, @@ -624,7 +626,7 @@ export class Icpay { } catch (e: any) { const status = e?.response?.status; const data = e?.response?.data; - console.log('[ICPay SDK] API notify attempt failed', { attempt, status, data }); + debugLog(this.config.debug || false, 'API notify attempt failed', { attempt, status, data }); // Proactively trigger a transaction sync if we get not found try { await this.triggerTransactionSync(canisterTransactionId); @@ -635,7 +637,7 @@ export class Icpay { } } if (!publicNotify) { - console.log('[ICPay SDK] API notify failed after retries (non-fatal)'); + debugLog(this.config.debug || false, 'API notify failed after retries (non-fatal)'); } } @@ -650,7 +652,7 @@ export class Icpay { payment: publicNotify }; - console.log('[ICPay SDK] sendFunds done', response); + debugLog(this.config.debug || false, 'sendFunds done', response); return response; } catch (error) { // eslint-disable-next-line no-console diff --git a/src/types/index.ts b/src/types/index.ts index dd1ecd6..6e6d588 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -16,6 +16,12 @@ export interface IcpayConfig { * (canisterId: string, idl: any) => ActorSubclass */ actorProvider?: (canisterId: string, idl: any) => ActorSubclass; + + /** + * Optional: Enable debug logging + * When set to true, all console.log messages will be output + */ + debug?: boolean; } export interface ConnectedWallet { diff --git a/src/utils.ts b/src/utils.ts index a290422..9ad84ff 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -19,3 +19,20 @@ export function toAccountIdentifier(principal: Principal, subaccount?: Uint8Arra const hash = createHash('sha224').update(data).digest(); return hash; } + +/** + * Debug logger utility + * Only outputs console.log messages when debug is enabled + * @param debug - Whether debug mode is enabled + * @param message - The message to log + * @param data - Optional data to log + */ +export function debugLog(debug: boolean, message: string, data?: any): void { + if (debug) { + if (data !== undefined) { + console.log(`[ICPay SDK] ${message}`, data); + } else { + console.log(`[ICPay SDK] ${message}`); + } + } +}