feat(ios): add initial API and iOS implementation
This commit is contained in:
commit
c124d500c0
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
*.csproj.user
|
||||
*.suo
|
||||
*.cache
|
||||
Thumbs.db
|
||||
*.DS_Store
|
||||
|
||||
*.bak
|
||||
*.cache
|
||||
*.log
|
||||
*.swp
|
||||
*.user
|
||||
|
||||
|
||||
scripts/ios/build/
|
||||
scripts/ios/Pods
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
79
README.md
Normal file
79
README.md
Normal file
@ -0,0 +1,79 @@
|
||||
# Stripe Native Payments
|
||||
Plugin for Cordova to use the [native android SDK](https://github.com/stripe/stripe-android) in Java and the [native iOS SDK](https://github.com/stripe/stripe-ios) using Swift from [Stripe](https://www.stripe.com/)
|
||||
|
||||
## Installing the plugin ##
|
||||
```
|
||||
cordova plugin add cordova-plugin-filepickerio --save
|
||||
```
|
||||
|
||||
## Using the plugin ##
|
||||
|
||||
|
||||
### Response format ##
|
||||
|
||||
|
||||
## Difference between iOS and Android
|
||||
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Thanks for considering contributing to this project.
|
||||
|
||||
### Finding something to do
|
||||
|
||||
Ask, or pick an issue and comment on it announcing your desire to work on it. Ideally wait until we assign it to you to minimize work duplication.
|
||||
|
||||
### Reporting an issue
|
||||
|
||||
- Search existing issues before raising a new one.
|
||||
|
||||
- Include as much detail as possible.
|
||||
|
||||
### Pull requests
|
||||
|
||||
- Make it clear in the issue tracker what you are working on, so that someone else doesn't duplicate the work.
|
||||
|
||||
- Use a feature branch, not master.
|
||||
|
||||
- Rebase your feature branch onto origin/master before raising the PR.
|
||||
|
||||
- Keep up to date with changes in master so your PR is easy to merge.
|
||||
|
||||
- Be descriptive in your PR message: what is it for, why is it needed, etc.
|
||||
|
||||
- Make sure the tests pass
|
||||
|
||||
- Squash related commits as much as possible.
|
||||
|
||||
### Coding style
|
||||
|
||||
- Try to match the existing indent style.
|
||||
|
||||
- Don't mix platform-specific stuff into the main code.
|
||||
|
||||
|
||||
|
||||
## Licence ##
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Rolamix Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
36
package.json
Normal file
36
package.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "cordova-plugin-stripe-payments",
|
||||
"description": "Stripe Card Entry plugin for Cordova. Available for Android and iOS.",
|
||||
"version": "0.0.5",
|
||||
"homepage": "https://github.com/rolamix/cordova-plugin-stripe-payments#readme",
|
||||
"author": "Rolamix <contact@rolamix.com> (https://rolamix.com)",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rolamix/cordova-plugin-stripe-payments.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/rolamix/cordova-plugin-stripe-payments/issues"
|
||||
},
|
||||
"cordova": {
|
||||
"id": "cordova-plugin-stripe-payments",
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
"cordova",
|
||||
"phonegap",
|
||||
"swift",
|
||||
"cordova-ios",
|
||||
"cordova-android",
|
||||
"stripe",
|
||||
"stripe payments",
|
||||
"card",
|
||||
"apple pay",
|
||||
"google pay",
|
||||
"ach",
|
||||
"credit card"
|
||||
]
|
||||
}
|
73
plugin.xml
Normal file
73
plugin.xml
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
id="cordova-plugin-stripe-payments"
|
||||
version="0.0.5">
|
||||
|
||||
<name>Stripe Payments</name>
|
||||
<description>Cordova plugin for Stripe payments using the native Android/iOS SDKs. Supports Apple Pay and card payments.</description>
|
||||
<repo>https://github.com/rolamix/cordova-plugin-stripe-payments</repo>
|
||||
<license>MIT</license>
|
||||
<keywords>cordova,stripe,payments,apple pay,credit cards,checkout</keywords>
|
||||
<issue>https://github.com/rolamix/cordova-plugin-stripe-payments/issues</issue>
|
||||
|
||||
<engines>
|
||||
<engine name="cordova" version=">=7.1.0"/>
|
||||
<engine name="cordova-android" version=">=7.1.0"/>
|
||||
<!-- If installing on Cordova IOS 5.0, make sure to add
|
||||
<preference name="SwiftVersion" value="4.1" />
|
||||
to your config.xml under the ios platform section.
|
||||
If installing on Cordova iOS < 5.0, you need to add cordova-plugin-add-swift-support
|
||||
to your project and specify <preference name="UseSwiftLanguageVersion" value="4" />
|
||||
in your config.xml file under the ios platform section. -->
|
||||
<engine name="cordova-ios" version=">=4.5.0"/>
|
||||
</engines>
|
||||
|
||||
<js-module src="www/StripePaymentsPlugin.js" name="StripePaymentsPlugin">
|
||||
<clobbers target="window.plugins.StripePaymentsPlugin" />
|
||||
</js-module>
|
||||
|
||||
<!-- Android -->
|
||||
<platform name="android">
|
||||
<config-file target="res/xml/config.xml" parent="/*">
|
||||
<feature name="StripePaymentsPlugin">
|
||||
<param name="android-package" value="com.rolamix.plugins.stripe.StripePaymentsPlugin"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<source-file src="src/android/StripePaymentsPlugin.java" target-dir="src/com/rolamix/plugins/stripe/" />
|
||||
<framework src="src/android/StripePaymentsPlugin.gradle" custom="true" type="gradleReference" />
|
||||
</platform>
|
||||
|
||||
|
||||
<!-- iOS -->
|
||||
<platform name="ios">
|
||||
<config-file target="config.xml" parent="/*">
|
||||
<feature name="StripePaymentsPlugin">
|
||||
<param name="ios-package" value="StripePaymentsPlugin"/>
|
||||
</feature>
|
||||
</config-file>
|
||||
|
||||
<framework src="Stripe" type="podspec" spec="~> 15.0.0" />
|
||||
<framework src="Alamofire" type="podspec" spec="~> 5.0.0-beta.3" />
|
||||
<!-- https://github.com/cordova-develop/cordova-plugin-pods3/blob/master/plugin.xml -->
|
||||
<!-- <pods use-frameworks="true">
|
||||
<pod name="Stripe" spec="" />
|
||||
<pod name="Alamofire" spec="" />
|
||||
</pods> -->
|
||||
|
||||
<source-file src="src/ios/APIClient.swift" />
|
||||
<source-file src="src/ios/AppDelegate.swift" />
|
||||
<source-file src="src/ios/PaymentOptions.swift" />
|
||||
<source-file src="src/ios/PluginConfig.swift" />
|
||||
<source-file src="src/ios/StripePaymentsPlugin.swift" />
|
||||
<header-file type="BridgingHeader" src="src/ios/StripePaymentsPlugin-Bridging-Header.h" />
|
||||
|
||||
<!-- <framework src="Foundation.framework" /> -->
|
||||
</platform>
|
||||
</plugin>
|
||||
|
||||
|
||||
|
||||
|
23
src/android/service/StripeService.java
Normal file
23
src/android/service/StripeService.java
Normal file
@ -0,0 +1,23 @@
|
||||
package com.stripe.example.service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.http.FieldMap;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
import retrofit2.http.POST;
|
||||
import rx.Observable;
|
||||
|
||||
/**
|
||||
* A Retrofit service used to communicate with a server.
|
||||
*/
|
||||
public interface StripeService {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("ephemeral_keys")
|
||||
Observable<ResponseBody> createEphemeralKey(@FieldMap Map<String, String> apiVersionMap);
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST("create_intent")
|
||||
Observable<ResponseBody> createPaymentIntent(@FieldMap Map<String, Object> params);
|
||||
}
|
58
src/ios/APIClient.swift
Normal file
58
src/ios/APIClient.swift
Normal file
@ -0,0 +1,58 @@
|
||||
import Alamofire
|
||||
import Stripe
|
||||
|
||||
class APIClient: NSObject, STPCustomerEphemeralKeyProvider {
|
||||
|
||||
static let shared = APIClient()
|
||||
|
||||
var ephemeralKeyUrl = ""
|
||||
|
||||
// MARK: STPCustomerEphemeralKeyProvider
|
||||
enum CustomerKeyError: Error {
|
||||
case ephemeralKeyUrl
|
||||
case invalidResponse
|
||||
}
|
||||
|
||||
func createCustomerKey(withAPIVersion apiVersion: String, completion: @escaping STPJSONResponseCompletionBlock) {
|
||||
let endpoint = ephemeralKeyUrl // "/api/passengers/me/ephemeral_keys"
|
||||
|
||||
guard let url = URL(string: endpoint) else {
|
||||
completion(nil, CustomerKeyError.ephemeralKeyUrl)
|
||||
return
|
||||
}
|
||||
|
||||
let parameters: [String: Any] = ["api_version": apiVersion]
|
||||
let headers: HTTPHeaders = [
|
||||
// "Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
|
||||
"Accept": "application/json"
|
||||
]
|
||||
|
||||
// TODO need xcode for this.
|
||||
if PluginConfig.extraHTTPHeaders.count > 0 {
|
||||
// for each HTTPHeader in extraHTTPHeaders
|
||||
// headers.add(header)
|
||||
}
|
||||
|
||||
Alamofire.request(url, method: .post, parameters: parameters, headers: headers)
|
||||
.validate(statusCode: 200..<300)
|
||||
.responseJSON { responseJSON in
|
||||
switch responseJSON.result {
|
||||
case .success(let json):
|
||||
guard let data = json as? [String: AnyObject] else {
|
||||
completion(nil, CustomerKeyError.invalidResponse)
|
||||
return
|
||||
}
|
||||
completion(data, nil)
|
||||
case .failure(let error):
|
||||
completion(nil, error)
|
||||
}
|
||||
// The docs also have this approach, not sure which is correct:
|
||||
// guard let json = responseJSON.result.value as? [AnyHashable: Any] else {
|
||||
// completion(nil, CustomerKeyError.invalidResponse)
|
||||
// return
|
||||
// }
|
||||
// completion(json, nil)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
11
src/ios/AppDelegate.swift
Normal file
11
src/ios/AppDelegate.swift
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
import UIKit
|
||||
import Stripe
|
||||
|
||||
// For Cordova, we can create an AppDelegate extension:
|
||||
// https://stackoverflow.com/a/29288792
|
||||
|
||||
//MARK: extension StripePaymentsPlugin
|
||||
extension AppDelegate {
|
||||
|
||||
}
|
15
src/ios/PaymentOptions.swift
Normal file
15
src/ios/PaymentOptions.swift
Normal file
@ -0,0 +1,15 @@
|
||||
public struct PaymentOptions {
|
||||
|
||||
// must be in smallest unit e.g. 1000 for $10.00
|
||||
public var price: UInt32 = 0
|
||||
// 'USD', 'MXN', 'JPY', 'GBP' etc. uppercase.
|
||||
public var currency: String = "USD"
|
||||
// 'US', 'PH', the ISO 2-letter code, uppercase.
|
||||
public var country: String = "US"
|
||||
|
||||
init(dict: [String:Any]) {
|
||||
price = dict["price"] as? UInt32 ?? 0
|
||||
currency = dict["currency"] as? String ?? "USD"
|
||||
country = dict["country"] as? String ?? "US"
|
||||
}
|
||||
}
|
22
src/ios/PluginConfig.swift
Normal file
22
src/ios/PluginConfig.swift
Normal file
@ -0,0 +1,22 @@
|
||||
import Alamofire
|
||||
|
||||
public class StripePaymentsPluginConfig {
|
||||
public var publishableKey: String? = nil
|
||||
public var ephemeralKeyUrl: String? = nil
|
||||
public var appleMerchantId: String? = nil
|
||||
public var companyName: String? = nil
|
||||
public var requestPaymentImmediately: Boolean? = true
|
||||
public var extraHTTPHeaders: [HTTPHeader]? = []
|
||||
// TODO:
|
||||
// We can add an option to execute the charge API-side, in which case
|
||||
// the developer would also need to provide their 'charge' endpoint,
|
||||
// meaning that the success/fail return value becomes meaningful.
|
||||
// The extraHTTPHeaders now allows us to do that, to be done later..
|
||||
|
||||
// TODO need xcode for this
|
||||
func parseExtraHeaders(dict: [String:String]) {
|
||||
// extraHTTPHeaders.push(new HTTPHeader(dict[something]))
|
||||
}
|
||||
}
|
||||
|
||||
let PluginConfig = StripePaymentsPluginConfig()
|
1
src/ios/StripePaymentsPlugin-Bridging-Header.h
Normal file
1
src/ios/StripePaymentsPlugin-Bridging-Header.h
Normal file
@ -0,0 +1 @@
|
||||
#import <Cordova/CDV.h>
|
273
src/ios/StripePaymentsPlugin.swift
Normal file
273
src/ios/StripePaymentsPlugin.swift
Normal file
@ -0,0 +1,273 @@
|
||||
|
||||
import UIKit
|
||||
import Stripe
|
||||
|
||||
// https://stripe.com/docs/apple-pay/apps
|
||||
// https://stripe.com/docs/mobile/ios/standard
|
||||
// https://github.com/zyra/cordova-plugin-stripe/blob/v2/src/ios/CordovaStripe.m
|
||||
// https://github.com/stripe/stripe-connect-rocketrides/blob/master/server/routes/api/rides.js
|
||||
// https://github.com/stripe/stripe-connect-rocketrides/blob/master/ios/RocketRides/RideRequestViewController.swift
|
||||
// https://github.com/stripe/stripe-ios/blob/master/Example/Standard%20Integration%20(Swift)/CheckoutViewController.swift
|
||||
// https://github.com/stripe/stripe-ios/blob/master/Example/Standard%20Integration%20(Swift)/MyAPIClient.swift
|
||||
|
||||
@objc(StripePaymentsPlugin) class StripePaymentsPlugin: CDVPlugin, STPPaymentContextDelegate {
|
||||
|
||||
private var paymentStatusCallback: String? = nil
|
||||
private let customerContext: STPCustomerContext
|
||||
private let paymentContext: STPPaymentContext
|
||||
|
||||
override func pluginInitialize() {
|
||||
super.pluginInitialize()
|
||||
}
|
||||
|
||||
@objc(addPaymentStatusObserver:)
|
||||
func addPaymentStatusObserver(command: CDVInvokedUrlCommand) {
|
||||
paymentStatusCallback = command.callbackId
|
||||
}
|
||||
|
||||
// MARK: Init Method
|
||||
|
||||
@objc(init:)
|
||||
public func init(command: CDVInvokedUrlCommand) {
|
||||
let error = "The Stripe Publishable Key and ephemeral key generation URL are required"
|
||||
|
||||
guard let dict = command.arguments[0] as? [String:Any] ?? nil else {
|
||||
errorCallback(command.callbackId, [ "status": "INIT_ERROR", "error": error ])
|
||||
return
|
||||
}
|
||||
|
||||
// Would be nice to figure a way to customize the UI, as Rocket Rides did,
|
||||
// https://github.com/stripe/stripe-connect-rocketrides/blob/master/ios/RocketRides/UIColor%2BPalette.swift
|
||||
// but this would be alot of work and a clumsy API so put that on hold to come up with a better way.
|
||||
PluginConfig.publishableKey = dict["publishableKey"] as? String ?? ""
|
||||
PluginConfig.ephemeralKeyUrl = dict["ephemeralKeyUrl"] as? String ?? ""
|
||||
PluginConfig.appleMerchantId = dict["appleMerchantId"] as? String ?? ""
|
||||
PluginConfig.companyName = dict["companyName"] as? String ?? ""
|
||||
PluginConfig.requestPaymentImmediately = dict["requestPaymentImmediately"] as? Boolean ?? true
|
||||
|
||||
if headersDict = dict["extraHTTPHeaders"] as? [String:String] {
|
||||
PluginConfig.parseExtraHeaders(headersDict)
|
||||
}
|
||||
|
||||
if !self.verifyConfig() {
|
||||
errorCallback(command.callbackId, [ "status": "INIT_ERROR", "error": error ])
|
||||
return
|
||||
}
|
||||
|
||||
APIClient.shared.ephemeralKeyUrl = PluginConfig.ephemeralKeyUrl
|
||||
STPPaymentConfiguration.shared().companyName = PluginConfig.companyName
|
||||
STPPaymentConfiguration.shared().publishableKey = PluginConfig.publishableKey
|
||||
|
||||
if !PluginConfig.appleMerchantId.isEmpty {
|
||||
STPPaymentConfiguration.shared().appleMerchantIdentifier = PluginConfig.appleMerchantId
|
||||
}
|
||||
|
||||
customerContext = STPCustomerContext(keyProvider: APIClient.shared)
|
||||
paymentContext = STPPaymentContext(customerContext: customerContext)
|
||||
|
||||
paymentContext.delegate = self
|
||||
paymentContext.hostViewController = self.viewController
|
||||
|
||||
successCallback(command.callbackId, [ "status": "INIT_SUCCESS" ])
|
||||
}
|
||||
|
||||
|
||||
// MARK: Public plugin API
|
||||
|
||||
@objc(showPaymentDialog:)
|
||||
public func showPaymentDialog(command: CDVInvokedUrlCommand) {
|
||||
var error = "[CONFIG]: Error parsing payment options or they were not provided"
|
||||
|
||||
// Ensure we have valid config.
|
||||
guard let options = command.arguments[0] as? [String:Any] ?? nil else {
|
||||
errorCallback(command.callbackId, [ "status": "PAYMENT_DIALOG_ERROR", "error": error ])
|
||||
return
|
||||
}
|
||||
|
||||
if !self.verifyConfig() {
|
||||
error = "[CONFIG]: Config is not set, init() must be called before using plugin"
|
||||
errorCallback(command.callbackId, [ "status": "PAYMENT_DIALOG_ERROR", "error": error ])
|
||||
return
|
||||
}
|
||||
|
||||
let paymentOptions = PaymentOptions(options)
|
||||
paymentContext.paymentAmount = paymentOptions.price
|
||||
paymentContext.paymentCurrency = paymentOptions.currency
|
||||
paymentContext.paymentCountry = paymentOptions.country
|
||||
|
||||
// This dialog collects a payment method from the user. When they close it, you get a context
|
||||
// change event with the payment info. NO charge has been created at that point, NO source
|
||||
// has been created from the payment method. All that has happened is the user entered
|
||||
// payment data and clicked 'ok'. That's all.
|
||||
// After that dialog closes - after paymentContextDidChange is called with
|
||||
// a selectedPaymentMethod - THEN you want to call requestPayment.
|
||||
paymentContext.presentPaymentMethodsViewController()
|
||||
successCallback(command.callbackId, [ "status": "PAYMENT_DIALOG_SHOWN" ])
|
||||
}
|
||||
|
||||
@objc(requestPayment:)
|
||||
public func requestPayment(command: CDVInvokedUrlCommand) {
|
||||
// Ensure we have valid config.
|
||||
if !self.verifyConfig() {
|
||||
let error = "[CONFIG]: Config is not set, init() must be called before using plugin"
|
||||
errorCallback(command.callbackId, [ "status": "REQUEST_PAYMENT_ERROR", "error": error ])
|
||||
return
|
||||
}
|
||||
|
||||
doRequestPayment(command.callbackId)
|
||||
}
|
||||
|
||||
func doRequestPayment(_ callbackId: String) {
|
||||
paymentContext.requestPayment()
|
||||
successCallback(callbackId, [ "status": "REQUEST_PAYMENT_STARTED" ])
|
||||
}
|
||||
|
||||
|
||||
// MARK: STPPaymentContextDelegate
|
||||
|
||||
func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
|
||||
let alertController = UIAlertController(
|
||||
preferredStyle: .alert,
|
||||
retryHandler: { (action) in
|
||||
// Retry payment context loading
|
||||
paymentContext.retryLoading()
|
||||
}
|
||||
)
|
||||
|
||||
var message = error?.localizedDescription ?? ""
|
||||
var callbackMessage: String = ""
|
||||
|
||||
if let customerKeyError = error as? APIClient.CustomerKeyError {
|
||||
switch customerKeyError {
|
||||
case .ephemeralKeyUrl:
|
||||
// Fail silently until base url string is set
|
||||
callbackMessage = "[ERROR]: Please assign a value to `APIClient.shared.ephemeralKeyUrl` before continuing. See `StripePaymentsPlugin.swift`."
|
||||
case .invalidResponse:
|
||||
// Use customer key specific error message
|
||||
callbackMessage = "[ERROR]: Missing or malformed response when attempting to call `APIClient.shared.createCustomerKey`. Please check internet connection and backend response."
|
||||
message = "Could not retrieve customer information"
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Use generic error message
|
||||
callbackMessage = "[ERROR]: Unrecognized error while loading payment context: \(error)"
|
||||
message = error.localizedDescription ?? "Could not retrieve payment information"
|
||||
}
|
||||
|
||||
print(callbackMessage)
|
||||
errorCallback(paymentStatusCallback, ["error": callbackMessage], keepCallback: true)
|
||||
|
||||
alertController.setMessage(message) // ??
|
||||
self.viewController.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
|
||||
var isLoading = paymentContext.isLoading
|
||||
var isPaymentReady = paymentContext.selectedPaymentMethod != nil
|
||||
var label = ""
|
||||
var image = ""
|
||||
|
||||
// https://stackoverflow.com/questions/11592313/how-do-i-save-a-uiimage-to-a-file
|
||||
if selectedPaymentMethod = paymentContext.selectedPaymentMethod {
|
||||
label = selectedPaymentMethod.label
|
||||
image = ""
|
||||
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
|
||||
if let filePath = paths.first?.appendingPathComponent("StripePaymentMethod.jpg") {
|
||||
// Save image.
|
||||
do {
|
||||
try UIImageJPEGRepresentation(selectedPaymentMethod.image, 1)?.write(to: filePath, options: .atomic)
|
||||
image = filePath
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
let resultMsg: [String : Any] = [
|
||||
"status": "PAYMENT_STATUS_CHANGED",
|
||||
"isLoading": isLoading,
|
||||
"isPaymentReady": isPaymentReady,
|
||||
"label": label,
|
||||
"image": image
|
||||
]
|
||||
|
||||
successCallback(paymentStatusCallback, resultMsg, keepCallback: true)
|
||||
|
||||
if isPaymentReady && PluginConfig.requestPaymentImmediately {
|
||||
doRequestPayment(paymentStatusCallback)
|
||||
}
|
||||
}
|
||||
|
||||
// This callback is triggered when requestPayment() completes successfully to create a Source.
|
||||
// This Source can then be used by the app to process a payment (create a charge, subscription etc.)
|
||||
func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPErrorBlock) {
|
||||
// Create charge using payment result
|
||||
let resultMsg: [String : Any] = [
|
||||
"status": "PAYMENT_CREATED",
|
||||
"source": paymentResult.source.stripeID
|
||||
]
|
||||
|
||||
successCallback(paymentStatusCallback, resultMsg, keepCallback: true)
|
||||
completion(nil)
|
||||
}
|
||||
|
||||
// This callback triggers due to:
|
||||
// a) the result of the payment info prompt, if the user cancels payment method selection
|
||||
// b) the result of requestPayment, if the user was prompted for more data and cancels
|
||||
// c) the result of requestPayment, if they attempt to verify a payment method and it fails
|
||||
// d) the output of paymentContext(didCreatePaymentResult:), in our case, always called with success.
|
||||
// In a full iOS app, in paymentContext(didCreatePaymentResult:) you would call your backend,
|
||||
// and return an appropriate error or success; however for the plugin, we are returning the
|
||||
// payment Source to the app, so we don't need paymentContext(didCreatePaymentResult:) to do anything
|
||||
// besides return success.
|
||||
// In later versions we may add the option for that method to call your backend directly so you
|
||||
// don't have to.
|
||||
func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
|
||||
var resultMsg: [String : Any] = [:]
|
||||
|
||||
switch status {
|
||||
case .success:
|
||||
resultMsg = [ "status": "PAYMENT_COMPLETED_SUCCESS" ]
|
||||
case .error:
|
||||
// Use generic error message
|
||||
print("[ERROR]: Unrecognized error while finishing payment: \(String(describing: error))");
|
||||
self.viewController.present(UIAlertController(message: "Could not complete payment"), animated: true)
|
||||
|
||||
resultMsg = [
|
||||
"status": "PAYMENT_COMPLETED_ERROR",
|
||||
error: "[ERROR]: Unrecognized error while finishing payment: \(String(describing: error))"
|
||||
]
|
||||
|
||||
errorCallback(paymentStatusCallback, resultMsg, keepCallback: true)
|
||||
return
|
||||
case .userCancellation:
|
||||
resultMsg = [ "status": "PAYMENT_CANCELED" ]
|
||||
}
|
||||
|
||||
successCallback(paymentStatusCallback, resultMsg, keepCallback: true)
|
||||
}
|
||||
|
||||
func successCallback(_ callbackId: String, _ data: [String:Any?], keepCallback: Bool = false) {
|
||||
var pluginResult = CDVPluginResult(
|
||||
status: .ok,
|
||||
messageAs: data
|
||||
)
|
||||
pluginResult?.setKeepCallbackAs(keepCallback)
|
||||
self.commandDelegate!.send(pluginResult, callbackId: callbackId)
|
||||
}
|
||||
|
||||
func errorCallback(_ callbackId: String, _ data: [String:Any?], keepCallback: Bool = false) {
|
||||
var pluginResult = CDVPluginResult(
|
||||
status: .error,
|
||||
messageAs: data
|
||||
)
|
||||
pluginResult?.setKeepCallbackAs(keepCallback)
|
||||
self.commandDelegate!.send(pluginResult, callbackId: callbackId)
|
||||
}
|
||||
|
||||
func verifyConfig() -> Bool {
|
||||
return PluginConfig.publishableKey != nil && !PluginConfig.publishableKey!.isEmpty
|
||||
&& PluginConfig.ephemeralKeyUrl != nil && !PluginConfig.ephemeralKeyUrl!.isEmpty
|
||||
}
|
||||
|
||||
}
|
||||
|
74
www/StripePaymentsPlugin.js
Normal file
74
www/StripePaymentsPlugin.js
Normal file
@ -0,0 +1,74 @@
|
||||
var exec = require('cordova/exec');
|
||||
|
||||
var StripePaymentsPlugin = function () { };
|
||||
|
||||
StripePaymentsPlugin._paymentStatusObserverList = [];
|
||||
|
||||
StripePaymentsPlugin._processFunctionList = function (array, param) {
|
||||
for (var i = 0; i < array.length; i++)
|
||||
array[i](param);
|
||||
};
|
||||
|
||||
var paymentStatusCallbackProcessor = function (state) {
|
||||
StripePaymentsPlugin._processFunctionList(StripePaymentsPlugin._paymentStatusObserverList, state);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the high level plugin config.
|
||||
* THIS METHOD MUST BE CALLED BEFORE ANY OTHER METHODS ON THE PLUGIN.
|
||||
* @param {object} config {publishableKey, ephemeralKeyUrl, appleMerchantId, companyName}
|
||||
*/
|
||||
StripePaymentsPlugin.prototype.init = function (config, successCallback, errorCallback) {
|
||||
exec(successCallback, errorCallback, 'StripePaymentsPlugin', 'init', [config]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds an observer to the stream of events returned while the payment request windows are open.
|
||||
*/
|
||||
StripePaymentsPlugin.prototype.addPaymentStatusObserver = function (callback) {
|
||||
StripePaymentsPlugin._paymentStatusObserverList.push(callback);
|
||||
exec(paymentStatusCallbackProcessor, function () { }, 'StripePaymentsPlugin', 'addPaymentStatusObserver', []);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prompts user for Media Library permissions, or returns immediately with their existing
|
||||
* permission if the dialog has already been shown in the lifetime of the app
|
||||
* This will prompt AGAIN if the user has changed permissions outside the app and returned
|
||||
* to the app.
|
||||
* @param {object} paymentOptions Options for the payment to collect, in the format
|
||||
* { price, currency, country }. Price must be in the smallest unit of currency, e.g.
|
||||
* 1000 for $10.00 USD; currency must be the 3-letter currency code, uppercase, e.g. 'USD';
|
||||
* country must be the ISO 2-letter country code e.g. 'US'.
|
||||
* @param {function} successCallback Success callback
|
||||
* @param {function} errorCallback Error callback
|
||||
*/
|
||||
StripePaymentsPlugin.prototype.showPaymentDialog = function (paymentOptions, successCallback, errorCallback) {
|
||||
if (!paymentOptions) {
|
||||
return errorCallback({ status: "PAYMENT_ERROR", error: '[CONFIG]: Payment options are required ' });
|
||||
}
|
||||
exec(successCallback, errorCallback, 'StripePaymentsPlugin', 'showPaymentDialog', [paymentOptions]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Finalize the payment. If this requires additional user input, Stripe will take care of it.
|
||||
* @param {function} successCallback Success callback
|
||||
* @param {function} errorCallback Error callback
|
||||
*/
|
||||
StripePaymentsPlugin.prototype.requestPayment = function (successCallback, errorCallback) {
|
||||
exec(successCallback, errorCallback, 'StripePaymentsPlugin', 'requestPayment', []);
|
||||
};
|
||||
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
|
||||
if (!window.plugins) {
|
||||
window.plugins = {};
|
||||
}
|
||||
|
||||
if (!window.plugins.StripePaymentsPlugin) {
|
||||
window.plugins.StripePaymentsPlugin = new StripePaymentsPlugin();
|
||||
}
|
||||
|
||||
if (typeof module != 'undefined' && module.exports) {
|
||||
module.exports = StripePaymentsPlugin;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user