@jimrising/easymerchantsdk-react-native 2.3.2 → 2.3.4
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 +5 -59
- package/android/build.gradle +1 -1
- package/ios/Models/Request.swift +202 -36
- package/ios/easymerchantsdk.podspec +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Add the EasyMerchant SDK to your `package.json` under `dependencies`:
|
|
|
19
19
|
|
|
20
20
|
```json
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@jimrising/easymerchantsdk-react-native": "^2.3.
|
|
22
|
+
"@jimrising/easymerchantsdk-react-native": "^2.3.4"
|
|
23
23
|
}
|
|
24
24
|
```
|
|
25
25
|
|
|
@@ -319,61 +319,7 @@ const initializeEnvironment = async () => {
|
|
|
319
319
|
|
|
320
320
|
**Note**: This step is required only for `makePayment` on iOS. Android and `makePaymentV2` do not require environment initialization.
|
|
321
321
|
|
|
322
|
-
### 4.
|
|
323
|
-
|
|
324
|
-
For `makePaymentV2`, obtain a client token from the EasyMerchant payment intent API:
|
|
325
|
-
|
|
326
|
-
```javascript
|
|
327
|
-
const apiUrls = {
|
|
328
|
-
sandbox: 'sandbox-url-here',
|
|
329
|
-
production: 'production-url-here'
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
const handleGetClientToken = async () => {
|
|
333
|
-
try {
|
|
334
|
-
const { apiKey, secretKey } = configMakePayment; // Use credentials from makePayment config
|
|
335
|
-
const apiUrl = apiUrls[configMakePaymentV2.environment];
|
|
336
|
-
const params = {
|
|
337
|
-
amount: 13.53,
|
|
338
|
-
intervals: [ "weekly","monthly"],
|
|
339
|
-
is_recurring: true,
|
|
340
|
-
recurring_start_date: "MM/DD/YYYY",
|
|
341
|
-
allowed_cycles: 2,
|
|
342
|
-
recurring_start_date_type: "fixed"
|
|
343
|
-
};
|
|
344
|
-
|
|
345
|
-
const response = await fetch(apiUrl, {
|
|
346
|
-
method: 'POST',
|
|
347
|
-
headers: {
|
|
348
|
-
'Content-Type': 'application/json',
|
|
349
|
-
'X-Api-Key': apiKey,
|
|
350
|
-
'X-Api-Secret': secretKey
|
|
351
|
-
},
|
|
352
|
-
body: JSON.stringify(params)
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
const data = await response.json();
|
|
356
|
-
if (data.status === true && data.client_token) {
|
|
357
|
-
configMakePaymentV2.clientToken = data.client_token;
|
|
358
|
-
console.log('Client Token:', data.client_token);
|
|
359
|
-
Alert.alert('Success', 'Client token retrieved successfully');
|
|
360
|
-
} else {
|
|
361
|
-
throw new Error(data.message || 'No client_token in response');
|
|
362
|
-
}
|
|
363
|
-
} catch (error) {
|
|
364
|
-
console.error('Get Client Token Error:', error);
|
|
365
|
-
Alert.alert('Error', `Failed to retrieve client token: ${error.message}`);
|
|
366
|
-
}
|
|
367
|
-
};
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
**Notes**:
|
|
371
|
-
- Call `handleGetClientToken` before `makePaymentV2`.
|
|
372
|
-
- The API endpoint varies by environment (`sandbox` or `production`).
|
|
373
|
-
- Use `apiKey` and `secretKey` from `configMakePayment` to authenticate the API request.
|
|
374
|
-
- Include recurring payment parameters if `is_recurring` is `true`.
|
|
375
|
-
|
|
376
|
-
### 5. Process Payments
|
|
322
|
+
### 4. Process Payments
|
|
377
323
|
|
|
378
324
|
#### Using `makePayment` (Direct Payment)
|
|
379
325
|
|
|
@@ -470,7 +416,7 @@ const handlePaymentV2 = async () => {
|
|
|
470
416
|
- **Validation**: Ensures `amount` is positive, at least one payment method is selected, and a `clientToken` is provided for `makePaymentV2`.
|
|
471
417
|
- **Error Handling**: Displays user-friendly alerts for errors.
|
|
472
418
|
|
|
473
|
-
###
|
|
419
|
+
### 5. Android Event Listeners
|
|
474
420
|
|
|
475
421
|
Set up event listeners for Android to handle payment success, status, and errors:
|
|
476
422
|
|
|
@@ -518,7 +464,7 @@ const setupAndroidEventListeners = () => {
|
|
|
518
464
|
|
|
519
465
|
**Note**: Call `setupAndroidEventListeners()` during app initialization and clean up listeners when done (returned function).
|
|
520
466
|
|
|
521
|
-
###
|
|
467
|
+
### 6. Check Payment Status (Android Only)
|
|
522
468
|
|
|
523
469
|
Check the status of a payment on Android:
|
|
524
470
|
|
|
@@ -539,7 +485,7 @@ const checkPaymentStatus = async () => {
|
|
|
539
485
|
};
|
|
540
486
|
```
|
|
541
487
|
|
|
542
|
-
###
|
|
488
|
+
### 7. Payment Reference (iOS Only)
|
|
543
489
|
|
|
544
490
|
Retrieve payment reference details on iOS using a `ref_token`:
|
|
545
491
|
|
package/android/build.gradle
CHANGED
|
@@ -39,7 +39,7 @@ repositories {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
dependencies {
|
|
42
|
-
implementation 'com.app:paysdk:1.6.0.
|
|
42
|
+
implementation 'com.app:paysdk:1.6.0.16'
|
|
43
43
|
implementation 'com.hbb20:ccp:2.7.3'
|
|
44
44
|
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
|
45
45
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1"
|
package/ios/Models/Request.swift
CHANGED
|
@@ -291,11 +291,11 @@ public final class Request: NSObject {
|
|
|
291
291
|
super.init()
|
|
292
292
|
|
|
293
293
|
// Environment must be set by host app
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
294
|
+
if EnvironmentConfig.isEnvironmentSet == false {
|
|
295
|
+
self.initializationErrorMessage = "environment not set"
|
|
296
|
+
Request.notifyValidationError(self.initializationErrorMessage!)
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
299
|
|
|
300
300
|
// ✅ Override amount if clientToken flow is used
|
|
301
301
|
if let token = clientToken, !token.isEmpty {
|
|
@@ -347,34 +347,40 @@ public final class Request: NSObject {
|
|
|
347
347
|
return
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
// ✅
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
350
|
+
// ✅ Handle recurring setup differently for API keys vs clientToken flow
|
|
351
|
+
if let token = clientToken, !token.isEmpty {
|
|
352
|
+
// --- Client Token Flow ---
|
|
353
|
+
if let savedIsRecurring = UserStoreSingleton.shared.isRecurring,
|
|
354
|
+
savedIsRecurring == "1" {
|
|
355
|
+
|
|
356
|
+
self.is_recurring = true
|
|
357
|
+
print("Recurring enabled (clientToken flow) → is_recurring = \(self.is_recurring ?? false)")
|
|
358
|
+
|
|
359
|
+
if let cycles = UserStoreSingleton.shared.allowCycles,
|
|
360
|
+
let num = Int(cycles) {
|
|
361
|
+
self.numOfCycle = num
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if let intervals = UserStoreSingleton.shared.interval {
|
|
365
|
+
let parts = intervals.split(separator: ",").map { String($0).trimmingCharacters(in: .whitespaces) }
|
|
366
|
+
self.recurringIntervals = parts.compactMap { RecurringIntervals(rawValue: $0) }
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if let startType = UserStoreSingleton.shared.startDateType {
|
|
370
|
+
self.recurringStartDateType = RecurringStartDateType(rawValue: startType)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if let startDate = UserStoreSingleton.shared.startDate {
|
|
374
|
+
self.recurringStartDate = startDate
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
self.is_recurring = false
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
// --- API Keys Flow ---
|
|
381
|
+
// Keep whatever was passed from outside (don’t override)
|
|
382
|
+
print("Recurring setup (API keys flow) → is_recurring = \(self.is_recurring ?? false)")
|
|
373
383
|
}
|
|
374
|
-
} else {
|
|
375
|
-
self.is_recurring = false
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
384
|
|
|
379
385
|
// Validate recurring start date not in past
|
|
380
386
|
if let startDateString = recurringStartDate,
|
|
@@ -512,6 +518,136 @@ public final class Request: NSObject {
|
|
|
512
518
|
}
|
|
513
519
|
|
|
514
520
|
//MARK: - Payment Intent Api
|
|
521
|
+
// func paymentIntentApi(completion: @escaping (Bool) -> Void) {
|
|
522
|
+
// guard let serviceURL = URL(string: EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.paymentIntent.path()) else {
|
|
523
|
+
// Request.notifyValidationError("Invalid payment URL.")
|
|
524
|
+
// print("Invalid payment URL for Payment Intent API")
|
|
525
|
+
// completion(false)
|
|
526
|
+
// return
|
|
527
|
+
// }
|
|
528
|
+
//
|
|
529
|
+
// var request = URLRequest(url: serviceURL)
|
|
530
|
+
// request.httpMethod = "POST"
|
|
531
|
+
// request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
532
|
+
// request.addValue(EnvironmentConfig.apiKey ?? "", forHTTPHeaderField: "X-Api-Key")
|
|
533
|
+
// request.addValue(EnvironmentConfig.apiSecret ?? "", forHTTPHeaderField: "X-Api-Secret")
|
|
534
|
+
//
|
|
535
|
+
// // Recurring date validation
|
|
536
|
+
// if let startDateString = recurringStartDate,
|
|
537
|
+
// let startDate = DateFormatter.recurringDateFormatter.date(from: startDateString) {
|
|
538
|
+
// let today = Calendar.current.startOfDay(for: Date())
|
|
539
|
+
// let startDay = Calendar.current.startOfDay(for: startDate)
|
|
540
|
+
//
|
|
541
|
+
// if startDay < today {
|
|
542
|
+
// Request.notifyValidationError("The recurring start date cannot be in the past. Please select today or a future date.")
|
|
543
|
+
// print("Recurring start date validation failed")
|
|
544
|
+
// completion(false)
|
|
545
|
+
// return
|
|
546
|
+
// }
|
|
547
|
+
// } else if recurringStartDate != nil {
|
|
548
|
+
// Request.notifyValidationError("Recurring date format should be dd/MM/yyyy")
|
|
549
|
+
// print("Invalid recurring date format")
|
|
550
|
+
// completion(false)
|
|
551
|
+
// return
|
|
552
|
+
// }
|
|
553
|
+
//
|
|
554
|
+
// let params: [String: Any] = [
|
|
555
|
+
// "amount": ((amount ?? 0) * 100).rounded() / 100,
|
|
556
|
+
// "allowed_cycles": numOfCycle ?? 0,
|
|
557
|
+
// "intervals": recurringIntervals?.map { $0.rawValue } ?? [],
|
|
558
|
+
// "is_recurring": self.is_recurring ?? false,
|
|
559
|
+
// "recurring_start_date": recurringStartDate ?? "",
|
|
560
|
+
// "recurring_start_date_type": recurringStartDateType?.rawValue ?? ""
|
|
561
|
+
// ]
|
|
562
|
+
//
|
|
563
|
+
// print("Payment Intent API Request Params: \(params)")
|
|
564
|
+
//
|
|
565
|
+
// do {
|
|
566
|
+
// request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
|
|
567
|
+
// } catch {
|
|
568
|
+
// Request.notifyValidationError("Failed to encode Payment Intent API data: \(error.localizedDescription)")
|
|
569
|
+
// print("Error encoding Payment Intent API data: \(error)")
|
|
570
|
+
// completion(false)
|
|
571
|
+
// return
|
|
572
|
+
// }
|
|
573
|
+
//
|
|
574
|
+
// let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
575
|
+
// if let error = error {
|
|
576
|
+
// Request.notifyValidationError("Payment Intent API error: \(error.localizedDescription)")
|
|
577
|
+
// print("Payment Intent API error: \(error)")
|
|
578
|
+
// completion(false)
|
|
579
|
+
// return
|
|
580
|
+
// }
|
|
581
|
+
//
|
|
582
|
+
// guard let httpResponse = response as? HTTPURLResponse else {
|
|
583
|
+
// Request.notifyValidationError("Invalid response from Payment Intent API")
|
|
584
|
+
// print("Invalid response from Payment Intent API")
|
|
585
|
+
// completion(false)
|
|
586
|
+
// return
|
|
587
|
+
// }
|
|
588
|
+
//
|
|
589
|
+
// print("Payment Intent API Response Status Code: \(httpResponse.statusCode)")
|
|
590
|
+
//
|
|
591
|
+
// if httpResponse.statusCode == 200 || httpResponse.statusCode == 201 {
|
|
592
|
+
// if let data = data {
|
|
593
|
+
// do {
|
|
594
|
+
// if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
595
|
+
// print("Payment Intent API Response: \(responseObject)")
|
|
596
|
+
// if let clientToken = responseObject["client_token"] as? String {
|
|
597
|
+
// UserStoreSingleton.shared.clientToken = clientToken
|
|
598
|
+
// print("Received clientToken: \(clientToken)")
|
|
599
|
+
// } else {
|
|
600
|
+
// Request.notifyValidationError("No client_token in Payment Intent API response")
|
|
601
|
+
// print("No client_token in Payment Intent API response")
|
|
602
|
+
// completion(false)
|
|
603
|
+
// return
|
|
604
|
+
// }
|
|
605
|
+
// if let paymentIntent = responseObject["payment_intent"] as? String {
|
|
606
|
+
// UserStoreSingleton.shared.paymentIntent = paymentIntent
|
|
607
|
+
// print("Received paymentIntent: \(paymentIntent)")
|
|
608
|
+
// }
|
|
609
|
+
// self.hostedCheckoutsApi { success in
|
|
610
|
+
// completion(success)
|
|
611
|
+
// }
|
|
612
|
+
// return
|
|
613
|
+
// } else {
|
|
614
|
+
// Request.notifyValidationError("Invalid response format from Payment Intent API")
|
|
615
|
+
// print("Invalid response format from Payment Intent API")
|
|
616
|
+
// completion(false)
|
|
617
|
+
// }
|
|
618
|
+
// } catch {
|
|
619
|
+
// Request.notifyValidationError("Failed to parse Payment Intent API response: \(error.localizedDescription)")
|
|
620
|
+
// print("Error parsing Payment Intent API response: \(error)")
|
|
621
|
+
// completion(false)
|
|
622
|
+
// }
|
|
623
|
+
// } else {
|
|
624
|
+
// Request.notifyValidationError("No response data received from Payment Intent API")
|
|
625
|
+
// print("No response data received from Payment Intent API")
|
|
626
|
+
// completion(false)
|
|
627
|
+
// }
|
|
628
|
+
// } else {
|
|
629
|
+
// // Default error message
|
|
630
|
+
// var message = "Payment Intent API failed with status code: \(httpResponse.statusCode)"
|
|
631
|
+
//
|
|
632
|
+
// if let data = data,
|
|
633
|
+
// let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
634
|
+
//
|
|
635
|
+
// // Prefer the "message" key from API response
|
|
636
|
+
// if let msg = responseObj["message"] as? String, !msg.isEmpty {
|
|
637
|
+
// message = "Payment Intent API error: \(msg)"
|
|
638
|
+
// }
|
|
639
|
+
// }
|
|
640
|
+
//
|
|
641
|
+
// // Send back the exact server error
|
|
642
|
+
// Request.notifyValidationError(message)
|
|
643
|
+
// print(message)
|
|
644
|
+
// completion(false)
|
|
645
|
+
// }
|
|
646
|
+
// }
|
|
647
|
+
// task.resume()
|
|
648
|
+
// }
|
|
649
|
+
|
|
650
|
+
// MARK: - Payment Intent Api
|
|
515
651
|
func paymentIntentApi(completion: @escaping (Bool) -> Void) {
|
|
516
652
|
guard let serviceURL = URL(string: EnvironmentConfig.baseURL + EnvironmentConfig.Endpoints.paymentIntent.path()) else {
|
|
517
653
|
Request.notifyValidationError("Invalid payment URL.")
|
|
@@ -527,8 +663,10 @@ public final class Request: NSObject {
|
|
|
527
663
|
request.addValue(EnvironmentConfig.apiSecret ?? "", forHTTPHeaderField: "X-Api-Secret")
|
|
528
664
|
|
|
529
665
|
// Recurring date validation
|
|
666
|
+
var apiStartDate = ""
|
|
530
667
|
if let startDateString = recurringStartDate,
|
|
531
668
|
let startDate = DateFormatter.recurringDateFormatter.date(from: startDateString) {
|
|
669
|
+
|
|
532
670
|
let today = Calendar.current.startOfDay(for: Date())
|
|
533
671
|
let startDay = Calendar.current.startOfDay(for: startDate)
|
|
534
672
|
|
|
@@ -538,6 +676,12 @@ public final class Request: NSObject {
|
|
|
538
676
|
completion(false)
|
|
539
677
|
return
|
|
540
678
|
}
|
|
679
|
+
|
|
680
|
+
// ✅ Convert dd/MM/yyyy -> MM/dd/yyyy for API
|
|
681
|
+
let apiFormatter = DateFormatter()
|
|
682
|
+
apiFormatter.dateFormat = "MM/dd/yyyy"
|
|
683
|
+
apiStartDate = apiFormatter.string(from: startDate)
|
|
684
|
+
|
|
541
685
|
} else if recurringStartDate != nil {
|
|
542
686
|
Request.notifyValidationError("Recurring date format should be dd/MM/yyyy")
|
|
543
687
|
print("Invalid recurring date format")
|
|
@@ -550,7 +694,7 @@ public final class Request: NSObject {
|
|
|
550
694
|
"allowed_cycles": numOfCycle ?? 0,
|
|
551
695
|
"intervals": recurringIntervals?.map { $0.rawValue } ?? [],
|
|
552
696
|
"is_recurring": self.is_recurring ?? false,
|
|
553
|
-
"recurring_start_date":
|
|
697
|
+
"recurring_start_date": apiStartDate, // ✅ Correct format for API
|
|
554
698
|
"recurring_start_date_type": recurringStartDateType?.rawValue ?? ""
|
|
555
699
|
]
|
|
556
700
|
|
|
@@ -587,6 +731,7 @@ public final class Request: NSObject {
|
|
|
587
731
|
do {
|
|
588
732
|
if let responseObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
589
733
|
print("Payment Intent API Response: \(responseObject)")
|
|
734
|
+
|
|
590
735
|
if let clientToken = responseObject["client_token"] as? String {
|
|
591
736
|
UserStoreSingleton.shared.clientToken = clientToken
|
|
592
737
|
print("Received clientToken: \(clientToken)")
|
|
@@ -596,10 +741,12 @@ public final class Request: NSObject {
|
|
|
596
741
|
completion(false)
|
|
597
742
|
return
|
|
598
743
|
}
|
|
744
|
+
|
|
599
745
|
if let paymentIntent = responseObject["payment_intent"] as? String {
|
|
600
746
|
UserStoreSingleton.shared.paymentIntent = paymentIntent
|
|
601
747
|
print("Received paymentIntent: \(paymentIntent)")
|
|
602
748
|
}
|
|
749
|
+
|
|
603
750
|
self.hostedCheckoutsApi { success in
|
|
604
751
|
completion(success)
|
|
605
752
|
}
|
|
@@ -620,14 +767,17 @@ public final class Request: NSObject {
|
|
|
620
767
|
completion(false)
|
|
621
768
|
}
|
|
622
769
|
} else {
|
|
770
|
+
// Default error message
|
|
623
771
|
var message = "Payment Intent API failed with status code: \(httpResponse.statusCode)"
|
|
772
|
+
|
|
624
773
|
if let data = data,
|
|
625
774
|
let responseObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
|
626
775
|
let msg = responseObj["message"] as? String, !msg.isEmpty {
|
|
627
|
-
message = msg
|
|
776
|
+
message = "Payment Intent API error: \(msg)"
|
|
628
777
|
}
|
|
778
|
+
|
|
629
779
|
Request.notifyValidationError(message)
|
|
630
|
-
print(
|
|
780
|
+
print(message)
|
|
631
781
|
completion(false)
|
|
632
782
|
}
|
|
633
783
|
}
|
|
@@ -790,7 +940,11 @@ public final class Request: NSObject {
|
|
|
790
940
|
if let isRecurring = recurringDetails["is_recurring"] {
|
|
791
941
|
UserStoreSingleton.shared.isRecurring = "\(isRecurring)"
|
|
792
942
|
print("Saved is_recurring: \(isRecurring)")
|
|
943
|
+
self.is_recurring = (UserStoreSingleton.shared.isRecurring == "1")
|
|
944
|
+
} else {
|
|
945
|
+
self.is_recurring = false
|
|
793
946
|
}
|
|
947
|
+
|
|
794
948
|
if let paymentIntentId = recurringDetails["payment_intent_id"] {
|
|
795
949
|
UserStoreSingleton.shared.paymentIntentId = "\(paymentIntentId)"
|
|
796
950
|
print("Saved payment_intent_id: \(paymentIntentId)")
|
|
@@ -803,8 +957,11 @@ public final class Request: NSObject {
|
|
|
803
957
|
UserStoreSingleton.shared.startDateType = "\(startDateType)"
|
|
804
958
|
print("Saved start_date_type: \(startDateType)")
|
|
805
959
|
}
|
|
960
|
+
} else {
|
|
961
|
+
self.is_recurring = false
|
|
962
|
+
UserStoreSingleton.shared.isRecurring = "0"
|
|
806
963
|
}
|
|
807
|
-
|
|
964
|
+
|
|
808
965
|
if self.appearanceSettings == nil, let appearanceSettings = dataObject["apperance_settings"] as? [String: Any] {
|
|
809
966
|
UserStoreSingleton.shared.body_bg_col = appearanceSettings["body_bg_col"] as? String
|
|
810
967
|
UserStoreSingleton.shared.border_radious = appearanceSettings["border_radious"] as? String
|
|
@@ -920,6 +1077,15 @@ extension DateFormatter {
|
|
|
920
1077
|
}()
|
|
921
1078
|
}
|
|
922
1079
|
|
|
1080
|
+
//extension DateFormatter {
|
|
1081
|
+
// static let recurringDateFormatter: DateFormatter = {
|
|
1082
|
+
// let formatter = DateFormatter()
|
|
1083
|
+
// formatter.dateFormat = "MM/dd/yyyy"
|
|
1084
|
+
// formatter.timeZone = .current
|
|
1085
|
+
// return formatter
|
|
1086
|
+
// }()
|
|
1087
|
+
//}
|
|
1088
|
+
|
|
923
1089
|
|
|
924
1090
|
|
|
925
1091
|
//extension UIApplication {
|