Make line item quantities adjustable
Configure the Checkout Session so customers can adjust line item quantity during checkout.
Create a Checkout Session with adjustable_quantity
enabled
Set adjustable_quantity
on your line_items
when creating a Checkout Session to enable your customers to update the quantity of an item during checkout.
You can customize the default settings for the minimum and maximum quantities allowed by setting adjustable_quantity.minimum
and adjustable_quantity.maximum
. By default, an item’s minimum adjustable quantity is 0
and the maximum adjustable quantity is 99
. You can specify a value of up to 999999
for adjustable_quantity.maximum
.
When using adjustable quantities with a line_items[].quantity
value greater than 99
(the default adjustable maximum), set adjustable_quantity.maximum
to be greater than or equal to that item’s quantity.
If you use adjustable quantities, change your configuration so that it uses adjustable_quantity.maximum
when creating the Checkout Session to reserve inventory quantity instead of the line_items
quantity.
Checkout prevents the customer from removing an item if it is the only item remaining.
curl https://api.payske.com/v1/checkout/sessions \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d "line_items[0][price_data][currency]"=usd \
-d "line_items[0][price_data][product_data][name]"=T-shirt \
-d "line_items[0][price_data][unit_amount]"=2000 \
-d "line_items[0][price_data][tax_behavior]"=exclusive \
-d "line_items[0][adjustable_quantity][enabled]"=true \
-d "line_items[0][adjustable_quantity][minimum]"=1 \
-d "line_items[0][adjustable_quantity][maximum]"=10 \
-d "line_items[0][quantity]"=1 \
-d "automatic_tax[enabled]"=true \
-d mode=payment \
-d success_url="https://example.com/success" \
-d cancel_url="https://example.com/cancel"
payske checkout sessions create \
-d "line_items[0][price_data][currency]"=usd \
-d "line_items[0][price_data][product_data][name]"="T-shirt" \
-d "line_items[0][price_data][unit_amount]"=2000 \
-d "line_items[0][price_data][tax_behavior]"=exclusive \
-d "line_items[0][adjustable_quantity][enabled]"=true \
-d "line_items[0][adjustable_quantity][minimum]"=1 \
-d "line_items[0][adjustable_quantity][maximum]"=10 \
-d "line_items[0][quantity]"=1 \
-d "automatic_tax[enabled]"=true \
--mode=payment \
--success-url="https://example.com/success" \
--cancel-url="https://example.com/cancel"
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://account.payske.com/api/key
Payske.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
Payske::Checkout::Session.create(
{
line_items: [
{
price_data: {
currency: 'usd',
product_data: {name: 'T-shirt'},
unit_amount: 2000,
tax_behavior: 'exclusive',
},
adjustable_quantity: {enabled: true, minimum: 1, maximum: 10},
quantity: 1,
},
],
automatic_tax: {enabled: true},
mode: 'payment',
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel',
},
)
# Set your secret key. Remember to switch to your live secret key in production.
# See your keys here: https://account.payske.com/api/key
import payske
payske.api_key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
payske.checkout.Session.create(
line_items=[
{
"price_data": {
"currency": "usd",
"product_data": {"name": "T-shirt"},
"unit_amount": 2000,
"tax_behavior": "exclusive",
},
"adjustable_quantity": {"enabled": True, "minimum": 1, "maximum": 10},
"quantity": 1,
},
],
automatic_tax={"enabled": True},
mode="payment",
success_url="https://example.com/success",
cancel_url="https://example.com/cancel",
)
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://account.payske.com/api/key
$payske = new \Payske\PayskeClient('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
$payske->checkout->sessions->create(
[
'line_items' => [
[
'price_data' => [
'currency' => 'usd',
'product_data' => ['name' => 'T-shirt'],
'unit_amount' => 2000,
'tax_behavior' => 'exclusive',
],
'adjustable_quantity' => ['enabled' => true, 'minimum' => 1, 'maximum' => 10],
'quantity' => 1,
],
],
'automatic_tax' => ['enabled' => true],
'mode' => 'payment',
'success_url' => 'https://example.com/success',
'cancel_url' => 'https://example.com/cancel',
]
);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://account.payske.com/api/key
Payske.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
SessionCreateParams params =
SessionCreateParams
.builder()
.addLineItem(
SessionCreateParams.LineItem
.builder()
.setPriceData(
SessionCreateParams.LineItem.PriceData
.builder()
.setCurrency("usd")
.setProductData(
SessionCreateParams.LineItem.PriceData.ProductData
.builder()
.setName("T-shirt")
.build()
)
.setUnitAmount(2000L)
.setTaxBehavior(SessionCreateParams.LineItem.PriceData.TaxBehavior.EXCLUSIVE)
.build()
)
.setAdjustableQuantity(
SessionCreateParams.LineItem.AdjustableQuantity
.builder()
.setEnabled(true)
.setMinimum(1L)
.setMaximum(10L)
.build()
)
.setQuantity(1L)
.build()
)
.setAutomaticTax(SessionCreateParams.AutomaticTax.builder().setEnabled(true).build())
.setMode(SessionCreateParams.Mode.PAYMENT)
.setSuccessUrl("https://example.com/success")
.setCancelUrl("https://example.com/cancel")
.build();
Session session = Session.create(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://account.payske.com/api/key
const payske = require('payske')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
const session = await payske.checkout.sessions.create({
line_items: [
{
price_data: {
currency: 'usd',
product_data: {name: 'T-shirt'},
unit_amount: 2000,
tax_behavior: 'exclusive',
},
adjustable_quantity: {enabled: true, minimum: 1, maximum: 10},
quantity: 1,
},
],
automatic_tax: {enabled: true},
mode: 'payment',
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel',
});
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://account.payske.com/api/key
payske.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
params := &payske.CheckoutSessionParams{
LineItems: []*payske.CheckoutSessionLineItemParams{
&payske.CheckoutSessionLineItemParams{
PriceData: &payske.CheckoutSessionLineItemPriceDataParams{
Currency: payske.String(string(payske.CurrencyUSD)),
ProductData: &payske.CheckoutSessionLineItemPriceDataProductDataParams{
Name: payske.String("T-shirt"),
},
UnitAmount: payske.Int64(2000),
TaxBehavior: payske.String("exclusive"),
},
AdjustableQuantity: &payske.CheckoutSessionLineItemAdjustableQuantityParams{
Enabled: payske.Bool(true),
Minimum: payske.Int64(1),
Maximum: payske.Int64(10),
},
Quantity: payske.Int64(1),
},
},
AutomaticTax: &payske.CheckoutSessionAutomaticTaxParams{Enabled: payske.Bool(true)},
Mode: payske.String(string(payske.CheckoutSessionModePayment)),
SuccessURL: payske.String("https://example.com/success"),
CancelURL: payske.String("https://example.com/cancel"),
};
result, _ := session.New(params);
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://account.payske.com/api/key
PayskeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
var options = new SessionCreateOptions
{
LineItems = new List<SessionLineItemOptions>
{
new SessionLineItemOptions
{
PriceData = new SessionLineItemPriceDataOptions
{
Currency = "usd",
ProductData = new SessionLineItemPriceDataProductDataOptions
{
Name = "T-shirt",
},
UnitAmount = 2000,
TaxBehavior = "exclusive",
},
AdjustableQuantity = new SessionLineItemAdjustableQuantityOptions
{
Enabled = true,
Minimum = 1,
Maximum = 10,
},
Quantity = 1,
},
},
AutomaticTax = new SessionAutomaticTaxOptions { Enabled = true },
Mode = "payment",
SuccessUrl = "https://example.com/success",
CancelUrl = "https://example.com/cancel",
};
var service = new SessionService();
service.Create(options);
Handling completed transactions
After the payment completes, you can make a request for the finalized line items and their quantities. If your customer removes a line item, it is also removed from the line items response. See the Fulfillment guide to learn how to create an event handler to handle completed Checkout Sessions.
# Set your secret key. Remember to switch to your live secret key in production!
# See your keys here: https://account.payske.com/api/key
Payske.api_key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
require 'sinatra'
# You can find your endpoint's secret in your webhook settings
endpoint_secret = 'whsec_...'
post '/webhook' do
event = nil
# Verify webhook signature and extract the event
# See https://payske.com/docs/webhooks/signatures for more information.
begin
sig_header = request.env['HTTP_PAYSKE_SIGNATURE']
payload = request.body.read
event = Payske::Webhook.construct_event(payload, sig_header, endpoint_secret)
rescue JSON::ParserError => e
# Invalid payload
return status 400
rescue Payske::SignatureVerificationError => e
# Invalid signature
return status 400
end
if event['type'] == 'checkout.session.completed'
checkout_session = event['data']['object']
line_items = Payske::Checkout::Session.list_line_items(checkout_session['id'], {limit: 100})
# Fulfill the purchase...
begin
fulfill_order(checkout_session, line_items)
rescue NotImplementedError => e
return status 400
end
end
status 200
end
def fulfill_order(checkout_session, line_items)
# TODO: Remove error and implement...
raise NotImplementedError.new(<<~MSG)
Given the Checkout Session "#{checkout_session.id}" load your internal order from the database here.
Then you can reconcile your order's quantities with the final line item quantity purchased. You can use `checkout_session.metadata` and `price.metadata` to store and later reference your internal order and item ids.
MSG
end
import payske
# Using Django
from django.http import HttpResponse
# Set your secret key. Remember to switch to your live secret key in production!
# See your keys here: https://account.payske.com/api/key
payske.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
# You can find your endpoint's secret in your webhook settings
endpoint_secret = 'whsec_...'
@csrf_exempt
def my_webhook_view(request):
payload = request.body
sig_header = request.META['HTTP_PAYSKE_SIGNATURE']
event = None
try:
event = payske.Webhook.construct_event(
payload, sig_header, endpoint_secret
)
except ValueError as e:
# Invalid payload
return HttpResponse(status=400)
except payske.error.SignatureVerificationError as e:
# Invalid signature
return HttpResponse(status=400)
# Handle the checkout.session.completed event
if event['type'] == 'checkout.session.completed':
session = event['data']['object']
line_items = payske.checkout.Session.list_line_items(session['id'], limit=100)
# Fulfill the purchase...
try:
fulfill_order(session, line_items)
except NotImplementedError as e:
return HttpResponse(status=400)
# Passed signature verification
return HttpResponse(status=200)
def fulfill_order(session, line_items):
# TODO: Remove error and implement...
raise NotImplementedError("Given the Checkout Session \"" + session['id'] + "\", load your internal order from the database here.\nThen you can reconcile your order's quantities with the final line item quantity purchased.\nYou can use `checkout_session.metadata` and `price.metadata` to store and later reference your internal order and item ids.")
// Set your secret key. Remember to switch to your live secret key in production!
// See your keys here: https://account.payske.com/api/key
\Payske\Payske::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
// You can find your endpoint's secret in your webhook settings
$endpoint_secret = 'whsec_...';
$payload = @file_get_contents('php://input');
$sig_header = $_SERVER['HTTP_PAYSKE_SIGNATURE'];
$event = null;
try {
$event = \Payske\Webhook::constructEvent(
$payload, $sig_header, $endpoint_secret
);
} catch(\UnexpectedValueException $e) {
// Invalid payload
http_response_code(400);
exit();
} catch(\Payske\Exception\SignatureVerificationException $e) {
// Invalid signature
http_response_code(400);
exit();
}
function fulfill_order($session, $line_items) {
// TODO: Remove error and implement...
throw new Exception("given the Checkout Session $session->id, load your internal order from the database here.\nThen you can reconcile your order's quantities with the final line item quantity purchased.\nYou can use `checkout_session.metadata` and `price.metadata` to store and later reference your internal order and item ids.")
}
// Handle the checkout.session.completed event
if ($event->type == 'checkout.session.completed') {
$session = $event->data->object;
$line_items = \Payske\Checkout\Session::allLineItems($session->id, ['limit' => 100]);
// Fulfill the purchase...
try {
fulfill_order($session, $line_items);
} catch(\Exception $e) {
http_response_code(400);
exit();
}
}
http_response_code(200);
// Set your secret key. Remember to switch to your live secret key in production!
// See your keys here: https://account.payske.com/api/key
Payske.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
// You can find your endpoint's secret in your webhook settings
String endpointSecret = "whsec_...";
public void fulfillOrder(Session session, LineItemCollection lineItems) {
// TODO: Remove error and implement...
String error = String.format("Given the Checkout Session \"%s\" load your internal order from the database here.\nThen you can reconcile your order's quantities with the final line item quantity purchased. You can use `checkout_session.metadata` and `price.metadata` to store and later reference your internal order and item ids.", session.getId());
throw new UnsupportedOperationException(error);
}
// Using the Spark framework (http://sparkjava.com)
public Object handle(Request request, Response response) {
String payload = request.body();
String sigHeader = request.headers("Payske-Signature");
Event event = null;
try {
event = Webhook.constructEvent(payload, sigHeader, endpointSecret);
} catch (JsonSyntaxException e) {
// Invalid payload
response.status(400);
return "";
} catch (SignatureVerificationException e) {
// Invalid signature
response.status(400);
return "";
}
// Handle the checkout.session.completed event
if ("checkout.session.completed".equals(event.getType())) {
Session session = (Session) event.getDataObjectDeserializer().getObject();
Map<String, Object> params = new HashMap<>();
params.put("limit", 100);
LineItemCollection lineItems = session.listLineItems(params);
// Fulfill the purchase...
try {
fulfillOrder(session, lineItems);
} catch (NotImplementedException e) {
response.status(400);
return "";
}
}
response.status(200);
return "";
}
// Set your secret key. Remember to switch to your live secret key in production!
// See your keys here: https://account.payske.com/api/key
const payske = require('payske')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
// Find your endpoint's secret in your Dashboard's webhook settings
const endpointSecret = 'whsec_...';
// Using Express
const app = require('express')();
// Use body-parser to retrieve the raw body as a buffer
const bodyParser = require('body-parser');
const fulfillOrder = (session, lineItems) => {
// TODO: Remove error and implement...
throw new Error(`
Given the Checkout Session ${session.id}, load your internal order from the database here.
Then you can reconcile your order's quantities with the final line item quantity purchased. You can use \`checkout_session.metadata\` and \`price.metadata\` to store and later reference your internal order and item ids.`
);
}
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => {
const payload = request.body;
const sig = request.headers['payske-signature'];
let event;
try {
event = payske.webhooks.constructEvent(payload, sig, endpointSecret);
} catch (err) {
return response.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the checkout.session.completed event
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
payske.checkout.sessions.listLineItems(
session.id,
{ limit: 100 },
function(err, lineItems) {
// Fulfill the purchase...
try {
fulfillOrder(session, lineItems);
} catch (err) {
return response.status(400).send(`Fulfillment Error: ${err.message}`);
}
}
);
}
response.status(200).end();
});
app.listen(4242, () => console.log('Running on port 4242'));
// Set your secret key. Remember to switch to your live secret key in production!
// See your keys here: https://account.payske.com/api/key
payske.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
func fulfillOrder(session payske.CheckoutSession) error {
params := &payske.CheckoutSessionListLineItemParams{
ID: payske.String(session.Id),
}
// Grab the line items for the session
params.Filters.AddFilter("limit", "", "100")
i := session.ListLineItems(params)
for i.Next() {
// Access the line item using i.LineItem()
}
// TODO: Remove error and implement...
return fmt.Errorf("given the Checkout Session %q, load your internal order from the database here.\nThen you can reconcile your order's quantities with the final line item quantity purchased.\nYou can use `checkout_session.metadata` and `price.metadata` to store and later reference your internal order and item ids.", session.Id)
}
http.HandleFunc("/webhook", func(w http.ResponseWriter, req *http.Request) {
const maxBodyBytes = int64(65536)
req.Body = http.MaxBytesReader(w, req.Body, maxBodyBytes)
body, err := ioutil.ReadAll(req.Body)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err)
w.WriteHeader(http.StatusServiceUnavailable)
return
}
// Pass the request body and Payske-Signature header to ConstructEvent, along with the webhook signing key
// You can find your endpoint's secret in your webhook settings
endpointSecret := "whsec_...";
event, err := webhook.ConstructEvent(body, req.Header.Get("Payske-Signature"), endpointSecret)
if err != nil {
fmt.Fprintf(os.Stderr, "Error verifying webhook signature: %v\n", err)
w.WriteHeader(http.StatusBadRequest) // Return a 400 error on a bad signature
return
}
// Handle the checkout.session.completed event
if event.Type == "checkout.session.completed" {
var session payske.CheckoutSession
err := json.Unmarshal(event.Data.Raw, &session)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err)
w.WriteHeader(http.StatusBadRequest)
return
}
// Fulfill the purchase...
if err := fulfillOrder(session); err != nil {
fmt.Fprintf(os.Stderr, "Error fulfilling order: %v\n", err)
w.WriteHeader(http.StatusBadRequest)
return
}
}
w.WriteHeader(http.StatusOK)
})
using System;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Payske;
// Set your secret key. Remember to switch to your live secret key in production!
// See your keys here: https://account.payske.com/api/key
PayskeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
namespace workspace.Controllers
{
[Route("api/[controller]")]
public class PayskeWebHook : Controller
{
// You can find your endpoint's secret in your webhook settings
const string secret = "whsec_...";
[HttpPost]
public async Task<IActionResult> Index()
{
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
try
{
var payskeEvent = EventUtility.ConstructEvent(
json,
Request.Headers["Payske-Signature"],
secret
);
// Handle the checkout.session.completed event
if (payskeEvent.Type == Events.CheckoutSessionCompleted)
{
var session = payskeEvent.Data.Object as Checkout.Session;
// Fulfill the purchase...
try
{
this.FulfillOrder(session);
}
catch (NotImplementedException ex)
{
return BadRequest();
}
}
return Ok();
}
catch (PayskeException e)
{
return BadRequest();
}
}
private void FulfillOrder(Checkout.Session session) {
var options = new SessionListLineItemsOptions
{
Limit = 100,
};
var service = new SessionService();
PayskeList<LineItem> lineItems = service.ListLineItems(session.Id, options);
// TODO: Remove error and implement...
throw new NotImplementedException($"Given the Checkout Session \"{session.Id}\" load your internal order from the database here.\n Then you can reconcile your order's quantities with the final line item quantity purchased. You can use `checkout_session.metadata` and `price.metadata` to store and later reference your internal order and item ids.");
}
}
}