I am wanting to display the price of the SKProduct item inside my label, rather than it being an alertView, as presented by SwiftyStoreKit.
In the viewDidLoad, I tried
coralsAppLabel.text = getInfo(PurchaseCorals)
but this results in the error that I cannot covert a type () to a UILabel.
This is based on the SwiftyStoreKit code below.
enum RegisteredPurchase : String {
case reefLifeCorals = "ReefLife4Corals"
}
#IBOutlet weak var coralsAppLabel: UILabel!
func getInfo(_ purchase: RegisteredPurchase) {
NetworkActivityIndicatorManager.networkOperationStarted()
SwiftyStoreKit.retrieveProductsInfo([purchase.rawValue]) { result in
NetworkActivityIndicatorManager.networkOperationFinished()
self.showAlert(self.alertForProductRetrievalInfo(result))
}
}
func alertForProductRetrievalInfo(_ result: RetrieveResults) -> UIAlertController {
if let product = result.retrievedProducts.first {
let priceString = product.localizedPrice!
return alertWithTitle(product.localizedTitle, message: "\(product.localizedDescription) - \(priceString)")
}
else if let invalidProductId = result.invalidProductIDs.first {
return alertWithTitle("Could not retrieve product info", message: "Invalid product identifier: \(invalidProductId)")
}
else {
let errorString = result.error?.localizedDescription ?? "Unknown error. Please contact support"
return alertWithTitle("Could not retrieve product info", message: errorString)
}
}
Any help is appreciated
The main problem here is that you're trying to assign Void (aka ()) value that your function getInfo implicitly returns to a String? property of UILabel. That's not going to work.
You can't easily return needed info from getInfo function either because it does asynchronous call. One way to accomplish what you need is to re-factor the code a bit to something like following (didn't check for syntax errors, so be wary):
override func viewDidLoad() {
super.viewDidLoad()
getProductInfoFor(PurchaseCorals, completion: { [weak self] (product, errorMessage) in
guard let product = product else {
self?.coralsAppLabel.text = errorMessage
return
}
let priceString = product.localizedPrice!
self?.coralsAppLabel.text = "\(product.localizedDescription) - \(priceString)"
})
}
func getProductInfoFor(_ purchase: RegisteredPurchase, completion: (product: SKProduct?, errorMessage: String?) -> Void) {
NetworkActivityIndicatorManager.networkOperationStarted()
SwiftyStoreKit.retrieveProductsInfo([purchase.rawValue]) { result in
NetworkActivityIndicatorManager.networkOperationFinished()
let extractedProduct = self.extractProductFromResults(result)
completion(product: extractedProduct.product, errorMessage: extractedProduct.errorMessage)
}
}
func extractProductFromResults(_ result: RetrieveResults) -> (product: SKProduct?, errorMessage: String?) {
if let product = result.retrievedProducts.first {
return (product: product, errorMessage: nil)
}
else if let invalidProductId = result.invalidProductIDs.first {
return (product: nil, errorMessage: "Invalid product identifier: \(invalidProductId)")
}
else {
let errorString = result.error?.localizedDescription ?? "Unknown error. Please contact support"
return (product: nil, errorMessage: errorString)
}
}
Here you have your SKProduct or errorMessage in viewDidLoad in the completion closure and you are free to do whatever you want with it: show alert, update label, etc. And overall this code should be a little bit more flexible and decoupled which is usually a good thing ;)
Related
I am building a simple currency converter app. When ViewController gets opened it calls a function from CoinManager.swift:
class ViewController: UIViewController {
var coinManager = CoinManager()
override func viewDidLoad() {
super.viewDidLoad()
coinManager.delegate = self
coinManager.getCoinPrice(for: "AUD", "AZN", firstCall: true)
}
...
}
CoinManager.swift:
protocol CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String)
func tellTableView(descriptions: [String], symbols: [String])
func didFailWithError(error: Error)
}
struct CoinManager {
var delegate: CoinManagerDelegate?
let baseURL = "https://www.cbr-xml-daily.ru/daily_json.js"
func getCoinPrice (for currency1: String,_ currency2: String, firstCall: Bool) {
if let url = URL(string: baseURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
if let safeData = data {
if let coinData = self.parseJSON(safeData) {
if firstCall {
var descriptions = [""]
let listOfCoins = Array(coinData.keys)
for key in listOfCoins {
descriptions.append(coinData[key]!.Name)
}
descriptions.removeFirst()
self.delegate?.tellTableView(descriptions: descriptions, symbols: listOfCoins)
}
if let coinInfo1 = coinData[currency1] {
let value1 = coinInfo1.Value
if let coinInfo2 = coinData[currency2] {
let value2 = coinInfo2.Value
//this line does not do anything the second time I call getCoinPrice:
self.delegate?.didUpdatePrice(price1: value1, currency1: currency1, price2: value2, currency2: currency2)
//And this one does work
print("delegate:\(currency1)")
} else {
print("no name matches currency2")
}
} else {
print("no name matches currency1")
}
}
}
}
task.resume()
}
}
func ParseJSON....
}
The method it calls (ViewController.swift):
extension ViewController: CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String) {
print("didUpdatePrice called")
DispatchQueue.main.async {
let price1AsString = String(price1)
let price2AsString = String(price2)
self.leftTextField.text = price1AsString
self.rightTextField.text = price2AsString
self.leftLabel.text = currency1
self.rightLabel.text = currency2
}
}
...
}
and finally, CurrencyViewController.swift:
var coinManager = CoinManager()
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
So when I launch the app i get following in my debug console:
didUpdatePrice called
delegate:AUD
And when I call getCoinPrice() from CurrencyViewController the delegate method does not get called. I know that my code goes through the delegate function line as I get this in debug console:
delegate:USD
I just can't wrap my head around it. The delegate method does not work when gets called second time. Even though it is called by the same algorithm
It's because you're creating a new object of CoinManager in CurrencyViewController where the delegate is not set. So you've to set the delegate every time you create a new instance of CoinManager.
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.delegate = self
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
Update: So, the above solution would require for you to make the delegate conformance in CurrencyViewController. If you're looking for an alternate solution you should probably pass the instance of coinManager in ViewController to CurrencyViewController. For that here are the things you need to update.
In CurrencyViewController:
class CurrencyViewController: UIViewController {
var coinManager: CoinManager! // you can optional unwrap if you intent to use CurrencyViewController without coinManager
//...
And in ViewController:
currencyViewController.coinManager = coinManager // passing the instance of coinManager
Can you share the full code of CoinManager? I see this part
if firstCall {
...
}
Maybe some block logic here or unhandled cases? And can you share the full code of protocol?
Also try to print something before this code:
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
As I understand, it is best to only test public methods of a class.
Let's have a look at this example. I have a view model for the view controller.
protocol MyViewModelProtocol {
var items: [SomeItem] { get }
var onInsertItemsAtIndexPaths: (([IndexPath]) -> Void)? { get set }
func viewLoaded()
}
class MyViewModel: MyViewModelProtocol {
func viewLoaded() {
let items = createDetailsCellModels()
updateCellModels(with: items)
requestDetails()
}
}
I want to test class viewLoaded(). This class calls two other methods - updateItems() and requestDetails()
One of the methods sets up the items and the other one call API to retrieve data and update those items. Items array us updated two times and onInsertItemsAtIndexPaths are called two times - when setting up those items and when updating with new data.
I can test whether after calling viewLoaded() expected items are set up and that onInsertItemsAtIndexPaths is called.
However, the test method will become rather complex.
What is your view, should I test those two methods separately or just write this one huge test?
By testing only viewLoaded(), my idea is that the implementation can change and I only care that results are what I expect.
I think the same thing, only public functions should be tested, since public ones use private ones, and your view on MVVM is correct. You can improve it by adding a DataSource and a Mapper that allows you to improve testing.
However, yes, the test seems huge to me, the tests should test simple units and ensure that small parts of the code work well, with the example you show is difficult, you need to divide by layers (clean code).
In the example you load the data into the viewModel and make it difficult to mockup the data. But if you have a Domain layer you can pass the UseCase mock to the viewModel and control the result. If you run a test on your example, the result will also depend on what the endpoint returns. (404, 200, empty array, data with error ...). So it is important, for testing purposes, to have a good separation by layers. (Presentation, Domain and Data) to be able to test each one separately.
I give you an example of how I would test a view mode, sure there are better and cooler examples, but it's an approach.
Here you can see a viewModel
protocol BeersListViewModel: BeersListViewModelInput, BeersListViewModelOutput {}
protocol BeersListViewModelInput {
func viewDidLoad()
func updateView()
func image(url: String?, index: Int) -> Cancellable?
}
protocol BeersListViewModelOutput {
var items: Box<BeersListModel?> { get }
var loadingStatus: Box<LoadingStatus?> { get }
var error: Box<Error?> { get }
}
final class DefaultBeersListViewModel {
private let beersListUseCase: BeersListUseCase
private var beersLoadTask: Cancellable? { willSet { beersLoadTask?.cancel() }}
var items: Box<BeersListModel?> = Box(nil)
var loadingStatus: Box<LoadingStatus?> = Box(.stop)
var error: Box<Error?> = Box(nil)
#discardableResult
init(beersListUseCase: BeersListUseCase) {
self.beersListUseCase = beersListUseCase
}
func viewDidLoad() {
updateView()
}
}
// MARK: Update View
extension DefaultBeersListViewModel: BeersListViewModel {
func updateView() {
self.loadingStatus.value = .start
beersLoadTask = beersListUseCase.execute(completion: { (result) in
switch result {
case .success(let beers):
let beers = beers.map { DefaultBeerModel(beer: $0) }
self.items.value = DefaultBeersListModel(beers: beers)
case .failure(let error):
self.error.value = error
}
self.loadingStatus.value = .stop
})
}
}
// MARK: - Images
extension DefaultBeersListViewModel {
func image(url: String?, index: Int) -> Cancellable? {
guard let url = url else { return nil }
return beersListUseCase.image(with: url, completion: { (result) in
switch result {
case .success(let imageData):
self.items.value?.items?[index].image.value = imageData
case .failure(let error ):
print("image error: \(error)")
}
})
}
}
Here you can see the viewModel test using mocks for the data and view.
class BeerListViewModelTest: XCTestCase {
private enum ErrorMock: Error {
case error
}
class BeersListUseCaseMock: BeersListUseCase {
var error: Error?
var expt: XCTestExpectation?
func execute(completion: #escaping (Result<[BeerEntity], Error>) -> Void) -> Cancellable? {
let beersMock = BeersMock.makeBeerListEntityMock()
if let error = error {
completion(.failure(error))
} else {
completion(.success(beersMock))
}
expt?.fulfill()
return nil
}
func image(with imageUrl: String, completion: #escaping (Result<Data, Error>) -> Void) -> Cancellable? {
return nil
}
}
func testWhenAPIReturnAllData() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "All OK")
beersListUseCaseMock.error = nil
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.items.bind { (_) in}
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNotNil(viewModel.items.value)
XCTAssertNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
func testWhenDataReturnsError() {
let beersListUseCaseMock = BeersListUseCaseMock()
beersListUseCaseMock.expt = self.expectation(description: "Error")
beersListUseCaseMock.error = ErrorMock.error
let viewModel = DefaultBeersListViewModel(beersListUseCase: beersListUseCaseMock)
viewModel.updateView()
waitForExpectations(timeout: 10, handler: nil)
XCTAssertNil(viewModel.items.value)
XCTAssertNotNil(viewModel.error.value)
XCTAssert(viewModel.loadingStatus.value == .stop)
}
}
in this way you can test the view, the business logic and the data separately, in addition to being a code that is very reusable.
Hope this helps you, I have it posted on github in case you need it.
https://github.com/cardona/MVVM
The UITest in question launches the app, taps a cell which pushes the Screen to be tested and then fails with a fatalError() when i make a change that i expect will call a fatalError().
How can i catch the fatalError on the UITest and use it to report that the UITest has failed?
Here is the UITest:
class ConcreteFoodScreenUITests: XCTestCase
{
let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
app.launch()
}
func testPathToConcreteFoodScreen() throws {
//Tap Concrete Cell in FoodDashboard to go to the ConcreteFoodScreen
XCTAssertTrue(app.otherElements["FoodDashboard"].exists)
app.scrollViews.otherElements.tables.staticTexts["100 g, 100cal, P: 90g, F: 80g, C: 70g"].tap()
//ConcreteFoodScreen
XCTAssertTrue(app.otherElements["ConcreteFoodScreen"].exists)
app.tables.cells.containing(.staticText, identifier:"Scale").children(matching: .textField).element.tap()
app.keys["5"].tap() //FIXME: Crashes with a Fatal Error
}
}
Here is the code that is being triggered that i want to know about:
class ScaleCellTextField: SWDecimalTextField {
//there is more code here but not relevant
func updateFoodEntry() {
fatalError()
// if let scale = Double(self.text!) {
// concreteFood.scale = scale
// }
}
}
You can see that i commented out some code here to get it working.
I'm afraid you can't. FatalError() ends your app's process, so I'm not sure you can catch this kind of event.
EDIT:
But...
You can use our dear good old Darwin Notifications... In order to add create a communication channel between your apps : the tested app and the tester app.
You'll need to add a file to both your targets:
typealias NotificationHandler = () -> Void
enum DarwinNotification : String {
case fatalError
}
class DarwinNotificationCenter {
let center: CFNotificationCenter
let prefix: String
var handlers = [String:NotificationHandler]()
init(prefix: String = "com.stackoverflow.answer.") {
center = CFNotificationCenterGetDarwinNotifyCenter()
self.prefix = prefix
}
var unsafeSelf: UnsafeMutableRawPointer {
return Unmanaged.passUnretained(self).toOpaque()
}
deinit {
CFNotificationCenterRemoveObserver(center, unsafeSelf, nil, nil)
}
func notificationName(for identifier: String) -> CFNotificationName {
let name = prefix + identifier
return CFNotificationName(name as CFString)
}
func identifierFrom(name: String) -> String {
if let index = name.range(of: prefix)?.upperBound {
return String(name[index...])
}
else {
return name
}
}
func handleNotification(name: String) {
let identifier = identifierFrom(name: name)
if let handler = handlers[identifier] {
handler()
}
}
func postNotification(for identifier: String) {
let name = notificationName(for: identifier)
CFNotificationCenterPostNotification(center, name, nil, nil, true)
}
func registerHandler(for identifier: String, handler: #escaping NotificationHandler) {
handlers[identifier] = handler
let name = notificationName(for: identifier)
CFNotificationCenterAddObserver(center,
unsafeSelf,
{ (_, observer, name, _, _) in
if let observer = observer, let name = name {
let mySelf = Unmanaged<DarwinNotificationCenter>.fromOpaque(observer).takeUnretainedValue()
mySelf.handleNotification(name: name.rawValue as String)
}
},
name.rawValue,
nil,
.deliverImmediately)
}
func unregisterHandler(for identifier: String) {
handlers[identifier] = nil
CFNotificationCenterRemoveObserver(center, unsafeSelf, notificationName(for: identifier), nil)
}
}
extension DarwinNotificationCenter {
func postNotification(for identifier: DarwinNotification) {
postNotification(for: identifier.rawValue)
}
func registerHandler(for identifier: DarwinNotification, handler: #escaping NotificationHandler) {
registerHandler(for: identifier.rawValue, handler: handler)
}
func unregisterHandler(for identifier: DarwinNotification) {
unregisterHandler(for: identifier.rawValue)
}
}
Then, simply, in your tested application:
#IBAction func onTap(_ sender: Any) {
// ... Do what you need to do, and instead of calling fatalError()
DarwinNotificationCenter().postNotification(for: .fatalError)
}
To catch it in your test, just do the following:
// This is the variable you'll update when notified
var fatalErrorOccurred = false
// We need a dispatch group to wait for the notification to be caught
let waitingFatalGroup = DispatchGroup()
waitingFatalGroup.enter()
let darwinCenter = DarwinNotificationCenter()
// Register the notification
darwinCenter.registerHandler(for: .fatalError) {
// Update the local variable
fatalErrorOccurred = true
// Let the dispatch group you're done
waitingFatalGroup.leave()
}
// Don't forget to unregister
defer {
darwinCenter.unregisterHandler(for: .fatalError)
}
// Perform you tests, here just a tap
app.buttons["BUTTON"].tap()
// Wait for the group the be left or to time out in 3 seconds
let _ = waitingFatalGroup.wait(timeout: .now() + 3)
// Check on the variable to know whether the notification has been received
XCTAssert(fatalErrorOccurred)
And that's it...
Disclaimer: You should not use testing code within your production code, so the use of DarwinNotification should not appear in production. Usually I use compilation directive to only have it in my code in debug or tests, not in release mode.
I've written a function called 'configureLabels()' that is supposed to make a 'GET' request and retrieve a value which is then supposed to be set as the text for a label. The request is async so I thought I would be able to use an escaping closure to update the UI when the request is finished being made. I'm relatively new to coding, so I am not sure what I've done wrong. I'd really appreciate anyone's help in figuring this out.
This is the code containing the 'configureLabels()' method:
import UIKit
import SwiftyJSON
class ItemDetailViewController: UIViewController {
#IBOutlet weak var numberOfFiberGrams: UILabel!
var ndbnoResults = [JSON]()
var ndbno = ""
let requestManager = RequestManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
configureLabels()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func configureLabels() {
requestManager.ndbnoRequest(ndbno: ndbno) { (results) in
let json = JSON(results)
let fiber = json["food"]["nutrients"][7].dictionaryValue
for (key, value) in fiber {
if key == "value" {
self.numberOfFiberGrams.text = "\(value.stringValue)"
} else {
self.numberOfFiberGrams.text = "Fail"
}
}
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
And here is the code containing the function that 'configureLabels()' calls:
func ndbnoRequest(ndbno: String, apiKey: String, completionHandler: #escaping (_ results: JSON?) -> Void) {
Alamofire.request("https://api.nal.usda.gov/ndb/V2/reports?ndbno=\(ndbno)&type=f&format=json&api_key=\(apiKey)", method: .get).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
completionHandler(json)
print("successful ndbno request")
case .failure(let error):
completionHandler(nil)
print(error)
}
}
}
Your code looks ok only issue I have find with your code is you are not calling the completionHandler in failure part, You need to always call completion block so it will gave you idea have you got response or not as of your completionHandler argument is type of [JSON] as of your not having response in failure part you are not calling completionHandler in it. What you can do is make it optional and call completionHandler with nil argument in case of failure.
func ndbnoRequest(ndbno: String, completionHandler: #escaping (_ results: [JSON]?) -> Void) {
let parameters = ["api_key": "tIgopGnvNSP7YJOQ17lGVwazeYI1TVhXNBA2Et9W", "format": "json", "ndbno": "\(ndbno)"]
Alamofire.request("https://api.nal.usda.gov/ndb/reports/V2", method: .get, parameters: parameters).responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
let ndbnoResults = json["foods"].arrayValue
completionHandler(ndbnoResults)
print("successful ndbno request")
case .failure(let error):
completionHandler(nil)
print("error with ndbno request")
}
}
}
Now call it this way and wrapped the optional in completion block so you can confirm you get response.
requestManager.ndbnoRequest(ndbno: ndbno) { (results) in
if let result = results {
let json = JSON(result)
let fiber = json["food"]["nutrients"][7].dictionaryValue
for (key, value) in fiber {
if key == "value" {
self.numberOfFiberGrams.text = "\(value.stringValue)"
} else {
self.numberOfFiberGrams.text = "Fail"
}
}
}
else {
print("Problem to get response")
}
}
Everything related to UI must be ALWAYS done on the main thread.
So try this:
DispatchQueue.main.async {
let json = JSON(results)
let fiber = json["food"]["nutrients"][7].dictionaryValue
for (key, value) in fiber {
if key == "value" {
self.numberOfFiberGrams.text = "\(value.stringValue)"
} else {
self.numberOfFiberGrams.text = "Fail"
}
}
}
P.S. I agree with Nirav about failure callback - you should handle it too. And I strongly recommend you to give functions and vars more readable and meaningful names, not "ndbnoRequest" and "ndbno". You won't remember what does it mean in few weeks :)
I have been working for days trying to figure out how to charge a card and save the card to a customer with Stripe using Swift, with Parse.com Cloud Code as the backend. I integrated the Stripe pod with my project successfully, and I have a token that is created, and prints out in the console to verify its existence. BUT I cannot do anything with it! I have searched everywhere for answers, and cannot figure out why I keep getting error. I think it has to do with the parameters that I am trying to feed to the Cloud Code, but I am unsure. I have read the docs for both Cloud Code and Stripe, and it was to no avail. This is my PaymentViewController.swift:
import UIKit
import Stripe
import PaymentKit
import Parse
import Bolts
class PaymentViewController: UIViewController, PTKViewDelegate {
#IBOutlet weak var saveBtn: UIButton!
var paymentView: PTKView = PTKView()
override func viewDidLoad() {
super.viewDidLoad()
var view : PTKView = PTKView(frame: CGRectMake(15,20,290,55))
paymentView = view
paymentView.delegate = self;
self.view.addSubview(self.paymentView)
saveBtn.enabled = false
}
func paymentView(view: PTKView!, withCard card: PTKCard!, isValid valid: Bool) {
if (valid) {
saveBtn.enabled = true
} else {
saveBtn.enabled = false
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func save(sender: AnyObject) {
var card: STPCard = STPCard()
card.number = self.paymentView.card.number
card.expMonth = self.paymentView.card.expMonth
card.expYear = self.paymentView.card.expYear
card.cvc = self.paymentView.card.cvc
STPAPIClient.sharedClient().createTokenWithCard(card, completion: { (tokenId: STPToken?, error: NSError?) -> Void in
if (error != nil) {
println(error)
println("what the..")
} else {
println(tokenId)
PFCloud.callFunctionInBackground("hello", withParameters: nil) {
(response: AnyObject?, error: NSError?) -> Void in
let responseString = response as? String
println(responseString)
}
PFCloud.callFunctionInBackground("createCharge", withParameters: nil, block: { (success: AnyObject?, error: NSError?) -> Void in
if error != nil {
println("error")
}
})
}
})
}
#IBAction func cancel(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
I have added the "Hello World" example to see if the Cloud Code was set up correctly, and that callFunction does work. My Cloud Code is:
var Stripe = require('stripe');
Stripe.initialize('My_Secret_Key');
Parse.Cloud.define("hello", function(request, response) {
response.success("Hello world!");
});
Parse.Cloud.define("createCharge", function(request, response) {
Stripe.Charges.create({
amount: 100 * 10, // $10 expressed in cents
currency: "usd",
card: "tok_3TnIVhEv9P24T0"
},{
success: function(httpResponse) {
response.success("Purchase made!");
},
error: function(httpResponse) {
response.error("Uh oh, something went wrong");
}
});
});
Any help would be truly appreciated!! I have been working tirelessly to figure this out! The console prints out
Uh oh, something went wrong (Code: 141, Version: 1.7.2)
The error you are seeing Uh oh, something went wrong (Code: 141, Version: 1.7.2) means that your createCharge Parse function returned an error. You may want to log the value of httpResponse to find the exact error from Stripe.
Looking at your code the error is most likely: Invalid Request Error: Cannot use token tok_3TnIVhEv9P24T0 more than once. You can confirm this in the Logs section of your Stripe Dashboard.
I see that you print the token, println(tokenId), you're also going to want to send that to your Parse function and set card equal to the value of the token you just created.
class PaymentViewController: UIViewController, PTKViewDelegate {
#IBOutlet weak var saveBtn: UIButton!
var paymentView: PTKView = PTKView()
override func viewDidLoad() {
super.viewDidLoad()
var view : PTKView = PTKView(frame: CGRectMake(15,20,290,55))
paymentView = view
paymentView.delegate = self;
self.view.addSubview(self.paymentView)
saveBtn.enabled = false
}
func paymentView(view: PTKView!, withCard card: PTKCard!, isValid valid: Bool) {
if (valid) {
saveBtn.enabled = true
} else {
saveBtn.enabled = false
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func save(sender: AnyObject) {
var card: STPCard = STPCard()
card.number = self.paymentView.card.number
card.expMonth = self.paymentView.card.expMonth
card.expYear = self.paymentView.card.expYear
card.cvc = self.paymentView.card.cvc
STPAPIClient.sharedClient().createTokenWithCard(card, completion: { (token: STPToken?, error: NSError?) -> Void in
if (error != nil) {
println(error)
println("not working")
} else {
//println(tokenId)
var coin = token!.tokenId
PFCloud.callFunctionInBackground("hello", withParameters: nil) {
(response: AnyObject?, error: NSError?) -> Void in
let responseString = response as? String
println(responseString)
}
var name = PFUser.currentUser()?.username as String!
var customer = PFUser.currentUser()?.objectId as String!
PFCloud.callFunctionInBackground("createCustomer", withParameters: ["coin" : coin, "name": name, "customer": customer], block: { (success: AnyObject?, error: NSError?) -> Void in
if error != nil {
println("create customer not working")
}
})
var customerId = customer!
PFCloud.callFunctionInBackground("createCharge", withParameters: ["customerId" : customerId], block: { (success: AnyObject?, error: NSError?) -> Void in
if error != nil {
println("not working")
}
})
}
})
}
#IBAction func cancel(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
And My updated cloud code is here:
var Stripe = require('stripe');
Stripe.initialize('sk_test_xxxxxxxxxxxxxx');
Parse.Cloud.define("hello", function(request, response) {
response.success("Hello world!");
});
Parse.Cloud.define("createCustomer", function(request, response) {
Stripe.Customers.create({
card: request.params.coin,
account_balance: -10*100,
metadata: {
name: request.params.name,
customer: request.params.customer, // e.g PFUser object ID
}
}, {
success: function(customer) {
response.success(customer.id);
},
error: function(error) {
response.error("Error:" +error);
}
})
});
Parse.Cloud.define("createCharge", function(request, response) {
Stripe.Charges.create({
amount: 100 * 10, // $10 expressed in cents
currency: "usd",
//card: request.params.coin
customer: request.params.customerId
},{
success: function(httpResponse) {
response.success("Purchase made!");
},
error: function(httpResponse) {
response.error(httpResponse)
response.error("Uh oh, something went wrong");
}
});
});
Ultimately I want to have the create customer in a different viewController.swift file and charge on another section of the app, but for right now I am testing it with them in the same PaymentViewController.swift file