Skip to content

Commit 74a45f4

Browse files
authored
Merge pull request #4 from Dartmind-OpenSource/setup/core-services
chore: Setup core services
2 parents 5b8891e + f605a44 commit 74a45f4

File tree

13 files changed

+269
-112
lines changed

13 files changed

+269
-112
lines changed

lib/src/config/environment.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ enum Environment {
2626
}
2727

2828
Map<String, String> get headers {
29-
final Map<String, String> headers = {
29+
final headers = <String, String>{
3030
'Accept': 'application/json',
3131
'Content-Type': 'application/json',
3232
};

lib/src/config/log_level.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ enum LogLevel {
4343
}
4444

4545
bool canLog(LogLevel minimumLevel) {
46-
return this.priority >= minimumLevel.priority;
46+
return priority >= minimumLevel.priority;
4747
}
4848
}

lib/src/config/mind_paystack_config.dart

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,40 @@ import 'package:mind_paystack/src/ui/theme.dart';
66
import 'package:mind_paystack/src/utils/logger.dart';
77

88
class MindPaystackConfig {
9+
10+
MindPaystackConfig({
11+
required this.publicKey,
12+
Environment environment = Environment.test,
13+
LogLevel logLevel = LogLevel.info,
14+
this.retryPolicy = const RetryPolicy(),
15+
String currency = 'NGN',
16+
String locale = 'en',
17+
MPTheme? theme,
18+
Duration? timeout,
19+
}) : _environment = environment,
20+
_logLevel = logLevel,
21+
_currency = currency,
22+
_locale = locale,
23+
_theme = theme,
24+
timeout = timeout ?? const Duration(seconds: 30) {
25+
_validateConfig();
26+
_initializeLogger();
27+
}
28+
29+
/// Create config from environment variables
30+
factory MindPaystackConfig.fromEnvironment() {
31+
const publicKey = String.fromEnvironment('PAYSTACK_PUBLIC_KEY');
32+
const environment =
33+
String.fromEnvironment('PAYSTACK_ENVIRONMENT', defaultValue: 'test');
34+
const logLevel =
35+
String.fromEnvironment('PAYSTACK_LOG_LEVEL', defaultValue: 'info');
36+
37+
return MindPaystackConfig(
38+
publicKey: publicKey,
39+
environment: Environment.fromString(environment),
40+
logLevel: LogLevel.fromString(logLevel),
41+
);
42+
}
943
/// API public key from Paystack dashboard
1044
final String publicKey;
1145

@@ -33,25 +67,6 @@ class MindPaystackConfig {
3367
/// Timeout duration for API requests
3468
final Duration timeout;
3569

36-
MindPaystackConfig({
37-
required this.publicKey,
38-
Environment environment = Environment.test,
39-
LogLevel logLevel = LogLevel.info,
40-
this.retryPolicy = const RetryPolicy(),
41-
String currency = 'NGN',
42-
String locale = 'en',
43-
MPTheme? theme,
44-
Duration? timeout,
45-
}) : _environment = environment,
46-
_logLevel = logLevel,
47-
_currency = currency,
48-
_locale = locale,
49-
_theme = theme,
50-
timeout = timeout ?? const Duration(seconds: 30) {
51-
_validateConfig();
52-
_initializeLogger();
53-
}
54-
5570
/// Environment getter
5671
Environment get environment => _environment;
5772

@@ -114,21 +129,6 @@ class MindPaystackConfig {
114129
MPLogger.info('Custom logger set');
115130
}
116131

117-
/// Create config from environment variables
118-
factory MindPaystackConfig.fromEnvironment() {
119-
const publicKey = String.fromEnvironment('PAYSTACK_PUBLIC_KEY');
120-
const environment =
121-
String.fromEnvironment('PAYSTACK_ENVIRONMENT', defaultValue: 'test');
122-
const logLevel =
123-
String.fromEnvironment('PAYSTACK_LOG_LEVEL', defaultValue: 'info');
124-
125-
return MindPaystackConfig(
126-
publicKey: publicKey,
127-
environment: Environment.fromString(environment),
128-
logLevel: LogLevel.fromString(logLevel),
129-
);
130-
}
131-
132132
/// Validate configuration
133133
void _validateConfig() {
134134
if (publicKey.isEmpty) {
@@ -193,7 +193,7 @@ class MindPaystackConfig {
193193
retryPolicy: retryPolicy ?? this.retryPolicy,
194194
currency: currency ?? this.currency,
195195
locale: locale ?? this.locale,
196-
theme: theme ?? this._theme,
196+
theme: theme ?? _theme,
197197
timeout: timeout ?? this.timeout,
198198
);
199199
}

lib/src/config/retry_policy.dart

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
22
import 'dart:math';
33

44
class RetryPolicy {
5+
6+
const RetryPolicy({
7+
this.maxAttempts = 3,
8+
this.baseDelay = 1000,
9+
this.maxDelay = 10000,
10+
this.backoffFactor = 2.0,
11+
Set<int>? retryStatusCodes,
12+
}) : retryStatusCodes = retryStatusCodes ?? const {408, 500, 502, 503, 504};
13+
14+
/// Create a policy for testing with shorter delays
15+
factory RetryPolicy.testing() {
16+
return const RetryPolicy(
17+
maxAttempts: 2,
18+
baseDelay: 100,
19+
maxDelay: 1000,
20+
backoffFactor: 1.5,
21+
);
22+
}
523
/// Maximum number of retry attempts
624
final int maxAttempts;
725

@@ -17,14 +35,6 @@ class RetryPolicy {
1735
/// Set of status codes that should trigger a retry
1836
final Set<int> retryStatusCodes;
1937

20-
const RetryPolicy({
21-
this.maxAttempts = 3,
22-
this.baseDelay = 1000,
23-
this.maxDelay = 10000,
24-
this.backoffFactor = 2.0,
25-
Set<int>? retryStatusCodes,
26-
}) : retryStatusCodes = retryStatusCodes ?? const {408, 500, 502, 503, 504};
27-
2838
/// Calculate delay for a specific attempt
2939
Duration getDelayForAttempt(int attempt) {
3040
if (attempt <= 0) return Duration.zero;
@@ -40,16 +50,6 @@ class RetryPolicy {
4050
return attempt < maxAttempts && retryStatusCodes.contains(statusCode);
4151
}
4252

43-
/// Create a policy for testing with shorter delays
44-
factory RetryPolicy.testing() {
45-
return const RetryPolicy(
46-
maxAttempts: 2,
47-
baseDelay: 100,
48-
maxDelay: 1000,
49-
backoffFactor: 1.5,
50-
);
51-
}
52-
5353
RetryPolicy copyWith({
5454
int? maxAttempts,
5555
int? baseDelay,

lib/src/core/exceptions.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
/// Base exception class for Mind Paystack SDK
44
class PaystackException implements Exception {
5+
6+
PaystackException({
7+
required this.message,
8+
required this.code,
9+
this.details,
10+
});
511
/// Human-readable error message
612
final String message;
713

@@ -11,12 +17,6 @@ class PaystackException implements Exception {
1117
/// Additional error details
1218
final Map<String, dynamic>? details;
1319

14-
PaystackException({
15-
required this.message,
16-
required this.code,
17-
this.details,
18-
});
19-
2020
@override
2121
String toString() => 'PaystackException: $message (Code: $code)';
2222
}

lib/src/core/retry_interceptor.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ import 'package:dio/dio.dart';
55
import 'package:mind_paystack/src/config/retry_policy.dart';
66

77
class RetryInterceptor extends Interceptor {
8-
final Dio dio;
9-
final RetryPolicy retryPolicy;
10-
final void Function(String message)? logger;
11-
final _pendingRequests = <String, Completer<void>>{};
128

139
RetryInterceptor({
1410
required this.dio,
1511
required this.retryPolicy,
1612
this.logger,
1713
});
14+
final Dio dio;
15+
final RetryPolicy retryPolicy;
16+
final void Function(String message)? logger;
17+
final _pendingRequests = <String, Completer<void>>{};
1818

1919
@override
2020
Future<void> onError(
@@ -150,6 +150,6 @@ extension RetryDioExtension on Dio {
150150
dio: this,
151151
retryPolicy: policy,
152152
logger: logger,
153-
));
153+
),);
154154
}
155155
}

lib/src/model/bank_model.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
class BankResponse {
2+
BankResponse({
3+
required this.name,
4+
required this.code,
5+
});
6+
7+
/// Factory method to create a Bank instance from JSON
8+
factory BankResponse.fromJson(Map<String, dynamic> json) {
9+
return BankResponse(
10+
name: json['name'] as String,
11+
code: json['code'] as String,
12+
);
13+
}
14+
final String name;
15+
final String code;
16+
17+
/// Converts a Bank instance to JSON
18+
Map<String, dynamic> toJson() {
19+
return {
20+
'name': name,
21+
'code': code,
22+
};
23+
}
24+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import 'package:mind_paystack/src/client/mind_paystack_client.dart';
2+
import 'package:mind_paystack/src/config/retry_policy.dart';
3+
import 'package:mind_paystack/src/model/bank_model.dart';
4+
import 'package:mind_paystack/src/ui/app_api_endpoints.dart';
5+
6+
abstract class BankPaymentService {
7+
Future<Map<String, dynamic>> initializePayment({
8+
required String accountNumber,
9+
required String bankCode,
10+
required double amount,
11+
required String currency,
12+
String? email,
13+
Map<String, dynamic>? metadata,
14+
});
15+
16+
Future<Map<String, dynamic>> verifyPayment(String paymentReference);
17+
18+
Future<Map<String, dynamic>> getPaymentStatus(String paymentId);
19+
20+
Future<List<BankResponse>> fetchBanks();
21+
}
22+
23+
class BankPaymentServiceImpl implements BankPaymentService {
24+
BankPaymentServiceImpl({
25+
required RetryPolicy retryPolicy,
26+
required MindPaystackClient mindPaystackClient,
27+
}) : _mindPaystackClient = mindPaystackClient,
28+
_retryPolicy = retryPolicy;
29+
final MindPaystackClient _mindPaystackClient;
30+
final RetryPolicy _retryPolicy;
31+
32+
@override
33+
Future<Map<String, dynamic>> initializePayment({
34+
required String accountNumber,
35+
required String bankCode,
36+
required double amount,
37+
required String currency,
38+
String? email,
39+
Map<String, dynamic>? metadata,
40+
}) async {
41+
final response = await _mindPaystackClient.post<Map<String, dynamic>>(
42+
AppApiEndpoints.initializePayment,
43+
data: {
44+
'account_number': accountNumber,
45+
'bank_code': bankCode,
46+
'amount': amount,
47+
'currency': currency,
48+
'email': email,
49+
'metadata': metadata,
50+
},
51+
);
52+
return response;
53+
}
54+
55+
@override
56+
Future<Map<String, dynamic>> verifyPayment(String paymentReference) async {
57+
final response = await _mindPaystackClient.get<Map<String, dynamic>>(
58+
AppApiEndpoints.verifyPayment,
59+
queryParams: {'paymentReference': paymentReference},
60+
);
61+
return response;
62+
}
63+
64+
@override
65+
Future<Map<String, dynamic>> getPaymentStatus(String paymentId) async {
66+
final response = await _mindPaystackClient.get<Map<String, dynamic>>(
67+
AppApiEndpoints.getPaymentStatus,
68+
queryParams: {'paymentId': paymentId},
69+
);
70+
71+
return response;
72+
}
73+
74+
@override
75+
Future<List<BankResponse>> fetchBanks() async {
76+
final response = await _mindPaystackClient.get<Map<String, dynamic>>(
77+
AppApiEndpoints.getBanks,
78+
);
79+
final transactions = response['data'] as List<dynamic>;
80+
return transactions
81+
.map(
82+
(json) =>
83+
BankResponse.fromJson(Map<String, dynamic>.from(json as Map)),
84+
)
85+
.toList();
86+
}
87+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import 'package:mind_paystack/src/client/mind_paystack_client.dart';
2+
import 'package:mind_paystack/src/config/retry_policy.dart';
3+
import 'package:mind_paystack/src/ui/app_api_endpoints.dart';
4+
5+
abstract class TransactionService {
6+
Future<Map<String, dynamic>> verifyTransaction(String transactionReference);
7+
8+
Future<Map<String, dynamic>> getTransactionStatus(String transactionId);
9+
}
10+
11+
class TransactionServiceImpl implements TransactionService {
12+
TransactionServiceImpl({
13+
required RetryPolicy retryPolicy,
14+
required MindPaystackClient mindPaystackClient,
15+
}) : _mindPaystackClient = mindPaystackClient,
16+
_retryPolicy = retryPolicy;
17+
final MindPaystackClient _mindPaystackClient;
18+
final RetryPolicy _retryPolicy;
19+
20+
@override
21+
Future<Map<String, dynamic>> getTransactionStatus(
22+
String transactionId,
23+
) async {
24+
final response = await _mindPaystackClient.get<Map<String, dynamic>>(
25+
AppApiEndpoints.getTransactionStatus,
26+
queryParams: {'transactionId': transactionId},
27+
);
28+
29+
return response;
30+
}
31+
32+
@override
33+
Future<Map<String, dynamic>> verifyTransaction(
34+
String transactionReference,
35+
) async {
36+
final response = await _mindPaystackClient.get<Map<String, dynamic>>(
37+
AppApiEndpoints.verifyTransaction,
38+
queryParams: {'transactionReference': transactionReference},
39+
);
40+
return response;
41+
}
42+
}

0 commit comments

Comments
 (0)