Help with Stripe Connect 'shared customers', tokens, and subscriptions

1,940 views
Skip to first unread message

Michael Dobbins

unread,
Oct 23, 2013, 6:57:23 PM10/23/13
to api-d...@lists.stripe.com
I would love to see some better examples of handling customer subscriptions with shared customers and Stripe Connect. I am having some trouble getting subscriptions of shared customers to work when subscribing to a 3rd party plan. I am sure I am doing something wrong.  I can't figure out where I need to use my api  secret key, and where to use the stripe connect account access_tokens (and publishable keys). I think I have tried every possible combination, and generating tokens for customers I created with my api key always results in a 'no such customer' error when I use the access tokens for the customer. FWIW, the Stripe_Token::create always returns a token and a card object - not a customer object like I had expected. If anyone has experience implementing subscriptions to shared customers for a plan created in a 3rd party account, I would welcome any advice you may have. Right now I am using my own publishable_key for stripe.js when gathering card info, and my own api key. Then I create the customer using my own api key. I use the stripe_plan::all method to get a list of all plans which were created for the connected account. Then I call Stripe_Customer::retrieve(ACCESS_TOKEN) , then call $customer->updateSubscription and pass the plan_id (which belongs to the 3rd party).

Also, I had to create a 2nd stripe account in order to test Stripe Connect. I hope that the access tokens are generated to use the TEST api secret keys in the connected account, and not live credentials; I don't want to actually charge any bank accounts during testing. It would be nice if there was a better way to create fake stripe accounts to test the stripe connect and shared customers, etc. That would be handy; I could create a fake stripe account that has a $5 balance, and then issue a refund to a customer for $100 and see what happens, etc. It is entirely possible I am doing something wrong, and there is a really easy way to test stripe connect. FWIW, I even tried using the test bank accounts and routing numbers provided in the recipient api docs when setting up the bank account for my fake stripe account; there is ajax validation, and you must specify a real routing number, but they allow you to supply a fake account number. That scares me. I don't want to actually issue payment to someone else's account by accident. I would really like some more information on how to properly test stripe connect and shared customers.

Here is the flow I am using right now. I have written this psuedo-code usig the PHP library and jQuery. Note that charges to shared customers on behalf of a 3rd party have not been tested. I only tested the shared customer subscriptions to 3rd party plans, which I can't get to work. What am I doing wrong??

<?php
$stripeRand
= 'as908fas098';
$my_publishable_key
= 'abc123';
$my_secret_key
= '123abc';
$stripe_connect_access_token
= 'def456';
$stripe_connect_publishable_key
= '456def';


$js
= "<script src=\"https://js.stripe.com/v2/\"></stripe>
<script>
    jQuery
(document).ready(function($){
       
Stripe.setPublishableKey('" . $my_publishable_key . "');
       
var stripeform = $('#" . $stripeRand . "');
        stripeform
.bind('validationpassed', function(e){
            e
.preventDefault();
            $
('.payment-errors', stripeform).empty();
            stripeform
.find('button').prop('disabled', true);
           
Stripe.card.createToken(stripeform, stripeResponseHandler_" . $stripeRand . ");
           
// Prevent the form from submitting with the default action
           
return false;
       
});  
   
});
   
function stripeResponseHandler_" . $stripeRand . "(status, response) {
       
//console.log('stripeResponseHandler_" . $stripeRand . " fired; status: ', status, '; response: ', response);
       
var stfrm = $('#" . $stripeRand . "');
       
if (!!response['error']) {
           
// show the errors on the form
            $
('.payment-errors', stfrm).text(response.error.message);
            stfrm
.find('button').prop('disabled', false);
           
return false;
       
} else {
           
var token = response['id'];
            $
('.payment-errors').text('Card accepted');
            stfrm
.append(\"<input type='hidden' name='stripeToken' value='\" + token + \"'/>\");
            stfrm.get(0).submit();
        }
    }
</script>"
;
$ast
= '<span class="colorRed"> *</span>';
$stripeform
= '
<form id="'
. $stripeRand  . '" class="validateme hijacksubmit" method="POST" action="#">
    <input type="hidden" name="stripe_card_form_submitted" value="1" />
    <fieldset class="ui-fieldset ui-corner-all">
        <legend>Stripe Form</legend>
        <span class="payment-errors error"></span>
        <table class="tableForm">
            <tr>
                <th>
                    Name on Card'
. $ast . '
                </th>
                <td>
                    <input type="text" name="customer[name]" class="required" maxlength="100" minlength="3" data-stripe="name" />
                </td>
            </tr>
            <tr>
                <th>
                    Email'
. $ast . '
                </th>
                <td>
                    <input type="text" name="customer[email]" class="required email" maxlength="100" minlength="3" />
                </td>
            </tr>
            <tr>
                <th>
                    Credit/Debit Card Number'
. $ast . '
                </th>
                <td>
                    <input type="text" name="number" class="required stripe-cardNumber nopost" data-stripe="number" />
                </td>
            </tr>
            <tr>
                <th>
                    CVC Code'
. $ast . '
                </th>
                <td>
                    <input type="text" name="cvc" class="required stripe-CVC nopost" size="4" style="width: 4em;" data-stripe="cvc" />
                </td>
            </tr>
            <tr>
                <th>
                    Expiration (MM/YYYY)'
. $ast . '
                </th>
                <td>
                    <input type="text" name="exp-month" class="required nopost stripe-expiry-month" size="2" style="width: 2em;" data-stripe="exp-month" /> / <input type="text" name="exp-year" class="required nopost stripe-expiry-year" size="4" style="width: 4em;" data-stripe="exp-year" />
                </td>
            </tr>
            <tr>
                <th>
                    Address1
                </th>
                <td>
                    <input type="text" class="nopost" name="address_line1" maxlength="100" minlength="5" data-stripe="address_line1" />
                </td>
            </tr>
            <tr>
                <th>
                    Address2
                </th>
                <td>
                    <input type="text" class="nopost" name="address_line2" maxlength="100" minlength="2" data-stripe="address_line2" />
                </td>
            </tr>
            <tr>
                <th>
                    City
                </th>
                <td>
                    <input type="text" class="nopost" name="address_city" maxlength="60" minlength="2" data-stripe="address_city" />
                </td>
            </tr>
            <tr>
                <th>
                    State
                </th>
                <td>
                    <input type="text" class="nopost" name="address_state" maxlength="60" minlength="2" data-stripe="address_state" />
                </td>
            </tr>
            <tr>
                <th>
                    Postal Code
                </th>
                <td>
                    <input type="text" class="nopost alphanumeric" name="address_zip" maxlength="10" minlength="5"  style="width: 10em;" data-stripe="address_zip" />
                </td>
            </tr>
            <tr>
                <th>
                    Country
                </th>
                <td>
                    <input type="text" class="nopost" name="address_country" maxlength="3" minlength="3" style="width: 3em;" data-stripe="address_country" />
                </td>
            </tr>
        </table>
        <button type="submit">Pay</button>
    </fieldset>
</form>
'
;
echo $stripieform
. $js;

//BEGIN PROCESS THE FORM - NOTE $attributes global var is a merge of $_GET and $_POST - so the fields submitted in the form :);
if(empty($stripe_errors) && !empty($attributes['stripe_card_form_submitted'])){
    $customer_args
= array(
       
'card' => $attributes['stripeToken']
       
, 'description'=>trim($attributes['customer']['name']) . ';' . trim($attributes['customer']['email'])
       
, 'email'=>$attributes['customer']['email']
   
);
   
//create a new customer
   
//ALWAYS create the customer record in the root application - NOT using the stripe connect access_token
   
//according to docs, this is how you create a shared customer
    $new_customer
= Stripe_Customer::create($customer_args, $my_secret_key);
    $cust_arr
= $new_customer->__toArray();
   
if(!empty($cust_arr['id'])){
       
//save the new customer to the db so we can ::charge them
       
        $sql
= "
        INSERT INTO
            stripe_customer
        SET ...
        "
;
        $insert_params
= array('customer_id'=>$cust_arr['id'], ...);
        $insert_result
= $dboc->Execute($sql, $insert_params);
       
if(!empty($insert_result['success'])){
           
//insert was successful
           
//now we have a saved customer. lets try a charge
           
//I HAVE NOT EVEN TESTED CHARGES ON BEHALF OF 3RD PARTIES YET, BUT THIS IS HOW I ASSUME IT WOULD WORK
             
//$cust_token = Stripe_Token::create(array('customer'=>$cust_arr['id']), $stripe_connect_access_token); //get a token representing shared customer; returns a token and a 'card' object
           
//$charge_params = array('customer'=>$cust_token, 'amount'=>1000, 'currency'=>'USD', 'description'=>'Test Charge1'); //make a test charge of $10.00 USD
           
//$charge = Stripe_Charge::create($charge_params, $stripe_connect_access_token);
           
//$charge_arr = $charge->__toArray();
           
           
//NOW LETS TRY TO CREATE A PLAN FOR 3rd PARTY AND SUBSCRIBE THIS SHARED USER TO THE PLAN
            $plan
= Stripe_Plan::create(array('amount'=>100, 'interval'=>'month', 'interval_count'=>1, 'name'=>'3rd party plan', 'id'=>'3rdPartyPlan', 'currency'=>'USD'), $stripe_connect_access_token); //THIS WORKS
            $new_plan_id
= $plan_result->id; //this is the plan_id of the new plan just created in 3rd party's stripe account on their behalf
            $cust_token
= Stripe_Token::create(array('customer'=>$cust_arr['id']), $stripe_connect_access_token);
            //get a token representing shared customer; returns a token and a 'card' object which has a null customer
            $shared_cust
= Stripe_Customer::retrieve($cust_token, $stripe_connect_access_token); //fails; returns 'no such customer' error.
            $subscribe_result
=  $shared_cust->updateSubscription(array("plan" => $new_plan_id, "prorate" => true, "quantity" => 1 ), $stripe_connect_access_token );
       
}       
   
}
}
//END PROCESS THE FORM




Amber Feng

unread,
Oct 24, 2013, 12:38:54 AM10/24/13
to api-d...@lists.stripe.com
Hi Michael,

Sorry for the confusion here -- our docs here admittedly aren't the
best, but we'll try to improve them going forward.

In your specific case, you're exactly right that you initially create
the customer with your own publishable and secret key. Once you have
the customer object on your own account, you create a token on the
connected account using that customer's ID (the API knows which
application you're trying to get the customer from). Then, you can
either use that token (which now contains the cloned credit card
details from the original customer) to create a once-off charge or
another customer to charge in the future. If you choose to create a
customer, this customer is completely independent of the original
customer on the application owner's account -- we don't do true
customer sharing.

More concretely, if your, the application owner's, account is A, and
the connected account is B:

- Create customer X on A's account using A's publishable key and secret key.
- Create a *token* on B's account using the access_token and customer X's token.
- With that token, create a once-off charge or customer Y on B's
account using the access_token.
- If you'd like to subscribe customer Y to a subscription, you can do
so with B's access_token.

Hope that makes sense.

Amber
> --
> You received this message because you are subscribed to the Google Groups
> "Stripe API Announcements and Discussion" group.
> To post to this group, send email to api-d...@lists.stripe.com.
> Visit this group at
> http://groups.google.com/a/lists.stripe.com/group/api-discuss/.
>
> To unsubscribe from this group and stop receiving emails from it, send an
> email to api-discuss...@lists.stripe.com.
Reply all
Reply to author
Forward
0 new messages