NSUrlRequest from curl for stripe

2019-05-16 23:35发布

问题:

I need to make a http post request using the following instructions:

curl https://api.stripe.com/v1/tokens \
   -u sk_test_BQokikJOvBiI2HlWgH4olfQ2: \
   -d "bank_account[country]=US" \
   -d "bank_account[routing_number]=110000000" \
   -d "bank_account[account_number]=000123456789"

I have no idea how to go from curl to NSUrlrequest, especially the -u (user?) part. The stripe SDK has left this out from their SDK and has no example on their site.

Thanks

回答1:

EDIT:I created another answer specifically for getting a token for a bank_account. This answer was for generally how to make a call using parse's back end using an example of creating a recipient.

The stipe documentation is a little off here, the call for creating a bank_account token is actually made using the publishable key directly from the app. Make sure not to use your secret key in the iOS app itself. Only your public key should be used via:

[Stripe setDefaultPublishableKey:@"pk_test_your_test_key_here"];

You need to use a web back end to implement the complete functionality of the stripe payment system. The ios sdk they include only gets you as far as getting a token from a credit card. The web back end is there you would implement the secret key. I use parse.com as my backend for stripe but many implement their own.

Stripe ios Tutorial

Below is a simple httpRequest cloud code that can perform most stripe tasks. Feed it a method, prefix, suffix, postfix, and then the parameters of the request. I'm not saying this is the best way to implement stripe's httpRequests, but it covers the bases for you to start working on it. The code below is tested, and it works, I created a john doe recipient in my stripe test sandbox.

Parse Cloud code:

var Stripe = require('stripe');
var STRIPE_SECRET_KEY = 'sk_test_yoursecretkeyhere';
var STRIPE_API_BASE_URL = 'api.stripe.com/v1/'
Stripe.initialize(STRIPE_SECRET_KEY);

Parse.Cloud.define("stripeHTTPRequest", function(request, response) 
{
    //check for suffix, and postfix
    var suffix = "";
    if (!isEmpty(request.params["suffix"])) {
        suffix = '/'+request.params['suffix'];
    }
    var postfix = "";
    if (!isEmpty(request.params["postfix"])) {
        postfix = '/'+request.params['postfix'];
    }   

    Parse.Cloud.httpRequest({
            method: request.params["method"],
            url: 'https://' + STRIPE_SECRET_KEY + ':@' + STRIPE_API_BASE_URL + request.params["prefix"] + suffix + postfix,
            params:request.params["parameters"],
            success: function(httpResponse) {
            response.success(httpResponse.text);
            },
            error: function(httpResponse) {
            response.error('Request failed with response code' + httpResponse.status);
            }
    });
});

function isEmpty(obj) {

    // null and undefined are "empty"
    if (obj == null) return true;

    // Assume if it has a length property with a non-zero value
    // that that property is correct.
    if (obj.length > 0)    return false;
    if (obj.length === 0)  return true;

    // Otherwise, does it have any properties of its own?
    // Note that this doesn't handle
    // toString and valueOf enumeration bugs in IE < 9
    for (var key in obj) {
        if (hasOwnProperty.call(obj, key)) return false;
    }

    return true;
}

So when it comes to creating a recipient, you would feed it a method of "POST", and a prefix of "recipients", and leave the suffix, and postfix empty. This would generate a url of such:

https://sk_test_yoursecretkeyhere:@api.stripe.com/v1/recipients

In addition to the method & pre/suf/postfix you would need to feed it parameters. You can do this by sending a dictionary of keyed objects. Using Stripe's documentation, lets create a recipient named john doe:

   -d "name=John Doe" \
   -d type=individual \
   -d tax_id=000000000 \
   -d "email=test@example.com" \
   -d "description=Recipient for John Doe"

Here is the iOS call of the cloud code using the John Doe example. I have implemented a general method the you pass the method, pre/suf/postfix's and parameters. I then create many additional methods to handle the specific stripe calls, like creating a recipient for example.

ViewController.m

-(void)createJohnDoe
{
    NSDictionary *parameters = @{@"name":@"John Doe",
                                 @"type":@"individual",
                                 @"tax_id":@"000000000",
                                 @"email":@"test@example.com",
                                 @"description":@"Recipient for John Doe"
                                 };
    [ELStripe executeStripeCloudCodeWithMethod:@"POST" prefix:@"recipients" suffix:nil postfix:nil parameters:parameters completionHandler:^(id jsonObject, NSError *error) {
        //jsonObject will be a dictionary that would need be parsed into your recipient object
        NSLog(@"jsonObject:%@",jsonObject);
    }];
}

ELStripe.m

   //Completion Handler Definition.
   typedef void (^ELStripeCompletionBlock)(id jsonObject, NSError *error);

   +(void)executeStripeCloudCodeWithMethod:(NSString *)method prefix:(NSString *)prefix suffix:(NSString *)suffix postfix:(NSString *)postfix parameters:(NSDictionary *)parameters completionHandler:(ELStripeCompletionBlock)handler
{

    [PFCloud callFunctionInBackground:@"stripeHTTPRequest" withParameters:@{@"method":method, @"prefix":prefix?prefix:@"", @"suffix":suffix?suffix:@"", @"postfix":postfix?postfix:@"", @"parameters":parameters} block:^(id object, NSError *error) {
        id jsonObject;
        if (!error) {
            NSError *jsonError = nil;
            jsonObject = [NSJSONSerialization JSONObjectWithData:[object dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&jsonError];
        }
        handler(jsonObject,error);
    }];
}


回答2:

STPCard has a method -(NSData *)formEncode that stripe.m uses to populate the body of the nsurlrequest. I used their format to create my own encode with bank account information instead of the card.

Stripe's token creation with a card:

Stripe.m

+ (void)createTokenWithCard:(STPCard *)card publishableKey:(NSString *)publishableKey operationQueue:(NSOperationQueue *)queue completion:(STPCompletionBlock)handler{
    if (card == nil) {
        [NSException raise:@"RequiredParameter" format:@"'card' is required to create a token"];
    }

    if (handler == nil) {
        [NSException raise:@"RequiredParameter" format:@"'handler' is required to use the token that is created"];
    }

    [self validateKey:publishableKey];

    NSURL *url = [self apiURLWithPublishableKey:publishableKey];

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    request.HTTPMethod = @"POST";
    request.HTTPBody = [card formEncode];
    [request setValue:[self JSONStringForObject:[self stripeUserAgentDetails]] forHTTPHeaderField:@"X-Stripe-User-Agent"];

    [[[STPAPIConnection alloc] initWithRequest:request] runOnOperationQueue:queue
                                                                 completion:^(NSURLResponse *response, NSData *body, NSError *requestError) {
                                                                     [self handleTokenResponse:response body:body error:requestError completion:handler];
                                                                 }];
}

STPCard.m

- (NSData *)formEncode
{
    NSMutableDictionary *params = [NSMutableDictionary dictionary];

    if (_number) params[@"number"] = _number;
    if (_cvc) params[@"cvc"] = _cvc;
    if (_name) params[@"name"] = _name;
    if (_addressLine1) params[@"address_line1"] = _addressLine1;
    if (_addressLine2) params[@"address_line2"] = _addressLine2;
    if (_addressCity) params[@"address_city"] = _addressCity;
    if (_addressState) params[@"address_state"] = _addressState;
    if (_addressZip) params[@"address_zip"] = _addressZip;
    if (_addressCountry) params[@"address_country"] = _addressCountry;
    if (_expMonth) params[@"exp_month"] = [NSString stringWithFormat:@"%lu", (unsigned long) _expMonth];
    if (_expYear) params[@"exp_year"] = [NSString stringWithFormat:@"%lu", (unsigned long) _expYear];

    NSMutableArray *parts = [NSMutableArray array];

    [params enumerateKeysAndObjectsUsingBlock:^(id key, id val, BOOL *stop) {
        if (val != [NSNull null]) {
            [parts addObject:[NSString stringWithFormat:@"card[%@]=%@", key, [STPUtils stringByURLEncoding:val]]];
        }
    }];

    return [[parts componentsJoinedByString:@"&"] dataUsingEncoding:NSUTF8StringEncoding];
}

My barebones bank_account creation:

BankAccount.h

#import <Foundation/Foundation.h>

@interface BankAccount : NSObject
 @property (strong, nonatomic) NSString *country, *routingNumber, *accountNumber;
- (NSData *)bankAccountEncode;
@end

BankAccount.m

#import "BankAccount.h"
#import "STPUtils.h"

@implementation BankAccount

//Similar to STPCard's formEncode
- (NSData *)bankAccountEncode
{
    NSMutableDictionary *params = [NSMutableDictionary dictionary];

    if (_country) params[@"country"] = @"US";
    if (_routingNumber) params[@"routing_number"] = _routingNumber;
    if (_accountNumber) params[@"account_number"] = _accountNumber;


    NSMutableArray *parts = [NSMutableArray array];

    [params enumerateKeysAndObjectsUsingBlock:^(id key, id val, BOOL *stop) {
        if (val != [NSNull null]) {
            [parts addObject:[NSString stringWithFormat:@"bank_account[%@]=%@", key, [STPUtils stringByURLEncoding:val]]];
        }
    }];

    return [[parts componentsJoinedByString:@"&"] dataUsingEncoding:NSUTF8StringEncoding];
}

@end

My Stripe.m Additions. Notice I use a BankAccount object, and then use it's - (NSData *)bankAccountEncode instead of STPCard's formEncode.

+ (void)createTokenWithBankAccount:(BankAccount *)bankAccount completion:(STPCompletionBlock)handler{
    [self createTokenWithBankAccount:bankAccount publishableKey:[self defaultPublishableKey] operationQueue:[NSOperationQueue mainQueue] completion:handler];
}

+ (void)createTokenWithBankAccount:(BankAccount *)bankAccount publishableKey:(NSString *)publishableKey operationQueue:(NSOperationQueue *)queue completion:(STPCompletionBlock)handler{
    if (bankAccount == nil) {
        [NSException raise:@"RequiredParameter" format:@"'card' is required to create a token"];
    }

    if (handler == nil) {
        [NSException raise:@"RequiredParameter" format:@"'handler' is required to use the token that is created"];
    }

    [self validateKey:publishableKey];

    NSURL *url = [self apiURLWithPublishableKey:publishableKey];

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    request.HTTPMethod = @"POST";

    //Changed this here to get the bank account encode instead of the credit card encode
    request.HTTPBody = [bankAccount bankAccountEncode];
    [request setValue:[self JSONStringForObject:[self stripeUserAgentDetails]] forHTTPHeaderField:@"X-Stripe-User-Agent"];

    [[[STPAPIConnection alloc] initWithRequest:request] runOnOperationQueue:queue
                                                                 completion:^(NSURLResponse *response, NSData *body, NSError *requestError) {
                                                                     [self handleTokenResponse:response body:body error:requestError completion:handler];
                                                                 }];
}