# 不用 Webhook 接受银行卡付款
了解如何在您的服务器确认银行卡付款并处理银行卡验证要求。
# Web
> This is a Web for when platform is web. View the full page at https://docs.stripe.com/payments/accept-a-payment-synchronously?platform=web.
> Stripe 建议使用更新的 [Payment Element](https://docs.stripe.com/payments/quickstart-checkout-sessions.md) 而非 Card Element。通过单一Element 即可支持多种支付方式。进一步了解 [Card Element 与 Payment Element 的适用场景](https://docs.stripe.com/payments/payment-card-element-comparison.md)。
为获得更广泛的支持并且未来可继续使用,请为异步付款使用[标准集成](https://docs.stripe.com/payments/accept-a-payment.md)。
此集成方式等待客户端返回响应,并在服务器端完成支付,无需使用*Webhook* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests)或处理离线事件。虽然这种方式看似更简单,但随着业务增长,这种集成方式难以扩展,并且存在若干限制:
如果您要从 Charges API 迁移现有的 Stripe 集成,请遵循[迁移指南](https://docs.stripe.com/payments/payment-intents/migration.md)。
- **仅支持卡** — 必须编写更多代码来分别支持 ACH 及流行的区域性支付方式。
- **重复扣款风险**——通过在客户每次尝试付款时同步创建新的 PaymentIntent,您可能会意外地对客户进行重复扣款。请务必遵循[最佳实践](https://docs.stripe.com/error-low-level.md#idempotency)。
- **在客户端的额外步骤** — 需进行 3DS 验证或受*强客户认证* (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase)等法规约束的银行卡需要在客户端执行额外的步骤。
如果您打算使用这种集成,请留意这些限制。否则,请使用[标准集成](https://docs.stripe.com/payments/accept-a-payment.md)。
## 设置 Stripe
首先,您需要有 Stripe 账户。[立即注册](https://dashboard.stripe.com/register)。
用我们的官方库从您的应用程序访问 Stripe API:
#### Ruby
```bash
# Available as a gem
sudo gem install stripe
```
```ruby
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
```
## 收集银行卡详情 [客户端]
用 Stripe.js 和 Stripe Elements 在客户端收集银行卡信息。Elements 是一组预构建的 UI 组件,用于收集并验证银行卡卡号、邮编和有效期。
Stripe Element 中包含一个 iframe,它通过一个 HTTPS 连接安全地将支付信息发送到 Stripe。结账页面上的地址也必须以 https:// 开头,不能是 http://,否则您的集成不能工作。
您可以在不使用 HTTPS 的情况下测试您的集成。准备好进行真实收款时[启用它](https://docs.stripe.com/security/guide.md#tls)。
#### HTML + JS
在您网站的每个页面头部包含 [Stripe.js](https://docs.stripe.com/js.md) 脚本。Elements 自动可以获取,这是 Stripe.js 的功能。
```html
```
在您网站的每个页面都包含脚本的好处是您可以利用 Stripe 的[高级欺诈功能](https://docs.stripe.com/radar.md),并且可以检测异常的浏览行为。
### 构建支付表单
为了安全地从客户那里收集银行卡详情,Elements 会为您创建一个由 Stripe 托管的 UI 组件。然后将它们作为 iframe 放入您的支付表单。要确定在哪里插入这些组件,请用您的支付表单中的唯一 ID 创建空的 DOM 元素(容器)。
#### HTML
```html
```
接下来,创建一个 [Stripe 对象](https://docs.stripe.com/js.md#stripe-function)实例,提供您可公开的 [API 密钥](https://docs.stripe.com/keys.md)作为第一个参数。之后,创建一个 [Elements 对象](https://docs.stripe.com/js.md#stripe-elements)实例,并用它在页面上的相关占位符内[挂载](https://docs.stripe.com/js.md#element-mount)一个 Card 元素。
```javascript
const stripe = Stripe('<>');
const elements = stripe.elements();
// Set up Stripe.js and Elements to use in checkout form
const style = {
base: {
color: "#32325d",
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#aab7c4"
}
},
invalid: {
color: "#fa755a",
iconColor: "#fa755a"
},
};
const cardElement = elements.create('card', {style});
cardElement.mount('#card-element');
```
`card` Element 通过插入一个可安全地收集所有必要的银行卡和账单信息的单一灵活的输入字段,对支付表单进行了简化,并且将需要的字段数降到了最低。
否则,对 `cardNumber`、`cardExpiry`、和 `cardCvc` Elements 加以组合,获取灵活的多重输入银行卡表单。
> 始终要收集邮编,以提高卡的接受率并减少欺诈。
>
> 此[单行 Card Element](https://docs.stripe.com/js/element/other_element?type=card) 会自动收集邮编并将其发给 Stripe。如果您用分割的 Elements([卡号](https://docs.stripe.com/js/element/other_element?type=cardNumber)、[有效期](https://docs.stripe.com/js/element/other_element?type=cardExpiry)、[CVC](https://docs.stripe.com/js/element/other_element?type=cardCvc))创建支付表单,那么为客户的邮编添加一个单独的输入字段。
### 创建 PaymentMethod
最后,在用户点击提交按钮时,用您的客户端上的 [stripe.createPaymentMethod](https://docs.stripe.com/js/payment_methods/create_payment_method) 收集银行卡详情并创建一个 [PaymentMethod](https://docs.stripe.com/api/payment_methods.md)。
```javascript
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
event.preventDefault();
const result = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
billing_details: {
// Include any additional collected billing details.
name: 'Jenny Rosen',
},
});
stripePaymentMethodHandler(result);
});
```
#### React
#### npm
从 npm 公共注册表安装 [React Stripe.js](https://www.npmjs.com/package/@stripe/react-stripe-js) 和 [Stripe.js 加载器](https://www.npmjs.com/package/@stripe/stripe-js)。
```bash
npm install --save @stripe/react-stripe-js @stripe/stripe-js
```
#### umd
对于不使用 npm 或模块的网站,我们也提供了一个 UMD 模块。
包含进 Stripe.js 脚本——它可以导出一个全局 `Stripe` 函数,也包含进 React Stripe.js 的 UMD 模块,它可以导出一个全局 `ReactStripe` 对象。为保持 PCI 合规,始终从 **js.stripe.com** 加载 Stripe.js 脚本。不要把脚本打包或自行保留副本。
```html
```
> 可通过 [CodeSandbox 内的演示](https://codesandbox.io/s/react-stripe-official-q1loc?fontsize=14&hidenavigation=1&theme=dark)尝试 React Stripe.js,不需要新建项目。
### 在您的页面添加 Stripe.js 和 Elements
使用 Element 组件时,将您的结账页面包裹到某个 [Elements 提供程序](https://docs.stripe.com/sdks/stripejs-react.md#elements-provider)中。用您的公钥调用 `loadStripe`,并将返回的 `Promise` 传递到 `Elements` 提供程序。
```jsx
import React from 'react';
import ReactDOM from 'react-dom';
import {Elements} from '@stripe/react-stripe-js';
import {loadStripe} from '@stripe/stripe-js';
import CheckoutForm from './CheckoutForm';
// Make sure to call `loadStripe` outside of a component's render to avoid
// recreating the `Stripe` object on every render.
const stripePromise = loadStripe('<>');
function App() {
return (
);
};
ReactDOM.render(, document.getElementById('root'));
```
### 添加并配置一个 CardElement 组件
用单个的 Element 组件构建表单,例如 `CardElement`。
#### JSX
```jsx
/**
* Use the CSS tab above to style your Element's container.
*/
import React from 'react';
import {CardElement} from '@stripe/react-stripe-js';
import './Styles.css'
const CARD_ELEMENT_OPTIONS = {
style: {
base: {
color: "#32325d",
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: "antialiased",
fontSize: "16px",
"::placeholder": {
color: "#aab7c4",
},
},
invalid: {
color: "#fa755a",
iconColor: "#fa755a",
},
},
};
function CardSection() {
return (
);
};
export default CardSection;
```
Elements 可完全地自定义。您可以[设计 Elements 的样式](https://docs.stripe.com/js/elements_object/create_element?type=card#elements_create-options),使其匹配您网站的外观,为客户提供无缝的结账体验。还可以设计多个输入状态的样式,例如选择了 Element 时。
`CardElement` 通过插入一个可安全地收集所有必要的银行卡和账单信息的单一灵活的输入字段,对表单进行了简化,并且将需要的字段数降到了最低。否则,对 `CardNumberElement`、`CardExpiryElement`、和 `CardCvcElement` Elements 加以组合,获取灵活的多重输入银行卡表单。
> 始终要收集邮编,以提高卡的接受率并减少欺诈。
>
> 此[单行 Card Element](https://docs.stripe.com/js/element/other_element?type=card) 会自动收集邮编并将其发给 Stripe。如果您用分割的 Elements([卡号](https://docs.stripe.com/js/element/other_element?type=cardNumber)、[有效期](https://docs.stripe.com/js/element/other_element?type=cardExpiry)、[CVC](https://docs.stripe.com/js/element/other_element?type=cardCvc))创建支付表单,那么为客户的邮编添加一个单独的输入字段。
### 创建 PaymentMethod
在您的支付表单的提交处理程序中,用 [stripe.createPaymentMethod](https://docs.stripe.com/js/payment_methods/create_payment_method) 收集银行卡详情并创建 [PaymentMethod](https://docs.stripe.com/api/payment_methods.md)。
从您的支付表单组件调用 `stripe.createPaymentMethod` 时,使用 [useStripe](https://docs.stripe.com/sdks/stripejs-react.md#usestripe-hook) and [useElements](https://docs.stripe.com/sdks/stripejs-react.md#useelements-hook) hook。
如果您不想用 hooks,而是想使用传统的类组件 (Class Components),则可以使用 [ElementsConsumer](https://docs.stripe.com/sdks/stripejs-react.md#elements-consumer)。
#### Hooks
```jsx
import React from 'react';
import {useStripe, useElements, CardElement} from '@stripe/react-stripe-js';
import CardSection from './CardSection';
export default function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const handleSubmit = async (event) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
event.preventDefault();
if (!stripe || !elements) {
// Stripe.js hasn't yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}const result = await stripe.createPaymentMethod({
type: 'card',
card: elements.getElement(CardElement),
billing_details: {
// Include any additional collected billing details.
name: 'Jenny Rosen',
},
});
stripePaymentMethodHandler(result);
};
return (
);
}
```
#### Class Components
```jsx
import React from 'react';
import {ElementsConsumer, CardElement} from '@stripe/react-stripe-js';
import CardSection from './CardSection';
class CheckoutForm extends React.Component {
handleSubmit = async (event) => {
// We don't want to let default form submission happen here,
// which would refresh the page.
event.preventDefault();
const {stripe, elements} = this.props
if (!stripe || !elements) {
// Stripe.js hasn't yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}const result = await stripe.createPaymentMethod({
type: 'card',
card: elements.getElement(CardElement),
billing_details: {
// Include any additional collected billing details.
name: 'Jenny Rosen',
}
});
stripePaymentMethodHandler(result);
};
render() {
const {stripe} = this.props;
return (
);
}
}
export default function InjectedCheckoutForm() {
return (
{({stripe, elements}) => (
)}
);
}
```
## 向服务器提交 PaymentMethod [客户端]
如果 *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs) 创建成功,则将它的 ID 发送到您的服务器。
```javascript
const stripePaymentMethodHandler = async (result) => {
if (result.error) {
// Show error in payment form
} else {
// Otherwise send paymentMethod.id to your server (see Step 4)
const res = await fetch('/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
payment_method_id: result.paymentMethod.id,
}),
})
const paymentResponse = await res.json();
// Handle server response (see Step 4)
handleServerResponse(paymentResponse);
}
}
```
## 创建 PaymentIntent [服务器端]
在您的服务器上设置一个端点来接收请求。[稍后](https://docs.stripe.com/payments/accept-a-payment-synchronously.md#confirm-payment)在处理需要额外验证的付款时,也会用到此端点。
用在您的客户端创建的 [PaymentMethod](https://docs.stripe.com/api/payment_methods/object.md) 的 ID [创建新的 PaymentIntent](https://docs.stripe.com/payments/payment-intents.md#creating-a-paymentintent)。您可以通过在创建 PaymentIntent 时将 [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm) 属性设置为 true 或在创建后调用 [confirm](https://docs.stripe.com/api/payment_intents/confirm.md) 来 *确认* (Confirming a PaymentIntent indicates that the customer intends to pay with the current or provided payment method. Upon confirmation, the PaymentIntent attempts to initiate a payment) PaymentIntent。也支持对银行卡付款进行[单独授权和捕获](https://docs.stripe.com/payments/place-a-hold-on-a-payment-method.md)。
如果支付需要额外的操作,如 3DS 验证,PaymentIntent 的状态将设置为 `requires_action`。如果支付失败,状态将设置回 `requires_payment_method`,您应该向用户显示错误。如果支付不需要任何额外的验证,则创建一个收款,且 PaymentIntent 状态设置为 `succeeded`。
> 在 [2019-02-11](https://docs.stripe.com/upgrades.md#2019-02-11) 以前的 API 版本中,`requires_payment_method` 显示为 `requires_source`,而 `requires_action` 显示为 `requires_source_action`。
#### curl
```bash
curl https://api.stripe.com/v1/payment_intents \
-u <>: \
-d "payment_method"="{{PAYMENT_METHOD_ID}}" \
-d "amount"=1099 \
-d "currency"="usd" \
-d "confirmation_method"="manual" \
-d "confirm"="true"
```
如果您想保存该卡以便以后重复使用,请创建一个 [Customer](https://docs.stripe.com/api/customers/create.md) 来存储 *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs),并在创建 PaymentIntent 时传递以下附加参数:
- [customer](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer)。设置为 *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments) 的 ID。
- [setup_future_usage](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-setup_future_usage)。设置为 `off_session`,以告知 Stripe 您计划在客户不在场时,将此 PaymentMethod 复用于*非会话期支付* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information)。设置此属性后,在 PaymentIntent 确认且用户完成所有必要操作后,PaymentMethod 将保存至客户账户。有关更多详情,请参阅有关[支付后保存银行卡](https://github.com/stripe-samples/saving-card-after-payment/tree/master/without-webhooks)的代码示例。
## 处理任何后续操作 [客户端]
编写代码来处理需要客户干预的情况。您在[第 4 步](https://docs.stripe.com/payments/accept-a-payment-synchronously.md#create-payment-intent)时在服务器上确认后,付款会直接成功。但是,当 PaymentIntent 要求客户采取额外操作时,例如通过 *3DS 验证* (3D Secure (3DS) provides an additional layer of authentication for credit card transactions that protects businesses from liability for fraudulent card payments)进行身份验证,这个代码就开始发挥作用。
用 [stripe.handleCardAction](https://docs.stripe.com/js/payment_intents/handle_card_action) 触发处理客户操作的 UI。如果验证成功,则 PaymentIntent 的状态变为 `requires_confirmation`。在服务器上再次确认 PaymentIntent,以完成付款。
测试时,使用需要验证的[测试卡卡号](https://docs.stripe.com/testing.md#regulatory-cards)(例如,4000002760003184)来强制实现该流程。使用不需要验证的卡(例如,4242424242424242)会跳过流程中的这个部分,然后在第 4 步去完成。
```javascript
const handleServerResponse = async (response) => {
if (response.error) {
// Show error from server on payment form
} else if (response.requires_action) {
// Use Stripe.js to handle the required card action
const { error: errorAction, paymentIntent } =
await stripe.handleCardAction(response.payment_intent_client_secret);
if (errorAction) {
// Show error from Stripe.js in payment form
} else {
// The card action has been handled
// The PaymentIntent can be confirmed again on the server
const serverResponse = await fetch('/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payment_intent_id: paymentIntent.id })
});
handleServerResponse(await serverResponse.json());
}
} else {
// Show success message
}
}
```
> `stripe.handleCardAction` 可能需要几秒钟才能完成。在此期间,请禁用表单以防止重复提交,并显示等待指示器(如加载动画)。如果收到错误信息,请将其展示给客户,重新启用表单,并隐藏等待指示器。如果客户需要执行额外步骤来完成支付(例如身份验证),Stripe.js 会引导他们完成该流程。
## 再次确认 PaymentIntent [服务器端]
该代码仅在支付需要额外验证时才执行,这与上一步的处理方式类似。该代码本身不是可选的,因为任何支付都需要这个额外步骤。
使用您在[上方](https://docs.stripe.com/payments/accept-a-payment-synchronously.md#create-payment-intent)设置的同一个端点再次*确认 PaymentIntent* (Confirming an intent indicates that the customer intends to use the current or provided payment method. Upon confirmation, the intent attempts to initiate the portions of the flow that have real-world side effects) 以完成付款,并*履行* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected)订单。请务必在尝试付款后一小时内确认。否则,付款将失败,并回到 `requires_payment_method`。
#### curl
```bash
curl https://api.stripe.com/v1/payment_intents/{{PAYMENT_INTENT_ID}}/confirm \
-u <>: \
-X "POST"
```
## 测试集成
我们为您提供了多张测试卡,供您在沙盒中使用,保证您的集成可顺利完成。可随意搭配 CVC 和未来的一个到期日。
| 卡号 | 描述 |
| ---------------- | --------------------------------- |
| 4242424242424242 | 成功并且立即处理付款。 |
| 4000002500003155 | 要求验证。Stripe 将触发一个模态,要求客户进行身份验证。 |
| 4000000000009995 | 始终会失败,显示拒付码 `insufficient_funds`。 |
如需完整的测试卡列表,可查看[测试](https://docs.stripe.com/testing.md)指南。
## Optional: 重新收集 CVC
When creating subsequent payments on a saved card, you might want to re-collect the CVC of the card as an additional fraud measure to verify the user.
先 [listing](https://docs.stripe.com/api/payment_methods/list.md) 与您的 *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments) 关联的支付方式,确定重新收集 CVC 时显示哪一个。在您的客户端,用 `cardCvc` Element 从用户那里重新收集某种支付方式的 CVC 值,然后用 [stripe.createToken](https://docs.stripe.com/js/tokens/create_token?type=cvc_update) 令牌化 CVC 数据。
在将 CVC 令牌发送到您的服务器后,在服务器上创建一个 PaymentIntent,在 `payment_method_options[card][cvc_token]` 参数中包含金额、货币和 CVC 令牌。与所有其他令牌一样,CVC 令牌也只能使用一次,因此每个 PaymentIntent 都应该使用它自己的唯一 CVC 令牌。
```curl
curl https://api.stripe.com/v1/payment_intents \
-u "<>:" \
-d payment_method={{PAYMENT_METHOD_ID}} \
-d customer={{CUSTOMER_ID}} \
-d amount=1099 \
-d currency=usd \
-d confirmation_method=manual \
-d confirm=true \
-d "payment_method_options[card][cvc_token]={{CVC_TOKEN_ID}}"
```
A payment might succeed even with a failed CVC check. To prevent this, configure your [Radar rules](https://docs.stripe.com/radar/rules.md#traditional-bank-checks) to block payments when CVC verification fails.
# iOS
> This is a iOS for when platform is ios. View the full page at https://docs.stripe.com/payments/accept-a-payment-synchronously?platform=ios.
为获得更广泛的支持并且未来可继续使用,请为异步付款使用[标准集成](https://docs.stripe.com/payments/accept-a-payment.md)。
此集成使用单一的客户端到服务器流程来接收付款,无需使用*Webhook* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests)或处理离线事件。虽然这种方式看似更简单,但随着业务增长,这种集成方式难以扩展,并且存在若干限制:
- **仅支持卡** — 必须编写更多代码来分别支持 ACH 及流行的区域性支付方式。
- **重复扣款风险**——通过在客户每次尝试付款时同步创建新的 PaymentIntent,您可能会意外地对客户进行重复扣款。请务必遵循[最佳实践](https://docs.stripe.com/error-low-level.md#should-retry)。
- **手动处理验证** — 需进行 3DS 验证或受*强客户认证* (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase)等法规约束的银行卡需要在客户端执行额外的步骤。
如果您打算使用这种集成,请留意这些限制。否则,请使用[标准集成](https://docs.stripe.com/payments/accept-a-payment.md)。
## 设置 Stripe [客户端] [服务器端]
首先,您需要有 Stripe 账户。[立即注册](https://dashboard.stripe.com/register)。
### 服务器端
该集成要求您的服务器上的端点与 Stripe API 通讯。请用我们的官方库从您的服务器访问 Stripe API:
#### Ruby
```bash
# Available as a gem
sudo gem install stripe
```
```ruby
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
```
### 客户端
[Stripe iOS SDK](https://github.com/stripe/stripe-ios) 是开源的,[有完整的文档](https://stripe.dev/stripe-ios/index.html),并且与支持 iOS 13 或更高版本操作系统的应用程序兼容。
#### Swift Package Manager
要安装 SDK,按这些步骤进行:
1. 在 Xcode 中,选择**文件** > **添加工具包依赖…**并输入 `https://github.com/stripe/stripe-ios-spm` 作为仓库 URL。
1. 从我们的[发布页面](https://github.com/stripe/stripe-ios/releases)选择最新的版本号。
1. 将 **StripePaymentsUI** 产品添加到[您的目标应用程序](https://developer.apple.com/documentation/swift_packages/adding_package_dependencies_to_your_app)。
#### CocoaPods
1. 如果您还没有[CocoaPods](https://guides.cocoapods.org/using/getting-started.html),请安装其最新版本。
1. 如果您目前没有 [Podfile](https://guides.cocoapods.org/syntax/podfile.html),请运行以下命令创建一个:
```bash
pod init
```
1. 将这一行代码添加到您的 `Podfile`:
```podfile
pod 'StripePaymentsUI'
```
1. 运行以下命令:
```bash
pod install
```
1. 今后,一定记得用 `.xcworkspace` 文件来打开您在 Xcode 中的项目,不要使用 `.xcodeproj` 文件。
1. 将来,要更新到 SDK 的最新版本,运行:
```bash
pod update StripePaymentsUI
```
#### Carthage
1. 如果您还没有[Carthage](https://github.com/Carthage/Carthage#installing-carthage),请安装其最新版本。
1. 将这一行代码添加到您的 `Cartfile`。
```cartfile
github "stripe/stripe-ios"
```
1. 按照 [Carthage 安装说明](https://github.com/Carthage/Carthage#if-youre-building-for-ios-tvos-or-watchos)进行。确保嵌入[这里](https://github.com/stripe/stripe-ios/tree/master/StripePaymentsUI/README.md#manual-linking)所列的所有必要框架。
1. 将来,要更新到 SDK 的最新版本,运行以下命令即可:
```bash
carthage update stripe-ios --platform ios
```
#### 手动框架
1. 前往我们的 [GitHub 发布页面](https://github.com/stripe/stripe-ios/releases/latest),下载并解压缩 **Stripe.xcframework.zip**。
1. 将 **StripePaymentsUI.xcframework** 拖拽到您的 Xcode 项目中 **General**(常规)设置的 **Embedded Binaries**(嵌入式二进制文件)部分。一定要选择 **Copy items if needed**(需要时复制项目)。
1. 为[这里](https://github.com/stripe/stripe-ios/tree/master/StripePaymentsUI/README.md#manual-linking)所列的所有必要框架重复第 2 步。
1. 将来,要更新到 SDK 的最新版本,重复第 1-3 步。
> 有关最新 SDK 发布及过往版本的详细信息,请查看 GitHub 上的[发布](https://github.com/stripe/stripe-ios/releases)页面。要想在新版本发布时接收通知,请[查看仓库的发布](https://help.github.com/en/articles/watching-and-unwatching-releases-for-a-repository#watching-releases-for-a-repository)。
在应用程序启动时使用您的 Stripe [公钥](https://dashboard.stripe.com/test/apikeys) 配置 SDK。这样可使您的应用程序向 Stripe API 发出请求。
#### Swift
```swift
import UIKitimportStripePaymentsUI
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {StripeAPI.defaultPublishableKey = "<>"
// do any other necessary launch configuration
return true
}
}
```
> 测试时使用您的[测试密钥](https://docs.stripe.com/keys.md#obtain-api-keys),发布应用时使用[真实模式](https://docs.stripe.com/keys.md#test-live-modes)密钥。
## 创建结账界面 [客户端]
用 [STPPaymentCardTextField](https://stripe.dev/stripe-ios/stripe-payments-ui/Classes/STPPaymentCardTextField.html) 在客户端安全地收集银行卡信息,这是 SDK 提供的一个临时 UI 组件,用于收集卡号、有效期、CVC 和邮编。

用以下代码创建一个银行卡组件实例和一个“Pay”按钮:
#### Swift
```swift
import UIKit
import StripePaymentsUI
class CheckoutViewController: UIViewController {
lazy var cardTextField: STPPaymentCardTextField = {
let cardTextField = STPPaymentCardTextField()
return cardTextField
}()
lazy var payButton: UIButton = {
let button = UIButton(type: .custom)
button.layer.cornerRadius = 5
button.backgroundColor = .systemBlue
button.titleLabel?.font = UIFont.systemFont(ofSize: 22)
button.setTitle("Pay", for: .normal)
button.addTarget(self, action: #selector(pay), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let stackView = UIStackView(arrangedSubviews: [cardTextField, payButton])
stackView.axis = .vertical
stackView.spacing = 20
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.leftAnchor.constraint(equalToSystemSpacingAfter: view.leftAnchor, multiplier: 2),
view.rightAnchor.constraint(equalToSystemSpacingAfter: stackView.rightAnchor, multiplier: 2),
stackView.topAnchor.constraint(equalToSystemSpacingBelow: view.topAnchor, multiplier: 2),
])
}
@objc
func pay() {
// ...
}
}
```
运行您的应用程序,确保结账页面可以显示银行卡组件及支付按钮。
## 收集银行卡详情 [客户端]
当您的客户准备结账时,创建一个 *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs),包含通过 Card Element 收集的详情。
#### Swift
```swift
class CheckoutViewController: UIViewController {
// ...
@objc
func pay() {
// Collect card details on the client
STPAPIClient.shared.createPaymentMethod(with: cardTextField.paymentMethodParams) { [weak self] paymentMethod, error in
// Create PaymentMethod failed
if let createError = error {
self?.displayAlert(title: "Payment failed", message: createError.localizedDescription)
}
if let paymentMethodId = paymentMethod?.stripeId {
self?.pay(withPaymentMethod: paymentMethodId)
}
}
}
func pay(withPaymentMethod paymentMethodId: String? = nil, withPaymentIntent paymentIntentId: String? = nil) {
// ...continued in the next step
}
// ...
}
```
## 创建 PaymentIntent [客户端] [服务器端]
Stripe 用一个 `PaymentIntent` 对象来表示您从客户收款的意图,跟踪您的收款尝试及整个过程中付款状态的变化情况。
### 在服务器
添加一个端点,用以下参数创建 PaymentIntent:
- [payment_method_id](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-payment_method)——上一步中 PaymentMethod 的 ID
- [return_url](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-return_url)——如果您已经[设置了返回 URL](https://docs.stripe.com/payments/3d-secure.md),则包含它
- [use_stripe_sdk](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-use_stripe_sdk)
- 对于用 [Stripe iOS SDK v16.0.0](https://github.com/stripe/stripe-ios/blob/master/CHANGELOG.md#1600-2019-07-18) 及以上版本进行集成的应用,该参数的值必须设置为 `true`
- 对于用旧版 SDK 进行集成的应用,**不要传递此参数**
- [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm)——将此设置为 true 以*确认* (Confirming a PaymentIntent indicates that the customer intends to pay with the current or provided payment method. Upon confirmation, the PaymentIntent attempts to initiate a payment) PaymentIntent
如果您想保存该卡以便以后重复使用,请创建一个 [Customer](https://docs.stripe.com/api/customers/create.md) 来存储 PaymentMethod,并在创建 PaymentIntent 时传递以下附加参数:
- [customer](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer) - 设置为 *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments) 的 ID。
- [setup_future_usage](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-setup_future_usage)——设置为 `off_session` 可告诉 Stripe 您打算在客户不在场时将这个 PaymentMethod 用于*会话外付款* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information)。设置这个属性便可在确认 PaymentIntent 并且用户完成所需的任何操作后,将 PaymentMethod 保存到 Customer。
创建 PaymentIntent 后,将以下内容返回到客户端:
- PaymentIntent 的 [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret)
- 如果 PaymentIntent 的 [status](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-status) 是 `requires_action`,则 `requiresAction: true`
> 参见一个[服务器实现示例](https://github.com/stripe-samples/accept-a-payment/tree/master/custom-payment-flow/server)。
### 在客户端
从您的服务器请求 `PaymentIntent`。此示例中向服务器传递一个项目列表来确定价格。始终在服务器端决定扣款金额,这是一个可信的环境,客户端不行。这样可防止客户自己选择价格。
#### Swift
```swift
let BackendUrl = "http://127.0.0.1:4242/"
class CheckoutViewController: UIViewController {
// ...
func displayAlert(title: String, message: String, restartDemo: Bool = false) {
// ...omitted for brevity
}
func pay(withPaymentMethod paymentMethodId: String? = nil, withPaymentIntent paymentIntentId: String? = nil) {
// Create a PaymentIntent on the server
let url = URL(string: BackendUrl + "pay")!
var json: [String: Any] = [:]
if let paymentMethodId = paymentMethodId {
json = [
"useStripeSdk": true,
"paymentMethodId": paymentMethodId,
"currency": "usd",
"items": [
"id": "photo_subscription"
]
]
}
else if let paymentIntentId = paymentIntentId {
json = [
"paymentIntentId": paymentIntentId,
]
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: json)
let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, requestError) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] else {
self?.displayAlert(title: "Payment failed", message: requestError?.localizedDescription ?? "")
return
}
let payError = json["error"] as? String
let clientSecret = json["clientSecret"] as? String
let requiresAction = json["requiresAction"] as? Bool
// Payment failed
if let payError = payError {
self?.displayAlert(title: "Payment failed", message: payError)
}
// Payment succeeded, no additional action required
else if clientSecret != nil && (requiresAction == nil || requiresAction == false) {
self?.displayAlert(title: "Payment succeeded", message: clientSecret ?? "", restartDemo: true)
}
// Payment requires additional action
else if clientSecret != nil && requiresAction == true && self != nil {
// ...continued in the next step
}
})
task.resume()
}
}
```
## 处理任何后续操作 [客户端]
编写代码来处理需要客户干预的情况。您在[第 4 步](https://docs.stripe.com/payments/accept-a-payment-synchronously.md#ios-create-payment-intent)时在服务器上确认后,付款会直接成功。但是,当 PaymentIntent 要求客户采取额外操作时,例如通过 *3DS 验证* (3D Secure (3DS) provides an additional layer of authentication for credit card transactions that protects businesses from liability for fraudulent card payments)进行身份验证,这个代码就开始发挥作用。
这些情况下,PaymentIntent 的状态会设置为 `requires_action`。在客户端,将 PaymentIntent ID 传递给 [STPPaymentHandler handleNextActionForPayment](https://stripe.dev/stripe-ios/stripe-payments/Classes/STPPaymentHandler.html#/c:@M@StripePayments@objc\(cs\)STPPaymentHandler\(im\)handleNextActionForPayment:withAuthenticationContext:returnURL:completion:)。`STPPaymentHandler` 用传入的 [STPAuthenticationContext](https://stripe.dev/stripe-ios/stripe-payments/Protocols/STPAuthenticationContext.html) 呈现视图控制器,并引导客户完成验证。更多信息,请阅读 [iOS 上的 3DS 验证](https://docs.stripe.com/payments/3d-secure.md?platform=ios)。
在客户端处理完所需操作后,PaymentIntent 的状态会变为 `requires_confirmation`。这一步可以使您的集成在后端同步*履行* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected)订单,并将履行结果返回到您的客户端。
将 PaymentIntent ID 发送到您的后端,并在一个小时内再次确认,从而完成付款。否则,付款尝试失败,并回到 `requires_payment_method`。
#### Swift
```swift
class CheckoutViewController: UIViewController {
// ...
// Create or confirm a PaymentIntent on the server
func pay(withPaymentMethod paymentMethodId: String? = nil, withPaymentIntent paymentIntentId: String? = nil) {
// ...
// Payment requires additional action
else if clientSecret != nil && requiresAction == true && self != nil {
let paymentHandler = STPPaymentHandler.shared()
paymentHandler.handleNextAction(forPayment: clientSecret!, with: self!, returnURL: nil) { status, paymentIntent, handleActionError in
switch (status) {
case .failed:
self?.displayAlert(title: "Payment failed", message: handleActionError?.localizedDescription ?? "")
break
case .canceled:
self?.displayAlert(title: "Payment canceled", message: handleActionError?.localizedDescription ?? "")
break
case .succeeded:
if let paymentIntent = paymentIntent, paymentIntent.status == STPPaymentIntentStatus.requiresConfirmation {
print("Re-confirming PaymentIntent after handling action")
self?.pay(withPaymentIntent: paymentIntent.stripeId)
}
else {
self?.displayAlert(title: "Payment succeeded", message: paymentIntent?.description ?? "", restartDemo: true)
}
break
@unknown default:
fatalError()
break
}
}
}
// ...
}
}
extension CheckoutViewController: STPAuthenticationContext {
func authenticationPresentingViewController() -> UIViewController {
return self
}
}
```
## 测试集成
我们为您提供了多张测试卡,供您在沙盒中使用,保证您的集成可顺利完成。可随意搭配 CVC 和未来的一个到期日。
| 卡号 | 描述 |
| ---------------- | --------------------------------- |
| 4242424242424242 | 成功并且立即处理付款。 |
| 4000002500003155 | 要求验证。Stripe 将触发一个模态,要求客户进行身份验证。 |
| 4000000000009995 | 始终会失败,显示拒付码 `insufficient_funds`。 |
如需完整的测试卡列表,可查看[测试](https://docs.stripe.com/testing.md)指南。
## Optional: 重新收集 CVC
When creating subsequent payments on a saved card, you might want to re-collect the CVC of the card as an additional fraud measure to verify the user.
先 [listing](https://docs.stripe.com/api/payment_methods/list.md) 与您的 *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments) 关联的支付方式,确定重新收集 CVC 时显示哪一个。重新收集了客户的 CVC 信息后,用 [STPAPIClient createTokenForCVCUpdate](https://stripe.dev/stripe-ios/stripe-payments/Classes/STPAPIClient.html#/c:@CM@StripePayments@StripeCore@objc\(cs\)STPAPIClient\(im\)createTokenForCVCUpdate:completion:) 令牌化 CVC 数据。
#### Swift
```swift
import UIKit
import StripePaymentsUI
class CheckoutViewController: UIViewController {
// ...
func tokenizeCVC() {
guard let cvc = cvc else {
return;
}
STPAPIClient.shared.createToken(forCVCUpdate: cvc) { (token, error) in
if error != nil || token == nil {
// handle error
} else {
let tokenId = token?.tokenId
// pass the token ID to your backend
}
}
}
// ...
}
```
将 CVC 令牌发送到您的服务器后,在服务器上创建一个 PaymentIntent,在 `payment_method_options[card][cvc_token]` 参数中包含金额、货币和 CVC 令牌。
```curl
curl https://api.stripe.com/v1/payment_intents \
-u "<>:" \
-d payment_method={{PAYMENT_METHOD_ID}} \
-d customer={{CUSTOMER_ID}} \
-d amount=1099 \
-d currency=usd \
-d confirmation_method=manual \
-d confirm=true \
-d "payment_method_options[card][cvc_token]={{CVC_TOKEN_ID}}"
```
A payment might succeed even with a failed CVC check. To prevent this, configure your [Radar rules](https://docs.stripe.com/radar/rules.md#traditional-bank-checks) to block payments when CVC verification fails.
## See also
- [接受 Apple Pay](https://docs.stripe.com/apple-pay.md)
# Android
> This is a Android for when platform is android. View the full page at https://docs.stripe.com/payments/accept-a-payment-synchronously?platform=android.
为获得更广泛的支持并且未来可继续使用,请为异步付款使用[标准集成](https://docs.stripe.com/payments/accept-a-payment.md)。
此集成使用单一的客户端到服务器流程来接收付款,无需使用*Webhook* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests)或处理离线事件。虽然这种方式看似更简单,但随着业务增长,这种集成方式难以扩展,并且存在若干限制:
- **仅支持卡** — 必须编写更多代码来分别支持 ACH 及流行的区域性支付方式。
- **重复扣款风险**——通过在客户每次尝试付款时同步创建新的 PaymentIntent,您可能会意外地对客户进行重复扣款。请务必遵循[最佳实践](https://docs.stripe.com/error-low-level.md#should-retry)。
- **手动处理验证** — 需进行 3DS 验证或受*强客户认证* (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase)等法规约束的银行卡需要在客户端执行额外的步骤。
如果您打算使用这种集成,请留意这些限制。否则,请使用[标准集成](https://docs.stripe.com/payments/accept-a-payment.md)。
## 设置 Stripe [客户端] [服务器端]
首先,您需要有 Stripe 账户。[立即注册](https://dashboard.stripe.com/register)。
### 服务器端
该集成要求您的服务器上的端点与 Stripe API 通讯。请用我们的官方库从您的服务器访问 Stripe API:
#### Ruby
```bash
# Available as a gem
sudo gem install stripe
```
```ruby
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
```
### 客户端
[Stripe Android SDK](https://github.com/stripe/stripe-android) 是开源的,且[有完整的文档](https://stripe.dev/stripe-android/)。
安装 SDK 时,将 `stripe-android` 添加到您的 [app/build.gradle](https://developer.android.com/studio/build/dependencies) 文件的 `dependencies` 块中:
#### Kotlin
```kotlin
plugins {
id("com.android.application")
}
android { ... }
dependencies {
// ...
// Stripe Android SDK
implementation("com.stripe:stripe-android:23.9.0")
// Include the financial connections SDK to support US bank account as a payment method
implementation("com.stripe:financial-connections:23.9.0")
}
```
> 有关最新 SDK 发布及过往版本的详细信息,请查看 GitHub 上的[发布](https://github.com/stripe/stripe-android/releases)页面。要想在新版本发布时接收通知,请[查看仓库的发布情况](https://docs.github.com/en/github/managing-subscriptions-and-notifications-on-github/configuring-notifications#configuring-your-watch-settings-for-an-individual-repository)。
用您的 Stripe [公钥](https://dashboard.stripe.com/apikeys)配置 SDK,以便它可以向 Stripe API 发送请求,例如在您的 `Application` 子类中:
#### Kotlin
```kotlin
import com.stripe.android.PaymentConfiguration
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
PaymentConfiguration.init(
applicationContext,
"<>"
)
}
}
```
> 测试时使用您的[测试密钥](https://docs.stripe.com/keys.md#obtain-api-keys),发布应用时使用[真实模式](https://docs.stripe.com/keys.md#test-live-modes)密钥。
我们的示例代码还用 [OkHttp](https://github.com/square/okhttp) 和 [GSON](https://github.com/google/gson) 向服务器发送了 HTTP 请求。
## 创建结账界面 [客户端]
用 [CardInputWidget](https://stripe.dev/stripe-android/payments-core/com.stripe.android.view/-card-input-widget/index.html) 在客户端安全地收集银行卡信息,这是 SDK 提供的一个临时 UI 组件,用于收集卡号、有效期、CVC 和邮编。

通过向结账页面的布局中添加以下内容,创建一个银行卡组件实例和一个“Pay”按钮:
```xml
```
运行您的应用程序,确保结账页面可以显示银行卡组件及支付按钮。
## 收集银行卡详情 [客户端]
当您的客户准备结账时,创建一个 *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs),包含通过 Card Element 收集的详情。
#### Kotlin
```kotlin
class CheckoutActivity : AppCompatActivity() {
private lateinit var stripe: Stripe
// ...
private fun pay() {
val weakActivity = WeakReference(this)
// Collect card details on the client
val cardInputWidget =
findViewById(R.id.cardInputWidget)
val params = cardInputWidget.paymentMethodCreateParams
if (params == null) {
return
}
// Configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API
stripe = Stripe(applicationContext, PaymentConfiguration.getInstance(applicationContext).publishableKey)
lifecycleScope.launch {
runCatching {
stripe.createPaymentMethod(params)
}.fold(
onSuccess = { result ->
// Create a PaymentIntent on the server with a PaymentMethod
print("Created PaymentMethod")
pay(result.id, null)
},
onFailure = {
displayAlert(weakActivity.get(), "Payment failed", "Error: $it")
}
)
}
}
private fun pay(paymentMethod: String?, paymentIntent: String?) {
// ...
}
}
```
## 创建 PaymentIntent [客户端] [服务器端]
Stripe 用一个 `PaymentIntent` 对象来表示您从客户收款的意图,跟踪您的收款尝试及整个过程中付款状态的变化情况。
### 在服务器
添加一个端点,用以下参数创建 PaymentIntent:
- [payment_method_id](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-payment_method)——上一步中 PaymentMethod 的 ID
- [use_stripe_sdk](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-use_stripe_sdk)
- 对于用 [Stripe Android SDK v10.0.0](https://github.com/stripe/stripe-android/blob/master/CHANGELOG.md#1000---2019-07-19) 及以上版本进行集成的应用,该参数的值必须设置为 `true`
- 对于用旧版 SDK 进行集成的应用,**不要传递此参数**
- [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm)——将此设置为 true 以*确认* (Confirming a PaymentIntent indicates that the customer intends to pay with the current or provided payment method. Upon confirmation, the PaymentIntent attempts to initiate a payment) PaymentIntent
如果您想保存该卡以便以后重复使用,请创建一个 [Customer](https://docs.stripe.com/api/customers/create.md) 来存储 PaymentMethod,并在创建 PaymentIntent 时传递以下附加参数:
- [customer](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer) - 设置为 *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments) 的 ID。
- [setup_future_usage](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-setup_future_usage)——设置为 `off_session` 可告诉 Stripe 您打算在客户不在场时将这个 PaymentMethod 用于*会话外付款* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information)。设置这个属性便可在确认 PaymentIntent 并且用户完成所需的任何操作后,将 PaymentMethod 保存到 Customer。
创建 PaymentIntent 后,将以下内容返回到客户端:
- PaymentIntent 的 [client secret](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-client_secret)
- 如果 PaymentIntent 的 [status](https://docs.stripe.com/api/payment_intents/object.md#payment_intent_object-status) 是 `requires_action`,则 `requiresAction: true`
> 参见一个[服务器实现示例](https://github.com/stripe-samples/accept-a-payment/tree/master/custom-payment-flow/server)。
### 在客户端
从您的服务器请求 `PaymentIntent`。此示例中向服务器传递一个项目列表来确定价格。始终在服务器端决定扣款金额,这是一个可信的环境,客户端不行。这样可防止客户自己选择价格。
#### Kotlin
```kotlin
class CheckoutActivity : AppCompatActivity() {
private val backendUrl = "http://10.0.2.2:4242/"
private val httpClient = OkHttpClient()
private fun displayAlert(activity: Activity?, title: String, message: String, restartDemo: Boolean = false) {
// omitted for brevity
}
private fun pay(paymentMethod: String?, paymentIntent: String?) {
val weakActivity = WeakReference(this)
var json = ""
if (!paymentMethod.isNullOrEmpty()) {
json = """
{
"useStripeSdk":true,
"paymentMethodId":"$paymentMethod",
"currency":"usd",
"items": [
{"id":"photo_subscription"}
]
}
"""
}
else if (!paymentIntent.isNullOrEmpty()) {
json = """
{
"paymentIntentId":"$paymentIntent"
}
"""
}
// Create a PaymentIntent on the server
val mediaType = "application/json; charset=utf-8".toMediaType()
val body = json.toRequestBody(mediaType)
val request = Request.Builder()
.url(backendUrl + "pay")
.post(body)
.build()
httpClient.newCall(request)
.enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
displayAlert(weakActivity.get(), "Payment failed", "Error: $e")
}
override fun onResponse(call: Call, response: Response) {
// Request failed
if (!response.isSuccessful) {
displayAlert(weakActivity.get(), "Payment failed", "Error: $response")
} else {
val responseData = response.body?.string()
var responseJson = JSONObject(responseData)
val payError: String? = responseJson.optString("error")
val clientSecret: String? = responseJson.optString("clientSecret")
val requiresAction: Boolean? = responseJson.optBoolean("requiresAction")
// Payment failed
if (payError != null && payError.isNotEmpty()) {
displayAlert(weakActivity.get(), "Payment failed", "Error: $payError")
}
// Payment succeeded
else if ((clientSecret != null && clientSecret.isNotEmpty())
&& (requiresAction == null || requiresAction == false)) {
displayAlert(weakActivity.get(), "Payment succeeded", "$clientSecret", restartDemo = true)
}
// Payment requires additional actions
else if ((clientSecret != null && clientSecret.isNotEmpty())
&& requiresAction == true) {
runOnUiThread {
if (weakActivity.get() != null) {
// ...continued in the next step
}
}
}
}
}
})
}
}
```
## 处理任何后续操作 [客户端]
如果付款时需要额外的操作,如 *3DS 验证* (3D Secure (3DS) provides an additional layer of authentication for credit card transactions that protects businesses from liability for fraudulent card payments),则 PaymentIntent 的状态将设置为 `requires_action`。在客户端,将 `PaymentIntent` ID 传递到 [handleNextActionForPayment](https://stripe.dev/stripe-android/payments-core/com.stripe.android/-stripe/handle-next-action-for-payment.html)。这个 SDK 将显示额外的活动,并引导客户完成验证。请参阅[在 Android 上支持 3DS 验证](https://docs.stripe.com/payments/3d-secure.md?platform=android),了解更多信息。
在客户端处理完所需操作后,PaymentIntent 的状态为 `requires_confirmation`。这一步可以使您的集成在后端同步*履行* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected)订单,并将履行结果返回到您的客户端。
将 PaymentIntent ID 发送到您的后端,并在一个小时内再次确认,从而完成付款。否则,付款尝试失败,并回到 `requires_payment_method`。
#### Kotlin
```kotlin
class CheckoutActivity : AppCompatActivity() {
// ...
private fun pay(paymentMethod: String?, paymentIntent: String?) {
// ...
val activity = weakActivity.get()!!
stripe.handleNextActionForPayment(activity, clientSecret)
// ...
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val weakActivity = WeakReference(this)
// Handle the result of stripe.handleNextActionForPayment
if (stripe.isPaymentResult(requestCode, data)) {
lifecycleScope.launch {
runCatching {
stripe.getPaymentIntentResult(requestCode, data!!)
}.fold(
onSuccess = { result ->
val paymentIntent = result.intent
val status = paymentIntent.status
if (status == StripeIntent.Status.Succeeded) {
val gson = GsonBuilder().setPrettyPrinting().create()
displayAlert(
weakActivity.get(),
"Payment succeeded",
gson.toJson(paymentIntent),
restartDemo = true
)
} else if (status == StripeIntent.Status.RequiresPaymentMethod) {
// Payment failed – allow retrying using a different payment method
displayAlert(
weakActivity.get(),
"Payment failed",
paymentIntent.lastPaymentError!!.message ?: ""
)
} else if (status == StripeIntent.Status.RequiresConfirmation) {
print("Re-confirming PaymentIntent after handling a required action")
pay(null, paymentIntent.id)
} else {
displayAlert(
weakActivity.get(),
"Payment status unknown",
"unhandled status: $status",
restartDemo = true
)
}
}, onFailure = {
// Payment request failed – allow retrying using the same payment method
displayAlert(weakActivity.get(), "Payment failed", it.toString())
}
)
}
}
}
}
```
付款验证的结果通过 [Activity#onActivityResult()](https://developer.android.com/reference/android/app/Activity.html#onActivityResult\(int,%20int,%20android.content.Intent\)) 返回到您的调用 Activity。通过在 `Activity#onActivityResult()` 中调用 [Stripe#onPaymentResult()](https://stripe.dev/stripe-android/payments-core/com.stripe.android/-stripe/on-payment-result.html) 来处理结果。[ApiResultCallback#onSuccess()](https://stripe.dev/stripe-android/payments-core/com.stripe.android/-api-result-callback/on-success.html) 中返回的 [PaymentIntentResult](https://stripe.dev/stripe-android/payments-core/com.stripe.android/-payment-intent-result/index.html) 有两个 getter:
- `getIntent()` - 确认/验证后检索到的一个 [PaymentIntent](https://stripe.dev/stripe-android/payments-core/com.stripe.android/-stripe-intent-result/index.html#com.stripe.android/StripeIntentResult/intent/#/PointingToDeclaration/) 对象
- `getOutcome()` - 一个表示支付验证结果的 [StripeIntentResult.Status](https://stripe.dev/stripe-android/payments-core/com.stripe.android/-stripe-intent-result/index.html#com.stripe.android/StripeIntentResult/outcome/#/PointingToDeclaration/) 值
- `SUCCEEDED` - 付款验证成功
- `FAILED` - 付款验证失败
- `CANCELED` - 客户取消了要求的付款验证
- `TIMEDOUT` - 付款验证尝试超时
## 测试集成
我们为您提供了多张测试卡,供您在沙盒中使用,保证您的集成可顺利完成。可随意搭配 CVC 和未来的一个到期日。
| 卡号 | 描述 |
| ---------------- | --------------------------------- |
| 4242424242424242 | 成功并且立即处理付款。 |
| 4000002500003155 | 要求验证。Stripe 将触发一个模态,要求客户进行身份验证。 |
| 4000000000009995 | 始终会失败,显示拒付码 `insufficient_funds`。 |
如需完整的测试卡列表,可查看[测试](https://docs.stripe.com/testing.md)指南。
## Optional: 重新收集 CVC
When creating subsequent payments on a saved card, you might want to re-collect the CVC of the card as an additional fraud measure to verify the user.
先 [listing](https://docs.stripe.com/api/payment_methods/list.md) 与您的 *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments) 关联的支付方式,确定重新收集 CVC 时显示哪一个。重新收集客户的 CVC 信息后,用 [Stripe#createCvcUpdateToken()](https://stripe.dev/stripe-android/payments-core/com.stripe.android/-stripe/create-cvc-update-token.html) 令牌化 CVC 数据。
#### Kotlin
```kotlin
class CheckoutActivity : AppCompatActivity() {
private val stripe: Stripe by lazy {
Stripe(this, "<>")
}
private fun tokenizeCvc(cvc: String) {
stripe.createCvcUpdateToken(
cvc,
callback = CvcUpdateResultCallback()
)
}
private class CvcUpdateResultCallback : ApiResultCallback {
override fun onSuccess(result: Token) {
val tokenId = result.id
// pass token ID to your backend
}
override fun onError(e: Exception) {
// handle error
}
}
}
```
将 CVC 令牌发送到您的服务器后,在服务器上创建一个 PaymentIntent,在 `payment_method_options[card][cvc_token]` 参数中包含金额、货币和 CVC 令牌。
```curl
curl https://api.stripe.com/v1/payment_intents \
-u "<>:" \
-d payment_method={{PAYMENT_METHOD_ID}} \
-d customer={{CUSTOMER_ID}} \
-d amount=1099 \
-d currency=usd \
-d confirmation_method=manual \
-d confirm=true \
-d "payment_method_options[card][cvc_token]={{CVC_TOKEN_ID}}"
```
A payment might succeed even with a failed CVC check. To prevent this, configure your [Radar rules](https://docs.stripe.com/radar/rules.md#traditional-bank-checks) to block payments when CVC verification fails.
## See also
- [接受 Google Pay](https://docs.stripe.com/google-pay.md)
# React Native
> This is a React Native for when platform is react-native. View the full page at https://docs.stripe.com/payments/accept-a-payment-synchronously?platform=react-native.
为获得更广泛的支持并且未来可继续使用,请为异步付款使用[标准集成](https://docs.stripe.com/payments/accept-a-payment.md)。
此集成使用单一的客户端到服务器流程来接收付款,无需使用*Webhook* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests)或处理离线事件。虽然这种方式看似更简单,但随着业务增长,这种集成方式难以扩展,并且存在若干限制:
- **仅支持卡** — 必须编写更多代码来分别支持 ACH 及流行的区域性支付方式。
- **重复扣款风险**——通过在客户每次尝试付款时同步创建新的 PaymentIntent,您可能会意外地对客户进行重复扣款。请务必遵循[最佳实践](https://docs.stripe.com/error-low-level.md#should-retry)。
- **手动处理验证** — 需进行 3DS 验证或受*强客户认证* (Strong Customer Authentication (SCA) is a regulatory requirement in effect as of September 14, 2019, that impacts many European online payments. It requires customers to use two-factor authentication like 3D Secure to verify their purchase)等法规约束的银行卡需要在客户端执行额外的步骤。
如果您打算使用这种集成,请留意这些限制。否则,请使用[标准集成](https://docs.stripe.com/payments/accept-a-payment.md)。
## 设置 Stripe [服务器端] [客户端]
### 服务器端
该集成要求您的服务器上的端点与 Stripe API 通讯。请用我们的官方库从您的服务器访问 Stripe API:
#### Ruby
```bash
# Available as a gem
sudo gem install stripe
```
```ruby
# If you use bundler, you can add this line to your Gemfile
gem 'stripe'
```
### 客户端
[React Native SDK](https://github.com/stripe/stripe-react-native) 是开源的,有完整的文档。在内部,它利用的是[原生 iOS](https://github.com/stripe/stripe-ios) 和 [Android](https://github.com/stripe/stripe-android) SDK。要安装 Stripe 的 React Native SDK,在您的项目目录中运行以下某个指令(取决于您使用的软件包管理器)。
#### yarn
```bash
yarn add @stripe/stripe-react-native
```
#### npm
```bash
npm install @stripe/stripe-react-native
```
然后,安装一些其他必要的依赖项:
- 对于 iOS,请转到 **ios** 目录并运行 `pod install` 以确保同时安装了所需的本地依赖项。
- 对于 Android,无需安装其他依赖项。
> 建议按照[官方 TypeScript 指南](https://reactnative.dev/docs/typescript#adding-typescript-to-an-existing-project)添加 TypeScript 支持。
### Stripe 初始化
要在您的 React Native 应用中初始化 Stripe,使用 `StripeProvider` 组件包裹您的支付界面,或者使用 `initStripe` 初始化方法。只需要 `publishableKey` 中的 API [公钥](https://docs.stripe.com/keys.md#obtain-api-keys)。下例显示的是用 `StripeProvider` 组件初始化 Stripe 的方式。
```jsx
import { useState, useEffect } from 'react';
import { StripeProvider } from '@stripe/stripe-react-native';
function App() {
const [publishableKey, setPublishableKey] = useState('');
const fetchPublishableKey = async () => {
const key = await fetchKey(); // fetch key from your server here
setPublishableKey(key);
};
useEffect(() => {
fetchPublishableKey();
}, []);
return (
{/* Your app code here */}
);
}
```
> 测试与开发时使用 API [测试模式](https://docs.stripe.com/keys.md#obtain-api-keys),发布时使用[真实模式](https://docs.stripe.com/keys.md#test-live-modes)密钥。
## 创建自己的结账页面 [客户端]
用 `CardField` 在客户端安全地收集银行卡信息,这是 SDK 提供的一个 UI 组件,用于收集卡号、有效期、CVC 和邮编。

将 `CardField` 组件添加到您的支付界面,安全地收集客户的银行卡详情。用 `onCardChange` 回调函数检查卡的非敏感信息,例如品牌,以及信息是否完整。
```javascript
import { CardField, useStripe } from '@stripe/stripe-react-native';
function PaymentScreen() {
// ...
return (
{
console.log('cardDetails', cardDetails);
}}
onFocus={(focusedField) => {
console.log('focusField', focusedField);
}}
/>
);
}
```
运行您的应用程序,确保结账页面可以显示 `CardField` 组件。
## 收集银行卡详情 [客户端]
当您的客户准备结账时,创建一个 *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs),包含通过 `CardField` 组件收集的详情。
```javascript
import { CardField, useStripe } from '@stripe/stripe-react-native';
function PaymentScreen() {
const { createPaymentMethod, handleNextAction } = useStripe();
const pay = async () => {
// Gather customer billing information (for example, email)
const billingDetails: CreatePaymentMethod.BillingDetails = {
email: 'email@stripe.com',
phone: '+48888000888',
addressCity: 'Houston',
addressCountry: 'US',
addressLine1: '1459 Circle Drive',
addressLine2: 'Texas',
addressPostalCode: '77063',
};
// Create payment method
const { paymentMethod, error } = await createPaymentMethod({
paymentMethodType: 'Card',
paymentMethodData: {
billingDetails,
}
});
};
// ...
}
```
## 向服务器提交 PaymentMethod [客户端]
如果 PaymentMethod 创建成功,则将它的 ID 发送到您的服务器。
```javascript
// ...
const pay = async () => {
// ...
// Send the PaymentMethod to your server to create a PaymentIntent
const response = await fetch(`/pay`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ paymentMethodId: paymentMethod.id }),
});
const { error, requires_action, payment_intent_client_secret } = await response.json();
if (error) {
// Error creating or confirming PaymentIntent
Alert.alert('Error', paymentIntentError);
return;
}
if (payment_intent_client_secret && !requires_action) {
// Payment succeeded
Alert.alert('Success', 'The payment was confirmed successfully!');
}
if (payment_intent_client_secret && requires_action) {
// ...continued below
}
};
// ...
```
## 创建 PaymentIntent [服务器端]
在您的服务器上设置一个端点来接收请求。[稍后](https://docs.stripe.com/payments/accept-a-payment-synchronously.md#confirm-payment)在处理需要额外验证的付款时,也会用到此端点。
用在您的客户端创建的 [PaymentMethod](https://docs.stripe.com/api/payment_methods/object.md) 的 ID [创建新的 PaymentIntent](https://docs.stripe.com/payments/payment-intents.md#creating-a-paymentintent)。您可以通过在创建 PaymentIntent 时将 [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm) 属性设置为 true 或在创建后调用 [confirm](https://docs.stripe.com/api/payment_intents/confirm.md) 来 *确认* (Confirming a PaymentIntent indicates that the customer intends to pay with the current or provided payment method. Upon confirmation, the PaymentIntent attempts to initiate a payment) PaymentIntent。也支持对银行卡付款进行[单独授权和捕获](https://docs.stripe.com/payments/place-a-hold-on-a-payment-method.md)。
如果支付需要额外的操作,如 3DS 验证,PaymentIntent 的状态将设置为 `requires_action`。如果支付失败,状态将设置回 `requires_payment_method`,您应该向用户显示错误。如果支付不需要任何额外的验证,则创建一个收款,且 PaymentIntent 状态设置为 `succeeded`。
> 在 [2019-02-11](https://docs.stripe.com/upgrades.md#2019-02-11) 以前的 API 版本中,`requires_payment_method` 显示为 `requires_source`,而 `requires_action` 显示为 `requires_source_action`。
#### curl
```bash
curl https://api.stripe.com/v1/payment_intents \
-u <>: \
-d "payment_method"="{{PAYMENT_METHOD_ID}}" \
-d "amount"=1099 \
-d "currency"="usd" \
-d "confirmation_method"="manual" \
-d "confirm"="true"
```
如果您想保存该卡以便以后重复使用,请创建一个 [Customer](https://docs.stripe.com/api/customers/create.md) 来存储 *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs),并在创建 PaymentIntent 时传递以下附加参数:
- [customer](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-customer)。设置为 *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments) 的 ID。
- [setup_future_usage](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-setup_future_usage)。设置为 `off_session`,以告知 Stripe 您计划在客户不在场时,将此 PaymentMethod 复用于*非会话期支付* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information)。设置此属性后,在 PaymentIntent 确认且用户完成所有必要操作后,PaymentMethod 将保存至客户账户。有关更多详情,请参阅有关[支付后保存银行卡](https://github.com/stripe-samples/saving-card-after-payment/tree/master/without-webhooks)的代码示例。
## 处理任何后续操作 [客户端]
您在第 4 步时在服务器上*确认* (Confirming an intent indicates that the customer intends to use the current or provided payment method. Upon confirmation, the intent attempts to initiate the portions of the flow that have real-world side effects)后,付款会直接成功。但是,一些付款流程需要客户采取额外的操作,例如用 *3DS 验证* (3D Secure (3DS) provides an additional layer of authentication for credit card transactions that protects businesses from liability for fraudulent card payments)进行验证。
对于需要任何后续操作的情况,PaymentIntent 的状态为 `requires_action`。在客户端,将 PaymentIntent 的*客户端密钥* (The client secret is a unique key returned from Stripe as part of a PaymentIntent. This key lets the client access important fields from the PaymentIntent (status, amount, currency) while hiding sensitive ones (metadata, customer))传递给 `handleNextAction`。原生处理程序会显示一个视图,并引导客户完成验证流程。在客户端处理完所需操作后,PaymentIntent 的状态会变为 `requires_confirmation`。这使您的集成能够在后端履行订单,并将*履行* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected)结果返回给客户端。
将 PaymentIntent ID 发送到您的后端,并在一个小时内再次确认,从而完成付款。否则,付款尝试失败,并回到 `requires_payment_method`。
```javascript
// ...
const pay = async () => {
// ...
// If PaymentIntent requires action, call handleNextAction
if (payment_intent_client_secret && requires_action) {
const { error, paymentIntent } = await handleNextAction(payment_intent_client_secret);
if (error) {
Alert.alert(`Error code: ${error.code}`, error.message);
} else if (paymentIntent) {
if (
paymentIntent.status === PaymentIntents.Status.RequiresConfirmation
) {
// Confirm the PaymentIntent again on your server
const response = await fetch(`/pay`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ payment_intent_id: paymentIntent.id }),
});
const { error, success } = await response.json();
if (error) {
// Error during confirming Intent
Alert.alert('Error', error);
} else if (success) {
Alert.alert('Success', 'The payment was confirmed successfully!');
}
} else {
// Payment succedeed
Alert.alert('Success', 'The payment was confirmed successfully!');
}
}
}
};
// ...
```
## 再次确认 PaymentIntent [服务器端]
该代码仅在支付需要额外验证时才执行,这与上一步的处理方式类似。该代码本身不是可选的,因为任何支付都需要这个额外步骤。
使用您在[上方](https://docs.stripe.com/payments/accept-a-payment-synchronously.md#create-payment-intent)设置的同一个端点再次*确认 PaymentIntent* (Confirming an intent indicates that the customer intends to use the current or provided payment method. Upon confirmation, the intent attempts to initiate the portions of the flow that have real-world side effects) 以完成付款,并*履行* (Fulfillment is the process of providing the goods or services purchased by a customer, typically after payment is collected)订单。请务必在尝试付款后一小时内确认。否则,付款将失败,并回到 `requires_payment_method`。
#### curl
```bash
curl https://api.stripe.com/v1/payment_intents/{{PAYMENT_INTENT_ID}}/confirm \
-u <>: \
-X "POST"
```
## 测试集成
我们为您提供了多张测试卡,供您在沙盒中使用,保证您的集成可顺利完成。可随意搭配 CVC 和未来的一个到期日。
| 卡号 | 描述 |
| ---------------- | --------------------------------- |
| 4242424242424242 | 成功并且立即处理付款。 |
| 4000002500003155 | 要求验证。Stripe 将触发一个模态,要求客户进行身份验证。 |
| 4000000000009995 | 始终会失败,显示拒付码 `insufficient_funds`。 |
如需完整的测试卡列表,可查看[测试](https://docs.stripe.com/testing.md)指南。
## Optional: 重新收集 CVC
在用保存的银行卡进行后续付款时,您可能需要重新收集银行卡的 CVC,以此作为额外的防欺诈措施来验证用户身份。
先 [listing](https://docs.stripe.com/api/payment_methods/list.md) 与您的 *Customer* (Customer objects represent customers of your business. They let you reuse payment methods and give you the ability to track multiple payments) 关联的支付方式,确定重新收集 CVC 时显示哪一个。重新收集客户的 CVC 信息后,用 `createTokenForCVCUpdate` 令牌化 CVC 数据。
```javascript
function PaymentScreen() {
// ...
const { createTokenForCVCUpdate } = useStripe();
const tokenizeCVC = async () => {
const { error, tokenId } = await createTokenForCVCUpdate();
if (error) {
// handle error
} else if (tokenId) {
// pass the token ID to your backend
}
};
}
```
将 CVC 令牌发送到您的服务器后,在服务器上创建一个 PaymentIntent,在 `payment_method_options[card][cvc_token]` 参数中包含金额、货币和 CVC 令牌。
```curl
curl https://api.stripe.com/v1/payment_intents \
-u "<>:" \
-d payment_method={{PAYMENT_METHOD_ID}} \
-d customer={{CUSTOMER_ID}} \
-d amount=1099 \
-d currency=usd \
-d confirmation_method=manual \
-d confirm=true \
-d "payment_method_options[card][cvc_token]={{CVC_TOKEN_ID}}"
```
A payment might succeed even with a failed CVC check. To prevent this, configure your [Radar rules](https://docs.stripe.com/radar/rules.md#traditional-bank-checks) to block payments when CVC verification fails.