Contents

POST [/instances/$INSTANCE]/private/orders#

Create a new order that a customer can pay for.

This request is not idempotent unless an order_id is explicitly specified. However, while repeating without an order_id will create another order, that is generally pretty harmless (as long as only one of the orders is returned to the wallet).

Note

This endpoint does not return a URL to redirect your user to confirm the payment. To get this URL use either GET [/instances/$INSTANCE]/orders/$ORDER_ID (with taler_pay_uri in the StatusUnpaidResponse), or GET [/instances/$INSTANCE]/private/orders/$ORDER_ID with the taler_pay_uri in the CheckPaymentUnpaidResponse). That said, it is also possible to construct the URL by combining the base URL with the information from the PostOrderResponse. The API is structured this way since the payment redirect URL is not unique for every order: there might be varying parameters such as the session id.

Required permission: orders-write

Request:

The request must be a PostOrderRequest.

Response:

200 OK:

The backend has successfully created the proposal. The response is a PostOrderResponse.

400 Bad Request:

The request body is malformed. Returned with TALER_EC_GENERIC_PARAMETER_MALFORMED, TALER_EC_GENERIC_PARAMETER_MISSING, TALER_EC_GENERIC_JSON_INVALID, TALER_EC_GENERIC_VERSION_MALFORMED, TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR, TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PAY_DEADLINE_IN_PAST, TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_DELIVERY_DATE_IN_PAST, TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_DEADLINE_IN_PAST, TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_WIRE_DEADLINE_IS_NEVER or TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_AFTER_WIRE_DEADLINE.

404 Not found:

Possible reasons are:

  1. The order given used products from the inventory, but those were not found in the inventory.

  2. The merchant instance is unknown (including possibly the instance being not configured for new orders).

  3. The wire method specified is not supported by the backend.

  4. An OTP device ID was specified and is unknown.

Returned with TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN, TALER_EC_MERCHANT_GENERIC_OTP_DEVICE_UNKNOWN, TALER_EC_MERCHANT_GENERIC_MONEY_POT_UNKNOWN, TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE or TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN.

409 Conflict:

A different proposal already exists under the specified order ID, or the requested currency is not supported by this backend. Returned with TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS, TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGES_FOR_WIRE_METHOD or TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGE_FOR_CURRENCY.

410 Gone:

The order given used products from the inventory that are out of stock. The response is a OutOfStockResponse.

451 Unavailable for Legal Reasons:

The order could not be created because of legal reasons, specifically no exchange would accept a payment at this time because we have not yet satisfied the respective legal requirements. The KYC status API can be used to determine details about how to proceed with the KYC process. Since v25, the body is an OrderRefusedErrorDetailResponse with an error code of TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS.

500 Internal Server Error:

The server experienced an internal failure. Returned with TALER_EC_GENERIC_DB_STORE_FAILED, TALER_EC_GENERIC_DB_FETCH_FAILED, TALER_EC_GENERIC_DB_COMMIT_FAILED, TALER_EC_GENERIC_DB_SOFT_FAILURE, TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, TALER_EC_GENERIC_ALLOCATION_FAILURE, TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_LOCALTIME, TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE or TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH.

Details:

interface PostOrderRequest {
  // The order must at least contain the minimal
  // order detail, but can override all.
  order: Order;

  // If set, the backend will then set the refund deadline to the
  // payment deadline plus the specified delay.
  // If it's not set, the default value of the backend might be
  // used. Note that both this value and the backend default
  // will be ignored if refund_deadline is set in order
  // as the refund_deadline takes precedence.
  // A value of "forever" is not allowed.
  refund_delay?: RelativeTime;

  // Specifies the payment target preferred by the client. Can be used
  // to select among the various (active) wire methods supported by the instance.
  payment_target?: string;

  // The session for which the payment is made (or replayed).
  // Only set for session-based payments.
  // Since protocol **v6**.
  session_id?: string;

  // Specifies that some products are to be included in the
  // order from the inventory.  For these inventory management
  // is performed (so the products must be in stock) and
  // details are completed from the product data of the backend.
  inventory_products?: MinimalInventoryProduct[];

  // Specifies a lock identifier that was used to
  // lock a product in the inventory.  Only useful if
  // inventory_products is set.  Used in case a frontend
  // reserved quantities of the individual products while
  // the shopping cart was being built.  Multiple UUIDs can
  // be used in case different UUIDs were used for different
  // products (i.e. in case the user started with multiple
  // shopping sessions that were combined during checkout).
  lock_uuids?: string[];

  // Should a token for claiming the order be generated?
  // False can make sense if the ORDER_ID is sufficiently
  // high entropy to prevent adversarial claims (like it is
  // if the backend auto-generates one). Default is 'true'.
  // Note: This is NOT related to tokens used for subscriptins or discounts.
  create_token?: boolean;

  // OTP device ID to associate with the order.
  // This parameter is optional.
  otp_id?: string;

}
interface PostOrderResponse {
  // Order ID of the response that was just created.
  order_id: string;

  // Deadline when the offer expires; the customer must pay before.
  // @since protocol **v21**.
  pay_deadline: Timestamp;

  // Token that authorizes the wallet to claim the order.
  // Provided only if "create_token" was set to 'true'
  // in the request.
  token?: ClaimToken;
}
interface OutOfStockResponse {

  // Product ID of an out-of-stock item.
  product_id: string;

  // Legacy integer quantity requested. Deprecated; see unit_requested_quantity.
  requested_quantity: Integer;

  // Requested quantity using "<integer>[.<fraction>]" syntax with up to six fractional digits.
  unit_requested_quantity: string;

  // Legacy integer availability (must be below requested_quantity).
  available_quantity: Integer;

  // Available quantity using "<integer>[.<fraction>]" syntax with up to six fractional digits.
  unit_available_quantity: string;

  // When do we expect the product to be again in stock?
  // Optional, not given if unknown.
  restock_expected?: Timestamp;
}
interface OrderRefusedErrorDetailResponse {

  // Numeric error code unique to the condition.
  // Will be MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS).
  code: ErrorCode;

  // Human-readable description of the error, i.e. "missing parameter", "commitment violation", ...
  // Should give a human-readable hint about the error's nature. Optional, may change without notice!
  hint?: string;

  // Detail about why a specific exchange was rejected.
  // Note that an exchange that was allowed is not listed.
  // It is possible that no exchanges were rejected (in which
  // case this array would be empty) and still the operation
  // failed because the total of the allowed amounts per
  // exchange ended up below the order total. Thus, that
  // is ultimately always the cause here (as per the code),
  // but the *other* reasons why exchanges might have been
  // rejected could be enlightening to the user and are
  // thus provided here.
  exchange_rejections: ExchangeRejectionDetail;
}
interface ExchangeRejectionDetail {

  // Base URL of the rejected exchange
  exchange_url: string;

  // Numeric error code unique to why
  // this exchange was not acceptable.
  // Can be MERCHANT_GENERIC_CURRENCY_MISMATCH,
  // MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LEGALLY_REFUSED
  // (zero deposit limit, likely KYC required),
  // MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE
  // (we failed to download /keys from the exchange),
  // MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED
  // (none of our bank accounts has a compatible wire method)
  code: ErrorCode;

  // Human-readable description of the error.
  // Should give a human-readable hint about the error's nature.
  // Optional, may change without notice!
  hint?: string;

}