comecerodocs
Guides

Accept payments

Build a checkout with the API. Create a cart, take a card, PayPal or Apple Pay payment, handle 3-D Secure and declines, and capture or void later.

This guide shows you how to sell from your own server with the API. You create a cart, pay for it, handle every result the payment can come back with, and find the order that the payment makes. At the end you can also capture or void a payment you only authorized, and pay with a saved card, PayPal or Apple Pay.

The examples use a test secret token, $COMECERO_TEST_TOKEN, so no money moves. See Account and test mode to get one and for the full list of test cards.

Card numbers that pass through your server bring your server into PCI DSS scope. If the shopper types the card in a browser, take the payment from the browser with a limited token instead, so the card goes from the browser to Comecero and never reaches your server. The calls are the same. See Authentication.

How a checkout works

  1. Create a cart with the items and the customer. Comecero works out tax, shipping and discounts.
  2. Create a payment on the cart. The amount and currency come from the cart.
  3. When the payment succeeds, the cart closes and becomes an order with the id in the cart's order_id.

There is no endpoint that creates an order directly. Orders come only from paid carts and paid invoices.

Create a cart and pay for it

Create the cart

Send the items and the customer to Create a cart. Every item needs a product_id of a product in your catalog.

Request
curl https://api.comecero.com/api/v1/carts \
  -H "Authorization: Bearer $COMECERO_TEST_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "USD",
    "items": [{ "product_id": "pdf-pro.test", "quantity": 1 }],
    "customer": {
      "name": "John Smith",
      "email": "john@example.com",
      "billing_address": { "country": "US", "postal_code": "27265" }
    }
  }'
Response
{
  "object": "cart",
  "cart_id": "0091307952465CafhFSTa1",
  "order_id": "8352-CSGYX-348",
  "open": true,
  "currency": "USD",
  "subtotal": 99.95,
  "tax": 7.5,
  "total": 107.45
}

A secret or trusted token may set the price of an item with reference_price and reference_currency. A limited token may not (403).

Check what the payment needs

Get the cart options before you show the payment step. It lists the payment method types that can pay for this cart (payment_methods), the card brands your gateways take (card_types), the shipping methods the shopper can pick (shipping_quotes) and the customer fields that must be set before a payment (customer_required_fields, such as billing_address.postal_code). A payment on a cart that is missing a required field is refused with 400.

Pay for the cart

Send the payment method to Pay for a cart. Do not send total or currency: they come from the cart.

curl https://api.comecero.com/api/v1/carts/0091307952465CafhFSTa1/payments \
  -H "Authorization: Bearer $COMECERO_TEST_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "payment_method": {
      "type": "credit_card",
      "data": {
        "number": "4444555566661111",
        "exp_month": 12,
        "exp_year": 2030,
        "cvv": "123",
        "cardholder_name": "John Smith"
      }
    }
  }'
Response
{
  "object": "payment",
  "payment_id": "0090460891496PyrYzd9DC",
  "success": true,
  "status": "completed",
  "currency": "USD",
  "total": 107.45,
  "order": "https://api.comecero.com/api/v1/orders/8352-CSGYX-348"
}

Handle the result

Look at the HTTP status first, then the payment's status.

ResponsestatusWhat it meansWhat you do
201completedPaid and captured. The order exists.Show the receipt.
201pendingAuthorized only, not captured. The order exists.Capture or void it later (see below).
201processingThe gateway answers later, for example ACH.Wait for the payment:completed or payment:created_failure webhook.
201initiatedThe shopper must act first: approve at PayPal or pass a 3-D Secure check. The cart stays open.Follow response_data. When the shopper is back, get the payment and commit it only while it is still initiated.
402Declined.Show the message and let the shopper try another method.
422The payment cannot be made now, for example the shopper's location is blocked.Show the message.
409The same payment was sent again soon after a success (code duplicate).Read the cart: it is already paid.
502, 504The gateway answered with an error, or did not answer in time.Get the payment before you try again. It may have gone through.

Declines

A declined payment comes back as 402 with type transaction_error. The failed payment is in error.meta.payment, with the gateway's reason in response_message. Try it with the decline test card 4444555566669999.

{
  "error": {
    "type": "transaction_error",
    "code": "payment_declined",
    "message": "The payment was declined or could not be processed. Please try another payment method, or contact your bank for assistance and try again.",
    "reference": "YrpsaAx",
    "status": 402,
    "meta": {
      "payment": { "payment_id": "0090460891496PyrYzd9DD", "status": "failed" }
    }
  }
}

Show message to the shopper and let them try again with another card. Do not parse message: use code and status. See Error handling.

Payments that wait for the shopper

When the shopper has to do something, the payment comes back 201 with status: initiated. The next step is in response_data. Add expand=response_data to the payment request, or get the response data of the payment.

  • response_data.redirect_url: send the shopper's browser there. For PayPal it is the page where they approve. For a card it is a 3-D Secure check page.
  • response_data.next_action (cards on Comecero Express Payments only): run the 3-D Secure check on your own page instead of redirecting. It carries client_secret, publishable_key and stripe_account: the values the payment provider's browser library needs to show the check (handleNextAction).

When the shopper is back, get the payment. The Comecero 3-D Secure page commits the payment itself when the check ends, so it may already be completed or pending. While it is still initiated (always the case after PayPal or the PayPal card check page), call Commit a payment. The amounts are worked out again from the cart first, so a change to shipping or tax is charged. On success the cart closes and the order is made, exactly as for a payment that succeeded at once.

Request
curl -X POST https://api.comecero.com/api/v1/payments/0090460891496PyrYzd9DC/commit \
  -H "Authorization: Bearer $COMECERO_TEST_TOKEN"

The commit answers with the payment, usually completed or pending. A payment that is already completed or cancelled answers 422, which is why you commit only while it is initiated. Two commits at the same time are safe: the second gets the first one's result.

After the payment: the order

A successful payment closes the cart and makes the order. The order id is the cart's order_id, and the payment's order field links to it. Get the order to show the receipt, or listen for the order:created webhook to start fulfillment.

  • payment_status is completed once the money is captured, or pending while it is only authorized.
  • fulfilled is true when every item is fulfilled. Digital items and services are fulfilled at once. Physical items wait for a shipment, or for automatic fulfillment (see Auto fulfill and capture delay).
  • The customer gets the receipt email, and the payment and order events fire. See Webhooks.

Authorize now, capture later

Send capture: false on the payment to only authorize it. The payment and the order are then pending: the shopper's money is held but not taken. Left out, capture follows your fulfillment settings (by default, payments are captured at once). A limited token cannot set it.

To take the money, call Capture a payment with no body to capture it all, or with a total to capture less. The part you do not capture is recorded as a refund.

To capture it all, send no body at all. A JSON body without a total, even {}, captures nothing and records a refund of the whole payment. The same goes for Capture an order.
Request
curl -X POST https://api.comecero.com/api/v1/payments/0090460891496PyrYzd9DC/capture \
  -H "Authorization: Bearer $COMECERO_TEST_TOKEN"

To cancel the hold instead, call Void a payment. The payment becomes cancelled and no money is taken. Both calls only work while the payment is pending (422 otherwise) and need a secret or trusted token.

Capture an order and Void an order do the same for every pending payment of an order. Money that was captured is given back with a refund, not a void. See Refunds.

Other ways to pay

A saved payment method

Add "save": true to the payment_method to save the card on the customer once the payment succeeds. A payment for a subscription always saves it. You can also save one ahead with Create a customer payment method. Cards (credit_card) and US bank accounts (ach) can be saved.

To pay with a saved method, send its id. With neither payment_method nor payment_method_id, the customer's default method is used.

{
  "customer_id": "0091307952401CUa8Hn2Lq",
  "payment_method_id": "0091307952433PMk2Lw7Qx"
}

List the customer's payment methods to find the ids. Card numbers are never returned, only the brand and the last four digits.

PayPal

Send type: paypal with the pages PayPal sends the shopper back to.

{
  "payment_method": {
    "type": "paypal",
    "data": {
      "success_url": "https://shop.example.com/checkout/paypal/return",
      "cancel_url": "https://shop.example.com/checkout"
    }
  }
}

The payment comes back initiated with response_data.redirect_url. Send the shopper there. When PayPal sends them to success_url, commit the payment. If the cart total went up too much since they approved, the commit leaves the payment initiated with a new redirect_url for them to approve again. Send use_billing_agreement: true when later payments, such as subscription renewals, must be charged without the shopper.

Apple Pay

Apple Pay runs in the browser with the Payment Request API. The cart builds the payment sheet for you.

  1. Get the browser payment request for the cart, with apple_pay as the payment method type. It returns methodData, details and paymentOptions, ready for new PaymentRequest(methodData, details, paymentOptions), plus payment_url and merchant_validation_url.
  2. In the onmerchantvalidation handler, send the validation URL Apple gives you to Validate the Apple Pay merchant and pass the returned data to event.complete().
  3. When the shopper changes the shipping address or option on the sheet, send the change to Update a browser payment request and pass the result to updateWith(). Tax and shipping are worked out again.
  4. When the shopper approves, post the payment to payment_url with payment_method.type set to apple_pay and the sheet's details in payment_method.data.details, then call complete() on the sheet with the result.

Google Pay uses the wallet payment method type instead, and so does Apple Pay when the cart options offer it under wallet rather than apple_pay: you send the token the wallet gives you (wallet and token). Its browser settings are on the wallet entry in the cart options. See the payment_method field of Pay for a cart.

Good practice

  • Never retry a payment by itself. After a 502, a 504 or a lost connection, get the cart or the payment first. A blind retry can charge the shopper twice.
  • Set your own payment_id when you can. An id can be used only once, so a request sent twice cannot make two payments.
  • Trust the webhook, not the browser. A shopper can close the tab after paying. Use order:created and payment:completed to start fulfillment.

On this page