Terminal
Canada

Stripe Terminal in Canada

Bringing your Terminal integration to Canada with Interac acceptance and reader localization.

In Canada, Stripe Terminal currently supports the Verifone P400 with the JavaScript SDK or the iOS SDK.

Terminal payments acceptance in Canada

Stripe Terminal supports Visa, Mastercard, American Express, and Interac payments in Canada. All transactions must be made in Canadian dollars (CAD).

Interac is the interbank network that handles routing of debit payments in Canada. Consumer debit cards in Canada are branded with an Interac logo and may also be “co-branded” with another payment network’s logo. Even if the card is co-branded, however, all Interac debit transactions must be routed through Interac.

Interac Debit payments may require customers to enter their PIN on the card reader to complete the transaction. Unlike Visa, Mastercard, and American Express transactions, Interac transactions are authorized and automatically captured in a single step.

Account and reader setup

In order to accept Terminal charges in Canada, your Stripe account or the Connect Accounts you are making transactions for must be in Canada.

You should also create reader locations with Canadian addresses. The reader uses its registered location to configure itself to accept in-person payments in Canada and to determine if localization is needed. In the absence of a registered location, the readers configure themselves based on the country in which the Stripe account is based.

We recommend creating a unique location for each physical operating site, and explicitly registering your readers to the location where they are being used.

Once your reader is registered to a Canadian location, connect your application to the Verifone P400 by following our guide to Connecting to the Verifone P400. For general information on using the Verifone P400, see the Verifone P400 Reader Guide.

Collecting payments in Canada

Adding Interac acceptance requires a few changes to the card_present payment flow.

Step 1: Create a PaymentIntent Server-side

The PaymentIntent object represents a single payment session. Each PaymentIntent has a payment_method_types array that lists the allowed payment methods for the transaction. To accept all available card brands in Canada, include card_present and interac_present in the PaymentIntent’s list of allowed payment_method_types.

As with all Terminal transactions, you must set the PaymentIntent’s capture_method to manual to precisely control the payment flow.

Currently, creating a PaymentIntent client-side using the iOS SDK is not supported for Interac payments.

curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=999 \ -d currency=cad \ -d "payment_method_types[]"=card_present \ -d "payment_method_types[]"=interac_present \ -d capture_method=manual
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' Stripe::PaymentIntent.create({ amount: 999, currency: 'cad', payment_method_types: ['card_present', 'interac_present'], capture_method: 'manual', })
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' stripe.PaymentIntent.create( amount=999, currency='cad', payment_method_types=['card_present', 'interac_present'], capture_method='manual', )
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); \Stripe\PaymentIntent::create([ 'amount' => 999, 'currency' => 'cad', 'payment_method_types' => ['card_present', 'interac_present'], 'capture_method' => 'manual', ]);
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; PaymentIntentCreateParams params = PaymentIntentCreateParams.builder() .setAmount(999L) .setCurrency("cad") .addAllPaymentMethodType(Arrays.asList("card_present", "interac_present")) .setCaptureMethod(PaymentIntentCreateParams.CaptureMethod.MANUAL) .build(); PaymentIntent.create(params);
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); const paymentIntent = await stripe.paymentIntents.create({ amount: 999, currency: 'cad', payment_method_types: ['card_present', 'interac_present'], capture_method: 'manual', });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" params := &stripe.PaymentIntentParams{ Amount: stripe.Int64(999), Currency: stripe.String(string(stripe.CurrencyCAD)), PaymentMethodTypes: stripe.StringSlice([]string{ "card_present", "interac_present", }), CaptureMethod: stripe.String(string(stripe.PaymentIntentCaptureMethodManual)), } intent, _ = paymentintent.New(params)
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; var options = new PaymentIntentCreateOptions { Amount = 999, Currency = "cad", PaymentMethodTypes = new List<string> { "card_present", "interac_present", }, CaptureMethod = "manual", }; var service = new PaymentIntentService(); var intent = service.Create(options);

Step 2: Collect a payment method Client-side

After you’ve created a PaymentIntent, the next step is to collect a payment method with the Stripe Terminal SDK. When your point of sale application calls collectPaymentMethod, the connected reader will begin waiting for a card.

The reader automatically localizes its interface. Before a card is presented, the localization is based on the address of its assigned location. After the card information has been collected, the localization is based on the language preference specified by the presented card.

Use the client secret created on the server as a parameter when calling collectPaymentMethod.

function checkout() { // ...Fetch the client secret from your backend terminal.collectPaymentMethod(clientSecret).then(function(result) { if (result.error) { console.error("collectPaymentMethod failed", error) } else { // Process the payment } }); }
async () => { // ...Fetch the client secret from your backend const result = await terminal.collectPaymentMethod(clientSecret); if (result.error) { console.error("collectPaymentMethod failed", error) } else { // Process the payment } }

Use the client secret created on the server as a parameter when calling retrievePaymentIntent, and then use the retrieved PaymentIntent to call collectPaymentMethod.

self.collectCancelable = nil // Action for a "Checkout" button func checkoutAction() { // ... Fetch the client secret from your backend Terminal.shared.retrievePaymentIntent(clientSecret: clientSecret) { retrieveResult, retrieveError in if let error = retrieveError { print("retrievePaymentIntent failed: \(error)") } else if let paymentIntent = retrieveResult { print("retrievePaymentIntent succeeded: \(paymentIntent)") self.collectCancelable = Terminal.shared.collectPaymentMethod(paymentIntent) { intentWithPaymentMethod, collectError in if let error = collectError { print("collectPaymentMethod failed: \(error)") } else if let intent = intentWithPaymentMethod { // Process the payment } } } }
@property (nonatomic, nullable, strong) SCPCancelable *collectCancelable; // Action for a "Checkout" button - (void)checkoutAction { // ... Fetch the client secret from your backend [[SCPTerminal shared] retrievePaymentIntent:clientSecret completion:^(SCPPaymentIntent *retrieveResult, NSError *retrieveError) { if (retrieveError) { NSLog(@"retrievePaymentIntent failed: %@", retrieveError); } else { self.collectCancelable = [[SCPTerminal shared] collectPaymentMethod:retrieveResult delegate:self completion:^(SCPPaymentIntent *collectResult, NSError *collectError) { if (collectError) { NSLog(@"collectPaymentMethod failed: %@", collectError); } else { // Process the payment } }]; } }]; }

Step 3: Process the payment Client-side

After successfully collecting a payment method from the customer, your integration should call processPayment to complete the payment. For Interac payments, this is the final step: if processPayment succeeds, then the payment is complete.

Unlike card_present payments, interac_present payments do not have a two-step authorization and capture flow. Your application should not continue to capture the PaymentIntent. If you attempt to capture an interac_present payment, the Stripe API returns an error.

terminal.processPayment(paymentIntent).then(function(processResult) { if (processResult.error) { // Handle confirmation error } else if (processResult.paymentIntent) { if (processResult.paymentIntent.status !== "succeeded") { // This code branch *won't* be called for Interac payments. // Capture the PaymentIntent from your backend and // mark the payment as complete. Here we use a hypothetical // client object, but you might choose to use webhooks or // trigger your captures another way. return this.apiClient.capturePaymentIntent({ paymentIntentId: processResult.paymentIntent.id }); } else { console.log("Single-message payment successful!"); return processResult; } } }).then(function(captureResult) { console.log("Payment Successful!"); return captureResult; }, function(captureError) { // Your backend might encounter errors while capturing the PaymentIntent. // You might want to surface these API errors to your application and // catch them here. See stripe.com/docs/api/errors for more information. });
async () => { const processResult = await this.terminal.processPayment(paymentIntent); if (processResult.error) { // Handle confirmation error } else if (processResult.paymentIntent) { if (processResult.paymentIntent.status !== "succeeded") { // This code branch *won't* be called for Interac payments. try { // Capture the PaymentIntent from your backend and mark the payment as // complete. Here we use a hypothetical client object, but you might // choose to use webhooks or trigger your captures another way. let captureResult = await this.apiClient.capturePaymentIntent({ paymentIntentId: processResult.paymentIntent.id }); console.log("Payment Successful!"); return captureResult; } catch (error) { // Your backend might encounter errors while capturing the PaymentIntent. // You might want to surface these API errors to your application and // catch them here. See stripe.com/docs/api/errors for more information. } } else { console.log("Single-message payment successful!"); return processResult; } } }
self.collectCancelable = nil // Action for a "Checkout" button func checkoutAction() { // ... Fetch the client secret from your backend Terminal.shared.retrievePaymentIntent(clientSecret: clientSecret) { retrieveResult, retrieveError in if let error = retrieveError { print("retrievePaymentIntent failed: \(error)") } else if let paymentIntent = retrieveResult { print("retrievePaymentIntent succeeded: \(paymentIntent)") self.collectCancelable = Terminal.shared.collectPaymentMethod(paymentIntent) { intentWithPaymentMethod, collectError in if let error = collectError { print("collectPaymentMethod failed: \(error)") } else if let intent = intentWithPaymentMethod { Terminal.shared.processPayment(intent) { processResult, processError in if let error = processError { print("processPayment failed: \(error)") } else if let paymentIntent = processResult { print("processPayment succeeded!") if paymentIntent.status == .requiresCapture { // Notify your backend to capture the PaymentIntent. // This branch *won't* get called for Interac payments. APIClient.shared.capturePaymentIntent(paymentIntent.stripeId) { captureError in if let error = captureError { print("Capture failed: \(error)") } else { print("Capture succeeded!") } } } else if paymentIntent.status == .succeeded { print("Single-message payment successful!") } else { print("Unexpected PaymentIntent state \(paymentIntent.status)") } } } } } } } }
@property (nonatomic, nullable, strong) SCPCancelable *collectCancelable; // Action for a "Checkout" button - (void)checkoutAction { // ... Fetch the client secret from your backend [[SCPTerminal shared] retrievePaymentIntent:clientSecret completion:^(SCPPaymentIntent *retrieveResult, NSError *retrieveError) { if (retrieveError) { NSLog(@"retrievePaymentIntent failed: %@", retrieveError); } else { self.collectCancelable = [[SCPTerminal shared] collectPaymentMethod:retrieveResult delegate:self completion:^(SCPPaymentIntent *collectResult, NSError *collectError) { if (collectError) { NSLog(@"collectPaymentMethod failed: %@", collectError); } else { [[SCPTerminal shared] processPayment:createResult completion:^(SCPPaymentIntent *processResult, SCPProcessPaymentError *processError) { if (processError) { NSLog(@"processPayment failed: %@", processError); } else { NSLog(@"processPayment succeeded"); if (processResult.status == SCPPaymentIntentStatusRequiresCapture) { // Notify your backend to capture the PaymentIntent. // This branch *won't* get called for Interac payments. [[APPAPIClient shared] capturePaymentIntent:processResult.stripeId completion:^(NSError *captureError) { if (captureError) { NSLog(@"Capture failed: %@", captureError); } else { NSLog(@"Capture succeeded!"); } }]; } else if (processResult.status == SCPPaymentIntentStatusSucceeded) { NSLog(@"Single-message payment successful!") } else { NSLog(@"Unexpected paymentIntent state %@", processResult.status) } } }]; } }]; } }]; }

In cases where the Interac card is co-branded, the payment_method_details.card.brand field on a PaymentIntent’s returned charge reports the co-brand. The type field on the payment_method for an Interac transaction is always interac_present.

Error messages for Interac

If an error occurs while processing an Interac card, an error message is returned similar to those received from other card payment types. You can test these errors to make sure your application handles them correctly.

Handling Interac processing failures

Because Interac in-person payments must be refunded while the cardholder is present, you must be especially careful to prevent unintended and duplicate payments in your integration. If a failure or decline occurs while processing Interac, be sure to use the same PaymentIntent that was created at the beginning of the transaction. Do not create a new PaymentIntent, as that can result in duplicate charges for the cardholder.

In case an unintended payment occurs (for example, if the customer decides to cancel the purchase after processPayment succeeds), your application should allow initiating a refund at the end of the checkout flow.

As a fallback, your application can also allow refunding unintended Interac payments using a different payment method (e.g., store credit or cash).

Refunds

For Visa, Mastercard, and American Express refunds, read our guide to card present refunds.

Interac in-person payments must be refunded while the cardholder is present. The cardholder must present the Interac card to the card reader; these payments cannot be refunded via the dashboard or the API.

Stripe Terminal supports in-person Interac refunds using the collectRefundPaymentMethod method. The collectRefundPaymentMethod method takes a charge ID, an amount to be refunded (up to or equal to the full amount of the charge), and currency code. Note that the same method is used for partial and full refunds; the amount must be passed to the method in both circumstances.

Be sure that your application can retrieve charge IDs from previous transactions, even ones that took place some time ago. We recommend adding an interface in your application that lets operators look up a customer’s previous transactions and choose which one to refund. Your application could inspect the payment_method_details property of a Charge to determine whether the charge was created using a card_present method or an interac_present method, for example.

Refund API methods

The Terminal JavaScript SDK includes three new methods to support Interac refunds:

this.terminal.collectRefundPaymentMethod("ch_1FLyVV2eZvKYlo2C9Z8rmX02", 2000, "cad").then( function(collectRefundPaymentMethodResult) { if (collectRefundPaymentMethodResult.error) { // Placeholder for handling result.error } else { return this.terminal.processRefund(); } } ).then( function(processRefundResult) { if (processRefundResult.error) { // Placeholder for handling refund.error } else { console.log("Charge fully refunded!"); return processRefundResult; } } );
async () => { const result = await this.terminal.collectRefundPaymentMethod( "ch_1FLyVV2eZvKYlo2C9Z8rmX02", 2000, "cad" ); if (result.error) { // Placeholder for handling result.error } else { const refund = await terminal.processRefund(); if (refund.error) { // Placeholder for handling refund.error } else { console.log("Charge fully refunded!"); return refund; } } }

The Terminal iOS SDK includes two new methods to support Interac refunds:

let refundParams = RefundParameters( chargeId: "ch_1FLyVV2eZvKYlo2C9Z8rmX02", amount: 1000, currency: "cad" ) self.refundCancelable = Terminal.shared.collectRefundPaymentMethod(refundParams) { collectError in if let error = collectError { // Handle collectRefundPaymentMethod error print("collectRefundPaymentMethod failed. \(error)") } else { // Process refund Terminal.shared.processRefund { processedRefund, processError in if let error = processError { print("Process refund failed. \(error)") // Handle process error } else if let refund = processedRefund, refund.status == .succeeded { print("Process refund successful! \(refund)") } else { print("Refund pending or unsuccessful.") } } } }
- (void)startRefund { SCPRefundParameters refundParams = [SCPRefundParameters initWithChargeId:@"ch_1FLyVV2eZvKYlo2C9Z8rmX02" amount:@1000 currency:@"cad"]; self.refundCancelable = [[SCPTerminal shared] collectRefundPaymentMethod:refundParams completion:^(SCPError *collectError) { if (collectError) { // Handle collect error NSLog(@"collectRefundPaymentMethod failed: %@", collectError); } else { // Process refund [[SCPTerminal shared] processRefund:^(SCPRefund *processResult, SCPProcessRefundError *processError) { if (processError) { // Handle process error NSLog(@"processRefund failed: %@", processError); } else if (processResult) { if (processResult.status == SCPRefundStatusSucceeded) { NSLog(@"Process refund successful! %@", processResult) } else { NSLog(@"Refund pending or unsuccessful.") } } }]; } }]; }

Handling in-person Interac refunds

Because interac_present payments must be refunded in-person, the Refund button is disabled in the Dashboard, and the Stripe API returns an error if the charge is refunded through the Refunds endpoint. Furthermore, in person refunds are only supported for charges that have been processed using an Interac payment method.

If you want to enable refunds for your Interac-holding customers, your application should find the charge that is to be refunded. You may want to design an interface that can return all charges for a given customer or PaymentIntent so that your point-of-sale operator can pick the charge that should be refunded:

var stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); var charges = await stripe.charges.list({ customer: 'cus_FrhkiKbRJmwttu', // if filtering by customer payment_intent: 'pi_1DatNX2eZvKYlo2C2Hx3k1gF', // if filtering by PaymentIntent });

Once the charge ID has been located, your application should determine whether it should be refunded via the Refunds API or with the card present refund methods listed above. We recommend that your application attempt to refund the charge using the API and, if that is unsuccessful, then try to refund the charge in person. This requires coordination between your backend code, which should call the Refund API, and your frontend code, which should call the card present refund methods:

Server-side code

curl https://api.stripe.com/v1/refunds \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d charge=ch_1FLyVV2eZvKYlo2C9Z8rmX02
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' refund = Stripe::Refund.create({ charge: 'ch_1FLyVV2eZvKYlo2C9Z8rmX02', })
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' refund = stripe.Refund.create( charge='ch_1FLyVV2eZvKYlo2C9Z8rmX02', )
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $refund = \Stripe\Refund::create([ 'charge' => 'ch_1FLyVV2eZvKYlo2C9Z8rmX02', ]);
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; RefundCreateParams params = RefundCreateParams.builder() .setCharge("ch_1FLyVV2eZvKYlo2C9Z8rmX02") .build(); Refund refund = Refund.create(params);
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); const refund = await stripe.refunds.create({ charge: 'ch_1FLyVV2eZvKYlo2C9Z8rmX02', });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" params := &stripe.RefundParams{ Charge: stripe.String("ch_1FLyVV2eZvKYlo2C9Z8rmX02"), } re, _ := refund.New(params)
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; var options = new RefundCreateOptions { Charge = "ch_1FLyVV2eZvKYlo2C9Z8rmX02", }; var service = new RefundService(); var refund = service.Create(options);

Client-side code

this.client.refundCharge({chargeId: "ch_1FLyVV2eZvKYlo2C9Z8rmX02"}).then( function(refundChargeResult) { return refundChargeResult; }, function(refundChargeError) { if (refundChargeError === "invalid_request_error") { return this.terminal.collectRefundPaymentMethod( "ch_1FLyVV2eZvKYlo2C9Z8rmX02", 2000, "cad" ).then( function(collectRefundPaymentMethodResult) { if (collectRefundPaymentMethodResult.error) { // Placeholder for handling collectRefundPaymentMethodResult.error } else { return this.terminal.processRefund(); } } ).then( function(processRefundResult) { if (processRefundResult.error) { // Placeholder for handling processRefundResult.error } else { console.log("Charge fully refunded!"); return processRefundResult; } } ) } else { // Placeholder for handling other refundChargeError messages } } );
try { let refund = await this.client.refundCharge({chargeId: "ch_1FLyVV2eZvKYlo2C9Z8rmX02"}); } catch (error) { if (error.message === "invalid_request_error") { const result = await this.terminal.collectRefundPaymentMethod( "ch_1FLyVV2eZvKYlo2C9Z8rmX02", 2000, "cad" ); if (result.error) { // Placeholder for handling result.error } else { const refund = await this.terminal.processRefund(); if (refund.error) { // Placeholder for handling refund.error } else { console.log("Charge fully refunded!"); return refund; } } }
// Your API client should reach out to your backend, which will use the // Refunds server-side API to attempt to refund the charge. APIClient.shared.refundCharge(chargeId: "ch_1FLyVV2eZvKYlo2C9Z8rmX02") { error, refund in if error.message == "invalid_request_error" { // Server-side refund failed, in-person refund likely necessary let refundParameters = SCPRefundParameters("ch_1FLyVV2eZvKYlo2C9Z8rmX02", amount: 2000, currency: "cad") self.refundCancelable = Terminal.shared.collectRefundPaymentMethod(refundParams) { collectError in if let error = collectError { // Handle collectRefundPaymentMethod error print("collectRefundPaymentMethod failed. \(error)") } else { // Process refund Terminal.shared.processRefund { processedRefund, processError in if let error = processError { print("Process refund failed. \(error)") // Handle process error } else if let refund = processedRefund, refund.status == .succeeded { print("Process refund successful! \(refund)") } else { print("Refund pending or unsuccessful.") } } } } } }
// Your API client should reach out to your backend, which will use the // Refunds server-side API to attempt to refund the charge. [[MyAPIClient shared] tryRefund:@"ch_1FLyVV2eZvKYlo2C9Z8rmX02" completion:^(error: NSError) { if (error) { // Server-side refund failed, in-person refund likely necessary SCPRefundParameters refundParams = [SCPRefundParameters initWithChargeId:@"ch_1FLyVV2eZvKYlo2C9Z8rmX02" amount:@1000 currency:@"cad"]; self.refundCancelable = [[SCPTerminal shared] collectRefundPaymentMethod:refundParams completion:^(SCPError *collectError) { if (collectError) { // Handle collect error NSLog(@"collectRefundPaymentMethod failed: %@", collectError); } else { // Process refund [[SCPTerminal shared] processRefund:^(SCPRefund *processResult, SCPProcessRefundError *processError) { if (processError) { // Handle process error NSLog(@"processRefund failed: %@", processError); } else if (processResult) { if (processResult.status == SCPRefundStatusSucceeded) { NSLog(@"Process refund successful! %@", processResult) } else { NSLog(@"Refund pending or unsuccessful.") } } }]; } }]; } }];

Interac testing

Simulated reader

The simulated reader can create Interac Debit transactions using the simulator configuration API. For more details, visit our JavaScript API reference.

Test card

You need a special Interac test card for testing your Interac integration; this can be ordered via the shop in the Dashboard. The Stripe-branded physical test card cannot be used as an Interac card.

The Interac physical test card works for both interac_present payments and interac_present refunds. You can use the same test amounts you use for testing card_present payments. To test a declined refund, create a partial refund with an amount ending with the following decimal values: 01, 05, 55, 65, or 75.

Translation

Language regulations require that services, including point-of-sale services, be provided in French unless English has been agreed upon by the cardholder and their card issuer. Terminal is built to help you comply with these requirements if they apply to your business.

The Verifone P400 interface displays text in both French and English if it is registered to a location with a Canadian address. Once the cardholder has presented their card, the reader determines the cardholder’s preferred language. Each screen after that point is translated according to the cardholder’s preferences.

If you are required to provide services in French or would like to translate text into both French and English, there are two areas of the Stripe Terminal interface where you can customize what is being displayed, and where you should ensure that French or French and English translations are available.

Cart display

The Verifone P400 reader’s built-in screen can display cart details using the setReaderDisplay method in the JavaScript and iOS SDKs. If your application shows this screen, this screen is shown before the cardholder taps or dips their card. If your business is required to provide services in French, be sure to pass strings for that function in French or in both French and English.

Receipts

There are two different ways you can provide receipts to your customers: using Stripe’s prebuilt receipts or by providing custom receipts.

Prebuilt receipts help you comply with translation and card network requirements. Prebuilt receipts are automatically translated into French if the default receipt language is set to French in your account settings; you can also override your default receipt language on a per-customer basis using preferred locales. If your business is required to provide services in French, make sure you provide correctly translated strings to the Stripe API when creating a PaymentIntent.

If you use custom receipts, you might have to adjust those receipts to comply with regulations in Canada. If you are required to provide services in French, make sure your receipt templates are translated properly. The Charge object returned by the Stripe API has a preferred_locales property for Interac payments that can help you decide the language in which your receipts should be presented.

Interac payments also come with unique receipt requirements. In addition to the generally required receipt fields, another field must be displayed when printing custom receipts for Interac payments:

Field Name Required value
account_type Account Type checking, saving or credit
Was this page helpful?
Questions? Contact us.
Developer tutorials on YouTube.
You can unsubscribe at any time. Read our privacy policy.