ОШИБКА при оформлении заказа Stripe: одноразовые кнопки оплаты подписки на одной странице?

#javascript #php #json #stripe-payments

#javascript #php #json #stripe-платежи

Вопрос:

ОБНОВЛЕНО Я создал страницу ценообразования, которая использует Stripe Checkout для использования как кнопки одноразовой оплаты для продукта 1, так и кнопки оплаты подписки для продукта 2.

введите описание изображения здесь

Моя цель — перенаправить кнопку одноразовой оплаты на Stripe Checkout с единовременным платежом и отдельно перенаправить платеж по подписке на проверку с повторяющимся платежом.

Согласно STRIPE, это можно сделать, используя подписку в качестве режима в CheckoutSession в create-checkout-session.php (примерный проект) :

Режим сеанса оформления заказа. Требуется при использовании цен или режима настройки. Пропустить подписку, если сеанс оформления заказа включает хотя бы один повторяющийся элемент.

В отличие от документов Stripe, следующая строка кода: 'mode' => 'subscription', активирует ТОЛЬКО платежи по подписке, но не перенаправляет одноразовые платежи. Чтобы одноразовые платежи работали, я должен изменить его на: 'mode' => 'payment', но тогда платежи по подписке не работают.

Вот код php, о котором идет речь:

  <?php
    
    require_once 'shared.php';
    
    $domain_url = $config['domain'];
    
    // Create new Checkout Session for the order
    // Other optional params include:
    // [billing_address_collection] - to display billing address details on the page
    // [customer] - if you have an existing Stripe Customer ID
    // [payment_intent_data] - lets capture the payment later
    // [customer_email] - lets you prefill the email input in the form
    // For full details see https://stripe.com/docs/api/checkout/sessions/create
    
    // ?session_id={CHECKOUT_SESSION_ID} means the redirect will have the session ID set as a query param
    $checkout_session = StripeCheckoutSession::create([
        'success_url' => $domain_url . '/success.html?session_id={CHECKOUT_SESSION_ID}',
        'cancel_url' => $domain_url . '/canceled.html',
        'payment_method_types' => ['card'],
        'mode' => 'subscription',
        'line_items' => [[
          'price' => $body->priceId,
          'quantity' => 1,
      ]]
    ]);
    
    echo json_encode(['sessionId' => $checkout_session['id']]);
 

И вот код javascript:

 // Create a Checkout Session with the selected plan ID
var createCheckoutSession = function(priceId) {
  return fetch("./create-checkout-session.php", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      priceId: priceId
    })
  }).then(function(result) {
    return result.json();
  });
};

// Handle any errors returned from Checkout
var handleResult = function(result) {
  if (result.error) {
    var displayError = document.getElementById("error-message");
    displayError.textContent = result.error.message;
  }
};

/* Get your Stripe publishable key to initialize Stripe.js */
fetch("./config.php")
  .then(function(result) {
    return result.json();
  })
  .then(function(json) {
    var publishableKey = json.publishableKey;
    var subscriptionPriceId = json.subscriptionPrice;
    var onetimePriceId = json.onetimePrice;

    var stripe = Stripe(publishableKey);
    // Setup event handler to create a Checkout Session when button is clicked
    document
      .getElementById("subscription-btn")
      .addEventListener("click", function(evt) {
        createCheckoutSession(subscriptionPriceId).then(function(data) {
          // Call Stripe.js method to redirect to the new Checkout page
          stripe
            .redirectToCheckout({
              sessionId: data.sessionId
            })
            .then(handleResult);
        });
      });

    // Setup event handler to create a Checkout Session when button is clicked
    document
      .getElementById("onetime-btn")
      .addEventListener("click", function(evt) {
        createCheckoutSession(onetimePriceId).then(function(data) {
          // Call Stripe.js method to redirect to the new Checkout page
          stripe
            .redirectToCheckout({
              sessionId: data.sessionId
            })
            .then(handleResult);
        });
      });
      
  });
 

Возможно ли вообще иметь как разовые платежи, так и повторяющиеся платежи на одной странице с Stripe Checkout? Как я могу это сделать?

Обновление в соответствии с Bemn:

 $checkout_session = StripeCheckoutSession::create([
  'success_url' => $domain_url . '/success.html?session_id={CHECKOUT_SESSION_ID}',
  'cancel_url' => $domain_url . '/canceled.html',
  'payment_method_types' => ['card'],
  'mode' => $body->mode
    'line_items' => [[
    'price' => $body->price_xxx,
    // For metered billing, do not pass quantity
    'quantity' => 1,
  ]],

  'line_items' => [[
    'price' => $body->price_zzz,
    // For metered billing, do not pass quantity
    'quantity' => 1,
  ]]
]);

echo json_encode(['sessionId' => $checkout_session['id']]);
 

И JS:

 // Create a Checkout Session with the selected plan ID
var createCheckoutSession = function(priceId, mode) {
  return fetch("./create-checkout-session.php", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      priceId: priceId,
      mode: mode // <-- passing the mode, e.g. 'payment' or 'subscription'
    })
  }).then(function(result) {
    return result.json();
  });
};
 

И HTML:

 <div data-stripe-priceid="pricexxx" data-stripe-mode="payment" id="onetime-btn" class="bold mt-2 d-inline-block w-100-after-md max-width-xxs py-2 btn btn-secondary">Ore Time</div>
    
<div data-stripe-priceid="pricexxx" data-stripe-mode="subscription" id="subscription-btn" class="bold mt-2 d-inline-block w-100-after-md max-width-xxs py-2 btn btn-secondary">Ore Time</div>
 

Комментарии:

1. Да, не уверен в документации, но я видел это много раз, когда на разных платформах присутствуют оба варианта. 🙂

2. Как вы реализуете это решение? Я пытался решить это на checkout-sessions.php досье. Вы знаете лучший способ?

3. Я помню, как создавал «продукт» на панели инструментов Stripe, а затем в коде создавал «планы» (т. Е. Планы платежей) «на лету» в зависимости от суммы подписки. В бэкэнде я использовал Stripe API следующим образом: $new_plan = StripePlan::create([ 'id' => $amount, 'product' => $product_id, 'nickname' => $amount / 100 . ' GBP per month', 'interval' => 'month', 'currency' => 'gbp', 'amount' => $amount, ]); откуда $product_id берется из панели инструментов Stripe. Это было около 18 месяцев назад, хотя я не уверен, насколько это актуально.

4. Затем я создал новую подписку, например, $subscription = StripeSubscription::create([ 'customer' => $customer->id, 'items' => [['plan' => $amount]], ]); (примечание. Я назвал планы в соответствии с суммой, чтобы я мог легко проверить, существует ли план на определенную сумму). Не уверен, насколько это полезно для вас, похоже, вы используете Stripe по-другому, чем я. Однако я подозреваю, что вам нужно посмотреть «продукты» и «планы».

5. stripe.com/docs/api/plans

Ответ №1:

Возможно ли вообще иметь как разовые платежи, так и повторяющиеся платежи на одной странице с Stripe Checkout?

Да. Ключ в том, что вы должны передать правильные параметры для генерации соответствующего идентификатора сеанса Stripe Checkout.

Как я могу это сделать?

  • Серверная часть: есть функция для приема идентификатора цены Stripe и режима оплаты в качестве входных данных и возврата идентификатора сеанса Stripe Checkout в качестве выходных данных.
  • Интерфейс: передать информацию о режиме оплаты /create-checkout-session.php . (см. Примечание, если вы не можете этого сделать)

Подробные сведения

Следующее решение предполагает, что:

  1. Вы генерируете идентификатор сеанса Stripe Checkout в серверной части. Затем этот идентификатор будет передан .createCheckoutSession() в интерфейсе js.
  2. У вас есть одноразовый продукт (назовем его PAY) и периодическая подписка (назовем ее подпиской).

Интерфейс

Я думаю, вы близки. Что вам нужно сделать, так это передать mode информацию в конечную точку вашего API:

 // Create a Checkout Session with the selected plan ID
var createCheckoutSession = function(priceId, mode) { // <-- add a mode parameter
  return fetch("./create-checkout-session.php", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      priceId: priceId,
      mode: mode // <-- passing the mode, e.g. 'payment' or 'subscription'
    })
  }).then(function(result) {
    return result.json();
  });
};
 

Если это так, каждая кнопка оформления заказа на странице должна содержать соответствующую информацию о priceId и режиме оплаты. Вы можете сделать это, сохранив их с помощью атрибута data:

 <div data-stripe-priceid="price_yyy" data-stripe-mode="subscription">Recurrent</div>
<div data-stripe-priceid="price_zzz" data-stripe-mode="payment">1-time</div>
 

Если это так, вы можете получить атрибуты данных, например click , по событию.

Примечание: Если вы не можете добавить дополнительный параметр для указания режима, вам нужно определить, является ли данный идентификатор цены одноразовым или повторяющимся продуктом в серверной части. См. https://stripe.com/docs/api/prices/object?lang=php#price_object-type для получения более подробной информации.

Серверная часть

Вот 2 примера фрагментов кода из документации Stripe. Прямое копирование их не работает.

Ссылка для ОПЛАТЫ: https://stripe.com/docs/checkout/integration-builder

 $checkout_session = StripeCheckoutSession::create([
  'payment_method_types' => ['card'],
  'line_items' => [[
    'price_data' => [
      'currency' => 'usd',
      'unit_amount' => 2000,
      'product_data' => [
        'name' => 'Stubborn Attachments',
        'images' => ["https://i.imgur.com/EHyR2nP.png"],
      ],
    ],
    'quantity' => 1,
  ]],
  'mode' => 'payment',
  'success_url' => $YOUR_DOMAIN . '/success.html',
  'cancel_url' => $YOUR_DOMAIN . '/cancel.html',
]);
 

В вашем случае вам может не понадобиться определять 'price_data' . Вместо этого вы должны использовать 'price' , как в следующем примере.

Ссылка на подраздел: https://stripe.com/docs/billing/subscriptions/checkout#create-session

 $checkout_session = StripeCheckoutSession::create([
  'success_url' => 'https://example.com/success.html?session_id={CHECKOUT_SESSION_ID}',
  'cancel_url' => 'https://example.com/canceled.html',
  'payment_method_types' => ['card'],
  'mode' => 'subscription',
  'line_items' => [[
    'price' => $body->priceId,
    // For metered billing, do not pass quantity
    'quantity' => 1,
  ]],
]);
 
  1. Взгляните на эту ссылку: https://stripe.com/docs/api/checkout/sessions/create . Например line_items , вы можете просто использовать 'price' и передавать идентификатор цены (например price_xxx ), что означает, что ваша 'line_items' воля выглядит следующим образом:
 'line_items' => [[
  'price' => $body->priceId,
  'quantity' => 1,
]],
 

Для 'mode' этого используйте значение из вашего запроса API. Это должно быть что-то вроде:

 'mode' => $body->mode
 

Что означает, что в вашем бэкэнде вам лучше определить функцию (например generate_checkout_session ) для:

  • проанализируйте тело json, полученное в запросе API
  • получить priceId и mode из проанализированных данных
  • используйте priceId и mode в StripeCheckoutSession::create и
  • возвращает идентификатор checkout_session

Надеюсь, это (и ссылочные URL-адреса) могут вам помочь.

Комментарии:

1. Вероятно, я где-то что-то упускаю, но мне интересно, как это работает, если клиент покупает смесь товаров, требующих разовых платежей, и товаров, оплаченных с помощью подписки в одной транзакции -?

2. @wkille Боюсь, вы не можете этого сделать (по крайней мере, вы не можете сгенерировать сеанс Stripe Checkout, смешивающий два вида элементов).

3. Я думаю, что я правильно следил, пока вы не дошли до этой части: «Это означает, что в вашем бэкэнде вам лучше определить функцию, принимающую priceId и mode из вашего запроса API и возвращающую идентификатор checkout_session».

4. Я добавил то, что получил от вашего решения, оно по-прежнему не работает. Я что-то упустил?

5. @ChosenJuan ваш серверный create-checkout-session.php файл получит строку JSON из интерфейса. $checkout_session = StripeCheckoutSession::create([ ... не знает, что priceId и mode автоматически, поэтому вам нужно проанализировать строку JSON, присвоить priceId переменным php значения и, mode полученные из интерфейса, и передать эти переменные в функцию сеанса create Stripe в ответе @Bemn вместо priceId and mode . См. geeksforgeeks.org/how-to-receive-json-post-with-php

Ответ №2:

При создании сеанса вы можете указать как цену за повторяющуюся сумму, взимаемую за подписку, так и другую цену за разовую плату, которую вы хотите взимать. Вы можете комбинировать несколько повторяющихся цен и единовременные цены в целом.

 $checkout_session = StripeCheckoutSession::create([
    'success_url' => $domain_url . '/success.html?session_id={CHECKOUT_SESSION_ID}',
    'cancel_url' => $domain_url . '/canceled.html',
    'payment_method_types' => ['card'],
    'mode' => 'subscription',
    'line_items' => [
      // Add a one-time Price for $10
      [
        'price' => 'price_123', 
        'quantity' => 1,
      ],
      // Add another one-time Price for $23
      [
        'price' => 'price_345', 
        'quantity' => 1,
      ],
      // Add a recurring Price for $100 monthly
      [
        'price' => 'price_ABC', 
        'quantity' => 1,
      ],
]);
 

Приведенный выше код создаст сеанс с 3 позициями. Один за 100 долларов в месяц, один за 10 долларов только один раз и один за 23 доллара только один раз. Общая сумма за сеанс составит 133 доллара при первом платеже. Он также запустит подписку на 100 долларов в месяц, а будущие счета-фактуры будут стоить 100 долларов, как и ожидалось.

Комментарии:

1. Это просто суммирует все цены вместе. Я хотел иметь одну кнопку для запуска единовременных платежей и другую кнопку для запуска ежемесячных платежей.

2. @ChosenJuan Ах, извините, я неправильно понял вопрос. Идея здесь заключалась бы в том, чтобы в вашей форме было две кнопки, которые вызывают ваш сервер, который создает сеанс оформления заказа с правильными позициями на основе того, что именно покупает клиент.

3. Да, точно, у меня будут две кнопки, которые перенаправляют либо на план подписки, либо на единовременную плату. Вы знаете, как решить эту проблему?

4. @ChosenJuan Я не уверен, какая именно часть вас блокирует. Ваш серверный код должен передавать правильные параметры для создания сеанса в зависимости от того, хотите ли вы одноразовый платеж или подписку. Это означает передачу вправо line_items и вправо mode . По этой причине ваш клиентский код JS отправит правильную информацию на сервер. После создания сеанса вы можете перенаправить его на сторону клиента.

5. Проблема не в элементах line_items, проблема в том, что режим может перенаправлять только на платежи по подписке или единовременные платежи, но не на оба. Как я могу изменить режим, чтобы принимать оба? Или вы знаете, как исправить эту проблему в формате Json или JS?

Ответ №3:

Что я получил, так это то, что вам просто нужно добавить проверку на то, является ли она одноразовой или подпиской, вот что вы можете сделать:

 JS FILE CHANGES:

var createCheckoutSession = function(priceId, $mode) {
  return fetch("./create-checkout-session.php", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      priceId: priceId,
      paymentType: $mode, // This vary based on the button clicked either one-time or subscription.
    })
  }).then(function(result) {
    return result.json();
  });
};

/* Get your Stripe publishable key to initialize Stripe.js */
fetch("./config.php")
  .then(function(result) {
    return result.json();
  })
  .then(function(json) {
    var publishableKey = json.publishableKey;
    var subscriptionPriceId = json.subscriptionPrice;
    var onetimePriceId = json.onetimePrice;

    var stripe = Stripe(publishableKey);
    // Setup event handler to create a Checkout Session when button is clicked
    document
      .getElementById("subscription-btn")
      .addEventListener("click", function(evt) {
        createCheckoutSession(subscriptionPriceId, 'subscription').then(function(data) {
          // Call Stripe.js method to redirect to the new Checkout page
          stripe
            .redirectToCheckout({
              sessionId: data.sessionId
            })
            .then(handleResult);
        });
      });

    // Setup event handler to create a Checkout Session when button is clicked
    document
      .getElementById("onetime-btn")
      .addEventListener("click", function(evt) {
        createCheckoutSession(onetimePriceId, 'onetime').then(function(data) {
          // Call Stripe.js method to redirect to the new Checkout page
          stripe
            .redirectToCheckout({
              sessionId: data.sessionId
            })
            .then(handleResult);
        });
      });
      
  });
 

Теперь нам нужно внести изменения в PHP-файл:

 PHP FILE CHANGES:

$checkout_session = StripeCheckoutSession::create([
        'success_url' => $domain_url . '/success.html?session_id={CHECKOUT_SESSION_ID}',
        'cancel_url' => $domain_url . '/canceled.html',
        'payment_method_types' => ['card'],
        'mode' => $body->paymentType, // Here is what we have got from front-end
        'line_items' => [[
          'price' => $body->priceId,
          'quantity' => 1,
      ]]
    ]);