diff --git a/.gitignore b/.gitignore index 020c425..5be94be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,38 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IJ +# +.idea +.gradle +local.properties + +# node.js +# +node_modules/ +npm-debug.log + /android/build +/android/react-native-paypal.iml /.tern-port diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..eb9dee3 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ios/lib/Paypal"] + path = ios/lib/Paypal + url = git@github.com:paypal/PayPal-iOS-SDK.git diff --git a/README.md b/README.md index fae0550..812e1e9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ A React Native interface for the PayPal Payment UI # Setup +Android +------- + 1. Add react-navive-paypal to your project ``` bash @@ -145,7 +148,39 @@ PayPal.paymentRequest(...).catch(function (error_code) { }) ``` +iOS +--- + ### TODO: -- Automated tests -- iOS version -- Future payment (subscriptions) + +* [ ] Refactor & cleanup +* [ ] Automated tests +* [ ] Future payment (subscriptions) + +### Installation + +Currently you have to install via `npm` from GitHub (or change the version specifier in `package.json` to `zeroseven/react-native-paypal#ios`): + +```shell +npm install --save zeroseven/react-native-paypal#ios +``` + +#### Install the PayPal-iOS-SDK + +You then have to install the PayPal-iOS-SDK into `node_modules/react-native-paypal/ios/lib/Paypal` + +Here's a one-liner to download and unpack version `2.13.0`: + +```shell +mkdir -p node_modules/react-native-paypal/ios/lib/Paypal && curl -L --progress https://github.com/paypal/PayPal-iOS-SDK/archive/2.13.0.tar.gz | tar -xz - -C node_modules/react-native-paypal/ios/lib/Paypal --strip-components=1 +``` + +Include PayPal as normally, following their directions. Their integration steps and iOS SDK can be found [here](https://github.com/paypal/PayPal-iOS-SDK). After doing that, also drag MFLReactNativePayPal.h and MFLReactNativePayPal.m into your project. + +#### Add `MFLReactNativePayPal.xcodeproj` + +Add `node_modules/react-native-paypal/ios/MFLReactNativePayPal.xcodeproj` +to the `Libraries` group in iOS and link `libMFLReactNativePayPal.a` as described in Step 2 of the +[React Native Manual Linking docs](https://facebook.github.io/react-native/docs/linking-libraries-ios.html#manual-linking). + +Follow steps 4 and 5 of [the PayPal instalation instructions](https://github.com/paypal/PayPal-iOS-SDK#if-you-dont-use-cocoapods-then), as well as the [additional steps here](https://github.com/paypal/PayPal-iOS-SDK#with-or-without-cocoapods). **This has to be done for the main app, not for the library you included.** diff --git a/android/src/main/java/br/com/vizir/rn/paypal/PayPal.java b/android/src/main/java/br/com/vizir/rn/paypal/PayPal.java index d47300a..f7fbfb3 100644 --- a/android/src/main/java/br/com/vizir/rn/paypal/PayPal.java +++ b/android/src/main/java/br/com/vizir/rn/paypal/PayPal.java @@ -3,12 +3,15 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.net.Uri; +import android.util.Log; import com.facebook.react.bridge.Callback; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.Promise; import com.paypal.android.sdk.payments.PayPalAuthorization; import com.paypal.android.sdk.payments.PayPalConfiguration; @@ -16,14 +19,18 @@ import com.paypal.android.sdk.payments.PayPalService; import com.paypal.android.sdk.payments.PaymentActivity; import com.paypal.android.sdk.payments.PaymentConfirmation; +import com.paypal.android.sdk.payments.PayPalFuturePaymentActivity; +import com.paypal.android.sdk.payments.PayPalProfileSharingActivity; +import com.paypal.android.sdk.payments.PayPalOAuthScopes; import java.util.Map; import java.util.HashMap; import java.math.BigDecimal; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; public class PayPal extends ReactContextBaseJavaModule { - private final int paymentIntentRequestCode; - private static final String ERROR_USER_CANCELLED = "USER_CANCELLED"; private static final String ERROR_INVALID_CONFIG = "INVALID_CONFIG"; @@ -33,11 +40,14 @@ public class PayPal extends ReactContextBaseJavaModule { private Context activityContext; private Activity currentActivity; - public PayPal(ReactApplicationContext reactContext, Context activityContext, int requestCode) { + private static final int REQUEST_CODE_PAYMENT = 179 + 1; + private static final int REQUEST_CODE_FUTURE_PAYMENT = 179 + 2; + private static final int REQUEST_CODE_PROFILE_SHARING = 179 + 3; + + public PayPal(ReactApplicationContext reactContext, Context activityContext) { super(reactContext); this.activityContext = activityContext; this.currentActivity = (Activity)activityContext; - this.paymentIntentRequestCode = requestCode; } @Override @@ -57,6 +67,90 @@ public String getName() { return constants; } + @ReactMethod + public void getMetadataId( + final Callback successCallback, + final Callback errorCallback + ) { + try { + String metadataId = PayPalConfiguration.getClientMetadataId(currentActivity); + successCallback.invoke(metadataId); + } catch (Exception e) { + errorCallback.invoke(e); + } + } + + @ReactMethod + public void shareProfile( + final ReadableMap payPalParameters, + final Callback successCallback, + final Callback errorCallback + ) { + this.successCallback = successCallback; + this.errorCallback = errorCallback; + + final String environment = payPalParameters.getString("environment"); + final String clientId = payPalParameters.getString("clientId"); + final String merchantName = payPalParameters.getString("merchantName"); + final String policyUri = payPalParameters.getString("policyUri"); + final String agreementUri = payPalParameters.getString("agreementUri"); + + PayPalConfiguration config = new PayPalConfiguration() + .environment(environment) + .clientId(clientId) + .merchantName(merchantName) + .merchantPrivacyPolicyUri(Uri.parse(policyUri)) + .merchantUserAgreementUri(Uri.parse(agreementUri)); + + // start service + Intent serviceIntent = new Intent(currentActivity, PayPalService.class); + serviceIntent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, config); + currentActivity.startService(serviceIntent); + + // // start activity + Intent activityIntent = + new Intent(activityContext, PayPalProfileSharingActivity.class) + .putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, config) + .putExtra(PayPalProfileSharingActivity.EXTRA_REQUESTED_SCOPES, getOauthScopes()); + + currentActivity.startActivityForResult(activityIntent, REQUEST_CODE_PROFILE_SHARING); + } + + @ReactMethod + public void futurePayment( + final ReadableMap payPalParameters, + final Callback successCallback, + final Callback errorCallback + ) { + this.successCallback = successCallback; + this.errorCallback = errorCallback; + + final String environment = payPalParameters.getString("environment"); + final String clientId = payPalParameters.getString("clientId"); + final String merchantName = payPalParameters.getString("merchantName"); + final String policyUri = payPalParameters.getString("policyUri"); + final String agreementUri = payPalParameters.getString("agreementUri"); + + PayPalConfiguration config = new PayPalConfiguration() + .environment(environment) + .clientId(clientId) + .merchantName(merchantName) + .merchantPrivacyPolicyUri(Uri.parse(policyUri)) + .merchantUserAgreementUri(Uri.parse(agreementUri)); + + // start service + Intent serviceIntent = new Intent(currentActivity, PayPalService.class); + serviceIntent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, config); + currentActivity.startService(serviceIntent); + + // // start activity + Intent activityIntent = + new Intent(activityContext, PayPalFuturePaymentActivity.class) + .putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, config); + + currentActivity.startActivityForResult(activityIntent, REQUEST_CODE_FUTURE_PAYMENT); + } + @ReactMethod public void paymentRequest( final ReadableMap payPalParameters, @@ -86,7 +180,7 @@ public void paymentRequest( .putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, config) .putExtra(PaymentActivity.EXTRA_PAYMENT, thingToBuy); - currentActivity.startActivityForResult(intent, paymentIntentRequestCode); + currentActivity.startActivityForResult(intent, REQUEST_CODE_PAYMENT); } private void startPayPalService(PayPalConfiguration config) { @@ -95,9 +189,47 @@ private void startPayPalService(PayPalConfiguration config) { currentActivity.startService(intent); } - public void handleActivityResult(final int requestCode, final int resultCode, final Intent data) { - if (requestCode != paymentIntentRequestCode) { return; } + private PayPalOAuthScopes getOauthScopes() { + /* create the set of required scopes + * Note: see https://developer.paypal.com/docs/integration/direct/identity/attributes/ for mapping between the + * attributes you select for this app in the PayPal developer portal and the scopes required here. + */ + Set scopes = new HashSet( + Arrays.asList(PayPalOAuthScopes.PAYPAL_SCOPE_EMAIL, PayPalOAuthScopes.PAYPAL_SCOPE_ADDRESS) ); + return new PayPalOAuthScopes(scopes); + } + + private void handleShareProfileActivityResult(final int resultCode, final Intent data) { + if (resultCode == Activity.RESULT_OK) { + PayPalAuthorization auth = data + .getParcelableExtra(PayPalProfileSharingActivity.EXTRA_RESULT_AUTHORIZATION); + if (auth != null) { + String authorization_code = auth.getAuthorizationCode(); + successCallback.invoke(authorization_code); + } + } else if (resultCode == Activity.RESULT_CANCELED) { + errorCallback.invoke(ERROR_USER_CANCELLED); + } else if (resultCode == PaymentActivity.RESULT_EXTRAS_INVALID) { + errorCallback.invoke(ERROR_INVALID_CONFIG); + } + } + + private void handleFutureActivityResult(final int resultCode, final Intent data) { + if (resultCode == Activity.RESULT_OK) { + PayPalAuthorization auth = data + .getParcelableExtra(PayPalFuturePaymentActivity.EXTRA_RESULT_AUTHORIZATION); + if (auth != null) { + String authorization_code = auth.getAuthorizationCode(); + successCallback.invoke(authorization_code); + } + } else if (resultCode == Activity.RESULT_CANCELED) { + errorCallback.invoke(ERROR_USER_CANCELLED); + } else if (resultCode == PaymentActivity.RESULT_EXTRAS_INVALID) { + errorCallback.invoke(ERROR_INVALID_CONFIG); + } + } + private void handlePaymentActivityResult(final int resultCode, final Intent data) { if (resultCode == Activity.RESULT_OK) { PaymentConfirmation confirm = data.getParcelableExtra(PaymentActivity.EXTRA_RESULT_CONFIRMATION); @@ -112,6 +244,18 @@ public void handleActivityResult(final int requestCode, final int resultCode, fi } else if (resultCode == PaymentActivity.RESULT_EXTRAS_INVALID) { errorCallback.invoke(ERROR_INVALID_CONFIG); } + } + + public void handleActivityResult(final int requestCode, final int resultCode, final Intent data) { + if (requestCode == REQUEST_CODE_FUTURE_PAYMENT) { + handleFutureActivityResult(resultCode, data); + } else if (requestCode == REQUEST_CODE_PAYMENT) { + handlePaymentActivityResult(resultCode, data); + } else if (requestCode == REQUEST_CODE_PROFILE_SHARING) { + handleShareProfileActivityResult(resultCode, data); + } else { + return; + } currentActivity.stopService(new Intent(currentActivity, PayPalService.class)); } diff --git a/android/src/main/java/br/com/vizir/rn/paypal/PayPalPackage.java b/android/src/main/java/br/com/vizir/rn/paypal/PayPalPackage.java index 59496d6..7ce86bb 100644 --- a/android/src/main/java/br/com/vizir/rn/paypal/PayPalPackage.java +++ b/android/src/main/java/br/com/vizir/rn/paypal/PayPalPackage.java @@ -2,6 +2,7 @@ import android.content.Intent; import android.content.Context; +import android.util.Log; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.JavaScriptModule; @@ -16,17 +17,15 @@ public class PayPalPackage implements ReactPackage { private Context context; private PayPal paypalModule; - private int paymentIntentRequestCode; - public PayPalPackage(Context activityContext, int paymentIntentRequestCode) { + public PayPalPackage(Context activityContext) { context = activityContext; - this.paymentIntentRequestCode = paymentIntentRequestCode; } @Override public List createNativeModules(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); - paypalModule = new PayPal(reactContext, context, paymentIntentRequestCode); + paypalModule = new PayPal(reactContext, context); modules.add(paypalModule); return modules; diff --git a/index.js b/index.js index 11dee5c..07ceeb3 100644 --- a/index.js +++ b/index.js @@ -1,17 +1,80 @@ 'use strict'; +var {NativeModules, Platform} = require('react-native') +var {PayPal, MFLReactNativePayPal} = NativeModules; -var {PayPal} = require('react-native').NativeModules; +var constants; -var constants = {}; -var constantNames = Object.keys(PayPal).filter(p => p == p.toUpperCase()); -constantNames.forEach(c => constants[c] = PayPal[c]); +if (Platform.OS === 'android') { + constants = {}; + var constantNames = Object.keys(PayPal).filter(p => p == p.toUpperCase()); + constantNames.forEach(c => constants[c] = PayPal[c]); +} else { + constants = { + SANDBOX: 0, + PRODUCTION: 1, + NO_NETWORK: 2, + + USER_CANCELLED: 'USER_CANCELLED', + INVALID_CONFIG: 'INVALID_CONFIG' + } +} var functions = { - paymentRequest(payPalParameters) { + futurePayment(payPalParameters) { + if (Platform.OS === 'android') { + return new Promise(function(resolve, reject) { + let successCallback = (result) => { + resolve(result); + }; + PayPal.futurePayment(payPalParameters, successCallback, reject); + }); + } else { + return new Promise(function(resolve, reject) { + let callback = (result) => { + result ? resolve(result) : reject(result); + }; + MFLReactNativePayPal.futurePayment( + payPalParameters.clientId, + payPalParameters.environment, + payPalParameters.merchantName, + payPalParameters.policyUri, + payPalParameters.agreementUri, + callback + ); + }); + } + }, + + shareProfile(payPalParameters) { + if (Platform.OS === 'android') { + return new Promise(function(resolve, reject) { + let successCallback = (code) => { + resolve({response: {code}}); + }; + PayPal.shareProfile(payPalParameters, successCallback, reject); + }); + } else { + return new Promise(function(resolve, reject) { + let callback = (result) => { + result ? resolve(result) : reject(result); + }; + MFLReactNativePayPal.shareProfile( + payPalParameters.clientId, + payPalParameters.environment, + payPalParameters.merchantName, + payPalParameters.policyUri, + payPalParameters.agreementUri, + callback + ); + }); + } + }, + + getMetadataId() { return new Promise(function(resolve, reject) { - PayPal.paymentRequest(payPalParameters, resolve, reject); + PayPal.getMetadataId(resolve, reject); }); - } + }, }; var exported = {}; diff --git a/ios/.flowconfig b/ios/.flowconfig new file mode 100644 index 0000000..a86e0f8 --- /dev/null +++ b/ios/.flowconfig @@ -0,0 +1,36 @@ +[ignore] + +# We fork some components by platform. +.*/*.web.js +.*/*.android.js + +# Some modules have their own node_modules with overlap +.*/node_modules/node-haste/.* + +# Ignore react-tools where there are overlaps, but don't ignore anything that +# react-native relies on +.*/node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js +.*/node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js +.*/node_modules/react-tools/src/browser/ui/React.js +.*/node_modules/react-tools/src/core/ReactInstanceHandles.js +.*/node_modules/react-tools/src/event/EventPropagators.js + +# Ignore commoner tests +.*/node_modules/react-tools/node_modules/commoner/test/.* + +# See https://github.com/facebook/flow/issues/442 +.*/react-tools/node_modules/commoner/lib/reader.js + +# Ignore jest +.*/react-native/node_modules/jest-cli/.* + +[include] + +[libs] +node_modules/react-native/Libraries/react-native/react-native-interface.js + +[options] +module.system=haste + +[version] +0.11.0 diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..b927355 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,28 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# node.js +# +node_modules/ +npm-debug.log diff --git a/ios/.npmignore b/ios/.npmignore new file mode 100644 index 0000000..c39012e --- /dev/null +++ b/ios/.npmignore @@ -0,0 +1,27 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate + +# node.js +# +node_modules/ +npm-debug.log diff --git a/ios/LICENSE.md b/ios/LICENSE.md new file mode 100644 index 0000000..8d183ab --- /dev/null +++ b/ios/LICENSE.md @@ -0,0 +1,10 @@ +``` +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Tj Fallon + * ---------------------------------------------------------------------------- + */ +``` diff --git a/ios/MFLReactNativePayPal.xcodeproj/project.pbxproj b/ios/MFLReactNativePayPal.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5dbf25a --- /dev/null +++ b/ios/MFLReactNativePayPal.xcodeproj/project.pbxproj @@ -0,0 +1,280 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 64D7A23C1C46907F008FCDA3 /* MFLReactNativePayPal.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 64D7A23B1C46907F008FCDA3 /* MFLReactNativePayPal.h */; }; + 64D7A23E1C46907F008FCDA3 /* MFLReactNativePayPal.m in Sources */ = {isa = PBXBuildFile; fileRef = 64D7A23D1C46907F008FCDA3 /* MFLReactNativePayPal.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 64D7A2361C46907F008FCDA3 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + 64D7A23C1C46907F008FCDA3 /* MFLReactNativePayPal.h in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 64D7A2381C46907F008FCDA3 /* libMFLReactNativePayPal.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMFLReactNativePayPal.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 64D7A23B1C46907F008FCDA3 /* MFLReactNativePayPal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MFLReactNativePayPal.h; sourceTree = ""; }; + 64D7A23D1C46907F008FCDA3 /* MFLReactNativePayPal.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MFLReactNativePayPal.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 64D7A2351C46907F008FCDA3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 64D7A22F1C46907F008FCDA3 = { + isa = PBXGroup; + children = ( + 64D7A23A1C46907F008FCDA3 /* MFLReactNativePayPal */, + 64D7A2391C46907F008FCDA3 /* Products */, + ); + sourceTree = ""; + }; + 64D7A2391C46907F008FCDA3 /* Products */ = { + isa = PBXGroup; + children = ( + 64D7A2381C46907F008FCDA3 /* libMFLReactNativePayPal.a */, + ); + name = Products; + sourceTree = ""; + }; + 64D7A23A1C46907F008FCDA3 /* MFLReactNativePayPal */ = { + isa = PBXGroup; + children = ( + 64D7A23B1C46907F008FCDA3 /* MFLReactNativePayPal.h */, + 64D7A23D1C46907F008FCDA3 /* MFLReactNativePayPal.m */, + ); + path = MFLReactNativePayPal; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 64D7A2371C46907F008FCDA3 /* MFLReactNativePayPal */ = { + isa = PBXNativeTarget; + buildConfigurationList = 64D7A2411C46907F008FCDA3 /* Build configuration list for PBXNativeTarget "MFLReactNativePayPal" */; + buildPhases = ( + 64D7A2341C46907F008FCDA3 /* Sources */, + 64D7A2351C46907F008FCDA3 /* Frameworks */, + 64D7A2361C46907F008FCDA3 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MFLReactNativePayPal; + productName = MFLReactNativePayPal; + productReference = 64D7A2381C46907F008FCDA3 /* libMFLReactNativePayPal.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 64D7A2301C46907F008FCDA3 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0720; + TargetAttributes = { + 64D7A2371C46907F008FCDA3 = { + CreatedOnToolsVersion = 7.2; + }; + }; + }; + buildConfigurationList = 64D7A2331C46907F008FCDA3 /* Build configuration list for PBXProject "MFLReactNativePayPal" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 64D7A22F1C46907F008FCDA3; + productRefGroup = 64D7A2391C46907F008FCDA3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 64D7A2371C46907F008FCDA3 /* MFLReactNativePayPal */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 64D7A2341C46907F008FCDA3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 64D7A23E1C46907F008FCDA3 /* MFLReactNativePayPal.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 64D7A23F1C46907F008FCDA3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 64D7A2401C46907F008FCDA3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 64D7A2421C46907F008FCDA3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/node_modules/react-native/React/**", + "$(SRCROOT)/../../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/lib/Paypal/CardIO", + "$(PROJECT_DIR)/lib/Paypal/PayPalMobile", + ); + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = ( + "-lc++", + "-ObjC", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 64D7A2431C46907F008FCDA3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/node_modules/react-native/React/**", + "$(SRCROOT)/../../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/lib/Paypal/CardIO", + "$(PROJECT_DIR)/lib/Paypal/PayPalMobile", + ); + OTHER_LDFLAGS = ( + "-lc++", + "-ObjC", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 64D7A2331C46907F008FCDA3 /* Build configuration list for PBXProject "MFLReactNativePayPal" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 64D7A23F1C46907F008FCDA3 /* Debug */, + 64D7A2401C46907F008FCDA3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 64D7A2411C46907F008FCDA3 /* Build configuration list for PBXNativeTarget "MFLReactNativePayPal" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 64D7A2421C46907F008FCDA3 /* Debug */, + 64D7A2431C46907F008FCDA3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 64D7A2301C46907F008FCDA3 /* Project object */; +} diff --git a/ios/MFLReactNativePayPal/MFLReactNativePayPal.h b/ios/MFLReactNativePayPal/MFLReactNativePayPal.h new file mode 100644 index 0000000..f341aaa --- /dev/null +++ b/ios/MFLReactNativePayPal/MFLReactNativePayPal.h @@ -0,0 +1,27 @@ +// +// MFLReactNativePayPal.h +// ReactPaypal +// +// Created by Tj on 6/22/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import + +typedef NS_ENUM(NSInteger, PayPalEnvironment) +{ + kPayPalEnvironmentSandbox, + kPayPalEnvironmentProduction, + kPayPalEnvironmentSandboxNoNetwork +}; + +typedef NS_ENUM(NSInteger, PaymentCompletionStatus) +{ + kPayPalPaymentCanceled, + kPayPalPaymentCompleted +}; + + +@interface MFLReactNativePayPal : NSObject + +@end diff --git a/ios/MFLReactNativePayPal/MFLReactNativePayPal.m b/ios/MFLReactNativePayPal/MFLReactNativePayPal.m new file mode 100644 index 0000000..47b6421 --- /dev/null +++ b/ios/MFLReactNativePayPal/MFLReactNativePayPal.m @@ -0,0 +1,151 @@ +// +// MFLReactNativePayPal.m +// ReactPaypal +// +// Created by Tj on 6/22/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import "MFLReactNativePayPal.h" +#import "RCTBridge.h" +#import "PayPalMobile.h" + + +NSString * const kPayPalPaymentStatusKey = @"status"; +NSString * const kPayPalPaymentConfirmationKey = @"confirmation"; + +@interface MFLReactNativePayPal () + +@property (copy) RCTResponseSenderBlock flowCompletedCallback; +@property(nonatomic, strong, readwrite) PayPalConfiguration *payPalConfig; + +@end + +@implementation MFLReactNativePayPal + +RCT_EXPORT_MODULE(); + +RCT_EXPORT_METHOD(futurePayment:(NSString *)clientId + forEnv:(int)environment + forMerchantName:(NSString *)merchantName + forMerchantPolicy:(NSString *)merchantPrivacyPolicyURL + forMerchantAgreement:(NSString *)merchantUserAgreementURL + forCallback:(RCTResponseSenderBlock)flowCompletedCallback) + +{ + NSString *envString = [self stringFromEnvironmentEnum:environment]; + + NSLog(clientId); + + [PayPalMobile initializeWithClientIdsForEnvironments:@{envString : clientId}]; + [PayPalMobile preconnectWithEnvironment:envString]; + + _payPalConfig = [[PayPalConfiguration alloc] init]; + + _payPalConfig.acceptCreditCards = YES; + _payPalConfig.merchantName = merchantName; + _payPalConfig.merchantPrivacyPolicyURL = [NSURL URLWithString:merchantPrivacyPolicyURL]; + _payPalConfig.merchantUserAgreementURL = [NSURL URLWithString:merchantUserAgreementURL]; + + self.flowCompletedCallback = flowCompletedCallback; + + PayPalFuturePaymentViewController *vc = [[PayPalFuturePaymentViewController alloc] initWithConfiguration:_payPalConfig delegate:self]; + + UIViewController *visibleVC = [[[UIApplication sharedApplication] keyWindow] rootViewController]; + do { + if ([visibleVC isKindOfClass:[UINavigationController class]]) { + visibleVC = [(UINavigationController *)visibleVC visibleViewController]; + } else if (visibleVC.presentedViewController) { + visibleVC = visibleVC.presentedViewController; + } + } while (visibleVC.presentedViewController); + + [visibleVC presentViewController:vc animated:YES completion:^{ + // self.flowCompletedCallback(@[[NSNull null]]); + }]; +} + +RCT_EXPORT_METHOD(shareProfile:(NSString *)clientId + forEnv:(int)environment + forMerchantName:(NSString *)merchantName + forMerchantPolicy:(NSString *)merchantPrivacyPolicyURL + forMerchantAgreement:(NSString *)merchantUserAgreementURL + forCallback:(RCTResponseSenderBlock)flowCompletedCallback) + +{ + NSString *envString = [self stringFromEnvironmentEnum:environment]; + + [PayPalMobile initializeWithClientIdsForEnvironments:@{envString : clientId}]; + [PayPalMobile preconnectWithEnvironment:envString]; + + NSLog(@"SHARE!"); + + _payPalConfig = [[PayPalConfiguration alloc] init]; + + _payPalConfig.merchantName = merchantName; + _payPalConfig.merchantPrivacyPolicyURL = [NSURL URLWithString:merchantPrivacyPolicyURL]; + _payPalConfig.merchantUserAgreementURL = [NSURL URLWithString:merchantUserAgreementURL]; + + self.flowCompletedCallback = flowCompletedCallback; + + NSSet *scopeValues = [NSSet setWithArray:@[kPayPalOAuth2ScopeEmail, kPayPalOAuth2ScopeFuturePayments, kPayPalOAuth2ScopePhone]]; + PayPalProfileSharingViewController *vc = [[PayPalProfileSharingViewController alloc] initWithScopeValues:scopeValues configuration:_payPalConfig delegate:self]; + + UIViewController *visibleVC = [[[UIApplication sharedApplication] keyWindow] rootViewController]; + do { + if ([visibleVC isKindOfClass:[UINavigationController class]]) { + visibleVC = [(UINavigationController *)visibleVC visibleViewController]; + } else if (visibleVC.presentedViewController) { + visibleVC = visibleVC.presentedViewController; + } + } while (visibleVC.presentedViewController); + + [visibleVC presentViewController:vc animated:YES completion:^{ + // self.flowCompletedCallback(@[[NSNull null]]); + }]; +} + +- (void)payPalProfileSharingViewController:(PayPalProfileSharingViewController *)profileSharingViewController + userDidLogInWithAuthorization:(NSDictionary *)profileSharingAuthorization { + [profileSharingViewController.presentingViewController dismissViewControllerAnimated:YES completion:^{ + NSLog(@"SUCCESS"); + + if (self.flowCompletedCallback) { + self.flowCompletedCallback(@[profileSharingAuthorization]); + } + }]; +} + +- (void)userDidCancelPayPalProfileSharingViewController:(PayPalProfileSharingViewController *)profileSharingViewController { + NSLog(@"PayPal Profile Sharing Authorization Canceled"); + [profileSharingViewController.presentingViewController dismissViewControllerAnimated:YES completion:^{ + self.flowCompletedCallback(@[[NSNull null]]); + }]; +} + +- (void)payPalFuturePaymentViewController:(PayPalFuturePaymentViewController *)futurePaymentViewController + didAuthorizeFuturePayment:(NSDictionary *)futurePaymentAuthorization +{ + [futurePaymentViewController.presentingViewController dismissViewControllerAnimated:YES completion:^{ + if (self.flowCompletedCallback) { + self.flowCompletedCallback(@[futurePaymentAuthorization]); + } + }]; +} + +- (void)payPalFuturePaymentDidCancel:(PayPalFuturePaymentViewController *)futurePaymentViewController { + [futurePaymentViewController.presentingViewController dismissViewControllerAnimated:YES completion:^{ + self.flowCompletedCallback(@[[NSNull null]]); + }]; +} + +- (NSString *)stringFromEnvironmentEnum:(PayPalEnvironment)env +{ + switch (env) { + case kPayPalEnvironmentProduction: return PayPalEnvironmentProduction; + case kPayPalEnvironmentSandbox: return PayPalEnvironmentSandbox; + case kPayPalEnvironmentSandboxNoNetwork: return PayPalEnvironmentNoNetwork; + } +} + +@end \ No newline at end of file diff --git a/ios/MFLReactNativePayPal/MFLReactNativePayPal.old.m b/ios/MFLReactNativePayPal/MFLReactNativePayPal.old.m new file mode 100644 index 0000000..5c58cf6 --- /dev/null +++ b/ios/MFLReactNativePayPal/MFLReactNativePayPal.old.m @@ -0,0 +1,131 @@ +// +// MFLReactNativePayPal.m +// ReactPaypal +// +// Created by Tj on 6/22/15. +// Copyright (c) 2015 Facebook. All rights reserved. +// + +#import "MFLReactNativePayPal.h" +#import "RCTBridge.h" +#import "PayPalMobile.h" + +NSString * const kPayPalPaymentStatusKey = @"status"; +NSString * const kPayPalPaymentConfirmationKey = @"confirmation"; + +@interface MFLReactNativePayPal () + +@property PayPalPayment *payment; +@property PayPalConfiguration *configuration; +@property (copy) RCTResponseSenderBlock flowCompletedCallback; + +@end + +@implementation MFLReactNativePayPal + +RCT_EXPORT_MODULE(); + +RCT_EXPORT_METHOD(initializePaypalEnvironment:(int)environment + forClientId:(NSString *)clientId ) +{ + dispatch_async(dispatch_get_main_queue(), ^{ + NSString *envString = [self stringFromEnvironmentEnum:environment]; + + [PayPalMobile initializeWithClientIdsForEnvironments:@{envString : clientId}]; + [PayPalMobile preconnectWithEnvironment:envString]; + }); +} + +#pragma mark React Exported Methods + +RCT_EXPORT_METHOD(preparePaymentOfAmount:(NSString *)amount + ofCurrency:(NSString *)currencyCode + withDescription:(NSString *)description) +{ + self.payment = [[PayPalPayment alloc] init]; + [self.payment setAmount:[[NSDecimalNumber alloc] initWithString:amount]]; + [self.payment setCurrencyCode:currencyCode]; + [self.payment setShortDescription:description]; +} + + +RCT_EXPORT_METHOD(prepareConfigurationForMerchant:(NSString *)merchantName + acceptingCreditCards:(BOOL)shouldAcceptCreditCards + withUserEmail:(NSString *)userEmail) +{ + self.configuration = [[PayPalConfiguration alloc] init]; + [self.configuration setMerchantName:merchantName]; + [self.configuration setAcceptCreditCards:shouldAcceptCreditCards]; + [self.configuration setDefaultUserEmail:userEmail]; +} + +RCT_EXPORT_METHOD(presentPaymentViewControllerForPreparedPurchase:(RCTResponseSenderBlock)flowCompletedCallback) +{ + self.flowCompletedCallback = flowCompletedCallback; + + PayPalPaymentViewController *vc = [[PayPalPaymentViewController alloc] initWithPayment:self.payment + configuration:self.configuration + delegate:self]; + + UIViewController *visibleVC = [[[UIApplication sharedApplication] keyWindow] rootViewController]; + do { + if ([visibleVC isKindOfClass:[UINavigationController class]]) { + visibleVC = [(UINavigationController *)visibleVC visibleViewController]; + } else if (visibleVC.presentedViewController) { + visibleVC = visibleVC.presentedViewController; + } + } while (visibleVC.presentedViewController); + + [visibleVC presentViewController:vc animated:YES completion:nil]; +} + +#pragma mark Paypal Delegate + +- (void)payPalPaymentDidCancel:(PayPalPaymentViewController *)paymentViewController +{ + [paymentViewController.presentingViewController dismissViewControllerAnimated:YES completion:^{ + if (self.flowCompletedCallback) { + self.flowCompletedCallback(@[[NSNull null], @{kPayPalPaymentStatusKey : @(kPayPalPaymentCanceled)}]); + } + }]; +} + +- (void)payPalPaymentViewController:(PayPalPaymentViewController *)paymentViewController + didCompletePayment:(PayPalPayment *)completedPayment +{ + [paymentViewController.presentingViewController dismissViewControllerAnimated:YES completion:^{ + if (self.flowCompletedCallback) { + self.flowCompletedCallback(@[[NSNull null], @{kPayPalPaymentStatusKey : @(kPayPalPaymentCompleted), + kPayPalPaymentConfirmationKey : completedPayment.confirmation}]); + } + }]; +} + +#pragma mark Utilities + +- (NSString *)stringFromEnvironmentEnum:(PayPalEnvironment)env +{ + switch (env) { + case kPayPalEnvironmentProduction: return PayPalEnvironmentProduction; + case kPayPalEnvironmentSandbox: return PayPalEnvironmentSandbox; + case kPayPalEnvironmentSandboxNoNetwork: return PayPalEnvironmentNoNetwork; + } +} + +@end +// RCT_EXPORT_METHOD(presentPaymentViewControllerForPreparedPurchase:(NSString *)clientId +// forEnv:(int)environment +// forMerchantName:(NSString *)merchantName +// forMerchantPolicy:(NSString *)merchantPrivacyPolicyURL +// forMerchantAgreement:(NSString *)merchantUserAgreementURL +// forCallback:(RCTResponseSenderBlock)flowCompletedCallback) + +// { +// NSString *envString = [self stringFromEnvironmentEnum:environment]; + +// _payPalConfig = [[PayPalConfiguration alloc] init]; + +// _payPalConfig.acceptCreditCards = YES; +// _payPalConfig.merchantName = @"Awesome Shirts, Inc."; +// _payPalConfig.merchantPrivacyPolicyURL = [NSURL URLWithString:@"https://www.paypal.com/webapps/mpp/ua/privacy-full"]; +// _payPalConfig.merchantUserAgreementURL = [NSURL URLWithString:@"https://www.paypal.com/webapps/mpp/ua/useragreement-full"]; \ No newline at end of file diff --git a/ios/lib/Paypal b/ios/lib/Paypal new file mode 160000 index 0000000..efcfab9 --- /dev/null +++ b/ios/lib/Paypal @@ -0,0 +1 @@ +Subproject commit efcfab9308aba9382b0019ea17196731ba64e1b1 diff --git a/package.json b/package.json index 46502a8..0233855 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,14 @@ "react-component", "react-native", "pay-pal", - "android" + "android", + "ios" ], "author": "contato@vizir.com.br", + "contributors": [ + "iOSGuy (http://github.com/MattFoley)", + "Timo Uhlmann (http://github.com/amiuhle)" + ], "license": "MIT", "bugs": { "url": "https://github.com/vizir/react-native-paypal/issues"