Search code examples
iosswiftparse-platformstripe-paymentsparse-cloud-code

Store credit card using Stripe + Parse with cloud code


I am attempting to store a users credit card into stripe. Once the token is made, I attempt to save the token with the user to Stripe as a customer. But I didnt find any answer to my problem, I just need to store a card to a user that already exist.

I tried to use the method Stripe.Customers.update but it store the new card removing the "default" card if the user had one. And using the method Stripe.Customers.create it create a new customer with the new card, but I need to store in specific user.

Cloud Code:

Parse.Cloud.define("stripeCreateCard", function(request,response)
{
    Stripe.initialize(STRIPE_SECRET_KEY);
    Stripe.Customers.create
    (
        request.params,
        {
            success:function(results)
            {
                response.success(results);
            },
            error:function(error)
            {
                response.error("Error:" +error); 
            }
        }
    );
});

Parse.Cloud.define("stripeUpdateCustomer", function(request, response) 
{
    Stripe.initialize(STRIPE_SECRET_KEY);
    Stripe.Customers.update
    (
        request.params["customerId"],
        request.params["data"],
        {
            success:function(results)
            {
                console.log(results["id"]);
                response.success(results);
            },
            error:function(error)
            {
                response.error("Error:" +error); 
            }
        }
    );
});

iOS Code:

class func getParamsForAddingCardToCustomer(custormerId: String, cardToken: String) -> NSDictionary {
        let params = NSMutableDictionary()

        params.setObject(["card" : cardToken], forKey: "data")
        params.setObject(custormerId, forKey: "customerId")

        return params
}

var params = ParamsHelper.getParamsForAddingCardToCustomer(stripeId, cardToken: token)

PFCloud.callFunctionInBackground("stripeCreateCard", withParameters: params as [NSObject : AnyObject]) {
        (response: AnyObject?, error: NSError?) -> Void in
        let responseString = response as? String

        if (error === nil) {
            println("Response: \(responseString) ")
        }
        else if (error != nil) {
            println("Error: \(error) \(error!.userInfo)")
        }
    }

I have tried several parameters to store the card as I need, but I always got error 'Received unknown parameters'

Anyone got any ideas how to store a card without removing or creating new customer?


Solution

  • Parse's stripe implementation is not as complete as it could be. In many cases you must use an HTTPRequest to perform stripe functions that Parse does not offer.

    For those cases I use the following iOS method and CloudCode to execute all of my stripe HTTPRequests. It is very easy to code while following the stripe CURL API documentation. I then build off this method to perform most stripe tasks, IE create/update/delete customers, cards, charges etc.

    I start by feeding it a Method, ie GET, POST, DELETE depending on whether you want to retrieve, create/update, or remove a stripe object.

    I then feed it a combination of optional pre/suf/postfixes to create a url.

    Example URL created:

    enter image description here

    Finally I give it the parameters, in the instance of creating a card and adding it to a customer this would only need to be a dictionary containing the tokenID.

    +(void)executeStripeCloudCodeWithMethod:(NSString *)method
                                     prefix:(NSString *)prefix
                                     suffix:(NSString *)suffix
                                    postfix:(NSString *)postfix
                              secondPostfix:(NSString *)secondPostfix
                                 parameters:(NSDictionary *)params
                          completionHandler:(ELStripeCompletionBlock)handler
    {
        NSDictionary *parameters = @{@"method":method,
                                     @"prefix":prefix?prefix:@"",
                                     @"suffix":suffix?suffix:@"",
                                     @"postfix":postfix?postfix:@"",
                                     @"secondPostfix":secondPostfix?secondPostfix:@"",
                                     @"params":params?params:[NSNull null]
                                     };
    
        [PFCloud callFunctionInBackground:@"stripeHTTPRequest"
                           withParameters:parameters
                                    block:^(id object, NSError *error) {
            id jsonObject;
            if (!error) {
                NSError *jsonError = nil;
                //Turn the json string into an NSDictionary
                jsonObject = [NSJSONSerialization JSONObjectWithData:[object dataUsingEncoding:NSUTF8StringEncoding]
                                                             options:kNilOptions error:&jsonError];
    
            }
            handler(jsonObject,error);
        }];
    }
    

    The cloud code that is executed:

    var STRIPE_SECRET_KEY = 'sk_test_your_test_code_here';
    var STRIPE_API_BASE_URL = 'api.stripe.com/v1/'
    Parse.Cloud.define("stripeHTTPRequest", function(request, response) 
    {
        //Check for valid pre/suf/postfixes, if they are not there do not include them.
        var prefix = request.params["prefix"];
        var suffix = "";
        var postfix = "";
        var secondPostfix = "";
        if (!isEmpty(request.params["suffix"])) suffix = '/'+request.params['suffix'];  
        if (!isEmpty(request.params["postfix"])) postfix = '/'+request.params['postfix'];   
        if (!isEmpty(request.params["secondPostfix"])) secondPostfix = '/'+request.params['secondPostfix'];
    
        Parse.Cloud.httpRequest(
        {
                method: request.params["method"],
                //Create URL from base url and pre/suf/postfixes
                url: 'https://'+STRIPE_API_BASE_URL + prefix + suffix + postfix + secondPostfix,
                headers: {
                    'Authorization': "Bearer " + STRIPE_SECRET_KEY
                },
                params:request.params["params"],
                success: function(httpResponse) 
                {
                    //response text is a json dictionary
                    response.success(httpResponse.text);
                },
                error: function(httpResponse) 
                {
                    response.error(httpResponse.text);
                }
        });
    });
    

    Using the method above I can create individual methods to perform most of the stripe tasks I need.

    Here is an example that will create a new card and attach it to a customer Stripe Card creation API

    + (void)createCardFromToken:(NSString *)tokenId customerId:(NSString *)customerId completionHandler:(ELCardCompletionBlock)handler
    {
    
        [ELStripe executeStripeCloudCodeWithMethod:@"POST" //I use post here because we are creating a card. POST would also be used for updating a customer/card or refunding a charge for example
                                            prefix:@"customers" //If you look at the documentation and the example URL I use "customers" here as the prefix
                                            suffix:customerId //The customerID is the suffix, this will be the customer you are going to add the card to
                                           postfix:@"cards" //I believe this is "sources" now
                                     secondPostfix:nil //Not needed for this URL
                                        parameters:@{
                                                     @"card":tokenId  //Only parameter is a tokenId, and I wrap this inside an NSDictionary
                                                     }
                                 completionHandler:^(id jsonObject, NSError *error) {
                                     if (error)
                                     {
                                         //Handle the error code here
    
                                         handler(nil,rejectError);
                                         return;
                                     }
                                     //If no error stripe returns a dictionary containing the card information. You can use this information to create a card object if so desired.
                                     handler([ELCard cardFromDictionary:jsonObject],error);
                                 }];
    }