diff --git a/go-sdk/client/payment.go b/go-sdk/client/payment.go index 7ce2fa5..6bbdbd9 100644 --- a/go-sdk/client/payment.go +++ b/go-sdk/client/payment.go @@ -12,26 +12,26 @@ import ( // Payment represents a payment information type Payment struct { - PaymentID string `json:"payment_id"` - AccountID string `json:"account_id"` - TokenID string `json:"token_id"` - ProductID string `json:"product_id"` - ProductTokenID string `json:"product_token_id"` - Count int `json:"count"` - Status string `json:"status"` - PayerEmail string `json:"payer_email,omitempty"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - PaidAt string `json:"paid_at,omitempty"` - ClosedAt string `json:"closed_at,omitempty"` - CloseReason string `json:"close_reason,omitempty"` - TransactionHash string `json:"transaction_hash,omitempty"` - BlockNumber int64 `json:"block_number,omitempty"` - GasUsed int64 `json:"gas_used,omitempty"` - GasPrice string `json:"gas_price,omitempty"` - TotalAmount string `json:"total_amount"` - FeeAmount string `json:"fee_amount"` - RecipientAmount string `json:"recipient_amount"` + PaymentID string `json:"payment_id"` + AccountID string `json:"account_id"` + TokenID string `json:"token_id"` + ProductID string `json:"product_id"` + ProductTokenID string `json:"product_token_id"` + Count int `json:"count"` + Status string `json:"status"` + PayerEmail string `json:"payer_email,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + PaidAt string `json:"paid_at,omitempty"` + ClosedAt string `json:"closed_at,omitempty"` + CloseReason string `json:"close_reason,omitempty"` + TransactionHash string `json:"transaction_hash,omitempty"` + BlockNumber int64 `json:"block_number,omitempty"` + GasUsed int64 `json:"gas_used,omitempty"` + GasPrice string `json:"gas_price,omitempty"` + TotalAmount string `json:"total_amount"` + FeeAmount string `json:"fee_amount"` + RecipientAmount string `json:"recipient_amount"` } // ListPaymentsResponse represents the response for listing payments @@ -258,7 +258,6 @@ type PaymentReceiver struct { Rate string `json:"rate"` // percentage } - // ExternalCreatePaymentRequest represents the request for creating an external payment type ExternalCreatePaymentRequest struct { ProductID string `json:"product_id"` @@ -277,7 +276,6 @@ type ExternalCreatePaymentResponse struct { Decimals int `json:"decimals"` } - // ExternalCreatePayment creates a new external payment func (c *Client) ExternalCreatePayment(req *ExternalCreatePaymentRequest) (*ExternalCreatePaymentResponse, error) { reqBody, err := json.Marshal(req) diff --git a/reddio-pay-sdk-ts/.gitignore b/reddio-pay-sdk-ts/.gitignore new file mode 100644 index 0000000..b74335e --- /dev/null +++ b/reddio-pay-sdk-ts/.gitignore @@ -0,0 +1,30 @@ +# Dependencies +node_modules/ + +# Build outputs +dist/ +*.tsbuildinfo + +# Environment files +.env +.env.local +.env.*.local + +# Test configuration (contains API keys) +test-config.js + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* \ No newline at end of file diff --git a/reddio-pay-sdk-ts/README.md b/reddio-pay-sdk-ts/README.md new file mode 100644 index 0000000..e1d43e7 --- /dev/null +++ b/reddio-pay-sdk-ts/README.md @@ -0,0 +1,179 @@ +# Reddio Pay TypeScript SDK + +TypeScript SDK for Reddio Pay services - A complete payment solution for crypto transactions. + +## Features + +- ๐Ÿ” Automatic JWT authentication and token refresh +- ๐Ÿ›๏ธ Product management (create, list, manage digital products) +- ๐Ÿ’ณ Payment processing (create orders, query status) +- ๐Ÿช™ Token support (multi-chain crypto tokens) +- ๐Ÿ“Š Payment analytics and reporting +- ๐Ÿ”” Webhook notifications +- ๐Ÿ“˜ Full TypeScript support with type definitions +- ๐ŸŒ Support for production and development environments + +## Installation + +```bash +npm install reddio-pay-sdk +# or +yarn add reddio-pay-sdk +``` + +## Quick Start + +```typescript +import { ReddioClient } from 'reddio-pay-sdk'; + +// Create client for production environment +const client = new ReddioClient({ + apiKey: 'your-api-key', + environment: 'prod' // or 'dev' for development +}); + +// Initialize the client (authenticate) +await client.initialize(); + +// Get product list +const products = await client.product.listProducts(); +console.log(`Found ${products.length} products`); + +// Clean up when done +client.destroy(); +``` + +## API Reference + +### Client Initialization + +```typescript +// Standard initialization +const client = new ReddioClient({ + apiKey: 'your-api-key', + environment: 'prod', // 'prod' or 'dev' + timeout: 30000 // optional, default 30 seconds +}); + +// Factory methods +const prodClient = ReddioClient.createProd('your-api-key'); +const devClient = ReddioClient.createDev('your-api-key'); +const customClient = ReddioClient.create('https://custom-url.com', 'your-api-key'); +``` + +### Product Management + +```typescript +// List all products +const products = await client.product.listProducts(); + +// Get product by ID +const product = await client.product.getProduct('product_id'); + +// Create new product +const newProduct = await client.product.createProduct({ + name: 'Premium Plan', + description: 'Full access to all features', + amount: '99.00', + currency: 'USD' +}); + +// Add token support to product +await client.product.addProductToken('product_id', 'token_id'); +``` + +### Payment Processing + +```typescript +// Create payment order +const payment = await client.payment.createPayment({ + productId: 'product_id', + receiverEmail: 'customer@example.com', + amount: '99.00', + currency: 'USD', + description: 'Order #12345', + callbackUrl: 'https://your-site.com/callback' +}); + +// Get payment status +const status = await client.payment.getPayment('payment_id'); + +// List payment history +const payments = await client.payment.listPayments({ + limit: 10, + offset: 0 +}); + +// Send payment success notification +await client.payment.sendPaymentSuccessNotification({ + paymentId: 'payment_id', + customMessage: 'Thank you for your purchase!' +}); +``` + +### Token Management + +```typescript +// List available tokens +const tokens = await client.token.listTokens(); + +// Get token details +const token = await client.token.getToken('token_id'); + +// Create new token +const newToken = await client.token.createToken({ + name: 'USDT', + symbol: 'USDT', + chainId: 1, + contractAddress: '0x...' +}); +``` + +### Webhook Configuration + +```typescript +// Update webhook URL +await client.account.updateWebhook('https://your-site.com/webhook'); +``` + +## Error Handling + +```typescript +import { ReddioPayError, AuthenticationError, NetworkError } from 'reddio-pay-sdk'; + +try { + await client.payment.createPayment(data); +} catch (error) { + if (error instanceof AuthenticationError) { + console.error('Authentication failed:', error.message); + } else if (error instanceof NetworkError) { + console.error('Network error:', error.message); + } else { + console.error('Unknown error:', error); + } +} +``` + +## Environment Variables + +Enable debug logging: +```bash +export LOG_LEVEL=debug +``` + +## Examples + +See the [examples](./examples) directory for more detailed usage examples. + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## License + +MIT License - see the [LICENSE](LICENSE) file for details. + +## Support + +- GitHub Issues: [https://github.com/reddio-com/reddio-pay-sdk/issues](https://github.com/reddio-com/reddio-pay-sdk/issues) +- Documentation: [https://docs.reddio.com](https://docs.reddio.com) \ No newline at end of file diff --git a/reddio-pay-sdk-ts/mock-payment-test.js b/reddio-pay-sdk-ts/mock-payment-test.js new file mode 100644 index 0000000..b281f3e --- /dev/null +++ b/reddio-pay-sdk-ts/mock-payment-test.js @@ -0,0 +1,206 @@ +const { ReddioClient, PaymentApi } = require('./dist/index.js'); + +console.log('๐Ÿงช Payment API Mock Test...\n'); + +// Mock HTTP Client +class MockHttpClient { + constructor(config) { + this.config = config; + console.log('๐Ÿ“ก Mock HTTP Client initialized:', config.baseURL); + } + + async initialize() { + console.log('๐Ÿ” Mock client authentication succeeded'); + return Promise.resolve(); + } + + async post(url, data) { + console.log('๐Ÿ“ค Mock POST:', url); + console.log(' Data:', JSON.stringify(data, null, 2)); + + if (url === '/external/payments') { + return { + data: { + payment: { + id: 'payment_mock_123456', + accountId: 'account_mock_001', + productId: data.productId, + receiverId: 'receiver_mock_001', + amount: data.amount, + currency: data.currency, + status: 'pending', + description: data.description, + metadata: data.metadata, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + }, + paymentUrl: 'https://pay.reddio.com/payment/payment_mock_123456' + } + }; + } + + if (url === '/external/payments/success/notify') { + return { + data: { + success: true, + message: 'Payment success notification sent successfully' + } + }; + } + + return { data: {} }; + } + + async get(url) { + console.log('๐Ÿ“ฅ Mock GET:', url); + + if (url.includes('/payments/payment_mock_123456')) { + return { + data: { + id: 'payment_mock_123456', + accountId: 'account_mock_001', + productId: 'test_product_123', + receiverId: 'receiver_mock_001', + amount: '10.00', + currency: 'USD', + status: 'completed', + txHash: '0x123456789abcdef', + description: 'Payment API test order', + metadata: { testCase: 'payment_api_test' }, + createdAt: '2024-10-22T10:00:00Z', + updatedAt: '2024-10-22T10:05:00Z', + receiver: { + id: 'receiver_mock_001', + email: 'test@example.com', + firstName: 'Test', + lastName: 'User', + createdAt: '2024-10-20T10:00:00Z', + updatedAt: '2024-10-20T10:00:00Z' + } + } + }; + } + + if (url.includes('/payments/product/')) { + return { + data: { + payments: [ + { + id: 'payment_mock_123456', + amount: '10.00', + currency: 'USD', + status: 'completed', + createdAt: '2024-10-22T10:00:00Z' + } + ] + } + }; + } + + if (url.includes('/payments/list')) { + return { + data: { + payments: [ + { + id: 'payment_mock_123456', + amount: '10.00', + currency: 'USD', + status: 'completed', + createdAt: '2024-10-22T10:00:00Z' + } + ], + pagination: { + totalCount: 1, + totalPages: 1, + currentPage: 1, + pageSize: 5 + } + } + }; + } + + return { data: {} }; + } + + destroy() { + console.log('๐Ÿงน Mock client resources cleaned up'); + } +} + +async function runMockTest() { + try { + console.log('1๏ธโƒฃ Creating Mock Payment API client...'); + const mockHttpClient = new MockHttpClient({ + baseURL: 'https://api.reddio.com', + apiKey: 'mock-api-key' + }); + + const paymentApi = new PaymentApi(mockHttpClient); + await mockHttpClient.initialize(); + console.log('โœ… Mock client created successfully\n'); + + // Test data + const testData = { + productId: 'test_product_123', + receiverEmail: 'test@example.com', + amount: '10.00', + currency: 'USD', + description: 'Payment API test order', + metadata: { + testCase: 'payment_api_test', + timestamp: new Date().toISOString() + }, + callbackUrl: 'https://example.com/callback' + }; + + console.log('2๏ธโƒฃ Testing create payment order...'); + const createdPayment = await paymentApi.createPayment(testData); + console.log('โœ… Payment order created successfully:'); + console.log(' Payment ID:', createdPayment.payment.id); + console.log(' Payment URL:', createdPayment.payLink); + console.log(' Status:', createdPayment.payment.status); + + console.log('\n3๏ธโƒฃ Testing get payment details...'); + const paymentDetails = await paymentApi.getPayment(createdPayment.payment.id); + console.log('โœ… Get payment details succeeded:'); + console.log(' Payment ID:', paymentDetails.id); + console.log(' Status:', paymentDetails.status); + console.log(' Receiver Email:', paymentDetails.receiver?.email); + + console.log('\n4๏ธโƒฃ Testing get payments by product ID...'); + const productPayments = await paymentApi.listPaymentsByProduct(testData.productId); + console.log('โœ… Get payments by product ID succeeded:'); + console.log(' Number of payments:', productPayments.payments.length); + + console.log('\n5๏ธโƒฃ Testing list all payments with pagination...'); + const allPayments = await paymentApi.listPayments({ limit: 5, offset: 0 }); + console.log('โœ… Get all payments succeeded:'); + console.log(' Number of payments:', allPayments.payments.length); + console.log(' Total count:', allPayments.pagination.totalCount); + + console.log('\n6๏ธโƒฃ Testing send payment success notification...'); + const notifyResult = await paymentApi.sendPaymentSuccessNotification({ + paymentId: createdPayment.payment.id, + customMessage: 'Mock test notification message' + }); + console.log('โœ… Notification sent successfully:'); + console.log(' Success:', notifyResult.success); + console.log(' Message:', notifyResult.message); + + mockHttpClient.destroy(); + + console.log('\n๐ŸŽ‰ All Payment API method tests passed!'); + console.log('\n๐Ÿ“‹ Test results:'); + console.log(' โœ… createPayment - Create payment order'); + console.log(' โœ… getPayment - Get payment details'); + console.log(' โœ… listPaymentsByProduct - Get payments by product ID'); + console.log(' โœ… listPayments - List all payments with pagination'); + console.log(' โœ… sendPaymentSuccessNotification - Send payment success notification'); + + } catch (error) { + console.error('โŒ Mock test failed:', error.message); + console.error(error.stack); + } +} + +runMockTest(); \ No newline at end of file diff --git a/reddio-pay-sdk-ts/package-lock.json b/reddio-pay-sdk-ts/package-lock.json new file mode 100644 index 0000000..0df1c47 --- /dev/null +++ b/reddio-pay-sdk-ts/package-lock.json @@ -0,0 +1,521 @@ +{ + "name": "reddio-pay-sdk-ts", + "version": "1.0.9", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "reddio-pay-sdk-ts", + "version": "1.0.9", + "license": "MIT", + "dependencies": { + "axios": "^1.6.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "ts-node": "^10.9.0", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.23.tgz", + "integrity": "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + } + } +} diff --git a/reddio-pay-sdk-ts/package.json b/reddio-pay-sdk-ts/package.json new file mode 100644 index 0000000..141c85b --- /dev/null +++ b/reddio-pay-sdk-ts/package.json @@ -0,0 +1,45 @@ +{ + "name": "reddio-pay-sdk-ts", + "version": "1.0.10", + "description": "TypeScript SDK for Reddio Pay API - Complete implementation matching Go SDK", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist/", + "README.md", + "LICENSE" + ], + "scripts": { + "build": "tsc", + "prepublishOnly": "npm run build", + "test": "echo \"Tests coming soon\" && exit 0", + "dev": "ts-node src/examples/basic-usage.ts" + }, + "keywords": [ + "reddio", + "payment", + "blockchain", + "crypto", + "sdk", + "typescript", + "web3", + "ethereum" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/your-username/reddio-pay-sdk.git", + "directory": "reddio-pay-sdk-ts" + }, + "engines": { + "node": ">=14.0.0" + }, + "dependencies": { + "axios": "^1.6.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0", + "ts-node": "^10.9.0" + } +} diff --git a/reddio-pay-sdk-ts/src/client/account-api.ts b/reddio-pay-sdk-ts/src/client/account-api.ts new file mode 100644 index 0000000..2264596 --- /dev/null +++ b/reddio-pay-sdk-ts/src/client/account-api.ts @@ -0,0 +1,11 @@ +import { HttpClient } from '../utils/http-client'; +import { UpdateWebhookRequest, UpdateWebhookResponse } from '../types/common'; + +export class AccountApi { + constructor(private httpClient: HttpClient) {} + + async updateWebhook(webhookUrl: string): Promise { + const reqBody: UpdateWebhookRequest = { webhook: webhookUrl }; + return await this.httpClient.put('/accounts/webhook', reqBody); + } +} \ No newline at end of file diff --git a/reddio-pay-sdk-ts/src/client/index.ts b/reddio-pay-sdk-ts/src/client/index.ts new file mode 100644 index 0000000..85c3d3e --- /dev/null +++ b/reddio-pay-sdk-ts/src/client/index.ts @@ -0,0 +1,8 @@ +/** + * Export all client modules + */ + +export { ReddioClient } from './reddio-client'; +export { ProductApi } from './product-api'; +export { TokenApi } from './token-api'; +export { PaymentApi } from './payment-api'; diff --git a/reddio-pay-sdk-ts/src/client/payment-api.ts b/reddio-pay-sdk-ts/src/client/payment-api.ts new file mode 100644 index 0000000..616913a --- /dev/null +++ b/reddio-pay-sdk-ts/src/client/payment-api.ts @@ -0,0 +1,53 @@ +import { HttpClient } from '../utils/http-client'; +import { PaginationOptions } from '../types/common'; +import { + Payment, + ExternalCreatePaymentRequest, + ExternalCreatePaymentResponse, + ListPaymentsResponse, + ListPaymentsResponseWithPagination, + ExternalSendNotifyForPaymentSuccessRequest, + SendNotifyForPaymentSuccessResponse +} from '../types/payment'; + + +export class PaymentApi { + constructor(private httpClient: HttpClient) {} + + async createPayment(request: ExternalCreatePaymentRequest): Promise { + const response = await this.httpClient.post('/external/payments', request); + return response; + } + + + async getPayment(paymentId: string): Promise { + const response = await this.httpClient.get(`/payments/${paymentId}`); + return response; + } + + + async listPaymentsByProduct(productId: string): Promise { + const response = await this.httpClient.get(`/payments/product/${productId}`); + return response; + } + + async listPayments(options: PaginationOptions = {}): Promise { + const params = new URLSearchParams(); + if (options.limit) { + params.append('limit', options.limit.toString()); + } + if (options.offset !== undefined) { + params.append('offset', options.offset.toString()); + } + + const url = `/payments/list${params.toString() ? '?' + params.toString() : ''}`; + const response = await this.httpClient.get(url); + return response; + } + + + async sendPaymentSuccessNotification(request: ExternalSendNotifyForPaymentSuccessRequest): Promise { + const response = await this.httpClient.post('/external/payments/success/notify', request); + return response; + } +} diff --git a/reddio-pay-sdk-ts/src/client/product-api.ts b/reddio-pay-sdk-ts/src/client/product-api.ts new file mode 100644 index 0000000..670d7b0 --- /dev/null +++ b/reddio-pay-sdk-ts/src/client/product-api.ts @@ -0,0 +1,59 @@ +import { HttpClient } from '../utils/http-client'; +import { + Product, + ListProductsResponse, + CreateProductRequest, + CreateProductResponse, + AddProductTokenRequest, + AddProductTokenResponse, + GetProductTokenStatusResponse +} from '../types'; + +/** + * Product API client for managing digital products + */ +export class ProductApi { + constructor(private httpClient: HttpClient) {} + + /** + * Get all products for the authenticated account + */ + async listProducts(): Promise { + const response = await this.httpClient.get('/products'); + return response.products; + } + + /** + * Create a new product + */ + async createProduct(request: CreateProductRequest): Promise { + const response = await this.httpClient.post('/products', request); + return response.product; + } + + /** + * Get product details by ID + */ + async getProduct(productId: string): Promise { + return await this.httpClient.get(`/products/${productId}`); + } + + /** + * Add a token to an existing product + */ + async addProductToken(productId: string, request: AddProductTokenRequest): Promise { + return await this.httpClient.post( + `/products/${productId}/tokens`, + request + ); + } + + /** + * Get product token status and sales information + */ + async getProductTokenStatus(productId: string): Promise { + return await this.httpClient.get( + `/products/${productId}/tokens/status` + ); + } +} diff --git a/reddio-pay-sdk-ts/src/client/reddio-client.ts b/reddio-pay-sdk-ts/src/client/reddio-client.ts new file mode 100644 index 0000000..3bad472 --- /dev/null +++ b/reddio-pay-sdk-ts/src/client/reddio-client.ts @@ -0,0 +1,76 @@ +import { HttpClient } from '../utils/http-client'; +import { ProductApi } from './product-api'; +import { TokenApi } from './token-api'; +import { PaymentApi } from './payment-api'; +import { AccountApi } from './account-api'; + +import { ClientConfig, resolveClientConfig } from '../types/common'; + +/** + * Main Reddio Pay SDK client + */ +export class ReddioClient { + private httpClient: HttpClient; + public readonly product: ProductApi; + public readonly token: TokenApi; + public readonly payment: PaymentApi; + public readonly account: AccountApi; + + constructor(config: ClientConfig) { + const resolvedConfig = resolveClientConfig(config); + + this.httpClient = new HttpClient(resolvedConfig); + this.product = new ProductApi(this.httpClient); + this.token = new TokenApi(this.httpClient); + this.payment = new PaymentApi(this.httpClient); + this.account = new AccountApi(this.httpClient); + } + + /** + * Static factory method: create client for production environment + */ + static createProd(apiKey: string, options?: Partial): ReddioClient { + return new ReddioClient({ + apiKey, + environment: 'prod', + ...options + }); + } + + /** + * Static factory method: create client for development environment + */ + static createDev(apiKey: string, options?: Partial): ReddioClient { + return new ReddioClient({ + apiKey, + environment: 'dev', + ...options + }); + } + + /** + * Static factory method: create client for custom environment + */ + static create(baseURL: string, apiKey: string, options?: Partial): ReddioClient { + return new ReddioClient({ + baseURL, + apiKey, + ...options + }); + } + + /** + * Initialize the client (authenticate and setup) + */ + async initialize(): Promise { + await this.httpClient.initialize(); + } + + /** + * Cleanup and destroy the client + */ + destroy(): void { + this.httpClient.destroy(); + } + +} \ No newline at end of file diff --git a/reddio-pay-sdk-ts/src/client/token-api.ts b/reddio-pay-sdk-ts/src/client/token-api.ts new file mode 100644 index 0000000..2685e47 --- /dev/null +++ b/reddio-pay-sdk-ts/src/client/token-api.ts @@ -0,0 +1,17 @@ +import { HttpClient } from '../utils/http-client'; +import { Token, ListTokensResponse } from '../types'; + +/** + * Token API client for managing supported tokens + */ +export class TokenApi { + constructor(private httpClient: HttpClient) {} + + /** + * Get list of all supported tokens + */ + async listTokens(): Promise { + const response = await this.httpClient.get('/tokens'); + return response.tokens; + } +} diff --git a/reddio-pay-sdk-ts/src/index.ts b/reddio-pay-sdk-ts/src/index.ts new file mode 100644 index 0000000..e6eaf7c --- /dev/null +++ b/reddio-pay-sdk-ts/src/index.ts @@ -0,0 +1,15 @@ +/** + * Main entry point for the Reddio Pay TypeScript SDK + */ + +// Export main client +export { ReddioClient } from './client'; + +// Export all types +export * from './types'; + +// Export utilities +export { HttpClient } from './utils/http-client'; + +// Export API clients for advanced usage +export { ProductApi, TokenApi, PaymentApi } from './client'; diff --git a/reddio-pay-sdk-ts/src/types/common.ts b/reddio-pay-sdk-ts/src/types/common.ts new file mode 100644 index 0000000..df0a2b8 --- /dev/null +++ b/reddio-pay-sdk-ts/src/types/common.ts @@ -0,0 +1,148 @@ +/** + * Common API response interfaces and base types + */ + +/** + * Default endpoint constants + */ +export const REDDIO_ENDPOINTS = { + PRODUCTION: 'https://reddio-service-prod.reddio.com', + DEVELOPMENT: 'https://reddio-service-dev.reddio.com', +} as const; + +export const DEFAULT_CONFIG = { + TIMEOUT: 30000, // 30 seconds default timeout + RETRY_ATTEMPTS: 3, // Default retry attempts + RETRY_DELAY: 1000, // Retry delay (ms) +} as const; + +export type ReddioEnvironment = 'prod' | 'dev'; + +/** + * API response interface + */ +export interface ApiResponse { + message: string; + data?: T; +} + +/** + * Pagination options + */ +export interface PaginationOptions { + limit?: number; + offset?: number; +} + +/** + * Paginated response + */ +export interface PaginatedResponse { + message: string; + data: T[]; + totalCount: number; + totalPages: number; + currentPage: number; + pageSize: number; +} + +/** + * Client config interface - baseURL is now optional + */ +export interface ClientConfig { + apiKey: string; // Required: API key + baseURL?: string; // Optional: custom API base URL + environment?: ReddioEnvironment; // Optional: environment, default 'production' + timeout?: number; // Optional: request timeout, default 30000ms +} + +/** + * Internal resolved config + */ +export interface ResolvedClientConfig { + apiKey: string; + baseURL: string; + timeout: number; +} + +/** + * Auth response (corresponds to Go SDK's LoginByAPIKeyResponse) + */ +export interface AuthResponse { + message: string; + access_token: string; + refresh_token: string; +} + + +export interface UpdateWebhookRequest { + webhook: string; +} + +// TypeScript equivalent of Go's UpdateWebhookResponse +export interface UpdateWebhookResponse { + message: string; +} + +/** + * Custom error classes + */ +export class ReddioPayError extends Error { + constructor( + message: string, + public statusCode?: number, + public code?: string + ) { + super(message); + this.name = 'ReddioPayError'; + } +} + +export class AuthenticationError extends ReddioPayError { + constructor(message: string = 'Authentication failed') { + super(message, 401, 'AUTH_ERROR'); + } +} + +export class NetworkError extends ReddioPayError { + constructor(message: string = 'Network request failed') { + super(message, 0, 'NETWORK_ERROR'); + } +} + +export class ValidationError extends ReddioPayError { + constructor(message: string = 'Validation failed') { + super(message, 400, 'VALIDATION_ERROR'); + } +} + +/** + * Config resolve utility function + */ +export function resolveClientConfig(config: ClientConfig): ResolvedClientConfig { + return { + apiKey: config.apiKey, + baseURL: resolveBaseURL(config), + timeout: config.timeout ?? DEFAULT_CONFIG.TIMEOUT, + }; +} + +/** + * Resolve baseURL - only two environments: prod and dev + */ +function resolveBaseURL(config: ClientConfig): string { + // 1. User explicitly specifies baseURL + if (config.baseURL) { + return config.baseURL; + } + + // 2. Select by environment (only two environments) + switch (config.environment) { + case 'dev': + return REDDIO_ENDPOINTS.DEVELOPMENT; + case 'prod': + default: + return REDDIO_ENDPOINTS.PRODUCTION; // Default to production + } +} + diff --git a/reddio-pay-sdk-ts/src/types/index.ts b/reddio-pay-sdk-ts/src/types/index.ts new file mode 100644 index 0000000..1c50dae --- /dev/null +++ b/reddio-pay-sdk-ts/src/types/index.ts @@ -0,0 +1,8 @@ +/** + * Export all type definitions + */ + +export * from './common'; +export * from './product'; +export * from './token'; +export * from './payment'; diff --git a/reddio-pay-sdk-ts/src/types/payment.ts b/reddio-pay-sdk-ts/src/types/payment.ts new file mode 100644 index 0000000..6479664 --- /dev/null +++ b/reddio-pay-sdk-ts/src/types/payment.ts @@ -0,0 +1,85 @@ +import { PaginationOptions } from './common'; + + +export interface PaymentReceiver { + id: string; + email: string; + firstName: string; + lastName: string; + createdAt: string; + updatedAt: string; +} + + +export interface PaginationMeta { + totalCount: number; + totalPages: number; + currentPage: number; + pageSize: number; +} + + +export interface Payment { + id: string; + accountId: string; + productId: string; + receiverId: string; + amount: string; + currency: string; + status: string; + txHash?: string; + description?: string; + metadata?: Record; + createdAt: string; + updatedAt: string; + receiver?: PaymentReceiver; +} + + +export interface PaymentReceiver { + type: string; // "fee" or "merchant" + recipient_address: string; + amount: string; // wei format + rate: string; // percentage +} + + +export interface ExternalCreatePaymentRequest { + product_id: string; + product_token_id: string; + count: number; +} + + +export interface ExternalCreatePaymentResponse { + message: string; + payment_id: string; + pay_link: string; + contract_address: string; + payment_receivers: PaymentReceiver[]; + token_address: string; + decimals: number; +} + + +export interface ListPaymentsResponse { + payments: Payment[]; +} + + +export interface ListPaymentsResponseWithPagination { + payments: Payment[]; + pagination: PaginationMeta; +} + + +export interface ExternalSendNotifyForPaymentSuccessRequest { + paymentId: string; + customMessage?: string; +} + + +export interface SendNotifyForPaymentSuccessResponse { + success: boolean; + message: string; +} diff --git a/reddio-pay-sdk-ts/src/types/product.ts b/reddio-pay-sdk-ts/src/types/product.ts new file mode 100644 index 0000000..610bff7 --- /dev/null +++ b/reddio-pay-sdk-ts/src/types/product.ts @@ -0,0 +1,70 @@ +/** + * Product related type definitions + */ + +export interface Product { + productId: string; + accountId: string; + name: string; + description?: string; + content: string; + active: boolean; + productTokens: ProductToken[]; + createdAt: string; + totalSaleCount: number; + totalSaleAmount: number; +} + +export interface ProductToken { + productTokenId: string; + productId: string; + accountId: string; + tokenId: string; + price: string; + recipientAddress: string; + paymentRouterAddress: string; + createdAt: string; + chainId: string; + chainName: string; +} + +export interface CreateProductRequest { + name: string; + description?: string; + content: string; + tokenIdList: string[]; + price: string; + recipientAddress: string; +} + +export interface CreateProductResponse { + message: string; + product: Product; +} + +export interface ListProductsResponse { + message: string; + products: Product[]; +} + +export interface AddProductTokenRequest { + tokenId: string; + price: string; + recipientAddress: string; +} + +export interface AddProductTokenResponse { + message: string; + productToken: ProductToken; +} + +export interface GetProductTokenStatusResponse { + message: string; + status: ProductTokenStatus[]; +} + +export interface ProductTokenStatus { + tokenName: string; + totalSaleCount: number; + totalSaleAmount: number; +} diff --git a/reddio-pay-sdk-ts/src/types/token.ts b/reddio-pay-sdk-ts/src/types/token.ts new file mode 100644 index 0000000..c489f8d --- /dev/null +++ b/reddio-pay-sdk-ts/src/types/token.ts @@ -0,0 +1,26 @@ +/** + * Token related type definitions + */ + +export interface Token { + tokenId: string; + name: string; + symbol: string; + contractAddress: string; + decimals: number; + chainId: number; + chainName: string; + chainSymbol: string; + explorerUrl: string; + iconUrl: string; + tokenType: string; + isActive: boolean; + currencyType: string; + createdAt: string; +} + +export interface ListTokensResponse { + message: string; + tokens: Token[]; + count: number; +} diff --git a/reddio-pay-sdk-ts/src/utils/http-client.ts b/reddio-pay-sdk-ts/src/utils/http-client.ts new file mode 100644 index 0000000..eb46727 --- /dev/null +++ b/reddio-pay-sdk-ts/src/utils/http-client.ts @@ -0,0 +1,207 @@ +import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; +import { ResolvedClientConfig, AuthResponse, AuthenticationError, NetworkError } from '../types/common'; + +const LOG_LEVEL = process.env.LOG_LEVEL || 'info'; +function debugLog(...args: any[]) { + if (LOG_LEVEL === 'debug') { + console.log(...args); + } +} + +export class HttpClient { + private axiosInstance: AxiosInstance; + private config: ResolvedClientConfig; + private accessToken?: string; + private refreshTokenValue?: string; + + constructor(config: ResolvedClientConfig) { + this.config = config; + + this.axiosInstance = axios.create({ + baseURL: config.baseURL, + timeout: config.timeout, + headers: { + 'Content-Type': 'application/json', + }, + }); + + this.setupInterceptors(); + } + + private setupInterceptors(): void { + // Request interceptor - debug log + this.axiosInstance.interceptors.request.use( + (config) => { + debugLog('[HTTP REQUEST]'); + debugLog(` Method: ${config.method?.toUpperCase()}`); + debugLog(` URL: ${config.baseURL}${config.url}`); + debugLog(` Headers: ${JSON.stringify(config.headers, null, 2)}`); + if (config.data) { + debugLog(` Body: ${JSON.stringify(config.data, null, 2)}`); + } + if (config.params) { + debugLog(` Query: ${JSON.stringify(config.params, null, 2)}`); + } + + if (this.accessToken) { + config.headers.Authorization = `Bearer ${this.accessToken}`; + debugLog(` Added Authorization: Bearer ${this.accessToken.substring(0, 20)}...`); + } + return config; + }, + (error) => { + debugLog('[HTTP REQUEST ERROR]', error); + return Promise.reject(error); + } + ); + + // Response interceptor - debug log + this.axiosInstance.interceptors.response.use( + (response) => { + debugLog('[HTTP RESPONSE]'); + debugLog(` Status: ${response.status} ${response.statusText}`); + debugLog(` Headers: ${JSON.stringify(response.headers, null, 2)}`); + debugLog(` Body: ${JSON.stringify(response.data, null, 2)}`); + return response; + }, + async (error) => { + debugLog('[HTTP RESPONSE ERROR]'); + if (error.response) { + debugLog(` Status: ${error.response.status} ${error.response.statusText}`); + debugLog(` Headers: ${JSON.stringify(error.response.headers, null, 2)}`); + debugLog(` Body: ${JSON.stringify(error.response.data, null, 2)}`); + } else if (error.request) { + debugLog(` Network error: ${error.message}`); + debugLog(` Request config: ${JSON.stringify(error.config, null, 2)}`); + } else { + debugLog(` Unknown error: ${error.message}`); + } + + if (error.response?.status === 401 && this.refreshTokenValue) { + try { + debugLog('Trying to refresh token...'); + await this.refreshToken(); + debugLog('Retrying original request...'); + return this.axiosInstance.request(error.config); + } catch (refreshError) { + throw new AuthenticationError('Token refresh failed'); + } + } + + if (error.response) { + const statusCode = error.response.status; + const message = error.response.data?.message || error.message; + + if (statusCode === 401) { + throw new AuthenticationError(message); + } else { + throw new Error(`Request failed with status code ${statusCode}: ${message}`); + } + } else if (error.request) { + throw new NetworkError('Network request failed'); + } else { + throw new Error(error.message); + } + } + ); + } + + async initialize(): Promise { + try { + debugLog('[AUTHENTICATION] Start authentication...'); + const response = await this.axiosInstance.post('/accounts/apikeys/login', { + api_key: this.config.apiKey + }); + const data = response.data; + this.accessToken = data.access_token; + this.refreshTokenValue = data.refresh_token; + debugLog('[AUTHENTICATION] Success'); + debugLog(` Access Token: ${this.accessToken?.substring(0, 20)}...`); + debugLog(` Refresh Token: ${this.refreshTokenValue?.substring(0, 20)}...`); + this.startTokenRefresh(); + } catch (error: unknown) { + debugLog('[AUTHENTICATION] Failed'); + if (axios.isAxiosError(error)) { + if (error.response) { + const statusCode = error.response.status; + const message = error.response.data?.message || error.message; + throw new AuthenticationError(`Authentication failed (${statusCode}): ${message}`); + } else { + throw new AuthenticationError(`Authentication failed: ${error.message}`); + } + } else { + const errorMessage = error instanceof Error ? error.message : 'Unknown authentication error'; + throw new AuthenticationError(`Authentication failed: ${errorMessage}`); + } + } + } + + private startTokenRefresh(): void { + debugLog('[TOKEN REFRESH] Start auto refresh (every 55 minutes)'); + setInterval(async () => { + try { + debugLog('[TOKEN REFRESH] Refreshing token...'); + await this.refreshToken(); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown refresh error'; + debugLog('[TOKEN REFRESH] Token refresh failed:', errorMessage); + } + }, 55 * 60 * 1000); + } + + private async refreshToken(): Promise { + try { + debugLog('[TOKEN REFRESH] Start refreshing token...'); + const response = await this.axiosInstance.post('/accounts/apikeys/login', { + api_key: this.config.apiKey + }); + const data = response.data; + this.accessToken = data.access_token; + this.refreshTokenValue = data.refresh_token; + debugLog('[TOKEN REFRESH] Token refreshed'); + debugLog(` New Access Token: ${this.accessToken?.substring(0, 20)}...`); + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + const message = error.response?.data?.message || error.message; + throw new Error(`Token refresh failed: ${message}`); + } else { + const errorMessage = error instanceof Error ? error.message : 'Unknown refresh error'; + throw new Error(`Token refresh failed: ${errorMessage}`); + } + } + } + + getBaseURL(): string { + return this.config.baseURL; + } + + async get(url: string, config?: AxiosRequestConfig): Promise { + debugLog(`[API CALL] GET ${url}`); + const response = await this.axiosInstance.get(url, config); + return response.data; + } + + async post(url: string, data?: any, config?: AxiosRequestConfig): Promise { + debugLog(`[API CALL] POST ${url}`); + const response = await this.axiosInstance.post(url, data, config); + return response.data; + } + + async put(url: string, data?: any, config?: AxiosRequestConfig): Promise { + debugLog(`[API CALL] PUT ${url}`); + const response = await this.axiosInstance.put(url, data, config); + return response.data; + } + + async delete(url: string, config?: AxiosRequestConfig): Promise { + debugLog(`[API CALL] DELETE ${url}`); + const response = await this.axiosInstance.delete(url, config); + return response.data; + } + + destroy(): void { + debugLog('[CLEANUP] HttpClient resources cleaned up'); + this.accessToken = undefined; + this.refreshTokenValue = undefined; + } +} \ No newline at end of file diff --git a/reddio-pay-sdk-ts/test-real-api.js b/reddio-pay-sdk-ts/test-real-api.js new file mode 100644 index 0000000..f68afd3 --- /dev/null +++ b/reddio-pay-sdk-ts/test-real-api.js @@ -0,0 +1,155 @@ +const { ReddioClient } = require('./dist/index.js'); + +// Try to load test config, use default if not found +let testConfig; +try { + testConfig = require('./test-config.js'); +} catch (error) { + console.log('โš ๏ธ test-config.js file not found, please create this file and add your API Key'); + console.log(' Example: module.exports = { API_KEY: "your-api-key-here" };'); + process.exit(1); +} + +/** + * Real API Test - with detailed RPC logs + */ +async function testRealAPIWithTimeout() { + console.log('๐ŸŒ Starting real API test (detailed RPC logs)...\n'); + console.log('='.repeat(80)); + + const API_KEY = testConfig.API_KEY; + + if (!API_KEY || API_KEY === 'your-api-key-here') { + console.log('โŒ Please set a valid API Key in test-config.js'); + process.exit(1); + } + + console.log('๐Ÿ”ง Testing production environment...\n'); + + await testEnvironmentWithTimeout({ + apiKey: API_KEY, + environment: 'prod' + }); +} + +async function testEnvironmentWithTimeout(config) { + const client = new ReddioClient(config); + + try { + console.log(`๐Ÿ“ Environment: ${config.environment}`); + console.log('โ”€'.repeat(80)); + + console.log('๐Ÿ”‘ Step 1: Initialize client...'); + await client.initialize(); + console.log('โœ… Client initialized successfully\n'); + console.log('โ”€'.repeat(80)); + + // Test product API + console.log('๐Ÿ“ฆ Step 2: Test product API...'); + await testAPIWithTimeout('Product', async () => { + const products = await client.product.listProducts(); + + if (products && products.products && Array.isArray(products.products)) { + console.log(`โœ… Product list succeeded! Total: ${products.products.length}`); + return products.products.length; + } else if (Array.isArray(products)) { + console.log(`โœ… Product list succeeded! Total: ${products.length}`); + return products.length; + } else { + console.log(`โš ๏ธ Product API returned unexpected format`); + return 'unknown'; + } + }); + console.log('โ”€'.repeat(80)); + + // Test token API + console.log('๐Ÿช™ Step 3: Test token API...'); + await testAPIWithTimeout('Token', async () => { + const tokens = await client.token.listTokens(); + + if (tokens === undefined || tokens === null) { + console.log('โš ๏ธ Token API returned undefined/null'); + return 'empty'; + } else if (tokens && tokens.tokens && Array.isArray(tokens.tokens)) { + console.log(`โœ… Token list succeeded! Total: ${tokens.tokens.length}`); + return tokens.tokens.length; + } else if (Array.isArray(tokens)) { + console.log(`โœ… Token list succeeded! Total: ${tokens.length}`); + return tokens.length; + } else { + console.log(`โš ๏ธ Token API returned unexpected format, type: ${typeof tokens}`); + return 'unknown'; + } + }); + console.log('โ”€'.repeat(80)); + + // Test payment API + console.log('๐Ÿ’ฐ Step 4: Test payment API...'); + await testAPIWithTimeout('Payment', async () => { + const payments = await client.payment.listPayments({ limit: 5 }); + + if (payments === undefined || payments === null) { + console.log('โš ๏ธ Payment API returned undefined/null'); + return 'empty'; + } else if (payments && payments.payments && Array.isArray(payments.payments)) { + console.log(`โœ… Payment list succeeded! Total: ${payments.payments.length}`); + return payments.payments.length; + } else if (Array.isArray(payments)) { + console.log(`โœ… Payment list succeeded! Total: ${payments.length}`); + return payments.length; + } else { + console.log(`โš ๏ธ Payment API returned unexpected format, type: ${typeof payments}`); + return 'unknown'; + } + }); + + } catch (error) { + console.log(`โŒ Test failed: ${error.message}`); + } finally { + console.log('โ”€'.repeat(80)); + console.log('๐Ÿงน Cleaning up client...'); + client.destroy(); + console.log('โœ… Client cleaned up'); + } +} + +async function testAPIWithTimeout(apiName, testFunction) { + const timeout = 15000; // 15 seconds timeout + + try { + console.log(`โฐ Start testing ${apiName} API (${timeout/1000} seconds timeout)...`); + + const result = await Promise.race([ + testFunction(), + new Promise((_, reject) => + setTimeout(() => reject(new Error(`${apiName} API test timeout`)), timeout) + ) + ]); + + console.log(`โœ… ${apiName} API test finished, result: ${result}\n`); + + } catch (error) { + if (error.message.includes('timeout')) { + console.log(`โฐ ${apiName} API test timeout - endpoint may be slow or stuck\n`); + } else { + console.log(`โŒ ${apiName} API test failed: ${error.message}\n`); + } + } +} + +// Run test +if (require.main === module) { + testRealAPIWithTimeout() + .then(() => { + console.log('='.repeat(80)); + console.log('๐ŸŽ‰ Test finished!'); + process.exit(0); // Force exit to prevent hanging + }) + .catch((error) => { + console.log('='.repeat(80)); + console.error('โŒ Test failed:', error); + process.exit(1); + }); +} + +module.exports = { testRealAPIWithTimeout }; \ No newline at end of file diff --git a/reddio-pay-sdk-ts/test-webhook.js b/reddio-pay-sdk-ts/test-webhook.js new file mode 100644 index 0000000..b99d589 --- /dev/null +++ b/reddio-pay-sdk-ts/test-webhook.js @@ -0,0 +1,55 @@ +const { ReddioClient } = require('./dist/index.js'); +const testConfig = require('./test-config.js'); + +async function testUpdateWebhook() { + console.log('--- Test Webhook Start ---'); + console.log('API_KEY:', testConfig.API_KEY); + console.log('Environment:', 'prod'); + const webhookUrl = 'https://example.com/webhook'; + console.log('Webhook URL:', webhookUrl); + + const client = new ReddioClient({ + apiKey: testConfig.API_KEY, + environment: 'prod' + }); + + try { + console.log('Initializing client...'); + await client.initialize(); + console.log('Client initialized.'); + + // ๆฃ€ๆŸฅ HttpClient ๆ˜ฏๅฆๅทฒไฟๅญ˜ token + if (client.httpClient && client.httpClient.accessToken) { + console.log('Access Token:', client.httpClient.accessToken); + } else { + console.log('No access token found after initialization.'); + } + + console.log('Calling updateWebhook...'); + const resp = await client.account.updateWebhook(webhookUrl); + console.log('Webhook update response:', resp); + + // ๆฃ€ๆŸฅๅ“ๅบ”ๅ†…ๅฎน + if (resp && resp.message) { + console.log('Webhook update message:', resp.message); + } else { + console.log('No message in webhook update response.'); + } + } catch (error) { + console.error('Webhook update failed:', error); + if (error.response) { + console.error('Error response data:', error.response.data); + console.error('Error status:', error.response.status); + console.error('Error headers:', error.response.headers); + } + if (error.statusCode) { + console.error('Error statusCode:', error.statusCode); + } + if (error.code) { + console.error('Error code:', error.code); + } + } + console.log('--- Test Webhook End ---'); +} + +testUpdateWebhook(); \ No newline at end of file diff --git a/reddio-pay-sdk-ts/tsconfig.json b/reddio-pay-sdk-ts/tsconfig.json new file mode 100644 index 0000000..c2c3eba --- /dev/null +++ b/reddio-pay-sdk-ts/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020"], + "module": "CommonJS", + "moduleResolution": "node", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +}