From 7c92ddc3065e6efd96b347783b9e455403b71b65 Mon Sep 17 00:00:00 2001 From: Cesar Sosa <101717495+cesar-sosa-hol@users.noreply.github.com> Date: Tue, 22 Jul 2025 22:15:45 +0200 Subject: [PATCH 1/8] update version --- CHANGELOG.md | 4 ++++ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b5b1ef..cc2410f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Changelog +### v1.7.11 Jul 22, 2025 + +* Upgraded plugin to support Flutter Embedding v2 and newer Flutter versions (specifically Flutter 3.29.3+ with Dart 3.7.2+). + ### v1.7.10 Jan 28, 2025 * Upgraded plugin to be compatible with AGP-8.x.x. diff --git a/pubspec.yaml b/pubspec.yaml index b86c1b8..80b664a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: square_in_app_payments description: An open source Flutter plugin for calling Square’s native In-App Payments SDK to take in-app payments on iOS and Android. -version: 1.7.10 +version: 1.7.11 homepage: https://github.com/square/in-app-payments-flutter-plugin documentation: https://github.com/square/in-app-payments-flutter-plugin From 14e490ff31b17065e553705b4098cf035bf0ed14 Mon Sep 17 00:00:00 2001 From: Cesar Sosa <101717495+cesar-sosa-hol@users.noreply.github.com> Date: Wed, 23 Jul 2025 02:00:36 +0200 Subject: [PATCH 2/8] fix issue in deploymeny --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 80b664a..34024eb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/square/in-app-payments-flutter-plugin documentation: https://github.com/square/in-app-payments-flutter-plugin environment: - sdk: '>=2.17.6 <3.0.0' + sdk: '>=2.17.6 <4.0.0' flutter: ">=1.10.0" dependencies: From ced6e365901851adb8f66e3fc8f813c30d374056 Mon Sep 17 00:00:00 2001 From: Cesar Sosa <101717495+cesar-sosa-hol@users.noreply.github.com> Date: Wed, 23 Jul 2025 02:02:24 +0200 Subject: [PATCH 3/8] update pubignore --- .pubignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .pubignore diff --git a/.pubignore b/.pubignore new file mode 100644 index 0000000..7108284 --- /dev/null +++ b/.pubignore @@ -0,0 +1,6 @@ +# .pubignore +example/.idea/ +example/android/gradle/wrapper/gradle-wrapper.jar +example/android/gradlew +example/android/gradlew.bat +example/example.iml From bc575780abc78c7128cc4a57a5b030057093fec6 Mon Sep 17 00:00:00 2001 From: Cesar Sosa <101717495+cesar-sosa-hol@users.noreply.github.com> Date: Wed, 23 Jul 2025 10:50:04 +0200 Subject: [PATCH 4/8] update ios project --- example/ios/Podfile.lock | 4 ++-- example/ios/Runner.xcodeproj/project.pbxproj | 8 ++++---- .../xcshareddata/xcschemes/Runner.xcscheme | 5 ++++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 78770c3..4f4c2f7 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -24,10 +24,10 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - square_in_app_payments: 83d45e99111e35ffeb7ac79593806abcee37aa3b + square_in_app_payments: 96f9a5f6fd3f5aed494f0266f334fb56e1adefa7 SquareBuyerVerificationSDK: d325f473a724cfa5684a837296a307152ca52854 SquareInAppPaymentsSDK: 4d7f1e984fee5d8d7dbd44a67661ca1aedee7f76 PODFILE CHECKSUM: 3095475c8b4475d5353089cfe9f0bebb1c4b418a -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index d8e9797..ff34489 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -163,7 +163,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -412,7 +412,7 @@ "@executable_path/Frameworks", ); OTHER_SWIFT_FLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = com.squareup.sqip.flutter.example; + PRODUCT_BUNDLE_IDENTIFIER = com.squareup.sqip.flutter.example1; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -547,7 +547,7 @@ "@executable_path/Frameworks", ); OTHER_SWIFT_FLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = com.squareup.sqip.flutter.example; + PRODUCT_BUNDLE_IDENTIFIER = com.squareup.sqip.flutter.example1; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -574,7 +574,7 @@ "@executable_path/Frameworks", ); OTHER_SWIFT_FLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = com.squareup.sqip.flutter.example; + PRODUCT_BUNDLE_IDENTIFIER = com.squareup.sqip.flutter.example1; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a..9c12df5 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ From caefaf02ca7e9e8bc1bd24b33d9e181b8ab4d9c2 Mon Sep 17 00:00:00 2001 From: Cesar Sosa <101717495+cesar-sosa-hol@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:08:59 +0200 Subject: [PATCH 5/8] update gitignores --- example/.gitignore | 2 -- example/android/.gitignore | 3 --- 2 files changed, 5 deletions(-) diff --git a/example/.gitignore b/example/.gitignore index 79c113f..54f735b 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -13,10 +13,8 @@ migrate_working_dir/ # IntelliJ related -*.iml *.ipr *.iws -.idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line diff --git a/example/android/.gitignore b/example/android/.gitignore index be3943c..be82512 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -1,8 +1,5 @@ -gradle-wrapper.jar /.gradle /captures/ -/gradlew -/gradlew.bat /local.properties GeneratedPluginRegistrant.java .cxx/ From 6a69c3d8c5436cff6e54b23d11bb1ae21818ad24 Mon Sep 17 00:00:00 2001 From: Cesar Sosa <101717495+cesar-sosa-hol@users.noreply.github.com> Date: Fri, 10 Oct 2025 16:19:43 +0200 Subject: [PATCH 6/8] first test --- .vscode/.c3-extension-settings.json | 1 + example/lib/widgets/buy_sheet.dart | 12 +++++++----- yarn.lock | 4 ++++ 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 .vscode/.c3-extension-settings.json create mode 100644 yarn.lock diff --git a/.vscode/.c3-extension-settings.json b/.vscode/.c3-extension-settings.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.vscode/.c3-extension-settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/example/lib/widgets/buy_sheet.dart b/example/lib/widgets/buy_sheet.dart index 3ec54b7..8206e0a 100644 --- a/example/lib/widgets/buy_sheet.dart +++ b/example/lib/widgets/buy_sheet.dart @@ -80,13 +80,15 @@ class BuySheetState extends State { break; case PaymentType.cardPayment: // call _onStartCardEntryFlow to start Card Entry without buyer verification (SCA) - await _onStartCardEntryFlow(); // OR call _onStartCardEntryFlowWithBuyerVerification to start Card Entry with buyer verification (SCA) // NOTE this requires _squareLocationSet to be set - // await _onStartCardEntryFlowWithBuyerVerification(); + await _onStartCardEntryFlowWithBuyerVerification(); + break; case PaymentType.buyerVerification: - await _onStartBuyerVerificationFlow(); + + var res = await _onStartBuyerVerificationFlow(); + print(res); break; case PaymentType.googlePay: if (_squareLocationSet && widget.googlePayEnabled!) { @@ -268,7 +270,7 @@ class BuySheetState extends State { ); } - Future _onStartBuyerVerificationFlow() async { + Future _onStartBuyerVerificationFlow() async { var money = Money( (b) => b ..amount = 100 @@ -290,7 +292,7 @@ class BuySheetState extends State { ..postalCode = "SE1 7", ); - await InAppPayments.startBuyerVerificationFlow( + return await InAppPayments.startBuyerVerificationFlow( onBuyerVerificationSuccess: _onBuyerVerificationSuccess, onBuyerVerificationFailure: _onBuyerVerificationFailure, buyerAction: "Charge", diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..fb57ccd --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + From ea54c8872920e56f6b808d7aa4a1551ec7ebb0c6 Mon Sep 17 00:00:00 2001 From: Cesar Sosa <101717495+cesar-sosa-hol@users.noreply.github.com> Date: Fri, 10 Oct 2025 16:20:10 +0200 Subject: [PATCH 7/8] remove files --- .vscode/.c3-extension-settings.json | 1 - yarn.lock | 4 ---- 2 files changed, 5 deletions(-) delete mode 100644 .vscode/.c3-extension-settings.json delete mode 100644 yarn.lock diff --git a/.vscode/.c3-extension-settings.json b/.vscode/.c3-extension-settings.json deleted file mode 100644 index 9e26dfe..0000000 --- a/.vscode/.c3-extension-settings.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index fb57ccd..0000000 --- a/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - From f28498ebecc8b8eab9d47cdfebf69f19c65ed1f7 Mon Sep 17 00:00:00 2001 From: Cesar Sosa <101717495+cesar-sosa-hol@users.noreply.github.com> Date: Thu, 16 Oct 2025 07:19:46 +0200 Subject: [PATCH 8/8] add Buyer Verification methods --- .../SquareInAppPaymentsFlutterPlugin.java | 101 ++++++-- .../flutter/internal/CardEntryModule.java | 71 +++++- .../flutter/internal/GooglePayModule.java | 145 ++++++++++++ example/lib/widgets/buy_sheet.dart | 179 +++++++++----- ios/Classes/FSQIPApplePay.h | 18 +- ios/Classes/FSQIPApplePay.m | 224 ++++++++++++++++++ ios/Classes/FSQIPCardEntry.h | 5 +- ios/Classes/FSQIPCardEntry.m | 115 ++++++++- ios/Classes/FSQIPSecureRemoteCommerce.h | 3 + ios/Classes/FSQIPSecureRemoteCommerce.m | 192 +++++++++++++++ .../SquareInAppPaymentsFlutterPlugin.m | 62 ++++- lib/in_app_payments.dart | 153 ++++++++++++ 12 files changed, 1163 insertions(+), 105 deletions(-) diff --git a/android/src/main/java/sqip/flutter/SquareInAppPaymentsFlutterPlugin.java b/android/src/main/java/sqip/flutter/SquareInAppPaymentsFlutterPlugin.java index a035787..6b091b9 100644 --- a/android/src/main/java/sqip/flutter/SquareInAppPaymentsFlutterPlugin.java +++ b/android/src/main/java/sqip/flutter/SquareInAppPaymentsFlutterPlugin.java @@ -91,12 +91,43 @@ public void onMethodCall(MethodCall call, Result result) { result.success(null); break; case "startCardEntryFlow": - boolean collectPostalCode = call.argument("collectPostalCode"); - cardEntryModule.startCardEntryFlow(result, collectPostalCode); + boolean sce_collectPostalCode = call.argument("collectPostalCode"); + cardEntryModule.startCardEntryFlow(result, sce_collectPostalCode); + break; + case "startCardEntryFlowWithBuyerVerification": + boolean sce_bv_collectPostalCode = call.argument("collectPostalCode"); + String sce_bv_locationId = call.argument("squareLocationId"); + String sce_bv_buyerAction = call.argument("buyerAction"); + HashMap sce_bv_moneyMap = call.argument("money"); + HashMap sce_bv_contactMap = call.argument("contact"); + String sce_bv_paymentSourceId = call.argument("paymentSourceId"); + + cardEntryModule.startCardEntryFlowWithBuyerVerification( + result, + sce_bv_collectPostalCode, + sce_bv_locationId, + sce_bv_buyerAction, + sce_bv_moneyMap, + sce_bv_contactMap, + sce_bv_paymentSourceId); break; case "startGiftCardEntryFlow": cardEntryModule.startGiftCardEntryFlow(result); break; + case "startGiftCardEntryFlowWithBuyerVerification": + String sge_bv_locationId = call.argument("squareLocationId"); + String sge_bv_buyerAction = call.argument("buyerAction"); + HashMap sge_bv_moneyMap = call.argument("money"); + HashMap sge_bv_contactMap = call.argument("contact"); + String sge_bv_paymentSourceId = call.argument("paymentSourceId"); + cardEntryModule.startGiftCardEntryFlowWithBuyerVerification( + result, + sge_bv_locationId, + sge_bv_buyerAction, + sge_bv_moneyMap, + sge_bv_contactMap, + sge_bv_paymentSourceId); + break; case "completeCardEntry": cardEntryModule.completeCardEntry(result); break; @@ -105,43 +136,65 @@ public void onMethodCall(MethodCall call, Result result) { cardEntryModule.showCardNonceProcessingError(result, errorMessage); break; case "initializeGooglePay": - String squareLocationId = call.argument("squareLocationId"); - int environment = call.argument("environment"); - googlePayModule.initializeGooglePay(squareLocationId, environment); + String igp_squareLocationId = call.argument("squareLocationId"); + int igp_environment = call.argument("environment"); + googlePayModule.initializeGooglePay(igp_squareLocationId, igp_environment); result.success(null); break; case "canUseGooglePay": googlePayModule.canUseGooglePay(result); break; case "requestGooglePayNonce": - String price = call.argument("price"); - String currencyCode = call.argument("currencyCode"); - int priceStatus = call.argument("priceStatus"); - googlePayModule.requestGooglePayNonce(result, price, currencyCode, priceStatus); + String rgp_price = call.argument("price"); + String rgp_currencyCode = call.argument("currencyCode"); + int rgp_priceStatus = call.argument("priceStatus"); + googlePayModule.requestGooglePayNonce(result, rgp_price, rgp_currencyCode, rgp_priceStatus); break; - case "startCardEntryFlowWithBuyerVerification": - boolean collectPostal = call.argument("collectPostalCode"); - String locationId = call.argument("squareLocationId"); - String buyerAction = call.argument("buyerAction"); - HashMap moneyMap = call.argument("money"); - HashMap contactMap = call.argument("contact"); - - cardEntryModule.startCardEntryFlowWithBuyerVerification(result, collectPostal, locationId, buyerAction, moneyMap, contactMap); + case "requestGooglePayNonceWithBuyerVerification": + String rgp_bv_locationId = call.argument("squareLocationId"); + String rgp_bv_buyerAction = call.argument("buyerAction"); + HashMap rgp_bv_moneyMap = call.argument("money"); + HashMap rgp_bv_contactMap = call.argument("contact"); + String rgp_bv_paymentSourceId = call.argument("paymentSourceId"); + String rgp_bv_price = call.argument("price"); + String rgp_bv_currencyCode = call.argument("currencyCode"); + int rgp_bv_priceStatus = call.argument("priceStatus"); + + googlePayModule.requestGooglePayNonceWithBuyerVerification( + result, + rgp_bv_buyerAction, + rgp_bv_moneyMap, + rgp_bv_locationId, + rgp_bv_contactMap, + rgp_bv_paymentSourceId, + rgp_bv_price, + rgp_bv_currencyCode, + rgp_bv_priceStatus); break; case "startBuyerVerificationFlow": - String locationId2 = call.argument("squareLocationId"); - String buyerAction2 = call.argument("buyerAction"); - HashMap moneyMap2 = call.argument("money"); - HashMap contactMap2 = call.argument("contact"); - String paymentSourceId = call.argument("paymentSourceId"); - - cardEntryModule.startBuyerVerificationFlow(result, buyerAction2, moneyMap2, locationId2, contactMap2, paymentSourceId); + String bv_locationId = call.argument("squareLocationId"); + String bv_buyerAction = call.argument("buyerAction"); + HashMap bv_moneyMap = call.argument("money"); + HashMap bv_contactMap = call.argument("contact"); + String bv_paymentSourceId = call.argument("paymentSourceId"); + + cardEntryModule.startBuyerVerificationFlow( + result, + bv_buyerAction, + bv_moneyMap, + bv_locationId, + bv_contactMap, + bv_paymentSourceId); break; case "startSecureRemoteCommerce": int amount = call.argument("amount"); // Not yet implemented result.notImplemented(); break; + case "startSecureRemoteCommerceWithBuyerVerification": + // Not yet implemented + result.notImplemented(); + break; default: result.notImplemented(); break; diff --git a/android/src/main/java/sqip/flutter/internal/CardEntryModule.java b/android/src/main/java/sqip/flutter/internal/CardEntryModule.java index f837b59..46989ef 100644 --- a/android/src/main/java/sqip/flutter/internal/CardEntryModule.java +++ b/android/src/main/java/sqip/flutter/internal/CardEntryModule.java @@ -52,6 +52,11 @@ public final class CardEntryModule { private Contact contact; private CardDetails cardResult; private String paymentSourceId; + private boolean collectPostalCode; + + private boolean shouldListen = false; + private boolean shouldContinueWithRequestCardEntry = false; + private boolean shouldContinueWithRequestGiftCardEntry = false; public CardEntryModule(Context context, MethodChannel channel) { this.channel = channel; @@ -84,6 +89,13 @@ public CardEntryActivityCommand handleEnteredCardInBackground(CardDetails cardDe public void attachActivityResultListener(ActivityPluginBinding activityPluginBinding, MethodChannel channel) { this.currentActivity = activityPluginBinding.getActivity(); activityPluginBinding.addActivityResultListener((requestCode, resultCode, data) -> { + // prevent multiple calls to the activity result listener when is called from GooglePay module + // added because "requestCode == BuyerVerification.DEFAULT_BUYER_VERIFICATION_REQUEST_CODE" is used in GooglePay module + if (!shouldListen) { + return false; + } + shouldListen = false; + if (requestCode == CardEntry.DEFAULT_CARD_ENTRY_REQUEST_CODE) { CardEntry.handleActivityResult(data, cardEntryActivityResult -> { if (cardEntryActivityResult.isSuccess() && CardEntryModule.this.contact != null) { @@ -116,7 +128,16 @@ public void attachActivityResultListener(ActivityPluginBinding activityPluginBin payload.put("token", result.getSuccessValue().getVerificationToken()); } channel.invokeMethod("onBuyerVerificationSuccess", payload); + if (shouldContinueWithRequestCardEntry) { + shouldContinueWithRequestCardEntry = false; + CardEntry.startCardEntryActivity(currentActivity, collectPostalCode); + } else if (shouldContinueWithRequestGiftCardEntry) { + shouldContinueWithRequestGiftCardEntry = false; + CardEntry.startGiftCardEntryActivity(currentActivity); + } } else if (result.isError()) { + shouldContinueWithRequestCardEntry = false; + shouldContinueWithRequestGiftCardEntry = false; Error error = result.getErrorValue(); Map errorMap = ErrorHandlerUtils.getCallbackErrorObject( error.getCode().name(), @@ -135,11 +156,13 @@ public void attachActivityResultListener(ActivityPluginBinding activityPluginBin } public void startCardEntryFlow(MethodChannel.Result result, boolean collectPostalCode) { + shouldListen = true; CardEntry.startCardEntryActivity(currentActivity, collectPostalCode); result.success(null); } public void completeCardEntry(MethodChannel.Result result) { + shouldListen = true; reference.set(new CardEntryActivityCommand.Finish()); countDownLatch.countDown(); result.success(null); @@ -152,28 +175,70 @@ public void showCardNonceProcessingError(MethodChannel.Result result, String err } public void startGiftCardEntryFlow(MethodChannel.Result result) { + shouldListen = true; CardEntry.startGiftCardEntryActivity(currentActivity); result.success(null); } - public void startCardEntryFlowWithBuyerVerification(MethodChannel.Result result, boolean collectPostalCode, String squareLocationId, String buyerActionString, Map moneyMap, Map contactMap) { + public void startGiftCardEntryFlowWithBuyerVerification( + MethodChannel.Result result, + String squareLocationId, + String buyerActionString, + Map moneyMap, + Map contactMap, + String paymentSourceId + ) { + shouldListen = true; + this.squareIdentifier = new SquareIdentifier.LocationToken(squareLocationId); Money money = getMoney(moneyMap); this.buyerAction = getBuyerAction(buyerActionString, money); this.contact = getContact(contactMap); - this.paymentSourceId = null; + this.paymentSourceId = paymentSourceId; - CardEntry.startCardEntryActivity(currentActivity, collectPostalCode); + shouldContinueWithRequestGiftCardEntry = true; + + VerificationParameters params = new VerificationParameters(paymentSourceId, buyerAction, squareIdentifier, contact); + BuyerVerification.verify(currentActivity, params); + result.success(null); + } + + public void startCardEntryFlowWithBuyerVerification( + MethodChannel.Result result, + boolean collectPostalCode, + String squareLocationId, + String buyerActionString, + Map moneyMap, + Map contactMap, + String paymentSourceId + ) { + shouldListen = true; + + this.collectPostalCode = collectPostalCode; + this.squareIdentifier = new SquareIdentifier.LocationToken(squareLocationId); + Money money = getMoney(moneyMap); + this.buyerAction = getBuyerAction(buyerActionString, money); + this.contact = getContact(contactMap); + this.paymentSourceId = paymentSourceId; + + shouldContinueWithRequestCardEntry = true; + + VerificationParameters params = new VerificationParameters(paymentSourceId, buyerAction, squareIdentifier, contact); + BuyerVerification.verify(currentActivity, params); result.success(null); } public void startBuyerVerificationFlow(MethodChannel.Result result, String buyerActionString, Map moneyMap, String squareLocationId, Map contactMap, String paymentSourceId) { + shouldListen = true; this.squareIdentifier = new SquareIdentifier.LocationToken(squareLocationId); Money money = getMoney(moneyMap); this.buyerAction = getBuyerAction(buyerActionString, money); this.contact = getContact(contactMap); this.paymentSourceId = paymentSourceId; + shouldContinueWithRequestCardEntry = false; + shouldContinueWithRequestGiftCardEntry = false; + VerificationParameters params = new VerificationParameters(paymentSourceId, buyerAction, squareIdentifier, contact); BuyerVerification.verify(currentActivity, params); result.success(null); diff --git a/android/src/main/java/sqip/flutter/internal/GooglePayModule.java b/android/src/main/java/sqip/flutter/internal/GooglePayModule.java index 1f5544a..4a57fa7 100644 --- a/android/src/main/java/sqip/flutter/internal/GooglePayModule.java +++ b/android/src/main/java/sqip/flutter/internal/GooglePayModule.java @@ -3,6 +3,12 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; import com.google.android.gms.tasks.Task; import com.google.android.gms.wallet.AutoResolveHelper; @@ -20,6 +26,15 @@ import sqip.GooglePayNonceResult; import sqip.flutter.internal.converter.CardConverter; import sqip.flutter.internal.converter.CardDetailsConverter; +import sqip.BuyerVerificationResult.Error; +import sqip.BuyerVerification; +import sqip.SquareIdentifier; +import sqip.BuyerAction; +import sqip.Contact; +import sqip.CardDetails; +import sqip.Money; +import sqip.VerificationParameters; +import sqip.Country; public final class GooglePayModule { @@ -32,9 +47,20 @@ public final class GooglePayModule { private static final int LOAD_PAYMENT_DATA_REQUEST_CODE = 4111; + private boolean shouldListen = false; + private boolean shouldContinueWithRequestGooglePay = false; + private final CardDetailsConverter cardDetailsConverter; private PaymentsClient googlePayClients; private String squareLocationId; + private SquareIdentifier squareIdentifier; + private BuyerAction buyerAction; + private Contact contact; + private CardDetails cardResult; + private String paymentSourceId; + private String price; + private String currencyCode; + private int priceStatus; private Activity currentActivity; @@ -46,6 +72,11 @@ public void attachActivityResultListener(final ActivityPluginBinding activityPlu this.currentActivity = activityPluginBinding.getActivity(); activityPluginBinding.addActivityResultListener((requestCode, resultCode, data) -> { + if (!shouldListen) { + return false; + } + shouldListen = false; + if (requestCode == LOAD_PAYMENT_DATA_REQUEST_CODE) { switch (resultCode) { case Activity.RESULT_OK: @@ -80,6 +111,48 @@ public void onResult(GooglePayNonceResult result) { break; } } + + if (requestCode == BuyerVerification.DEFAULT_BUYER_VERIFICATION_REQUEST_CODE) { + BuyerVerification.handleActivityResult(data, result -> { + if (result.isSuccess()) { + Map payload = new LinkedHashMap<>(); + if (paymentSourceId == null) { + payload = cardDetailsConverter.toMapObject(cardResult); + payload.put("token", result.getSuccessValue().getVerificationToken()); + } else { + payload.put("nonce", paymentSourceId); + payload.put("token", result.getSuccessValue().getVerificationToken()); + } + channel.invokeMethod("onBuyerVerificationSuccess", payload); + if (shouldContinueWithRequestGooglePay) { + shouldContinueWithRequestGooglePay = false; + if (price != null && currencyCode != null && priceStatus != 0) { + PaymentDataRequest request = createPaymentChargeRequest( + squareLocationId, + price, + currencyCode, + priceStatus + ); + AutoResolveHelper.resolveTask( + googlePayClients.loadPaymentData(request), + currentActivity, + LOAD_PAYMENT_DATA_REQUEST_CODE); + } + } + } else if (result.isError()) { + shouldContinueWithRequestGooglePay = false; + Error error = result.getErrorValue(); + Map errorMap = ErrorHandlerUtils.getCallbackErrorObject( + error.getCode().name(), + error.getMessage(), + error.getDebugCode(), + error.getDebugMessage()); + channel.invokeMethod("onBuyerVerificationError", errorMap); + } + }); + + this.contact = null; + } return false; }); } @@ -115,11 +188,50 @@ public void requestGooglePayNonce(MethodChannel.Result result, String price, Str return; } + shouldListen = true; + PaymentDataRequest request = createPaymentChargeRequest(squareLocationId, price, currencyCode, priceStatus); AutoResolveHelper.resolveTask(googlePayClients.loadPaymentData(request), currentActivity, LOAD_PAYMENT_DATA_REQUEST_CODE); result.success(null); } + public void requestGooglePayNonceWithBuyerVerification( + MethodChannel.Result result, + String buyerActionString, + HashMap moneyMap, + String locationId, + HashMap contactMap, + String paymentSourceId, + String price, + String currencyCode, + int priceStatus) { + if (googlePayClients == null) { + result.error(ErrorHandlerUtils.USAGE_ERROR, + ErrorHandlerUtils.getPluginErrorMessage(FL_GOOGLE_PAY_NOT_INITIALIZED), + ErrorHandlerUtils.getDebugErrorObject(FL_GOOGLE_PAY_NOT_INITIALIZED, FL_MESSAGE_GOOGLE_PAY_NOT_INITIALIZED)); + return; + } + + shouldListen = true; + + this.squareLocationId = locationId; + this.squareIdentifier = new SquareIdentifier.LocationToken(locationId); + Money money = getMoney(moneyMap); + this.buyerAction = getBuyerAction(buyerActionString, money); + this.contact = getContact(contactMap); + this.paymentSourceId = paymentSourceId; + + this.price = price; + this.currencyCode = currencyCode; + this.priceStatus = priceStatus; + + shouldContinueWithRequestGooglePay = true; + + VerificationParameters params = new VerificationParameters(paymentSourceId, buyerAction, squareIdentifier, contact); + BuyerVerification.verify(currentActivity, params); + result.success(null); + } + private PaymentDataRequest createPaymentChargeRequest(String squareLocationId, String price, String currencyCode, int priceStatus) { TransactionInfo transactionInfo = TransactionInfo.newBuilder() .setTotalPriceStatus(priceStatus) @@ -128,4 +240,37 @@ private PaymentDataRequest createPaymentChargeRequest(String squareLocationId, S .build(); return GooglePay.createPaymentDataRequest(squareLocationId, transactionInfo); } + + private Contact getContact(Map contactMap) { + Object givenName = contactMap.get("givenName"); + Object familyName = contactMap.get("familyName"); + Object addressLines = contactMap.get("addressLines"); + Object city = contactMap.get("city"); + Object countryCode = contactMap.get("countryCode"); + Object email = contactMap.get("email"); + Object phone = contactMap.get("phone"); + Object postalCode = contactMap.get("postalCode"); + Object region = contactMap.get("region"); + Country country = Country.valueOf((countryCode != null) ? countryCode.toString() : "US"); + + return new Contact.Builder() + .familyName((familyName != null) ? familyName.toString() : "") + .email((email != null) ? email.toString() : "") + .addressLines((addressLines != null) ? (ArrayList) addressLines : new ArrayList<>()) + .city((city != null) ? city.toString() : "") + .countryCode(country) + .postalCode((postalCode != null) ? postalCode.toString() : "") + .phone((phone != null) ? phone.toString() : "") + .region((region != null) ? region.toString() : "") + .build((givenName != null) ? givenName.toString() : ""); + } + + private Money getMoney(Map moneyMap) { + return new Money((Integer) moneyMap.get("amount"), sqip.Currency.valueOf((String) moneyMap.get("currencyCode"))); + } + + private BuyerAction getBuyerAction(String buyerActionString, Money money) { + return buyerActionString.equals("Store") ? new BuyerAction.Store() : new BuyerAction.Charge(money); + } + } diff --git a/example/lib/widgets/buy_sheet.dart b/example/lib/widgets/buy_sheet.dart index 8206e0a..65065f7 100644 --- a/example/lib/widgets/buy_sheet.dart +++ b/example/lib/widgets/buy_sheet.dart @@ -76,42 +76,61 @@ class BuySheetState extends State { switch (selection) { case PaymentType.giftcardPayment: // call _onStartGiftCardEntryFlow to start Gift Card Entry. - await _onStartGiftCardEntryFlow(); + await _onStartGiftCardEntryFlowWithBuyerVerification(); break; case PaymentType.cardPayment: // call _onStartCardEntryFlow to start Card Entry without buyer verification (SCA) // OR call _onStartCardEntryFlowWithBuyerVerification to start Card Entry with buyer verification (SCA) // NOTE this requires _squareLocationSet to be set await _onStartCardEntryFlowWithBuyerVerification(); - break; case PaymentType.buyerVerification: - var res = await _onStartBuyerVerificationFlow(); print(res); break; case PaymentType.googlePay: if (_squareLocationSet && widget.googlePayEnabled!) { - _onStartGooglePay(); + _onStartGooglePayWithBuyerVerification(); } else { _showSquareLocationIdNotSet(); } break; case PaymentType.applePay: if (_applePayMerchantIdSet && widget.applePayEnabled!) { - _onStartApplePay(); + _onStartApplePayWithBuyerVerification(); } else { _showapplePayMerchantIdNotSet(); } break; case PaymentType.secureRemoteCommerce: - await _onStartSecureRemoteCommerceFlow(); + await _onStartSecureRemoteCommerceFlowWithBuyerVerification(); break; case null: throw UnimplementedError(); } } + Money get _money => Money( + (b) => b + ..amount = 100 + ..currencyCode = 'USD', + ); + + Contact get _contact => Contact( + (b) => b + ..givenName = "John" + ..familyName = "Doe" + ..addressLines = BuiltList([ + "London Eye", + "Riverside Walk", + ]).toBuilder() + ..city = "London" + ..countryCode = "GB" + ..email = "johndoe@example.com" + ..phone = "8001234567" + ..postalCode = "SE1 7", + ); + void printCurlCommand(String nonce, String? verificationToken) { var hostUrl = 'https://connect.squareup.com'; if (squareApplicationId.startsWith('sandbox')) { @@ -236,73 +255,46 @@ class BuySheetState extends State { ); } - Future _onStartCardEntryFlowWithBuyerVerification() async { - var money = Money( - (b) => b - ..amount = 100 - ..currencyCode = 'USD', - ); - - var contact = Contact( - (b) => b - ..givenName = "John" - ..familyName = "Doe" - ..addressLines = BuiltList([ - "London Eye", - "Riverside Walk", - ]).toBuilder() - ..city = "London" - ..countryCode = "GB" - ..email = "johndoe@example.com" - ..phone = "8001234567" - ..postalCode = "SE1 7", - ); - - await InAppPayments.startCardEntryFlowWithBuyerVerification( + Future _onStartGiftCardEntryFlowWithBuyerVerification() async { + await InAppPayments.startGiftCardEntryFlowWithBuyerVerification( + onCardNonceRequestSuccess: _onCardEntryCardNonceRequestSuccess, + onCardEntryCancel: _onCancelCardEntryFlow, onBuyerVerificationSuccess: _onBuyerVerificationSuccess, onBuyerVerificationFailure: _onBuyerVerificationFailure, - onCardEntryCancel: _onCancelCardEntryFlow, buyerAction: "Charge", - money: money, + money: _money, squareLocationId: squareLocationId, - contact: contact, - collectPostalCode: true, + contact: _contact, + paymentSourceId: "REPLACE_WITH_PAYMENT_SOURCE_ID", ); } - Future _onStartBuyerVerificationFlow() async { - var money = Money( - (b) => b - ..amount = 100 - ..currencyCode = 'USD', - ); - - var contact = Contact( - (b) => b - ..givenName = "John" - ..familyName = "Doe" - ..addressLines = BuiltList([ - "London Eye", - "Riverside Walk", - ]).toBuilder() - ..city = "London" - ..countryCode = "GB" - ..email = "johndoe@example.com" - ..phone = "8001234567" - ..postalCode = "SE1 7", - ); - - return await InAppPayments.startBuyerVerificationFlow( + Future _onStartCardEntryFlowWithBuyerVerification() async { + await InAppPayments.startCardEntryFlowWithBuyerVerification( onBuyerVerificationSuccess: _onBuyerVerificationSuccess, onBuyerVerificationFailure: _onBuyerVerificationFailure, + onCardNonceRequestSuccess: _onCardEntryCardNonceRequestSuccess, + onCardEntryCancel: _onCancelCardEntryFlow, buyerAction: "Charge", - money: money, + money: _money, squareLocationId: squareLocationId, - contact: contact, + contact: _contact, paymentSourceId: "REPLACE_WITH_PAYMENT_SOURCE_ID", + collectPostalCode: true, ); } + Future _onStartBuyerVerificationFlow() async => + await InAppPayments.startBuyerVerificationFlow( + onBuyerVerificationSuccess: _onBuyerVerificationSuccess, + onBuyerVerificationFailure: _onBuyerVerificationFailure, + buyerAction: "Charge", + money: _money, + squareLocationId: squareLocationId, + contact: _contact, + paymentSourceId: "REPLACE_WITH_PAYMENT_SOURCE_ID", + ); + void _onCancelCardEntryFlow() { _showOrderSheet(); } @@ -327,6 +319,33 @@ class BuySheetState extends State { } } + Future _onStartGooglePayWithBuyerVerification() async { + try { + await InAppPayments.requestGooglePayNonceWithBuyerVerification( + onBuyerVerificationSuccess: _onBuyerVerificationSuccess, + onBuyerVerificationFailure: _onBuyerVerificationFailure, + buyerAction: "Charge", + money: _money, + squareLocationId: squareLocationId, + contact: _contact, + paymentSourceId: "REPLACE_WITH_PAYMENT_SOURCE_ID", + priceStatus: google_pay_constants.totalPriceStatusFinal, + price: getCookieAmount(), + currencyCode: 'USD', + onGooglePayNonceRequestSuccess: _onGooglePayNonceRequestSuccess, + onGooglePayNonceRequestFailure: _onGooglePayNonceRequestFailure, + onGooglePayCanceled: onGooglePayEntryCanceled, + ); + } on PlatformException catch (ex) { + showAlertDialog( + context: BuySheet.scaffoldKey.currentContext!, + title: "Failed to start GooglePay", + description: ex.toString(), + status: false, + ); + } + } + void _onGooglePayNonceRequestSuccess(CardDetails result) async { if (!_chargeServerHostReplaced) { _showUrlNotSetAndPrintCurlCommand(result.nonce); @@ -386,6 +405,35 @@ class BuySheetState extends State { } } + Future _onStartApplePayWithBuyerVerification() async { + try { + await InAppPayments.requestApplePayNonceWithBuyerVerification( + onBuyerVerificationSuccess: _onBuyerVerificationSuccess, + onBuyerVerificationFailure: _onBuyerVerificationFailure, + buyerAction: "Charge", + money: _money, + squareLocationId: squareLocationId, + contact: _contact, + paymentSourceId: "REPLACE_WITH_PAYMENT_SOURCE_ID", + price: getCookieAmount(), + summaryLabel: 'Cookie', + countryCode: 'US', + currencyCode: 'USD', + paymentType: ApplePayPaymentType.finalPayment, + onApplePayNonceRequestSuccess: _onApplePayNonceRequestSuccess, + onApplePayNonceRequestFailure: _onApplePayNonceRequestFailure, + onApplePayComplete: _onApplePayEntryComplete, + ); + } on PlatformException catch (ex) { + showAlertDialog( + context: BuySheet.scaffoldKey.currentContext!, + title: "Failed to start ApplePay", + description: ex.toString(), + status: false, + ); + } + } + void _onBuyerVerificationSuccess(BuyerVerificationDetails result) async { if (!_chargeServerHostReplaced) { _showUrlNotSetAndPrintCurlCommand( @@ -477,6 +525,21 @@ class BuySheetState extends State { ); } + Future _onStartSecureRemoteCommerceFlowWithBuyerVerification() async { + await InAppPayments.startSecureRemoteCommerceWithBuyerVerification( + onBuyerVerificationSuccess: _onBuyerVerificationSuccess, + onBuyerVerificationFailure: _onBuyerVerificationFailure, + buyerAction: "Charge", + money: _money, + squareLocationId: squareLocationId, + contact: _contact, + paymentSourceId: "REPLACE_WITH_PAYMENT_SOURCE_ID", + amount: 100, + onMaterCardNonceRequestSuccess: _onMaterCardNonceRequestSuccess, + onMasterCardNonceRequestFailure: _onMasterCardNonceRequestFailure, + ); + } + void _onMaterCardNonceRequestSuccess(CardDetails result) async { if (!_chargeServerHostReplaced) { _showUrlNotSetAndPrintCurlCommand(result.nonce); diff --git a/ios/Classes/FSQIPApplePay.h b/ios/Classes/FSQIPApplePay.h index 4b79b5e..029677d 100644 --- a/ios/Classes/FSQIPApplePay.h +++ b/ios/Classes/FSQIPApplePay.h @@ -16,25 +16,33 @@ #import @import SquareInAppPaymentsSDK; - +@import SquareBuyerVerificationSDK; @interface FSQIPApplePay : NSObject - (void)initWithMethodChannel:(FlutterMethodChannel *)channel; - - (void)initializeApplePay:(FlutterResult)result merchantId:(NSString *)merchantId; - - (void)canUseApplePay:(FlutterResult)result; - - (void)requestApplePayNonce:(FlutterResult)result countryCode:(NSString *)countryCode currencyCode:(NSString *)currencyCode summaryLabel:(NSString *)summaryLabel price:(NSString *)price paymentType:(NSString *)paymentType; - - (void)completeApplePayAuthorization:(FlutterResult)result isSuccess:(BOOL)isSuccess errorMessage:(NSString *__nullable)errorMessage; +- (void)requestApplePayNonceWithVerification:(FlutterResult)result + countryCode:(NSString *)countryCode + currencyCode:(NSString *)currencyCode + summaryLabel:(NSString *)summaryLabel + price:(NSString *)price + paymentType:(NSString *)paymentType + squareLocationId:(NSString *)squareLocationId + buyerActionString:(NSString *)buyerActionString + moneyMap:(NSDictionary *)moneyMap + contactMap:(NSDictionary *)contactMap + paymentSourceId:(NSString *)paymentSourceId; +- (void)applyTheme:(NSDictionary *)theme; @end diff --git a/ios/Classes/FSQIPApplePay.m b/ios/Classes/FSQIPApplePay.m index 71f40b7..639e292 100644 --- a/ios/Classes/FSQIPApplePay.m +++ b/ios/Classes/FSQIPApplePay.m @@ -17,6 +17,11 @@ #import "FSQIPApplePay.h" #import "FSQIPErrorUtilities.h" #import "Converters/SQIPCardDetails+FSQIPAdditions.h" +#import "FSQIPBuyerVerification.h" +#import "Converters/SQIPCard+FSQIPAdditions.h" +#import "Converters/SQIPCardDetails+FSQIPAdditions.h" +#import "Converters/UIFont+FSQIPAdditions.h" +#import "Converters/UIColor+FSQIPAdditions.h" API_AVAILABLE(ios(11.0)) typedef void (^CompletionHandler)(PKPaymentAuthorizationResult *_Nonnull); @@ -28,9 +33,13 @@ @interface FSQIPApplePay () @property (strong, readwrite) FlutterMethodChannel *channel; @property (strong, readwrite) NSString *applePayMerchantId; @property (strong, readwrite) CompletionHandler completionHandler; +@property (strong, readwrite) SQIPTheme *theme; @end +static NSString *const FSQIPOnBuyerVerificationSuccessEventName = @"onBuyerVerificationSuccess"; +static NSString *const FSQIPOnBuyerVerificationErrorEventName = @"onBuyerVerificationError"; + // flutter plugin debug error codes static NSString *const FSQIPApplePayNotInitialized = @"fl_apple_pay_not_initialized"; static NSString *const FSQIPApplePayNotSupported = @"fl_apple_pay_not_supported"; @@ -45,6 +54,7 @@ @implementation FSQIPApplePay - (void)initWithMethodChannel:(FlutterMethodChannel *)channel { self.channel = channel; + self.theme = [[SQIPTheme alloc] init]; } - (void)initializeApplePay:(FlutterResult)result merchantId:(NSString *)merchantId @@ -104,6 +114,98 @@ - (void)requestApplePayNonce:(FlutterResult)result result(nil); } +- (void)requestApplePayNonceWithVerification:(FlutterResult)result + countryCode:(NSString *)countryCode + currencyCode:(NSString *)currencyCode + summaryLabel:(NSString *)summaryLabel + price:(NSString *)price + paymentType:(NSString *)paymentType + squareLocationId:(NSString *)squareLocationId + buyerActionString:(NSString *)buyerActionString + moneyMap:(NSDictionary *)moneyMap + contactMap:(NSDictionary *)contactMap + paymentSourceId:(NSString *)paymentSourceId; +{ + + if (!self.applePayMerchantId) { + result([FlutterError errorWithCode:FlutterInAppPaymentsUsageError + message:[FSQIPErrorUtilities pluginErrorMessageFromErrorCode:FSQIPApplePayNotInitialized] + details:[FSQIPErrorUtilities debugErrorObject:FSQIPApplePayNotInitialized debugMessage:FSQIPMessageApplePayNotInitialized]]); + return; + } + if (!SQIPInAppPaymentsSDK.canUseApplePay) { + result([FlutterError errorWithCode:FlutterInAppPaymentsUsageError + message:[FSQIPErrorUtilities pluginErrorMessageFromErrorCode:FSQIPApplePayNotSupported] + details:[FSQIPErrorUtilities debugErrorObject:FSQIPApplePayNotSupported debugMessage:FSQIPMessageApplePayNotSupported]]); + return; + } + + SQIPMoney * money = [self _getMoney:moneyMap]; + SQIPBuyerAction * buyerAction = [self _getBuyerAction:buyerActionString money:money]; + SQIPContact * contact = [self _getContact:contactMap]; + + SQIPVerificationParameters *params = [[SQIPVerificationParameters alloc] initWithPaymentSourceID:paymentSourceId + buyerAction:buyerAction + locationID:squareLocationId + contact:contact]; + + UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController; + if ([rootViewController isKindOfClass:[UINavigationController class]]) { + [rootViewController.navigationController popViewControllerAnimated:YES]; + } else { + [rootViewController dismissViewControllerAnimated:YES completion:nil]; + } + + [SQIPBuyerVerificationSDK.shared verifyWithParameters:params + theme:self.theme + viewController:rootViewController + success:^(SQIPBuyerVerifiedDetails *_Nonnull verifiedDetails) { + NSDictionary *verificationResult = + @{ + @"nonce" : paymentSourceId, + @"token" : verifiedDetails.verificationToken + }; + [self.channel invokeMethod:FSQIPOnBuyerVerificationSuccessEventName + arguments:verificationResult]; + + PKPaymentRequest *paymentRequest = + [PKPaymentRequest squarePaymentRequestWithMerchantIdentifier:self.applePayMerchantId + countryCode:countryCode + currencyCode:currencyCode]; + if ([paymentType isEqual: @"PENDING"]) { + paymentRequest.paymentSummaryItems = @[ + [PKPaymentSummaryItem summaryItemWithLabel:summaryLabel + amount:[NSDecimalNumber decimalNumberWithString:price] + type:PKPaymentSummaryItemTypePending] + ]; + } else { + paymentRequest.paymentSummaryItems = @[ + [PKPaymentSummaryItem summaryItemWithLabel:summaryLabel + amount:[NSDecimalNumber decimalNumberWithString:price] + type:PKPaymentSummaryItemTypeFinal] + ]; + } + + PKPaymentAuthorizationViewController *paymentAuthorizationViewController = + [[PKPaymentAuthorizationViewController alloc] initWithPaymentRequest:paymentRequest]; + + paymentAuthorizationViewController.delegate = self; + UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController; + [rootViewController presentViewController:paymentAuthorizationViewController animated:YES completion:nil]; + result(nil); + } + failure:^(NSError *_Nonnull error) { + NSString *debugCode = error.userInfo[SQIPErrorDebugCodeKey]; + NSString *debugMessage = error.userInfo[SQIPErrorDebugMessageKey]; + [self.channel invokeMethod:FSQIPOnBuyerVerificationErrorEventName + arguments:[FSQIPErrorUtilities callbackErrorObject:FlutterInAppPaymentsUsageError + message:error.localizedDescription + debugCode:debugCode + debugMessage:debugMessage]]; + }]; + result(nil); +} + - (void)completeApplePayAuthorization:(FlutterResult)result isSuccess:(BOOL)isSuccess errorMessage:(NSString *__nullable)errorMessage @@ -166,4 +268,126 @@ - (void)paymentAuthorizationViewControllerDidFinish:(nonnull PKPaymentAuthorizat [self.channel invokeMethod:@"onApplePayComplete" arguments:nil]; } + +- (void)applyTheme:(NSDictionary *)theme +{ + // Create a new theme with default value + self.theme = [[SQIPTheme alloc] init]; + if (theme[@"font"]) { + self.theme.font = [self.theme.font fromJsonDictionary:theme[@"font"]]; + } + if (theme[@"saveButtonFont"]) { + self.theme.saveButtonFont = [self.theme.saveButtonFont fromJsonDictionary:theme[@"saveButtonFont"]]; + } + if (theme[@"backgroundColor"]) { + self.theme.backgroundColor = [self.theme.backgroundColor fromJsonDictionary:theme[@"backgroundColor"]]; + } + if (theme[@"foregroundColor"]) { + self.theme.foregroundColor = [self.theme.foregroundColor fromJsonDictionary:theme[@"foregroundColor"]]; + } + if (theme[@"textColor"]) { + self.theme.textColor = [self.theme.textColor fromJsonDictionary:theme[@"textColor"]]; + } + if (theme[@"placeholderTextColor"]) { + self.theme.placeholderTextColor = [self.theme.placeholderTextColor fromJsonDictionary:theme[@"placeholderTextColor"]]; + } + if (theme[@"tintColor"]) { + self.theme.tintColor = [self.theme.tintColor fromJsonDictionary:theme[@"tintColor"]]; + } + if (theme[@"messageColor"]) { + self.theme.messageColor = [self.theme.messageColor fromJsonDictionary:theme[@"messageColor"]]; + } + if (theme[@"errorColor"]) { + self.theme.errorColor = [self.theme.errorColor fromJsonDictionary:theme[@"errorColor"]]; + } + if (theme[@"saveButtonTitle"]) { + self.theme.saveButtonTitle = theme[@"saveButtonTitle"]; + } + if (theme[@"saveButtonTextColor"]) { + self.theme.saveButtonTextColor = [self.theme.saveButtonTextColor fromJsonDictionary:theme[@"saveButtonTextColor"]]; + } + if (theme[@"keyboardAppearance"]) { + self.theme.keyboardAppearance = [self _keyboardAppearanceFromString:theme[@"keyboardAppearance"]]; + } +} + +#pragma mark - Private Methods +- (UIKeyboardAppearance)_keyboardAppearanceFromString:(NSString *)keyboardTypeName +{ + if ([keyboardTypeName isEqualToString:@"Dark"]) { + return UIKeyboardAppearanceDark; + } else if ([keyboardTypeName isEqualToString:@"Light"]) { + return UIKeyboardAppearanceLight; + } else { + return UIKeyboardAppearanceDefault; + } +} + +- (SQIPMoney *)_getMoney:(NSDictionary *)moneyMap { + SQIPMoney *money = [[SQIPMoney alloc] initWithAmount:[moneyMap[@"amount"] longValue] + currency:[FSQIPBuyerVerification currencyForCurrencyCode:moneyMap[@"currencyCode"]]]; + return money; +} + +- (SQIPBuyerAction *)_getBuyerAction:(NSString *)buyerActionString money:(SQIPMoney *)money { + SQIPBuyerAction *buyerAction = nil; + if ([@"Store" isEqualToString:buyerActionString]) { + buyerAction = [SQIPBuyerAction storeAction]; + } else { + buyerAction = [SQIPBuyerAction chargeActionWithMoney:money]; + } + return buyerAction; +} + +- (SQIPContact *)_getContact:(NSDictionary *)contactMap { + NSString *givenName = contactMap[@"givenName"]; + NSString *familyName = contactMap[@"familyName"]; + NSArray *addressLines = contactMap[@"addressLines"]; + NSString *city = contactMap[@"city"]; + NSString *countryCode = contactMap[@"countryCode"]; + NSString *email = contactMap[@"email"]; + NSString *phone = contactMap[@"phone"]; + NSString *postalCode = contactMap[@"postalCode"]; + NSString *region = contactMap[@"region"]; + + SQIPContact *contact = [[SQIPContact alloc] init]; + contact.givenName = givenName; + + if (![familyName isEqual:[NSNull null]]) { + contact.familyName = familyName; + } + + if (![email isEqual:[NSNull null]]) { + contact.email = email; + } + + if (![addressLines isEqual:[NSNull null]]) { + contact.addressLines = addressLines; + NSLog(@"%@", addressLines); + } + + if (![city isEqual:[NSNull null]]) { + contact.city = city; + } + + if (![region isEqual:[NSNull null]]) { + contact.region = region; + } + + if (![postalCode isEqual:[NSNull null]]) { + contact.postalCode = postalCode; + } + + if (![postalCode isEqual:[NSNull null]]) { + contact.postalCode = postalCode; + } + + contact.country = [FSQIPBuyerVerification countryForCountryCode:countryCode]; + + if (![phone isEqual:[NSNull null]]) { + contact.phone = phone; + } + return contact; +} + @end diff --git a/ios/Classes/FSQIPCardEntry.h b/ios/Classes/FSQIPCardEntry.h index 14ab31f..0d2725c 100644 --- a/ios/Classes/FSQIPCardEntry.h +++ b/ios/Classes/FSQIPCardEntry.h @@ -22,11 +22,12 @@ - (void)initWithMethodChannel:(FlutterMethodChannel *)channel; - (void)startCardEntryFlow:(FlutterResult)result collectPostalCode:(BOOL)collectPostalCode; -- (void)startCardEntryFlowWithVerification:(FlutterResult)result collectPostalCode:(BOOL)collectPostalCode locationId:(NSString *)locationId buyerActionString:(NSString *)buyerActionString moneyMap:(NSDictionary *)moneyMap contactMap:(NSDictionary *)contactMap; +- (void)startCardEntryFlowWithVerification:(FlutterResult)result collectPostalCode:(BOOL)collectPostalCode locationId:(NSString *)locationId buyerActionString:(NSString *)buyerActionString moneyMap:(NSDictionary *)moneyMap contactMap:(NSDictionary *)contactMap paymentSourceId:(NSString *)paymentSourceId; - (void)startGiftCardEntryFlow:(FlutterResult)result; +- (void)startGiftCardEntryFlowWithVerification:(FlutterResult)result locationId:(NSString *)locationId buyerActionString:(NSString *)buyerActionString moneyMap:(NSDictionary *)moneyMap contactMap:(NSDictionary *)contactMap paymentSourceId:(NSString *)paymentSourceId; - (void)completeCardEntry:(FlutterResult)result; - (void)showCardNonceProcessingError:(FlutterResult)result errorMessage:(NSString *)errorMessage; -- (void)setTheme:(FlutterResult)result theme:(NSDictionary *)theme; +- (void)applyTheme:(NSDictionary *)theme; - (void)startBuyerVerificationFlow:(FlutterResult)result buyerActionString:(NSString *)buyerActionString moneyMap:(NSDictionary *)moneyMap locationId:(NSString *)locationId contactMap:(NSDictionary *)contactMap paymentSourceId:(NSString *)paymentSourceId; @end diff --git a/ios/Classes/FSQIPCardEntry.m b/ios/Classes/FSQIPCardEntry.m index 7edba4d..b452a2c 100644 --- a/ios/Classes/FSQIPCardEntry.m +++ b/ios/Classes/FSQIPCardEntry.m @@ -70,7 +70,7 @@ - (void)startCardEntryFlow:(FlutterResult)result collectPostalCode:(BOOL)collect result(nil); } -- (void)startCardEntryFlowWithVerification:(FlutterResult)result collectPostalCode:(BOOL)collectPostalCode locationId:(NSString *)locationId buyerActionString:(NSString *)buyerActionString moneyMap:(NSDictionary *)moneyMap contactMap:(NSDictionary *)contactMap +- (void)startCardEntryFlowWithVerification:(FlutterResult)result collectPostalCode:(BOOL)collectPostalCode locationId:(NSString *)locationId buyerActionString:(NSString *)buyerActionString moneyMap:(NSDictionary *)moneyMap contactMap:(NSDictionary *)contactMap paymentSourceId:(NSString *)paymentSourceId { SQIPMoney * money = [self _getMoney:moneyMap]; SQIPBuyerAction * buyerAction = [self _getBuyerAction:buyerActionString money:money]; @@ -79,18 +79,53 @@ - (void)startCardEntryFlowWithVerification:(FlutterResult)result collectPostalCo self.locationId = locationId; self.buyerAction = buyerAction; self.contact = contact; - SQIPCardEntryViewController *cardEntryForm = [self _makeCardEntryForm]; - cardEntryForm.collectPostalCode = collectPostalCode; - cardEntryForm.delegate = self; - self.cardEntryViewController = cardEntryForm; + SQIPVerificationParameters *params = [[SQIPVerificationParameters alloc] initWithPaymentSourceID:paymentSourceId + buyerAction:self.buyerAction + locationID:self.locationId + contact:self.contact]; + UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController; if ([rootViewController isKindOfClass:[UINavigationController class]]) { - [((UINavigationController *)rootViewController) pushViewController:cardEntryForm animated:YES]; + [rootViewController.navigationController popViewControllerAnimated:YES]; } else { - UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:cardEntryForm]; - [rootViewController presentViewController:navigationController animated:YES completion:nil]; + [rootViewController dismissViewControllerAnimated:YES completion:nil]; } + + [SQIPBuyerVerificationSDK.shared verifyWithParameters:params + theme:self.theme + viewController:rootViewController + success:^(SQIPBuyerVerifiedDetails *_Nonnull verifiedDetails) { + NSDictionary *verificationResult = + @{ + @"nonce" : paymentSourceId, + @"token" : verifiedDetails.verificationToken + }; + [self.channel invokeMethod:FSQIPOnBuyerVerificationSuccessEventName arguments:verificationResult]; + + SQIPCardEntryViewController *cardEntryForm = [self _makeCardEntryForm]; + cardEntryForm.collectPostalCode = collectPostalCode; + cardEntryForm.delegate = self; + self.cardEntryViewController = cardEntryForm; + self.contact = nil; + + UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController; + if ([rootViewController isKindOfClass:[UINavigationController class]]) { + [((UINavigationController *)rootViewController) pushViewController:cardEntryForm animated:YES]; + } else { + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:cardEntryForm]; + [rootViewController presentViewController:navigationController animated:YES completion:nil]; + } + } + failure:^(NSError *_Nonnull error) { + NSString *debugCode = error.userInfo[SQIPErrorDebugCodeKey]; + NSString *debugMessage = error.userInfo[SQIPErrorDebugMessageKey]; + [self.channel invokeMethod:FSQIPOnBuyerVerificationErrorEventName + arguments:[FSQIPErrorUtilities callbackErrorObject:FlutterInAppPaymentsUsageError + message:error.localizedDescription + debugCode:debugCode + debugMessage:debugMessage]]; + }]; result(nil); } @@ -111,6 +146,66 @@ - (void)startGiftCardEntryFlow:(FlutterResult)result result(nil); } +- (void)startGiftCardEntryFlowWithVerification:(FlutterResult)result locationId:(NSString *)locationId buyerActionString:(NSString *)buyerActionString moneyMap:(NSDictionary *)moneyMap contactMap:(NSDictionary *)contactMap paymentSourceId:(NSString *)paymentSourceId +{ + SQIPMoney * money = [self _getMoney:moneyMap]; + SQIPBuyerAction * buyerAction = [self _getBuyerAction:buyerActionString money:money]; + SQIPContact * contact = [self _getContact:contactMap]; + + self.locationId = locationId; + self.buyerAction = buyerAction; + self.contact = contact; + + SQIPVerificationParameters *params = [[SQIPVerificationParameters alloc] initWithPaymentSourceID:paymentSourceId + buyerAction:self.buyerAction + locationID:self.locationId + contact:self.contact]; + + UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController; + if ([rootViewController isKindOfClass:[UINavigationController class]]) { + [rootViewController.navigationController popViewControllerAnimated:YES]; + } else { + [rootViewController dismissViewControllerAnimated:YES completion:nil]; + } + + [SQIPBuyerVerificationSDK.shared verifyWithParameters:params + theme:self.theme + viewController:rootViewController + success:^(SQIPBuyerVerifiedDetails *_Nonnull verifiedDetails) { + NSDictionary *verificationResult = + @{ + @"nonce" : paymentSourceId, + @"token" : verifiedDetails.verificationToken + }; + [self.channel invokeMethod:FSQIPOnBuyerVerificationSuccessEventName + arguments:verificationResult]; + SQIPCardEntryViewController *cardEntryForm = [self _makeGiftCardEntryForm]; + cardEntryForm.delegate = self; + self.cardEntryViewController = cardEntryForm; + self.contact = nil; + + UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController; + if ([rootViewController isKindOfClass:[UINavigationController class]]) { + [((UINavigationController *)rootViewController) pushViewController:cardEntryForm animated:YES]; + } else { + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:cardEntryForm]; + [rootViewController presentViewController:navigationController animated:YES completion:nil]; + } + } + failure:^(NSError *_Nonnull error) { + NSString *debugCode = error.userInfo[SQIPErrorDebugCodeKey]; + NSString *debugMessage = error.userInfo[SQIPErrorDebugMessageKey]; + [self.channel invokeMethod:FSQIPOnBuyerVerificationErrorEventName + arguments:[FSQIPErrorUtilities callbackErrorObject:FlutterInAppPaymentsUsageError + message:error.localizedDescription + debugCode:debugCode + debugMessage:debugMessage]]; + }]; + result(nil); +} + + + #pragma mark - SQIPCardEntryViewControllerDelegate - (void)cardEntryViewController:(SQIPCardEntryViewController *)cardEntryViewController didObtainCardDetails:(SQIPCardDetails *)cardDetails completionHandler:(CompletionHandler)completionHandler @@ -211,7 +306,7 @@ - (void)showCardNonceProcessingError:(FlutterResult)result errorMessage:(NSStrin result(nil); } -- (void)setTheme:(FlutterResult)result theme:(NSDictionary *)theme +- (void)applyTheme:(NSDictionary *)theme { // Create a new theme with default value self.theme = [[SQIPTheme alloc] init]; @@ -251,8 +346,6 @@ - (void)setTheme:(FlutterResult)result theme:(NSDictionary *)theme if (theme[@"keyboardAppearance"]) { self.theme.keyboardAppearance = [self _keyboardAppearanceFromString:theme[@"keyboardAppearance"]]; } - - result(nil); } - (void)startBuyerVerificationFlow:(FlutterResult)result buyerActionString:(NSString *)buyerActionString moneyMap:(NSDictionary *)moneyMap locationId:(NSString *)locationId contactMap:(NSDictionary *)contactMap paymentSourceId:(NSString *)paymentSourceId diff --git a/ios/Classes/FSQIPSecureRemoteCommerce.h b/ios/Classes/FSQIPSecureRemoteCommerce.h index 8d970b5..574e2e9 100644 --- a/ios/Classes/FSQIPSecureRemoteCommerce.h +++ b/ios/Classes/FSQIPSecureRemoteCommerce.h @@ -16,10 +16,13 @@ #import @import SquareInAppPaymentsSDK; +@import SquareBuyerVerificationSDK; @interface FSQIPSecureRemoteCommerce : NSObject - (void)initWithMethodChannel:(FlutterMethodChannel *)channel; - (void)startSecureRemoteCommerce:(FlutterResult) result amount:(NSInteger)amount; +- (void)startSecureRemoteCommerceWithVerification:(FlutterResult) result amount:(NSInteger)amount buyerActionString:(NSString *)buyerActionString moneyMap:(NSDictionary *)moneyMap contactMap:(NSDictionary *)contactMap squareLocationId:(NSString *)squareLocationId paymentSourceId:(NSString *)paymentSourceId; +- (void)applyTheme:(NSDictionary *)theme; @end diff --git a/ios/Classes/FSQIPSecureRemoteCommerce.m b/ios/Classes/FSQIPSecureRemoteCommerce.m index 50da0bb..60bb923 100644 --- a/ios/Classes/FSQIPSecureRemoteCommerce.m +++ b/ios/Classes/FSQIPSecureRemoteCommerce.m @@ -15,15 +15,22 @@ */ #import "FSQIPErrorUtilities.h" +#import "FSQIPBuyerVerification.h" #import "FSQIPSecureRemoteCommerce.h" #import "Converters/SQIPCardDetails+FSQIPAdditions.h" +#import "Converters/SQIPCard+FSQIPAdditions.h" +#import "Converters/UIFont+FSQIPAdditions.h" +#import "Converters/UIColor+FSQIPAdditions.h" @interface FSQIPSecureRemoteCommerce () @property (strong, readwrite) FlutterMethodChannel *channel; +@property (strong, readwrite) SQIPTheme *theme; @end +static NSString *const FSQIPOnBuyerVerificationSuccessEventName = @"onBuyerVerificationSuccess"; +static NSString *const FSQIPOnBuyerVerificationErrorEventName = @"onBuyerVerificationError"; static NSString *const FSQIPOnMaterCardNonceRequestSuccessEventName = @"OnMaterCardNonceRequestSuccess"; static NSString *const FSQIPOnMasterCardNonceRequestFailureEventName = @"OnMasterCardNonceRequestFailure"; @@ -31,6 +38,7 @@ @implementation FSQIPSecureRemoteCommerce - (void)initWithMethodChannel:(FlutterMethodChannel *)channel{ self.channel = channel; + self.theme = [[SQIPTheme alloc] init]; } - (void)startSecureRemoteCommerce:(FlutterResult)result amount:(NSInteger)amount{ @@ -58,4 +66,188 @@ - (void)startSecureRemoteCommerce:(FlutterResult)result amount:(NSInteger)amount result(nil); } +- (void)startSecureRemoteCommerceWithVerification:(FlutterResult)result amount:(NSInteger)amount buyerActionString:(NSString *)buyerActionString moneyMap:(NSDictionary *)moneyMap contactMap:(NSDictionary *)contactMap squareLocationId:(NSString *)squareLocationId paymentSourceId:(NSString *)paymentSourceId{ + SQIPMoney * money = [self _getMoney:moneyMap]; + SQIPBuyerAction * buyerAction = [self _getBuyerAction:buyerActionString money:money]; + SQIPContact * contact = [self _getContact:contactMap]; + + SQIPVerificationParameters *params = [[SQIPVerificationParameters alloc] initWithPaymentSourceID:paymentSourceId + buyerAction:buyerAction + locationID:squareLocationId + contact:contact]; + + UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController; + if ([rootViewController isKindOfClass:[UINavigationController class]]) { + [rootViewController.navigationController popViewControllerAnimated:YES]; + } else { + [rootViewController dismissViewControllerAnimated:YES completion:nil]; + } + + [SQIPBuyerVerificationSDK.shared verifyWithParameters:params + theme:self.theme + viewController:rootViewController + success:^(SQIPBuyerVerifiedDetails *_Nonnull verifiedDetails) { + NSDictionary *verificationResult = + @{ + @"nonce" : paymentSourceId, + @"token" : verifiedDetails.verificationToken + }; + [self.channel invokeMethod:FSQIPOnBuyerVerificationSuccessEventName + arguments:verificationResult]; + + UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController; + + SQIPSecureRemoteCommerceParameters params; + params.amount = amount; + + [[SQIPSecureRemoteCommerce alloc] + createPaymentRequest: rootViewController + secureRemoteCommerceParameters: params + completionHandler:^(SQIPCardDetails * _Nullable cardDetails, NSError * _Nullable error) { + if(cardDetails != NULL){ + [self.channel invokeMethod:FSQIPOnMaterCardNonceRequestSuccessEventName arguments:[cardDetails jsonDictionary]]; + }else if (error != NULL){ + NSString *debugCode = error.userInfo[SQIPErrorDebugCodeKey]; + NSString *debugMessage = error.userInfo[SQIPErrorDebugMessageKey]; + [self.channel invokeMethod:FSQIPOnMasterCardNonceRequestFailureEventName + arguments:[FSQIPErrorUtilities callbackErrorObject:FlutterInAppPaymentsUsageError + message:error.localizedDescription + debugCode:debugCode + debugMessage:debugMessage]]; + } + }]; + } + failure:^(NSError *_Nonnull error) { + NSString *debugCode = error.userInfo[SQIPErrorDebugCodeKey]; + NSString *debugMessage = error.userInfo[SQIPErrorDebugMessageKey]; + [self.channel invokeMethod:FSQIPOnBuyerVerificationErrorEventName + arguments:[FSQIPErrorUtilities callbackErrorObject:FlutterInAppPaymentsUsageError + message:error.localizedDescription + debugCode:debugCode + debugMessage:debugMessage]]; + }]; + result(nil); + +} + +- (void)applyTheme:(NSDictionary *)theme +{ + // Create a new theme with default value + self.theme = [[SQIPTheme alloc] init]; + if (theme[@"font"]) { + self.theme.font = [self.theme.font fromJsonDictionary:theme[@"font"]]; + } + if (theme[@"saveButtonFont"]) { + self.theme.saveButtonFont = [self.theme.saveButtonFont fromJsonDictionary:theme[@"saveButtonFont"]]; + } + if (theme[@"backgroundColor"]) { + self.theme.backgroundColor = [self.theme.backgroundColor fromJsonDictionary:theme[@"backgroundColor"]]; + } + if (theme[@"foregroundColor"]) { + self.theme.foregroundColor = [self.theme.foregroundColor fromJsonDictionary:theme[@"foregroundColor"]]; + } + if (theme[@"textColor"]) { + self.theme.textColor = [self.theme.textColor fromJsonDictionary:theme[@"textColor"]]; + } + if (theme[@"placeholderTextColor"]) { + self.theme.placeholderTextColor = [self.theme.placeholderTextColor fromJsonDictionary:theme[@"placeholderTextColor"]]; + } + if (theme[@"tintColor"]) { + self.theme.tintColor = [self.theme.tintColor fromJsonDictionary:theme[@"tintColor"]]; + } + if (theme[@"messageColor"]) { + self.theme.messageColor = [self.theme.messageColor fromJsonDictionary:theme[@"messageColor"]]; + } + if (theme[@"errorColor"]) { + self.theme.errorColor = [self.theme.errorColor fromJsonDictionary:theme[@"errorColor"]]; + } + if (theme[@"saveButtonTitle"]) { + self.theme.saveButtonTitle = theme[@"saveButtonTitle"]; + } + if (theme[@"saveButtonTextColor"]) { + self.theme.saveButtonTextColor = [self.theme.saveButtonTextColor fromJsonDictionary:theme[@"saveButtonTextColor"]]; + } + if (theme[@"keyboardAppearance"]) { + self.theme.keyboardAppearance = [self _keyboardAppearanceFromString:theme[@"keyboardAppearance"]]; + } +} + +#pragma mark - Private Methods +- (UIKeyboardAppearance)_keyboardAppearanceFromString:(NSString *)keyboardTypeName +{ + if ([keyboardTypeName isEqualToString:@"Dark"]) { + return UIKeyboardAppearanceDark; + } else if ([keyboardTypeName isEqualToString:@"Light"]) { + return UIKeyboardAppearanceLight; + } else { + return UIKeyboardAppearanceDefault; + } +} + +- (SQIPMoney *)_getMoney:(NSDictionary *)moneyMap { + SQIPMoney *money = [[SQIPMoney alloc] initWithAmount:[moneyMap[@"amount"] longValue] + currency:[FSQIPBuyerVerification currencyForCurrencyCode:moneyMap[@"currencyCode"]]]; + return money; +} + +- (SQIPBuyerAction *)_getBuyerAction:(NSString *)buyerActionString money:(SQIPMoney *)money { + SQIPBuyerAction *buyerAction = nil; + if ([@"Store" isEqualToString:buyerActionString]) { + buyerAction = [SQIPBuyerAction storeAction]; + } else { + buyerAction = [SQIPBuyerAction chargeActionWithMoney:money]; + } + return buyerAction; +} + +- (SQIPContact *)_getContact:(NSDictionary *)contactMap { + NSString *givenName = contactMap[@"givenName"]; + NSString *familyName = contactMap[@"familyName"]; + NSArray *addressLines = contactMap[@"addressLines"]; + NSString *city = contactMap[@"city"]; + NSString *countryCode = contactMap[@"countryCode"]; + NSString *email = contactMap[@"email"]; + NSString *phone = contactMap[@"phone"]; + NSString *postalCode = contactMap[@"postalCode"]; + NSString *region = contactMap[@"region"]; + + SQIPContact *contact = [[SQIPContact alloc] init]; + contact.givenName = givenName; + + if (![familyName isEqual:[NSNull null]]) { + contact.familyName = familyName; + } + + if (![email isEqual:[NSNull null]]) { + contact.email = email; + } + + if (![addressLines isEqual:[NSNull null]]) { + contact.addressLines = addressLines; + NSLog(@"%@", addressLines); + } + + if (![city isEqual:[NSNull null]]) { + contact.city = city; + } + + if (![region isEqual:[NSNull null]]) { + contact.region = region; + } + + if (![postalCode isEqual:[NSNull null]]) { + contact.postalCode = postalCode; + } + + if (![postalCode isEqual:[NSNull null]]) { + contact.postalCode = postalCode; + } + + contact.country = [FSQIPBuyerVerification countryForCountryCode:countryCode]; + + if (![phone isEqual:[NSNull null]]) { + contact.phone = phone; + } + return contact; +} @end diff --git a/ios/Classes/SquareInAppPaymentsFlutterPlugin.m b/ios/Classes/SquareInAppPaymentsFlutterPlugin.m index 3f52c3a..2daf76a 100644 --- a/ios/Classes/SquareInAppPaymentsFlutterPlugin.m +++ b/ios/Classes/SquareInAppPaymentsFlutterPlugin.m @@ -68,25 +68,44 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result [self.cardEntryModule startCardEntryFlow:result collectPostalCode:collectPostalCode]; } else if ([@"startGiftCardEntryFlow" isEqualToString:call.method]) { [self.cardEntryModule startGiftCardEntryFlow:result]; + } else if ([@"startGiftCardEntryFlowWithBuyerVerification" isEqualToString:call.method]) { + NSString *paymentSourceId = call.arguments[@"paymentSourceId"]; + NSString *squareLocationId = call.arguments[@"squareLocationId"]; + NSString *buyerActionString = call.arguments[@"buyerAction"]; + NSDictionary *moneyMap = call.arguments[@"money"]; + NSDictionary *contactMap = call.arguments[@"contact"]; + + [self.cardEntryModule startGiftCardEntryFlowWithVerification:result + locationId:squareLocationId + buyerActionString:buyerActionString + moneyMap:moneyMap + contactMap:contactMap + paymentSourceId:paymentSourceId]; } else if ([@"startCardEntryFlowWithBuyerVerification" isEqualToString:call.method]) { BOOL collectPostalCode = [call.arguments[@"collectPostalCode"] boolValue]; + NSString *paymentSourceId = call.arguments[@"paymentSourceId"]; NSString *squareLocationId = call.arguments[@"squareLocationId"]; NSString *buyerActionString = call.arguments[@"buyerAction"]; NSDictionary *moneyMap = call.arguments[@"money"]; NSDictionary *contactMap = call.arguments[@"contact"]; + [self.cardEntryModule startCardEntryFlowWithVerification:result collectPostalCode:collectPostalCode locationId:squareLocationId buyerActionString:buyerActionString moneyMap:moneyMap - contactMap:contactMap]; + contactMap:contactMap + paymentSourceId:paymentSourceId]; } else if ([@"completeCardEntry" isEqualToString:call.method]) { [self.cardEntryModule completeCardEntry:result]; } else if ([@"showCardNonceProcessingError" isEqualToString:call.method]) { [self.cardEntryModule showCardNonceProcessingError:result errorMessage:call.arguments[@"errorMessage"]]; } else if ([@"setFormTheme" isEqualToString:call.method]) { NSDictionary *theme = call.arguments[@"theme"]; - [self.cardEntryModule setTheme:result theme:theme]; + [self.applePayModule applyTheme:theme]; + [self.cardEntryModule applyTheme:theme]; + [self.secureRemoteCommerceModule applyTheme:theme]; + result(nil); } else if ([@"initializeApplePay" isEqualToString:call.method]) { [self.applePayModule initializeApplePay:result merchantId:call.arguments[@"merchantId"]]; } else if ([@"canUseApplePay" isEqualToString:call.method]) { @@ -103,6 +122,31 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result summaryLabel:summaryLabel price:price paymentType:paymentType]; + } else if ([@"requestApplePayNonceWithBuyerVerification" isEqualToString:call.method]) { + NSString *price = call.arguments[@"price"]; + NSString *summaryLabel = call.arguments[@"summaryLabel"]; + NSString *countryCode = call.arguments[@"countryCode"]; + NSString *currencyCode = call.arguments[@"currencyCode"]; + NSString *paymentType = call.arguments[@"paymentType"]; + + NSString *paymentSourceId = call.arguments[@"paymentSourceId"]; + NSString *squareLocationId = call.arguments[@"squareLocationId"]; + NSString *buyerActionString = call.arguments[@"buyerAction"]; + NSDictionary *moneyMap = call.arguments[@"money"]; + NSDictionary *contactMap = call.arguments[@"contact"]; + + [self.applePayModule requestApplePayNonceWithVerification:result + countryCode:countryCode + currencyCode:currencyCode + summaryLabel:summaryLabel + price:price + paymentType:paymentType + squareLocationId:squareLocationId + buyerActionString:buyerActionString + moneyMap:moneyMap + contactMap:contactMap + paymentSourceId:paymentSourceId]; + } else if ([@"completeApplePayAuthorization" isEqualToString:call.method]) { BOOL isSuccess = [call.arguments[@"isSuccess"] boolValue]; NSString *errorMessage = call.arguments[@"errorMessage"]; @@ -123,6 +167,20 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result NSNumber *amount = call.arguments[@"amount"]; [self.secureRemoteCommerceModule startSecureRemoteCommerce:result amount:amount.integerValue]; + } else if ([@"startSecureRemoteCommerceWithBuyerVerification" isEqualToString:call.method]) { + NSNumber *amount = call.arguments[@"amount"]; + NSString *buyerActionString = call.arguments[@"buyerAction"]; + NSDictionary *moneyMap = call.arguments[@"money"]; + NSDictionary *contactMap = call.arguments[@"contact"]; + NSString *squareLocationId = call.arguments[@"squareLocationId"]; + NSString *paymentSourceId = call.arguments[@"paymentSourceId"]; + [self.secureRemoteCommerceModule startSecureRemoteCommerceWithVerification:result + amount:amount.integerValue + buyerActionString:buyerActionString + moneyMap:moneyMap + contactMap:contactMap + squareLocationId:squareLocationId + paymentSourceId:paymentSourceId]; } else { result(FlutterMethodNotImplemented); } diff --git a/lib/in_app_payments.dart b/lib/in_app_payments.dart index aac5b49..c8728e6 100644 --- a/lib/in_app_payments.dart +++ b/lib/in_app_payments.dart @@ -273,6 +273,47 @@ class InAppPayments { } } + static Future requestGooglePayNonceWithBuyerVerification({ + required BuyerVerificationSuccessCallback onBuyerVerificationSuccess, + required BuyerVerificationErrorCallback onBuyerVerificationFailure, + required String buyerAction, + required Money money, + required String squareLocationId, + required Contact contact, + required String paymentSourceId, + required String price, + required String currencyCode, + required int priceStatus, + required GooglePayNonceRequestSuccessCallback + onGooglePayNonceRequestSuccess, + required GooglePayNonceRequestFailureCallback + onGooglePayNonceRequestFailure, + required GooglePayCancelCallback onGooglePayCanceled, + }) async { + assert(price.isNotEmpty, 'price should not be empty.'); + assert(currencyCode.isNotEmpty, 'currencyCode should not be empty.'); + _buyerVerificationSuccessCallback = onBuyerVerificationSuccess; + _buyerVerificationErrorCallback = onBuyerVerificationFailure; + _googlePayNonceRequestSuccessCallback = onGooglePayNonceRequestSuccess; + _googlePayNonceRequestFailureCallback = onGooglePayNonceRequestFailure; + _googlePayCancelCallback = onGooglePayCanceled; + + var params = { + 'buyerAction': buyerAction, + 'money': _standardSerializers.serializeWith(Money.serializer, money), + 'contact': + _standardSerializers.serializeWith(Contact.serializer, contact), + 'squareLocationId': squareLocationId, + 'paymentSourceId': paymentSourceId, + 'price': price, + 'currencyCode': currencyCode, + 'priceStatus': priceStatus, + }; + + await _channel.invokeMethod( + 'requestGooglePayNonceWithBuyerVerification', params); + } + static Future initializeApplePay(String applePayMerchantId) async { assert(applePayMerchantId.isNotEmpty, 'applePayMerchantId should not be empty.'); @@ -325,6 +366,54 @@ class InAppPayments { } } + static Future requestApplePayNonceWithBuyerVerification( + {required BuyerVerificationSuccessCallback onBuyerVerificationSuccess, + required BuyerVerificationErrorCallback onBuyerVerificationFailure, + required String buyerAction, + required Money money, + required String squareLocationId, + required Contact contact, + required String paymentSourceId, + required String price, + required String summaryLabel, + required String countryCode, + required String currencyCode, + required ApplePayPaymentType paymentType, + required ApplePayNonceRequestSuccessCallback + onApplePayNonceRequestSuccess, + required ApplePayNonceRequestFailureCallback + onApplePayNonceRequestFailure, + required ApplePayCompleteCallback onApplePayComplete}) async { + assert(summaryLabel.isNotEmpty, 'summaryLabel should not be empty.'); + assert(price.isNotEmpty, 'price should not be empty.'); + assert(countryCode.isNotEmpty, 'countryCode should not be empty.'); + assert(currencyCode.isNotEmpty, 'currencyCode should not be empty.'); + + _buyerVerificationSuccessCallback = onBuyerVerificationSuccess; + _buyerVerificationErrorCallback = onBuyerVerificationFailure; + _applePayNonceRequestSuccessCallback = onApplePayNonceRequestSuccess; + _applePayNonceRequestFailureCallback = onApplePayNonceRequestFailure; + _applePayCompleteCallback = onApplePayComplete; + + var params = { + 'buyerAction': buyerAction, + 'money': _standardSerializers.serializeWith(Money.serializer, money), + 'contact': + _standardSerializers.serializeWith(Contact.serializer, contact), + 'squareLocationId': squareLocationId, + 'paymentSourceId': paymentSourceId, + 'price': price, + 'summaryLabel': summaryLabel, + 'countryCode': countryCode, + 'currencyCode': currencyCode, + 'paymentType': _standardSerializers.serializeWith( + ApplePayPaymentType.serializer, paymentType), + }; + + await _channel.invokeMethod( + 'requestApplePayNonceWithBuyerVerification', params); + } + static Future completeApplePayAuthorization( {required bool isSuccess, String errorMessage = ''}) async { var params = { @@ -338,14 +427,19 @@ class InAppPayments { {required BuyerVerificationSuccessCallback onBuyerVerificationSuccess, required BuyerVerificationErrorCallback onBuyerVerificationFailure, required CardEntryCancelCallback onCardEntryCancel, + required CardEntryCardNonceRequestSuccessCallback + onCardNonceRequestSuccess, required String buyerAction, required Money money, required String squareLocationId, required Contact contact, + required String paymentSourceId, bool collectPostalCode = true}) async { _buyerVerificationSuccessCallback = onBuyerVerificationSuccess; _buyerVerificationErrorCallback = onBuyerVerificationFailure; _cardEntryCancelCallback = onCardEntryCancel; + _cardEntryCardNonceRequestSuccessCallback = onCardNonceRequestSuccess; + var params = { 'buyerAction': buyerAction, 'money': _standardSerializers.serializeWith(Money.serializer, money), @@ -353,11 +447,40 @@ class InAppPayments { _standardSerializers.serializeWith(Contact.serializer, contact), 'squareLocationId': squareLocationId, 'collectPostalCode': collectPostalCode, + 'paymentSourceId': paymentSourceId, }; await _channel.invokeMethod( 'startCardEntryFlowWithBuyerVerification', params); } + static Future startGiftCardEntryFlowWithBuyerVerification({ + required BuyerVerificationSuccessCallback onBuyerVerificationSuccess, + required BuyerVerificationErrorCallback onBuyerVerificationFailure, + required CardEntryCancelCallback onCardEntryCancel, + required CardEntryCardNonceRequestSuccessCallback onCardNonceRequestSuccess, + required String buyerAction, + required Money money, + required String squareLocationId, + required Contact contact, + required String paymentSourceId, + }) async { + _buyerVerificationSuccessCallback = onBuyerVerificationSuccess; + _buyerVerificationErrorCallback = onBuyerVerificationFailure; + _cardEntryCancelCallback = onCardEntryCancel; + _cardEntryCardNonceRequestSuccessCallback = onCardNonceRequestSuccess; + + var params = { + 'buyerAction': buyerAction, + 'money': _standardSerializers.serializeWith(Money.serializer, money), + 'contact': + _standardSerializers.serializeWith(Contact.serializer, contact), + 'squareLocationId': squareLocationId, + 'paymentSourceId': paymentSourceId, + }; + await _channel.invokeMethod( + 'startGiftCardEntryFlowWithBuyerVerification', params); + } + static Future startBuyerVerificationFlow( {required BuyerVerificationSuccessCallback onBuyerVerificationSuccess, required BuyerVerificationErrorCallback onBuyerVerificationFailure, @@ -397,6 +520,36 @@ class InAppPayments { var params = {'amount': amount}; await _channel.invokeMethod('startSecureRemoteCommerce', params); } + + static Future startSecureRemoteCommerceWithBuyerVerification( + {required BuyerVerificationSuccessCallback onBuyerVerificationSuccess, + required BuyerVerificationErrorCallback onBuyerVerificationFailure, + required String buyerAction, + required Money money, + required String squareLocationId, + required Contact contact, + required String paymentSourceId, + required int amount, + required MasterCardNonceRequestSuccessCallback + onMaterCardNonceRequestSuccess, + required MasterCardNonceRequestFailureCallback + onMasterCardNonceRequestFailure}) async { + _buyerVerificationSuccessCallback = onBuyerVerificationSuccess; + _buyerVerificationErrorCallback = onBuyerVerificationFailure; + _masterCardNonceRequestSuccessCallback = onMaterCardNonceRequestSuccess; + _masterCardNonceRequestFailureCallback = onMasterCardNonceRequestFailure; + var params = { + 'buyerAction': buyerAction, + 'money': _standardSerializers.serializeWith(Money.serializer, money), + 'contact': + _standardSerializers.serializeWith(Contact.serializer, contact), + 'squareLocationId': squareLocationId, + 'paymentSourceId': paymentSourceId, + 'amount': amount, + }; + await _channel.invokeMethod( + 'startSecureRemoteCommerceWithBuyerVerification', params); + } } class InAppPaymentsException implements Exception {