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