Omar Al-Eisa
Omar Al-Eisa

Reputation: 67

setup STPpaymentMethodsViewController Stripe Xcode

I am attempting to setup STPpaymentmethodsviewcontroller using stripe documentation. it states I need to setup STPcustomercontext but it doesn't specify where. below is my current MYAPICLIENT code, code inside viewcontroller for STPpaymentmethodsviewcontroller, backend function code (customer is created upon account creation with firebase function working, writes customer id to database), firebase database structure, and error being presented upon attempt to load STPpaymentmethodsviewcontroller.

MYAPICLIENT:

class MyAPIClient: NSObject, STPEphemeralKeyProvider {

static let sharedClient = MyAPIClient()
var baseURLString: String? = "https://fresh-9883c.firebaseio.com/"
var baseURL: URL {
    if let urlString = self.baseURLString, let url = URL(string: urlString) {
        return url
    } else {
        fatalError()
    }
}

func createCustomerKey(withAPIVersion apiVersion: String, completion: @escaping STPJSONResponseCompletionBlock) {
    let url = self.baseURL.appendingPathComponent("ephemeral_keys")
    Alamofire.request(url, method: .post, parameters: [
        "api_version": apiVersion,
        ])
        .validate(statusCode: 200..<500)
        .responseJSON { responseJSON in
            switch responseJSON.result {
            case .success(let json):
                completion(json as? [String: AnyObject], nil)
            case .failure(let error):
                completion(nil, error)
            }
    }
}
func completeCharge(_ result: STPPaymentResult, amount: Int, completion: @escaping STPErrorBlock) {
    // 1
    let url = baseURL.appendingPathComponent("charge")
    // 2
    let params: [String: Any] = [
        "source": result.source.stripeID,
        "amount": amount,
        "currency": Constants.defaultCurrency,
        "description": Constants.defaultDescription
    ]
    // 3
    Alamofire.request(url, method: .post, parameters: params)
        .validate(statusCode: 200..<300)
        .responseString { response in
            switch response.result {
            case .success:
                completion(nil)
            case .failure(let error):
                completion(error)
            }
    }
}

}

CartViewController:

func handlePaymentMethodsButtonTapped(){
    let customerContext = STPCustomerContext(keyProvider: MyAPIClient.sharedClient)
    let paymentMethodsViewController = STPPaymentMethodsViewController(configuration: STPPaymentConfiguration.shared(), theme: STPTheme.default(), customerContext: customerContext, delegate: self)
    let navigationController = UINavigationController(rootViewController: paymentMethodsViewController)
    present(navigationController, animated: true)
}
@IBAction func confirmOrder(_ sender: UIButton) {
    // append cart to database and dequeue in my orders (active , completed)
    guard CartSingleton1.sharedInstance.orderDict.count > 0 else {
        let alertController = UIAlertController(title: "Warning", message: "Cart is Empty", preferredStyle: .alert)
        let alertAction = UIAlertAction(title: "OK", style: .default)
            alertController.addAction(alertAction)
        present(alertController, animated: true)
        return
        }
    handlePaymentMethodsButtonTapped()

}

}
extension CartViewController: STPPaymentMethodsViewControllerDelegate, STPPaymentContextDelegate {


func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
    self.navigationController?.popViewController(animated: true)
}

func paymentContextDidChange(_ paymentContext: STPPaymentContext) {

}

func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPErrorBlock) {
    MyAPIClient.sharedClient.completeCharge(paymentResult, amount: 100, completion: { (error: Error?) in
        if let error = error {
            completion(error)
        } else {
            completion(nil)
        }
    })
}

func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
    switch  status {
    case .error:
        print(error as Any)
    case .success:
        print("success")
        //self.showReciept()
    case .userCancellation:
        return
    }
}

func paymentMethodsViewController(_ paymentMethodsViewController: STPPaymentMethodsViewController, didSelect paymentMethod: STPPaymentMethod) {
    let selectedPaymentMethod = paymentMethod
    print("selectedPaymentMethod",selectedPaymentMethod)
}
func paymentMethodsViewController(_ paymentMethodsViewController: STPPaymentMethodsViewController, didFailToLoadWithError error: Error) {
    print("did fail to load paymentmethodsview",error)
    dismiss(animated: true)
}

func paymentMethodsViewControllerDidFinish(_ paymentMethodsViewController: STPPaymentMethodsViewController) {
     dismiss(animated: true)
}

func paymentMethodsViewControllerDidCancel(_ paymentMethodsViewController: STPPaymentMethodsViewController) {
    dismiss(animated: true)
}

}

Firebase Cloud Functions:

'use strict';


const functions = require('firebase-functions');
const admin = require('firebase-admin');
const logging = require('@google-cloud/logging')();

admin.initializeApp(functions.config().firebase);

const stripe = require('stripe')(functions.config().stripe.token);
const currency = functions.config().stripe.currency || 'USD';

//[START chargecustomer]
//charge the stripe customer whenever an amount is written to the realtime       database
exports.createStripeCharge = functions.database.ref('/stripe_customers/{userId}/charges/{id}').onWrite((event) => {
  const val = event.data.val();

  if (val === null || val.id || val.error) return null;

  return     admin.database().ref(`/stripe_customers/${event.params.userId}/customer_id`).once('value').then((snapshot) => {
return snapshot.val();
  }).then((customer) => {
const amount = val.amount;
const idempotency_key = event.params.id;
let charge = {amount, currency, customer};
if (val.source !== null) charge.source = val.source;
return stripe.charges.create(charge, {idempotency_key});
  }).then((response) => {
return event.data.adminRef.set(response);
  }).catch((error) => {
return event.data.adminRef.child('error').set(userFacingMessage(error));
  }).then(() => {
return reportError(error, {user: events.params.userId});
  });
});
// [end chargecustomer]]

// when user is created register them with stripe
exports.createStripeCustomer = functions.auth.user().onCreate((event) => {
  const data = event.data;
  return stripe.customers.create({
    email: data.email,
  }).then((customer) => {
    return    admin.database().ref(`/stripe_customers/${data.uid}/customer_id`).set(customer.id);
  });
});

// add a payment source (card) for a user by writing a stripe payment source   token to realtime database
exports.addPaymentSource =.  functions.database.ref('/stripe_customers/{userId}/sources/{pushId}/token').onWrite((event) => {
  const source = event.data.val();
  if (sourve === null) return null;
  return     admin.database.ref(`/stripe_customers/${event.params.userId}/customer_id`).once('value').then((snapshot) => {
return snapshot.val();
  }).then((customer) => {
return stripe.customers.createSource(customer, {source});
  }).then((response) => {
return event.data.adminRef.parent.set(response);
  }, (error) => {
return event.data.adminRef.parent.child('error').set(userFacingMessage(error));
  }).then(() => {
return reportError(error, {user: event.params.userId});
  });
});

// when a user deletes their account, clean up after the
exports.cleanupUser = functions.auth.user().onDelete((event) => {
  return admin.database().ref(`/stripe_customers/${event.data.uid}`).once('value').then((snapshot) => {
return snapshot.val();
  }).then((customer) => {
return stripe.customers.del(customer.customer_id);
  }).then(() => {
return admin.database().ref(`/stripe_customers/${event.data.uid}`).remove();
  });
});

function reportError(err, context = {}) {
const logName = 'errors';
const lof = logging.log(logName);

const metadata = {
  resource: {
    type: 'cloud_function',
    labels: {function_name: process.env.FUNCTION_NAME},
  },
};

const errorEvent = {
  message: err.stack,
  serviceContext: {
    service: process.env.FUNCTION_NAME,
    resourceType: 'cloud_function',
  },
  context: context,
};

return new Promise((resolve, reject) => {
  log.write(log.entry(metadata, errorEvent), (error) => {
    if (error) {
      reject(error);
    }
    resolve();
  });
});
}
// end [reportError]


// sanitize the error message for the user
function userFacingMessage(error) {
  returnerror.type ? error.message : 'an error occurred, developers have been   altered';
}

Firebase Database Structure:

(I removed my email from picture, writing function working)

Firebase Database Structure

ERROR in Xcode Console:

did fail to load paymentmethodsview 

responseSerializationFailed(Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}))

Upvotes: 0

Views: 1475

Answers (1)

hpar
hpar

Reputation: 748

It's hard to say what's going on here without more context. However, here's my guess: you haven't defined an ephemeral keys method on your Firebase backend, and so createCustomerWithKey's JSON request is failing: it's expecting the exact ephemeral key JSON your backend receives from the Stripe API. You need to implement the "/ephemeral_keys" endpoint in your backend application using the code here:

https://stripe.com/docs/mobile/ios/standard#prepare-your-api

Upvotes: 1

Related Questions