Skip to content

Commit

Permalink
Merge pull request #12 from stripe/feat/upgrade-react-native
Browse files Browse the repository at this point in the history
upgrade react native
  • Loading branch information
arekkubaczkowski authored Dec 13, 2021
2 parents c95c8db + 9b0ba13 commit 9777614
Show file tree
Hide file tree
Showing 44 changed files with 2,813 additions and 2,217 deletions.
6 changes: 3 additions & 3 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
*.pbxproj -text
# specific for windows script files
*.bat text eol=crlf
# Windows files should use crlf line endings
# https://help.github.com/articles/dealing-with-line-endings/
*.bat text eol=crlf
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ project.xcworkspace
.settings
local.properties
android.iml
*.hprof

# Cocoapods
#
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ You'll need to run `pod install` in your `ios` directory to install the native d

### Requirements

The SDK uses TypeScript features available in Babel version `7.9.0` and above.
Alternatively use the `plugin-transform-typescript` plugin in your project.
- The SDK uses TypeScript features available in Babel version `7.9.0` and above.
Alternatively use the `plugin-transform-typescript` plugin in your project.

#### Android

Expand Down
3 changes: 1 addition & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath 'com.android.tools.build:gradle:4.2.2'
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
Expand All @@ -33,7 +33,6 @@ android {
targetSdkVersion getExtOrIntegerDefault('targetSdkVersion')
versionCode 1
versionName "1.0"

}

buildTypes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
private var collectPaymentMethodCancelable: Cancelable? = null
private var collectSetupIntentCancelable: Cancelable? = null
private var installUpdateCancelable: Cancelable? = null
private var readReusableCardCancelable: Cancelable? = null

private var paymentIntents: HashMap<String, PaymentIntent?> = HashMap()
private var setupIntents: HashMap<String, SetupIntent?> = HashMap()
Expand Down Expand Up @@ -329,13 +330,17 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
fun createPaymentIntent(params: ReadableMap, promise: Promise) {
val amount = getIntOr(params, "amount") ?: 0
val currency = getStringOr(params, "currency") ?: ""
val setupFutureUsage = getStringOr(params, "currency")

val intentParams = PaymentIntentParameters.Builder()
.setAmount(amount.toLong())
.setCurrency(currency)
.build()

Terminal.getInstance().createPaymentIntent(intentParams, object : PaymentIntentCallback {
setupFutureUsage?.let {
intentParams.setSetupFutureUsage(it)
}

Terminal.getInstance().createPaymentIntent(intentParams.build(), object : PaymentIntentCallback {
override fun onSuccess(paymentIntent: PaymentIntent) {
paymentIntents[paymentIntent.id] = paymentIntent

Expand Down Expand Up @@ -476,6 +481,23 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :
})
}

@ReactMethod
fun cancelReadReusableCard(promise: Promise) {
val cancelable = readReusableCardCancelable ?: run {
promise.resolve(createError(CommonErrorType.Failed.toString(), "readReusableCard could not be canceled because the command has already been canceled or has completed."))
return
}
cancelable.cancel(object : Callback {
override fun onSuccess() {
promise.resolve(WritableNativeMap())
}

override fun onFailure(e: TerminalException) {
promise.resolve(createError(CommonErrorType.Failed.toString(), e.localizedMessage))
}
})
}

@ReactMethod
fun collectSetupIntentPaymentMethod(params: ReadableMap, promise: Promise) {
val setupIntentId = getStringOr(params, "setupIntentId")
Expand Down Expand Up @@ -657,7 +679,7 @@ class StripeTerminalReactNativeModule(reactContext: ReactApplicationContext) :

var reusableCardParams = ReadReusableCardParameters.Builder().setCustomer(customer).build();

Terminal.getInstance().readReusableCard(reusableCardParams, object : PaymentMethodCallback {
readReusableCardCancelable = Terminal.getInstance().readReusableCard(reusableCardParams, object : PaymentMethodCallback {
override fun onSuccess(paymentMethod: PaymentMethod) {
val pm = mapFromPaymentMethod(paymentMethod);
val result = WritableNativeMap()
Expand Down
Binary file added docs/assets/log-screen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
174 changes: 174 additions & 0 deletions docs/collect-payments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Collect payments

## Prepare your application and backend to collect payments using Stripe Terminal.

Collecting payments with Stripe Terminal requires writing a payment flow in your application. Use the Stripe Terminal SDK to create and update a [PaymentIntent](https://stripe.com/docs/api#payment_intents), an object representing a single payment session.

Designed to be robust to failures, the Terminal integration splits the payment process into several steps, each of which can be retried safely:

- [Create a PaymentIntent](#create-a-paymentintent)
- [Collect a payment method](#collect-a-payment-method)

- [Process the payment](#process-the-payment)
- [Capture the payments](#capture-the-payments)

Authorization on the customer’s card takes place in [Step 3](#process-the-payment), when the SDK processes the payment.

## Create a PaymentIntent

The first step when collecting payments is to start the payment flow. When a customer begins checking out, your application must create a `PaymentIntent` object. This represents a new payment session on Stripe.

Use [test amounts](https://stripe.com/docs/terminal/references/testing#physical-test-cards) to try producing different results. An amount ending in 00 results in an approved payment.

The following example shows how to create a `PaymentIntent` on your server:

```ts
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('sk_test_7mJuPfZsBzc3JkrANrFrcDqC');

const intent = await stripe.paymentIntents.create({
amount: 1000,
currency: 'usd',
payment_method_types: ['card_present'],
capture_method: 'manual',
});
```

For Terminal payments, the `payment_method_types` parameter must include `card_present`. To control the payment flow for `card_present` payments, set the `capture_method` to `manual`.

> To accept Interac payments in Canada, you will need to also include interac_present in payment_method_types. For more details, visit our [Canada documentation](https://stripe.com/docs/terminal/payments/regional?integration-country=CA).
The PaymentIntent contains a [client secret](https://stripe.com/docs/api/payment_intents/object#payment_intent_object-client_secret), a key that is unique to the individual PaymentIntent. To use the client secret, you must obtain it from the PaymentIntent on your server and [pass it to the client side](https://stripe.com/docs/payments/payment-intents#passing-to-client).

```ts
const express = require('express');
const app = express();

app.post('/create_payment_intent', async (req, res) => {
const intent = res.json({ client_secret: intent.client_secret }); // ... Fetch or create the PaymentIntent
});

app.listen(3000, () => {
console.log('Running on port 3000');
});
```

Use the client secret as a parameter when calling `retrievePaymentIntent` to get a `PaymentIntent` object on client side.

## Collect a payment method

After you’ve created a PaymentIntent, the next step is to collect a payment method with the SDK.

Before you do this, you need to retrieve a PaymentIntent object on the client side which is required to collect a payment method.

```tsx
const { paymentIntent, error } = await retrievePaymentIntent(clientSecret);

if (error) {
// Placeholder for handling exception
} else if (paymentIntent) {
// Placeholder for collecting payment method
}
```

In order to collect a payment method, your app needs to be connected to a reader. The connected reader will wait for a card to be presented after your app calls `collectPaymentMethod`.

```tsx
const { paymentIntent, error } = await collectPaymentMethod(paymentIntentId);

if (error) {
// Placeholder for handling exception
} else if (paymentIntent) {
// Placeholder for processing paymentIntent
}
```

This method collects encrypted payment method data using the connected card reader, and associates the encrypted data with the local PaymentIntent.

You can cancel collecting a payment method using `cancelCollectPaymentMethod` method returned by the React Native SDK.

> Collecting a payment method happens locally and requires no authorization or updates to the Payment Intents API object until the next step, [process the payment](#process-the-payment).
### Handle events

When collecting a payment method using a reader like the [BBPOS Chipper 2X BT](https://stripe.com/docs/terminal/readers/bbpos-chipper2xbt), without a built-in display, your app must be able to display events from the payment method collection process to users. These events help users successfully collect payments (e.g., retrying a card, trying a different card, or using a different read method).

When a transaction begins, the SDK passes a `Reader.InputOptions` value to your app’s reader display handler, denoting the acceptable types of input (e.g., Swipe, Insert, Tap). In your app’s checkout UI, prompt the user to present a card using one of these options.

During the transaction, the SDK might request your app to display additional prompts (e.g., Retry Card) to your user by passing a `Reader.DisplayMessage` value to your app’s reader display handler. Make sure your checkout UI displays these messages to the user.

```tsx
useStripeTerminal({
onDidRequestReaderInput: (options) => {
Alert.alert(options.join('/'));
},
onDidRequestReaderDisplayMessage: (message) => {
Alert.alert(message);
},
});
```

## Process the payment

After successfully collecting a payment method from the customer, the next step is to process the payment with the SDK. You can either process automatically or display a confirmation screen, where the customer can choose to proceed with the payment or cancel (e.g., to pay with cash, or use a different payment method).

When you’re ready to proceed with the payment, call processPayment with the updated PaymentIntent from [Step 2](#collect-a-payment-method). A successful `processPayment` call results in a PaymentIntent with a status of `requires_capture`.

```tsx
const { paymentIntent, error } = await processPayment(paymentIntentId);
if (error) {
// Placeholder for handling exception
} else if (paymentIntent) {
// Placeholder for notifying your backend to capture paymentIntent.id
}
```

> You must manually capture payments processed by the Terminal SDKs. Set up your backend to [capture the payment](#capture-a-payment) within two days. Otherwise, the authorization expires and funds get released back to the customer.
### Handle processing failures

When processing a payment fails, the SDK returns an error that includes the updated `PaymentIntent`. Your application should inspect the `PaymentIntent` to decide how to deal with the error.

| PAYMENTINTENT STATUS | | RESOLUTION |
| ------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `requires_payment_method` | Payment method declined | Try collecting a different payment method by calling `collectPaymentMethod` again with the same PaymentIntent. |
| `requires_confirmation` | Temporary connectivity problem | Call `processPayment` again with the same PaymentIntent to retry the request. |
| PaymentIntent is `nil` | Request to Stripe timed out, unknown PaymentIntent status | Retry processing the original PaymentIntent. Don’t create a new one, as that could result in multiple authorizations for the cardholder. |

## Capture the payments

Stripe Terminal uses a two-step process to prevent unintended and duplicate payments. When the SDK returns a processed PaymentIntent to your app, the payment is authorized but not captured. Read the [auth and capture](https://stripe.com/docs/payments/capture-later) documentation for more information about the difference.

When your app receives a processed PaymentIntent from the SDK, make sure it notifies your backend to capture the payment. Create an endpoint on your backend that accepts a PaymentIntent ID and sends a request to the Stripe API to capture it:

```ts
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = require('stripe')('sk_test_7mJuPfZsBzc3JkrANrFrcDqC');

const paymentIntent = await stripe.paymentIntents.capture(
'{{PAYMENT_INTENT_ID}}'
);
```

A successful `capture` call will result in a PaymentIntent with a status of `succeeded`.

**Reconcile payments**
To monitor the payments activity of your business, you may want to reconcile PaymentIntents with your internal orders system on your server at the end of a day’s activity.

A PaymentIntent that retains a `requires_capture` status may represent two things:

**Unnecessary authorization on your customer’s card statement**

- Cause: User abandons your app’s checkout flow in the middle of a transaction
- Solution: If the uncaptured PaymentIntent is not associated with a completed order on your server, you can [cancel](https://stripe.com/docs/api/payment_intents/cancel) it. A canceled PaymentIntent can no longer be used to perform charges.

**Incomplete collection of funds from a customer**

- Cause: Failure of the request from your app notifying your backend to capture the payment
- Solution: If the uncaptured PaymentIntent is associated with a completed order on your server, and no other payment has been taken for the order (e.g., a cash payment), you can [capture](https://stripe.com/docs/api/payment_intents/capture) it.

**Collect tips US only**

In the US, eligible users can [collect tips when capturing payments](https://stripe.com/docs/terminal/features/collecting-tips/on-receipt).
62 changes: 62 additions & 0 deletions docs/connect-to-a-reader.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Connect to a reader

## Connect your application to a reader.

The Stripe Terminal SDK comes with a built-in simulated card reader, so you can develop and test your app without connecting to physical hardware. Whether your integration is complete or you’re just starting out, use the simulated reader to emulate all the Terminal flows in your app.

Note that the simulated reader does not provide a UI. After connecting to it in your app, you can see it working when calls to the Stripe SDK succeed.

To use the simulated reader, call `discoverReaders` to search for readers, with the `simulated` option set to `true`. When `onFinishDiscoveringReaders` callback is called without any errors, call `connectBluetoothReader` to connect to the simulated reader.

When connecting to a Bluetooth reader using `connectBluetoothReader`, your integration must provide the `locationId` parameter to the method, even for the simulated reader. Since the simulated reader can’t be associated with a real location, you may provide the simulated reader’s mock locationId instead.

```tsx
function DiscoverReadersScreen() {
const { discoverReaders, connectBluetoothReader, discoveredReaders } =
useStripeTerminal({
didUpdateDiscoveredReaders: (readers) => {
// once you get the list of available readers you can make a connection.
handleConnectBluetoothReader(readers[0].id);
},
});

useEffect(() => {
handleDiscoverReaders();
}, []);

const handleDiscoverReaders = async () => {
// List of discovered readers will be available within useStripeTerminal hook
const { error } = await discoverReaders({
discoveryMethod: 'bluetoothScan',
simulated: true,
});

if (error) {
Alert.alert(
'Discover readers error: ',
`${error.code}, ${error.message}`
);
}
};

const handleConnectBluetoothReader = async (id: string) => {
const { reader, error } = await connectBluetoothReader({
readerId: discoveredReaders[0].id,
// for simulated mode you can provide the simulated reader’s mock locationId
locationId: discoveredReaders[0].locationId,
});

if (error) {
console.log('connectBluetoothReader error', error);
} else {
console.log('Reader connected successfully', reader);
}
};

return <View />;
}
```

## Next steps

Congratulations! You’ve connected your application to the reader. Next, collect your first Stripe Terminal payment.
54 changes: 54 additions & 0 deletions docs/display.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Display cart details

## Dynamically update cart details on the reader screen.

The built-in screen of the Verifone P400 and BBPOS WisePOS E can display line items. During the checkout process, you can update the reader’s screen to show individual items in the transaction, along with the total price.

![img](https://b.stripecdn.com/docs-statics-srv/assets/frame-4.c75473bf488c72dbd45a6c61d46ed203.png)

## Set the reader display

To set the line items and total displayed on the reader during a payment, pass line items and total information into the setReaderDisplay method. The object you pass in will drive the contents displayed on the reader’s screen.

Note that the amounts passed to the setReaderDisplay method are only used for display purposes. The reader will not automatically calculate tax or the total; your application must calculate those before displaying the values. Similarly, the total passed to setReaderDisplay does not control the amount charged to the customer; make sure the amount displayed on the reader matches the amount you’re charging your customer.

To reset the reader’s display from a line item interface to the splash screen, call the clearReaderDisplay method.

### React Native

```tsx
const { error } = await setReaderDisplay({
currency: 'usd',
tax: 100,
total: 1998,
lineItems: [
{
displayName: 'Caramel latte',
quantity: 1,
amount: 659,
},
{
displayName: 'Dozen donuts',
quantity: 1,
amount: 1239,
},
],
});
if (error) {
// Placeholder for handling exception
} else {
// Placeholder for handling successful operation
}
```

## Pre-dip a card

> Pre-dipping a card is only supported for payments in the U.S.
The Verifone P400 and BBPOS WisePOS E support the ability to present a card to the reader before the transaction amount is finalized.

This option—known as pre-dip, pre-tap, or pre-swipe—can help speed up transaction times by allowing a customer to present a payment method before the end of the transaction.

The setReaderDisplay method prepares the reader for pre-dipping. The customer can present a payment method at any point after the method is called.

Even if a customer pre-dips their card, your application must still complete the full payment flow.
Loading

0 comments on commit 9777614

Please sign in to comment.