Using Terminal with Connect
Integrate Stripe Terminal with your Connect platform.
Stripe Terminal is fully compatible with Stripe Connect, making it easy for your platform or marketplace to accept in-person payments.
The way Terminal creates API objects depends on whether you use direct charges or destination charges. If you use direct charges, all Terminal API objects belong to connected accounts. If you use destination charges, all Terminal API objects are created on your platform account. In both cases, you can use Locations to group readers as you see fit.
Direct charges
With direct charges, API objects belong to the connected account rather than the platform. The connected account creates payments, and it’s responsible for the cost of Stripe fees, refunds, and chargebacks.
In the Dashboard, you can view your Terminal data by logging in as the connected account.
Creating Locations and Readers Server-side
With direct charges, payment objects are created belonging to the connected account. Other Terminal API objects like Locations and Readers must be created belonging to the same connected account.
To create a Location belonging to a connected account, use the Stripe-Account
header.
curl https://api.stripe.com/v1/terminal/locations \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d display_name=HQ \
-H "Stripe-Account: {CONNECTED_ACCOUNT_ID}"
Before you can connect your application to the Verifone P400, you must register the reader to a Stripe account. To register a reader to a connected account, use the Stripe-Account
header. You may also provide a location
parameter. If you do not provide a location, the reader is categorized as ungrouped on the connected account.
curl https://api.stripe.com/v1/terminal/readers \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d registration_code="{REGISTRATION_CODE}" \
-d label="Test Reader" \
-d location="{LOCATION_ID}" \
-H "Stripe-Account: {CONNECTED_ACCOUNT_ID}"
Creating Connection Tokens Server-side
When creating a ConnectionToken for the Terminal SDK, set the Stripe-Account
header to the connected account accepting payments. You may also provide a location
parameter to control access to readers. If you provide a location, the ConnectionToken is only usable with readers assigned to that location. If you do not provide a location, the ConnectionToken is usable with all readers.
curl https://api.stripe.com/v1/terminal/connection_tokens \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d location="{LOCATION_ID}" \
-H "Stripe-Account: {CONNECTED_ACCOUNT_ID}"
Creating Payment Intents Client-side Server-side
With the iOS and Android SDKs, you can create a PaymentIntent on the client or server. The JavaScript SDK only supports server-side creation.
Client-side
When creating a PaymentIntent client-side for direct charges, you don’t need to specify any additional parameters for the PaymentIntent. Instead, when creating a ConnectionToken, set the Stripe-Account
header to the connected account accepting payments. The iOS and Android SDK create the PaymentIntent on the same connected account the ConnectionToken belongs to. For more information, see Create Payment Intents Client-side.
Server-side
The JavaScript SDK requires you to create the PaymentIntent on your server. For iOS or Android, you might choose to create the PaymentIntent on your server if the information required to start a payment isn’t readily available in your app. For more information, see Create Payment Intents Server-side.
When creating a PaymentIntent server-side for direct charges, set the Stripe-Account
header to the connected account.
curl https://api.stripe.com/v1/payment_intents \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d amount=1099 \
-d currency=usd \
-d payment_method_types[]=card_present \
-d capture_method=manual \
-H "Stripe-Account: {CONNECTED_ACCOUNT_ID}"
Destination charges
When using destination charges, payment objects are created on your connected accounts but belong to your platform account. Each payment creates a transfer to a connected account automatically.
In the Dashboard, you can view your Terminal data directly when logged into your platform account.
Creating Locations and Readers Server-side
With destination charges, payment objects are created on your platform account, and then funds get transferred to the correct connected account. Similarly, Terminal API objects get created on your platform account, even though they belong to connected accounts.
The best way to group Reader objects by connected account is by assigning them to locations. To create a Location for a connected account, use a display name that identifies the account.
curl https://api.stripe.com/v1/terminal/locations \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d display_name="{CONNECTED_ACCOUNT_NAME}"
Before you can connect your application to the Verifone P400, you must register the reader to your platform account. To group readers by connected account, you must also provide a location
parameter. If you don’t provide a location
, the reader is categorized as ungrouped on your platform account.
curl https://api.stripe.com/v1/terminal/readers \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d registration_code="{REGISTRATION_CODE}" \
-d label="Test Reader" \
-d location="{LOCATION_ID}"
Creating Connection Tokens Server-side
When creating a ConnectionToken for the Terminal SDK, use your platform account’s secret key. Do not set the Stripe-Account
header. Provide a location
parameter to control access to readers. If you provide a location, the ConnectionToken is only usable with readers assigned to that location. If you do not provide a location, the ConnectionToken is usable with all readers.
curl https://api.stripe.com/v1/terminal/connection_tokens \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d location="{LOCATION_ID}"
Creating Payment Intents Client-side Server-side
When creating a PaymentIntent using destination charges, provide the on_behalf_of and transfer_data[destination], and application_fee_amount parameters.
The on_behalf_of
parameter is the ID of the connected account that will become the settlement merchant for the payment. When on_behalf_of
is set, Stripe automatically:
- Settles charges in the country of the specified account, thereby minimizing declines and avoiding currency conversions
- Uses the fee structure for the connected account’s country
- Lists the connected account’s address and phone number on the customer’s credit card statement, as opposed to the platform’s address and phone number (only occurs if the account and platform are in different countries)
For transfer_data[destination]
, set the ID of the connected account that should receive the transfer.
Finally, you may withhold an application fee for your platform by providing the application_fee_amount parameter.
Client-side
With the iOS and Android SDKs, you can create a PaymentIntent client-side and provide the onBehalfOf
, transferDataDestination
, and applicationFeeAmount
parameters.
import UIKit
import StripeTerminal
class PaymentViewController: UIViewController {
// Action for a "Checkout" button
func checkoutAction() {
let params = PaymentIntentParameters(amount: 1000, currency: "usd")
params.onBehalfOf = "{CONNECTED_ACCOUNT_ID}"
params.transferDataDestination = "{CONNECTED_ACCOUNT_ID}"
params.applicationFeeAmount = 200
Terminal.shared.createPaymentIntent(params) { createResult, createError in
if let error = createError {
print("createPaymentIntent failed: \(error)")
}
else if let paymentIntent = createResult {
print("createPaymentIntent succeeded")
// ...
}
}
}
// ...
}
#import "APPPaymentViewController.h"
#import <StripeTerminal/StripeTerminal.h>
@implementation APPPaymentViewController
// Action for a "Checkout" button
- (void)checkoutAction {
SCPPaymentIntentParameters *params = [[SCPPaymentIntentParameters alloc] initWithAmount:1000
currency:@"usd"];
params.onBehalfOf = @"{CONNECTED_ACCONT_ID}";
params.transferDataDestination = @"{CONNECTED_ACCONT_ID}";
params.applicationFeeAmount = @(200);
[[SCPTerminal shared] createPaymentIntent:params completion:^(SCPPaymentIntent *createResult, NSError *createError) {
if (createError) {
NSLog(@"createPaymentIntent failed: %@", createError);
}
else {
NSLog(@"createPaymentIntent succeeded");
// ...
}
}
// ...
@end
PaymentIntentParameters params = new PaymentIntentParameters.Builder()
.setAmount(1000)
.setCurrency("usd")
.setOnBehalfOf("{CONNECTED_ACCOUNT_ID}")
.setTransferDataDestination("{CONNECTED_ACCOUNT_ID}")
.setApplicationFeeAmount(200)
.build();
Terminal.getInstance().createPaymentIntent(params, new PaymentIntentCallback() {
@Override
public void onSuccess(PaymentIntent paymentIntent) {
// Placeholder for collecting a payment method with paymentIntent
}
@Override
public void onFailure(TerminalException exception) {
// Placeholder for handling exception
}
}
})
val params = PaymentIntentParameters.Builder()
.setAmount(100)
.setCurrency("usd")
.setOnBehalfOf("{CONNECTED_ACCOUNT_ID}")
.setTransferDataDestination("{CONNECTED_ACCOUNT_ID}")
.setApplicationFeeAmount(200)
.build();
Terminal.getInstance().createPaymentIntent(params, object: PaymentIntentCallback {
override fun onSuccess(paymentIntent: PaymentIntent) {
// Placeholder for collecting a payment method with paymentIntent
}
override fun onFailure(exception: TerminalException) {
// Placeholder for handling exception
}
})
Server-side
The JavaScript SDK requires you to create the PaymentIntent on your server. For iOS or Android, you might choose to create the PaymentIntent on your server if the information required to start a payment isn’t readily available in your app. For more information, see Create Payment Intents Server-side.
curl https://api.stripe.com/v1/payment_intents \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d amount=1099 \
-d currency=usd \
-d payment_method_types[]=card_present \
-d capture_method=manual \
-d application_fee_amount=200 \
-d on_behalf_of="{CONNECTED_ACCOUNT_ID}" \
-d transfer_data[destination]="{CONNECTED_ACCOUNT_ID}"
Next steps
Congratulations! Your Connect platform is now set up to accept in-person payments with Terminal. Next, configure the customer-facing checkout experience in your app, or test your current integration with a physical reader.