Stripe 订阅 - Charing 3D Secure 卡号返回 subscription_payment_intent_requires_action

Stripe Subscriptions - Charing a 3D Secure card number returning subscription_payment_intent_requires_action

我们正在将 3D 安全结帐与我们当前的订阅计费解决方案集成。我们提供包含 7 天免费试用的月度计划。对于集成,我们使用 SetupIntent 来确定客户的卡信息是否需要 3D 安全授权。使用要求 3D 安全的 Stripe 测试卡,我们在结帐页面的 javascript 中调用 handleCardSetup() 函数。然后授权成功后,附上客户的支付方式给客户。然后继续结帐,我们创建订阅。我们在订阅上扩展 latest_invoice.payment_intent

根据 Stripe 的文档:

"SetupIntents are automatically created for subscriptions that don’t require an initial payment. If authentication and authorization are required, they’re executed as well. If both succeed, or if they aren’t needed, no action is required, and the subscription.pending_setup_intent field is null."

当我查看调用 Subscription.create(params) 的响应时,我发现 pending_setup_intent 字段等于 null。但是,在 Stripe 的仪表板上查看订阅时,我看到尝试向卡收费时返回了 402 错误,响应如下:

{
  "error": {
    "code": "subscription_payment_intent_requires_action",
    "message": "Payment for this subscription requires additional user action before it can be completed successfully. Please refer to the use of the `enable_incomplete_payments` parameter here: https://stripe.com/docs/billing/lifecycle#incomplete-opt-in",
    "type": "card_error"
  }
}

什么给了?我在某处错过了一步吗?我们目前使用的是 Stripe 的最新版本 API:2019-05-16。我附上了用于创建 SetupIntent、将 PaymentMethod 附加到 Customer 以及创建 Subscription 的代码。如果我的代码中有任何错误,请告诉我。

设置意图:

public static String createSetupIntent() {
    try {
        Map<String, Object> setupIntentParams = new HashMap<>();
        ArrayList<String> paymentMethodTypes = new ArrayList<>();
        paymentMethodTypes.add("card");
        setupIntentParams.put("payment_method_types", paymentMethodTypes);
        SetupIntent setupIntent = SetupIntent.create(setupIntentParams);
        return setupIntent.getClientSecret();
    } catch (AuthenticationException | InvalidRequestException | ApiConnectionException | ApiException ex) {
        throw new Error("Unable to create SetupIntent", ex);
    } catch (StripeException ex) {
        throw new Error("Unable to create SetupIntent", ex);
    }
}

Javascript:

var customerInfo = {
  payment_method_data: {
    billing_details: {
      name: document.getElementById('cardholder_name').value,
      address: {
        postal_code: document.getElementById('cardholder_zip').value
      }
    }
  }
};

stripe.handleCardSetup(clientSecret, card, customerInfo).then(function (result) {
    if (result.error) {
        setStripeError(result.error);
        hideLoading('hosted_content');
    } else {
        var paymentMethodId = result.setupIntent.payment_method;

        $.post({
            url: backendURL + 'attachpaymentmethod',
            data: {payment_id: paymentMethodId},
            success: function (response) {
                document.getElementById('payment-form').submit(); //sends a post to endpoint that handles creating subscription
            },
            error: function (response) {
                hideLoading('hosted_content');
                setStripeError(response.responseJSON.error);
            }
        });

    }
});

付款方式:

public static void updatePaymentMethod(User user, String paymentMethodId) throws CardException {
    Customer customer = getCustomer(user); //Retrieves customer if user has stripeId, otherwise create a new customer
    try {
        PaymentMethod paymentMethod = PaymentMethod.retrieve(paymentMethodId);
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("customer", customer.getId());
        paymentMethod.attach(params);
    } catch (AuthenticationException | InvalidRequestException | ApiConnectionException | ApiException ex) {
        throw new Error("Unable to update Stripe payment method", ex);
    } catch (StripeException ex) {
        throw new Error("Unable to update Stripe payment method", ex);
    }
}

订阅:

Map<String, Object> planItem = new HashMap<>();
planItem.put("plan", planId);
Map<String, Object> items = new HashMap<>();
items.put("0", planItem);

List<String> expandList = new LinkedList<String>();
expandList.add("latest_invoice.payment_intent");

Map<String, Object> subscriptionOptions = new HashMap<>();
subscriptionOptions.put("customer", user.getStripeId());
subscriptionOptions.put("trial_period_days", trialDays);
subscriptionOptions.put("items", items);
subscriptionOptions.put("expand", expandList);
try {
    Subscription subscription = Subscription.create(subscriptionOptions);
    System.out.println(subscription); //pending_setup_intent is equal to null
} catch (AuthenticationException | InvalidRequestException | ApiConnectionException | ApiException ex) {
    throw new Error("Unable to create Stripe subscription", ex);
} catch (StripeException ex) {
    throw new Error("Unable to create Stripe subscription", ex);
}

我联系了 Stripe 支持人员。他们告诉我,我使用的测试卡的目的是要求对每笔交易进行 3DS 身份验证。这意味着即使我认为我在验证后保存了该卡,但除非经过验证,否则每次使用该卡都会被拒绝。所以我的解决方案是使用不同的测试卡,只需要验证一次,之后每次使用都会被批准。

此外,根据我阅读的一些关于 SCA 的文档 here,SCA 要求的卡上的经常性交易是豁免的,并且只需要验证一次。因此,建议使用只需要身份验证一次的测试卡,因为它的行为更接近 SCA 开始实施时的预期行为。