diff --git a/.env.local.example b/.env.local.example
new file mode 100644
index 0000000..f987bc5
--- /dev/null
+++ b/.env.local.example
@@ -0,0 +1 @@
+NETCETERA_API_KEY=ta_clé_ici
diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml
index b7938ee..df2530b 100644
--- a/.github/workflows/build-and-release.yml
+++ b/.github/workflows/build-and-release.yml
@@ -6,7 +6,7 @@ on:
jobs:
build:
- runs-on: macos-14
+ runs-on: macos-15
outputs:
version: ${{ steps.get-version.outputs.version }}
should-release: ${{ steps.check-release.outputs.should-release }}
@@ -17,10 +17,10 @@ jobs:
with:
fetch-depth: 0
- - name: Set up Xcode 16.2
+ - name: Set up Xcode 26.2
uses: maxim-lobanov/setup-xcode@v1
with:
- xcode-version: '16.2'
+ xcode-version: '26.2'
- name: Download iOS and iOS Simulator Platforms
run: |
@@ -38,12 +38,12 @@ jobs:
IPHONESIMULATOR_SDK=$(xcrun --sdk iphonesimulator --show-sdk-path)
echo "Device SDK: $IPHONEOS_SDK"
echo "Simulator SDK: $IPHONESIMULATOR_SDK"
-
- if [ ! -d "$IPHONEOS_SDK" ]; then
+
+ if [ ! -d "$IPHONEOS_SDK" ]; then
echo "❌ iOS SDK not found at $IPHONEOS_SDK"
exit 1
fi
-
+
if [ ! -d "$IPHONESIMULATOR_SDK" ]; then
echo "❌ iOS Simulator SDK not found at $IPHONESIMULATOR_SDK"
exit 1
@@ -52,7 +52,7 @@ jobs:
- name: Get version from plist
id: get-version
run: |
- VERSION=$(plutil -extract CFBundleShortVersionString raw Sources/Monext/AppMetadata.plist)
+ VERSION=$(plutil -extract CFBundleShortVersionString raw Sources/Monext/Resources/AppMetadata.plist)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "📦 Version détectée: $VERSION"
@@ -60,10 +60,8 @@ jobs:
id: check-release
run: |
VERSION="${{ steps.get-version.outputs.version }}"
-
- # Vérifier si on est sur master/main
+
if [[ "${{ github.ref }}" == "refs/heads/master" ]] || [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
- # Vérifier si le tag existe déjà
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
echo "should-release=false" >> $GITHUB_OUTPUT
echo "🏷️ Tag v$VERSION existe déjà, pas de release"
@@ -72,22 +70,10 @@ jobs:
echo "✨ Nouvelle version détectée: v$VERSION"
fi
else
- # Pas sur master/main (PR ou autre branche)
echo "should-release=false" >> $GITHUB_OUTPUT
echo "🔧 Branche: ${{ github.ref_name }}, pas de release"
fi
- - name: Configure API Key
- env:
- NETCETERA_API_KEY: ${{ secrets.NETCETERA_API_KEY }}
- run: |
- if [ -z "$NETCETERA_API_KEY" ]; then
- echo "⚠️ NETCETERA_API_KEY secret non configuré"
- exit 1
- fi
- sed -i '' "s/\${NETCETERA_API_KEY}/$NETCETERA_API_KEY/g" Sources/Monext/AppMetadata.plist
- echo "🔑 Clé API configurée"
-
- name: Clean build folders
run: |
rm -rf .build build dist Package.resolved .swiftpm/xcode
@@ -98,7 +84,13 @@ jobs:
swift package show-dependencies
- name: Build XCFramework
+ env:
+ NETCETERA_API_KEY: ${{ secrets.NETCETERA_API_KEY }}
run: |
+ if [ -z "$NETCETERA_API_KEY" ]; then
+ echo "⚠️ NETCETERA_API_KEY secret non configuré"
+ exit 1
+ fi
chmod +x build_xcframework.sh
bash -x build_xcframework.sh
@@ -119,7 +111,7 @@ jobs:
release:
needs: build
- runs-on: macos-latest
+ runs-on: macos-15
if: needs.build.outputs.should-release == 'true'
steps:
@@ -146,30 +138,30 @@ jobs:
fi
cat > release-notes.md << EOF
## 🚀 Monext v$VERSION
-
+
### 📋 Changements
$COMMITS
-
+
### 🔧 Installation
Monext iOS SDK is available via [Swift Package Manager](https://www.swift.org/documentation/package-manager/).
-
+
### Swift Package Manager
-
- 1. **Add the Package Dependency:**
+
+ 1. **Add the Package Dependency:**
Follow Apple's guide on [Adding Package Dependencies to Your App](https://developer.apple.com/documentation/xcode/adding_package_dependencies_to_your_app).
-
+
2. **Enter the Repository URL:**
\`\`\`
https://github.com/Monext/monext-ios-sdk
- \`\`\`
-
- 3. **Specify the Version:**
+ \`\`\`
+
+ 3. **Specify the Version:**
Use version \`$VERSION\` or later.
-
+
### 📦 Contenu de la release
- Monext.xcframework
- Archive zip
-
+
---
**Build Date:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')
**Commit:** \`${{ github.sha }}\`
@@ -178,7 +170,7 @@ jobs:
- name: Create Release and Upload Assets
uses: softprops/action-gh-release@v2
with:
- tag_name: v${{ needs.build.outputs.version }}
+ tag_name: ${{ needs.build.outputs.version }}
name: 🚀 Monext v${{ needs.build.outputs.version }}
body_path: release-notes.md
draft: false
@@ -204,8 +196,7 @@ jobs:
RELEASE_RESULT="${{ needs.release.result }}"
BRANCH="${{ github.ref_name }}"
SHOULD_RELEASE="${{ needs.build.outputs.should-release }}"
-
- # Déterminer le statut global
+
if [[ "$BUILD_RESULT" == "failure" ]]; then
OVERALL_STATUS="failure"
NOTIFICATION_TYPE="Build"
@@ -222,13 +213,12 @@ jobs:
OVERALL_STATUS="unknown"
NOTIFICATION_TYPE="Build"
fi
-
- # Configuration selon le statut
+
if [[ "$OVERALL_STATUS" == "success" ]]; then
ICON_COLOR="Good"
ICON="CheckmarkStarburst"
STATUS_TEXT="✅ Success"
-
+
if [[ "$RELEASE_RESULT" == "success" ]]; then
EXTRA_MESSAGE="Publication sur Github Releases réussie."
RELEASE_URL="https://github.com/${{ github.repository }}/releases/tag/v${VERSION}"
@@ -237,12 +227,9 @@ jobs:
\"title\": \"🚀 Voir la release\",
\"url\": \"${RELEASE_URL}\"
},"
- echo "✅ Release v${VERSION} créée avec succès!"
- echo "🔗 ${RELEASE_URL}"
else
EXTRA_MESSAGE="Build réussi sur la branche ${BRANCH} (pas de release créée)."
RELEASE_ACTION=""
- echo "✅ Build v${VERSION} réussi sur ${BRANCH} (should-release: ${SHOULD_RELEASE})"
fi
elif [[ "$OVERALL_STATUS" == "failure" ]]; then
ICON_COLOR="Attention"
@@ -250,19 +237,14 @@ jobs:
STATUS_TEXT="❌ Échec"
EXTRA_MESSAGE="Échec lors du processus de ${NOTIFICATION_TYPE}."
RELEASE_ACTION=""
-
- echo "❌ Échec du ${NOTIFICATION_TYPE}"
else
ICON_COLOR="Warning"
ICON="Warning"
STATUS_TEXT="⚠️ Statut inconnu"
EXTRA_MESSAGE="Le statut du workflow est indéterminé."
RELEASE_ACTION=""
-
- echo "⚠️ Statut inconnu"
fi
-
- # Envoi de la notification Teams
+
curl -X POST "${TEAMS_WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d @- << EOF
@@ -283,73 +265,31 @@ jobs:
{
"type": "Column",
"width": "auto",
- "height": "stretch",
- "items": [
- {
- "type": "Icon",
- "name": "${ICON}",
- "style": "Filled",
- "color": "${ICON_COLOR}"
- }
- ],
+ "items": [{ "type": "Icon", "name": "${ICON}", "style": "Filled", "color": "${ICON_COLOR}" }],
"spacing": "None"
},
{
"type": "Column",
"width": "stretch",
- "items": [
- {
- "type": "TextBlock",
- "text": "Publication **SDK iOS** - ${STATUS_TEXT}",
- "wrap": true,
- "size": "Large",
- "weight": "Bolder"
- }
- ],
+ "items": [{ "type": "TextBlock", "text": "Publication **SDK iOS** - ${STATUS_TEXT}", "wrap": true, "size": "Large", "weight": "Bolder" }],
"spacing": "Small"
}
],
"spacing": "None"
},
- {
- "type": "TextBlock",
- "text": "_version ${VERSION}_",
- "wrap": true,
- "isSubtle": true,
- "spacing": "Small"
- },
+ { "type": "TextBlock", "text": "_version ${VERSION}_", "wrap": true, "isSubtle": true, "spacing": "Small" },
{
"type": "FactSet",
"facts": [
- {
- "title": "Repository:",
- "value": "${{ github.repository }}"
- },
- {
- "title": "Branch/Tag:",
- "value": "${BRANCH}"
- },
- {
- "title": "Auteur:",
- "value": "${{ github.actor }}"
- },
- {
- "title": "Build Status:",
- "value": "${BUILD_RESULT}"
- },
- {
- "title": "Release Status:",
- "value": "${RELEASE_RESULT}"
- }
+ { "title": "Repository:", "value": "${{ github.repository }}" },
+ { "title": "Branch/Tag:", "value": "${BRANCH}" },
+ { "title": "Auteur:", "value": "${{ github.actor }}" },
+ { "title": "Build Status:", "value": "${BUILD_RESULT}" },
+ { "title": "Release Status:", "value": "${RELEASE_RESULT}" }
],
"separator": true
},
- {
- "type": "TextBlock",
- "text": "${EXTRA_MESSAGE}",
- "wrap": true,
- "separator": true
- }
+ { "type": "TextBlock", "text": "${EXTRA_MESSAGE}", "wrap": true, "separator": true }
],
"actions": [
${RELEASE_ACTION}
@@ -364,10 +304,7 @@ jobs:
]
}
EOF
-
- echo "✅ Notification ${NOTIFICATION_TYPE} (${OVERALL_STATUS}) envoyée avec succès"
-
- # Faire échouer le step si le statut global est en échec
+
if [[ "$OVERALL_STATUS" == "failure" ]]; then
exit 1
fi
diff --git a/.github/workflows/test-and-sonar.yml b/.github/workflows/test-and-sonar.yml
index 4d8139a..f28099d 100644
--- a/.github/workflows/test-and-sonar.yml
+++ b/.github/workflows/test-and-sonar.yml
@@ -21,24 +21,50 @@ jobs:
- name: Create symlink for compatibility
run: |
- mkdir -p ${{ github.workspace }}/../
- ln -s ${{ github.workspace }} ${{ github.workspace }}/../Monext
-
+ mkdir -p ${{ github.workspace }}/../
+ ln -s ${{ github.workspace }} ${{ github.workspace }}/../Monext
+
+ - name: Setup Xcode 26.2
+ uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: '26.2'
+
- name: Install xcresultparser
run: |
brew install xcresultparser
+ - name: Build plugin executable for macOS host
+ run: |
+ xcodebuild build \
+ -scheme InjectSecretsExecutable \
+ -configuration Debug \
+ -destination "platform=macOS,arch=arm64" \
+ -derivedDataPath ~/SharedDerivedData
+
- name: Install SonarQube Scanner
run: |
wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-6.2.1.4610.zip
unzip sonar-scanner-cli-6.2.1.4610.zip
echo "${PWD}/sonar-scanner-6.2.1.4610/bin" >> $GITHUB_PATH
-
+
- name: Execute Coverage
+ env:
+ NETCETERA_API_KEY: "ta_clé_ici"
run: |
set -o pipefail
- xcodebuild test -sdk ${{ matrix.sdk }} -scheme "Monext" -destination '${{ matrix.destination }}' -enableCodeCoverage YES -resultBundlePath sdk.xcresult -quiet
- xcodebuild test -project ./Example/Example.xcodeproj -scheme Example -destination '${{ matrix.destination }}' -enableCodeCoverage YES -resultBundlePath ./example.xcresult
+ xcodebuild test \
+ -sdk ${{ matrix.sdk }} \
+ -scheme "Monext" \
+ -destination '${{ matrix.destination }}' \
+ -enableCodeCoverage YES \
+ -resultBundlePath sdk.xcresult \
+ -derivedDataPath ~/SharedDerivedData
+ xcodebuild test \
+ -project ./Example/Example.xcodeproj \
+ -scheme Example \
+ -destination '${{ matrix.destination }}' \
+ -enableCodeCoverage YES \
+ -resultBundlePath ./example.xcresult
xcrun xcresulttool merge sdk.xcresult example.xcresult --output-path=results.xcresult
xcresultparser -o xml --coverage results.xcresult > sonarqube-generic-coverage.xml
diff --git a/.gitignore b/.gitignore
index f96f838..3ff8de2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -73,3 +73,6 @@ sonarqube-generic-coverage.xml
Example/Config/Secrets.xcconfig
Sources/Monext/AppMetadata.plist
Example/fastlane/report.xml
+/dist
+.env.local
+Sources/Monext/APIConfig.swift
diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Monext.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Monext.xcscheme
index 21c8b6f..2345c8f 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/Monext.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/Monext.xcscheme
@@ -81,4 +81,4 @@
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
-
+
\ No newline at end of file
diff --git a/Example/Example.entitlements b/Example/Example.entitlements
index 2eb6d71..938e10d 100644
--- a/Example/Example.entitlements
+++ b/Example/Example.entitlements
@@ -4,7 +4,7 @@
com.apple.developer.in-app-payments
- merchant.homo.sdk.monext.com
+ merchant.com.payline.prod
diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj
index 1d740c5..1689921 100644
--- a/Example/Example.xcodeproj/project.pbxproj
+++ b/Example/Example.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 5ED276AF2F2A6B90007E91D0 /* Monext in Frameworks */ = {isa = PBXBuildFile; productRef = 5ED276AE2F2A6B90007E91D0 /* Monext */; };
E075B1FD2E0414C400417D6F /* Monext in Frameworks */ = {isa = PBXBuildFile; productRef = E075B1FC2E0414C400417D6F /* Monext */; };
E0A0F2222E69D84600658ED3 /* Monext in Embed Frameworks */ = {isa = PBXBuildFile; productRef = E075B1FC2E0414C400417D6F /* Monext */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
/* End PBXBuildFile section */
@@ -88,6 +89,7 @@
buildActionMask = 2147483647;
files = (
E075B1FD2E0414C400417D6F /* Monext in Frameworks */,
+ 5ED276AF2F2A6B90007E91D0 /* Monext in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -161,6 +163,7 @@
name = Example;
packageProductDependencies = (
E075B1FC2E0414C400417D6F /* Monext */,
+ 5ED276AE2F2A6B90007E91D0 /* Monext */,
);
productName = Example;
productReference = D99B79E32CA4E2B30010B87C /* Example.app */;
@@ -625,6 +628,10 @@
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
+ 5ED276AE2F2A6B90007E91D0 /* Monext */ = {
+ isa = XCSwiftPackageProductDependency;
+ productName = Monext;
+ };
E075B1FC2E0414C400417D6F /* Monext */ = {
isa = XCSwiftPackageProductDependency;
productName = Monext;
diff --git a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
index 4c16703..db9651a 100644
--- a/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/Example/Example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -1,13 +1,13 @@
{
- "originHash" : "d0f40a25e31746a39b3945353beba95b68b3e502a59f689c285cf8b5267b4dc1",
+ "originHash" : "dab88b11e72284b325cdcf8c9f497c4292569dbc29cf275e4d9e674e51887ddd",
"pins" : [
{
"identity" : "spm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ios-3ds-sdk/SPM.git",
"state" : {
- "revision" : "421449969baa6ef80c33fd077c217f724ccfd348",
- "version" : "2.5.30"
+ "revision" : "53d5820988951162d9172c3ea7b2ea4bac4447b9",
+ "version" : "2.6.0"
}
}
],
diff --git a/Package.resolved b/Package.resolved
index a4a5314..d846d32 100644
--- a/Package.resolved
+++ b/Package.resolved
@@ -1,13 +1,13 @@
{
- "originHash" : "8629114665fa2f623453a96b1eedfb885f805145a1b23a20941a8a2f06d2ba2e",
+ "originHash" : "6b0be16ab9fa84b396d800d553c96b1c7e68d119c3d610ee552d5cf43374d0ce",
"pins" : [
{
"identity" : "spm",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ios-3ds-sdk/SPM.git",
"state" : {
- "revision" : "421449969baa6ef80c33fd077c217f724ccfd348",
- "version" : "2.5.30"
+ "revision" : "53d5820988951162d9172c3ea7b2ea4bac4447b9",
+ "version" : "2.6.0"
}
},
{
@@ -15,8 +15,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/nalexn/ViewInspector.git",
"state" : {
- "revision" : "a6fcac8485bc8f57b2d2b55bb6d97138e8659e4b",
- "version" : "0.10.2"
+ "revision" : "e9a06346499a3a889165647e3f23f8a7b2609a1c",
+ "version" : "0.10.3"
}
}
],
diff --git a/Package.swift b/Package.swift
index d11799d..28f11ab 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,11 +1,14 @@
-// swift-tools-version: 6.0
+// swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "Monext",
defaultLocalization: "en",
- platforms: [.iOS(.v16)],
+ platforms: [
+ .iOS(.v16),
+ .macOS(.v13)
+ ],
products: [
.library(
name: "Monext",
@@ -14,22 +17,41 @@ let package = Package(
),
],
dependencies: [
- .package(url: "https://github.com/nalexn/ViewInspector.git", from: "0.9.0"),
- .package(url: "https://github.com/ios-3ds-sdk/SPM.git", exact: "2.5.30")
+ .package(url: "https://github.com/nalexn/ViewInspector.git", from: "0.10.3"),
+ .package(url: "https://github.com/ios-3ds-sdk/SPM.git", exact: "2.6.00")
],
targets: [
+
+ // MARK: - Executable du plugin
+ .executableTarget(
+ name: "InjectSecretsExecutable"
+ ),
+
+ // MARK: - Build plugin
+ .plugin(
+ name: "InjectSecrets",
+ capability: .buildTool(),
+ dependencies: ["InjectSecretsExecutable"]
+ ),
+
+ // MARK: - SDK principal
.target(
name: "Monext",
dependencies: [
.product(name: "ThreeDS_SDK", package: "SPM"),
],
- path: "Sources",
+ path: "Sources/Monext",
resources: [
- .process("Monext/AppMetadata.plist"),
- .process("Monext/Resources/Images.xcassets"),
- .process("Monext/Resources/Localizable.xcstrings")
- ]
+ .process("AppMetadata.plist"),
+ .process("Resources/Images.xcassets"),
+ .process("Resources/Localizable.xcstrings")
+ ],
+ plugins: [
+ "InjectSecrets"
+ ]
),
+
+ // MARK: - Tests
.testTarget(
name: "MonextTests",
dependencies: [
@@ -37,8 +59,8 @@ let package = Package(
.product(name: "ViewInspector", package: "ViewInspector")
],
resources: [
- .process("API/TestResources")
- ]
+ .process("API/TestResources")
+ ]
),
]
)
diff --git a/Plugins/InjectSecrets/plugin.swift b/Plugins/InjectSecrets/plugin.swift
new file mode 100644
index 0000000..c747503
--- /dev/null
+++ b/Plugins/InjectSecrets/plugin.swift
@@ -0,0 +1,23 @@
+//
+// plugin.swift
+// Monext
+//
+// Created by lucas bianciotto on 26/02/2026.
+//
+
+import PackagePlugin
+import Foundation
+
+@main
+struct InjectSecretsPlugin: BuildToolPlugin {
+ func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
+ let outputFileURL = context.pluginWorkDirectoryURL.appending(path: "APIConfig.swift")
+
+ return [.buildCommand(
+ displayName: "Inject API Secrets",
+ executable: try context.tool(named: "InjectSecretsExecutable").url,
+ arguments: [outputFileURL.path(percentEncoded: false)],
+ outputFiles: [outputFileURL]
+ )]
+ }
+}
diff --git a/Sources/InjectSecretsExecutable/main.swift b/Sources/InjectSecretsExecutable/main.swift
new file mode 100644
index 0000000..24ab39a
--- /dev/null
+++ b/Sources/InjectSecretsExecutable/main.swift
@@ -0,0 +1,94 @@
+//
+// main.swift
+// Monext
+//
+// Created by lucas bianciotto on 26/02/2026.
+//
+
+import Foundation
+
+// MARK: - Helpers
+
+func readEnvFile(at path: String) -> [String: String] {
+ guard let content = try? String(contentsOfFile: path, encoding: .utf8) else {
+ return [:]
+ }
+
+ var result: [String: String] = [:]
+ for line in content.components(separatedBy: .newlines) {
+ let trimmed = line.trimmingCharacters(in: .whitespaces)
+ // Ignorer les commentaires et les lignes vides
+ guard !trimmed.isEmpty, !trimmed.hasPrefix("#") else { continue }
+ let parts = trimmed.components(separatedBy: "=")
+ guard parts.count >= 2 else { continue }
+ let key = parts[0].trimmingCharacters(in: .whitespaces)
+ let value = parts.dropFirst().joined(separator: "=").trimmingCharacters(in: .whitespaces)
+ result[key] = value
+ }
+ return result
+}
+
+func resolveKey(named name: String) -> String {
+ // 1. Variables d'environnement (CI / GitHub Actions)
+ if let value = ProcessInfo.processInfo.environment[name], !value.isEmpty {
+ fputs("✅ \(name) found in environment variables\n", stderr)
+ return value
+ }
+
+ // 2. Fichier .env.local (développement local)
+ let workDir = FileManager.default.currentDirectoryPath
+ let envLocalPath = URL(fileURLWithPath: workDir).appendingPathComponent(".env.local").path
+ let envVars = readEnvFile(at: envLocalPath)
+
+ if let value = envVars[name], !value.isEmpty {
+ fputs("✅ \(name) found in .env.local\n", stderr)
+ return value
+ }
+
+ // 3. Clé vide avec warning
+ fputs("⚠️ Warning: \(name) not found. Set it in .env.local or as environment variable.\n", stderr)
+ fputs(" → Copy .env.local.example to .env.local and fill in your values.\n", stderr)
+ return ""
+}
+
+func toByteArray(_ string: String) -> String {
+ return string.utf8
+ .map { String(format: "0x%02x", $0) }
+ .joined(separator: ", ")
+}
+
+// MARK: - Main
+
+guard CommandLine.arguments.count > 1 else {
+ fputs("❌ Usage: InjectSecretsExecutable \n", stderr)
+ exit(1)
+}
+
+let outputPath = CommandLine.arguments[1]
+
+let netecteraAPIKey = resolveKey(named: "NETCETERA_API_KEY")
+let netecteraBytes = toByteArray(netecteraAPIKey)
+
+let content = """
+// ⚠️ Auto-generated by InjectSecretsPlugin - do not edit manually
+// This file is gitignored and regenerated at each build
+
+internal enum APIConfig {
+
+ /// Netcetera 3DS SDK API Key
+ /// Stored as bytes to avoid plain text exposure in the binary
+ static var netecteraAPIKey: String {
+ let bytes: [UInt8] = [\(netecteraBytes)]
+ return String(bytes: bytes, encoding: .utf8) ?? ""
+ }
+
+}
+"""
+
+do {
+ try content.write(toFile: outputPath, atomically: true, encoding: .utf8)
+ fputs("✅ APIConfig.swift generated at \(outputPath)\n", stderr)
+} catch {
+ fputs("❌ Failed to write APIConfig.swift: \(error)\n", stderr)
+ exit(1)
+}
diff --git a/Sources/Monext/AppMetadata.plist b/Sources/Monext/AppMetadata.plist
index 30b4e4f..9a368d1 100644
--- a/Sources/Monext/AppMetadata.plist
+++ b/Sources/Monext/AppMetadata.plist
@@ -2,10 +2,8 @@
- NetceteraAPIKey
- ${NETCETERA_API_KEY}
CFBundleShortVersionString
- 1.0.5
+ 1.0.6
CFBundleVersion
diff --git a/Sources/Monext/Configuration/MonextConfig.swift b/Sources/Monext/Configuration/MonextConfig.swift
index 7a06c5d..2497acf 100644
--- a/Sources/Monext/Configuration/MonextConfig.swift
+++ b/Sources/Monext/Configuration/MonextConfig.swift
@@ -19,10 +19,15 @@ public struct MonextConfig {
return dict
}
- // MARK: - Monext Configuration
- public static var NetecteraAPIKey: String {
- loadPlist()?["NetceteraAPIKey"] as? String ?? ""
- }
+ // MARK: - Sensitive (embarquée dans le binaire via build plugin)
+
+ /// Clé API Netcetera pour le 3DS SDK.
+ /// Générée à la compilation depuis .env.local ou les variables d'environnement CI.
+ /// Non visible dans le bundle distribué.
+ public static var NetecteraAPIKey: String {
+ APIConfig.netecteraAPIKey
+ }
+
}
diff --git a/Sources/Monext/ThreeDS/Models/ChallengeStatus.swift b/Sources/Monext/ThreeDS/Models/ChallengeStatus.swift
index 6d990ef..1354adb 100644
--- a/Sources/Monext/ThreeDS/Models/ChallengeStatus.swift
+++ b/Sources/Monext/ThreeDS/Models/ChallengeStatus.swift
@@ -8,7 +8,7 @@
import Foundation
@preconcurrency import ThreeDS_SDK
-public enum ChallengeStatus: Sendable {
+public enum ChallengeStatus: @unchecked Sendable {
case completed(ThreeDS_SDK.CompletionEvent)
case cancelled
case timedout
diff --git a/Tests/MonextTests/ThreeDS/Mock/MockThreeDS2Service.swift b/Tests/MonextTests/ThreeDS/Mock/MockThreeDS2Service.swift
index 4a48495..fa3986a 100644
--- a/Tests/MonextTests/ThreeDS/Mock/MockThreeDS2Service.swift
+++ b/Tests/MonextTests/ThreeDS/Mock/MockThreeDS2Service.swift
@@ -130,11 +130,14 @@ class MockThreeDS2Service: NSObject, ThreeDS2Service, @unchecked Sendable {
if let tx = transactionToReturn {
return tx
} else {
- // Avoid constructing SDK Transaction types that may be unavailable in test target.
throw ThreeDS2Error.SDKRuntime(message: "No transaction configured on MockThreeDS2Service", errorCode: nil, cause: nil)
}
}
+ func createTransaction(directoryServerId: String, messageVersion: String?, additionalParameters: [String: String]?) throws -> any ThreeDS_SDK.Transaction {
+ return try createTransaction(directoryServerId: directoryServerId, messageVersion: messageVersion)
+ }
+
func getWarnings() throws -> [ThreeDS_SDK.Warning] {
guard didInitialize else {
throw ThreeDS2Error.SDKNotInitialized(message: "SDK not initialized", cause: nil)
diff --git a/build_xcframework.sh b/build_xcframework.sh
index 505b5f6..cff1ea2 100755
--- a/build_xcframework.sh
+++ b/build_xcframework.sh
@@ -29,7 +29,17 @@ build_framework() {
echo "Destination: $dest"
echo
- (xcodebuild -scheme "$scheme" -configuration "$CONFIGURATION" -destination "$dest" -sdk "$sdk" -derivedDataPath "$BUILD_DIR" SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES OTHER_SWIFT_FLAGS="-no-verify-emitted-module-interface") || exit 12
+ # La variable NETCETERA_API_KEY est transmise depuis l'environnement CI
+ # ou depuis .env.local en local — le build plugin InjectSecrets s'en charge
+ (xcodebuild \
+ -scheme "$scheme" \
+ -configuration "$CONFIGURATION" \
+ -destination "$dest" \
+ -sdk "$sdk" \
+ -derivedDataPath "$BUILD_DIR" \
+ SKIP_INSTALL=NO \
+ BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
+ OTHER_SWIFT_FLAGS="-no-verify-emitted-module-interface") || exit 12
product_path="$BUILD_DIR/Build/Products/$CONFIGURATION-$sdk"
framework_path="$BUILD_DIR/Build/Products/$CONFIGURATION-$sdk/PackageFrameworks/$scheme.framework"
@@ -56,14 +66,43 @@ build_framework() {
mkdir "$modules_path/$scheme.swiftmodule"
cp -pv "$product_path/$scheme.swiftmodule"/*.* "$modules_path/$scheme.swiftmodule/" || exit 16
- # Copy Bundle
+ # Copy Bundle (copie le .bundle entier, pas son contenu)
bundle_dir="$product_path/${PACKAGE}_$scheme.bundle"
if [ -d "$bundle_dir" ]; then
- cp -prv "$bundle_dir"/* "$framework_path/" || exit 17
+ cp -prv "$bundle_dir" "$framework_path/" || exit 17
else
- echo "BUNDLE NOT FOUND!!!"
- read -n 1 -p "Wait:" mainmenuinput
+ echo "⚠️ BUNDLE NOT FOUND at $bundle_dir"
fi
+
+ # Create Info.plist
+ VERSION=$(plutil -extract CFBundleShortVersionString raw Sources/Monext/Resources/AppMetadata.plist 2>/dev/null || echo "1.0.0")
+ cat > "$framework_path/Info.plist" << EOF
+
+
+
+
+ CFBundleExecutable
+ $scheme
+ CFBundleIdentifier
+ com.monext.sdk
+ CFBundleName
+ $scheme
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ $VERSION
+ CFBundleVersion
+ 1
+ MinimumOSVersion
+ 16.0
+ CFBundleSupportedPlatforms
+
+ iPhoneOS
+
+
+
+EOF
+ echo "✅ Info.plist créé pour $scheme ($sdk)"
}
create_xcframework() {
@@ -100,8 +139,15 @@ rm -rf "$BUILD_DIR"
rm -rf "$DIST_DIR"
reset_package_type
-
set_package_type_as_dynamic "$PACKAGE"
+
+echo "🔧 Building plugin executable for macOS host..."
+xcodebuild build \
+ -scheme InjectSecretsExecutable \
+ -configuration Release \
+ -derivedDataPath "$BUILD_DIR" \
+ -destination "platform=macOS" || exit 10
+
build_framework "$PACKAGE" "$SIMULATOR_SDK"
build_framework "$PACKAGE" "$DEVICE_SDK"
create_xcframework "$PACKAGE" "$SIMULATOR_SDK" "$DEVICE_SDK"