- POST [/instances/$INSTANCE]/orders/$ORDER_ID/pay#
Pay for an order by giving a deposit permission for coins. Typically used by the customer’s wallet. Note that this request does not include the usual
h_contractargument to authenticate the wallet, as the hash of the contract is implied by the signatures of the coins. Furthermore, this API doesn’t really return useful information about the order.Request:
The request must be a pay request.
Response:
- 200 OK:
The exchange accepted all of the coins. The body is a payment response. The
frontendshould now fulfill the contract. Note that it is possible that refunds have been granted.- 400 Bad request:
Either the client request is malformed or some specific processing error happened that may be the fault of the client as detailed in the JSON body of the response. This includes the case where the payment is insufficient (sum is below the required total amount, for example because the wallet calculated the fees wrong). Applicable error codes:
MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND: Wallet tried to pay with a non-existent denomination.
- 402 Payment required:
There used to be a sufficient payment, but due to refunds the amount effectively paid is no longer sufficient. (If the amount is generally insufficient, we return “400 Bad Request”, only if this is because of refunds we return 402.)
- 403 Forbidden:
One of the coin signatures was not valid.
- 404 Not found:
The merchant backend could not find the order or the instance or a token family or the Donau charity specified in the contract and thus cannot process the payment. Applicable error codes:
MERCHANT_GENERIC_TOKEN_KEY_UNKNOWNMERCHANT_GENERIC_ORDER_UNKNOWNMERCHANT_GENERIC_DONAU_CHARITY_UNKNOWNMERCHANT_GENERIC_INSTANCE_UNKNOWN
- 408 Request timeout:
The backend took too long to process the request. Likely the merchant’s connection to the exchange timed out. Try again. Applicable error codes:
MERCHANT_GENERIC_EXCHANGE_TIMEOUT
- 409 Conflict:
The exchange rejected the payment because a coin was already spent (or used in a different way for the same purchase previously), or the merchant rejected the payment because the order was already fully paid (and then return signatures with refunds). If a coin was already spent (this includes re-using the same coin after a refund), the response will include the
exchange_urlfor which the payment failed, in addition to the response from the exchange to the/batch-depositrequest. Applicable error codes:MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS: Exchange reported insufficient funds for one of the coins.
- 410 Gone:
The offer has expired and is no longer available or the provided payment has expired. Applicable error codes:
MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_DEPOSIT_EXPIRED: payment expiredMERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED: offer expired
- 412 Precondition failed:
The given exchange is not acceptable for this merchant, as it is not in the list of accepted exchanges and not audited by an approved auditor. TODO: Status code may be changed to 409 in the future as 412 is technically wrong.
- 451 Unavailable for Legal Reasons:
The exchange has rejected the deposit by the merchant for legal reasons. This is not exactly a client failure (and possibly nobody’s fault except for the regulator). In any case, the wallet should refresh the deposited coins of the affected exchange and may try to pay with coins from another exchange if possible (it has such coins and the merchant accepts coins from another exchange). The body is a PaymentDeniedLegallyResponse with details about the failure. Since protocol v17.
- 501 Not implemented:
This is returned if an optional feature required to process this particular payment is no longer implemented. This should only be possible if a different version of the backend software was deployed between order creation and payment.
Applicable error codes:
MERCHANT_GENERIC_DONAU_NOT_CONFIGURED: returned if donations are not supportedMERCHANT_GENERIC_FEATURE_NOT_AVAILABLE: usually returned if a token type is not supported
- 502 Bad gateway:
The merchant’s interaction with the exchange failed in some way. The client might want to try again later. This includes failures such as the denomination key of a coin not being known to the exchange as far as the merchant can tell. Applicable error codes:
MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUSMERCHANT_GENERIC_EXCHANGE_REPLY_MALFORMED
- 504 Gateway timeout:
The merchant’s interaction with the exchange took too long. The client might want to try again later.
The backend will return verbatim the error codes received from the exchange’s deposit API. If the wallet made a mistake, like by double-spending for example, the frontend should pass the reply verbatim to the browser/wallet. If the payment was successful, the frontend MAY use this to trigger some business logic.
Details:
interface PaymentResponse { // Signature on TALER_PaymentResponsePS with the public // key of the merchant instance. sig: EddsaSignature; // Text to be shown to the point-of-sale staff as a proof of // payment. pos_confirmation?: string; // Signed tokens. Returned in the same order as the // token envelopes were provided in the request. Specifically, // the order will follow the order of the outputs from the // contract terms, and then within each output follow the // order in which the wallet_data contained the respective // blinded envelopes. The donation tokens will be present // at the offset matching the place where a donation receipt // was indicated in the outputs array, and of course be skipped // if the PayWalletData did not have a donau field. // @since protocol **v21** token_sigs?: SignedTokenEnvelope[]; }
interface PayRequest { // The coins used to make the payment. coins: CoinPaySig[]; // Input tokens required by choice indicated by choice_index. // @since protocol **v21** tokens?: TokenUseSig[]; // Custom inputs from the wallet for the contract. wallet_data?: PayWalletData; // The session for which the payment is made (or replayed). // Only set for session-based payments. session_id?: string; }
interface SignedTokenEnvelope { // Blind signature made by the merchant. blind_sig: TokenIssueBlindSig; }
type TokenIssueBlindSig = RSATokenIssueBlindSig | CSTokenIssueBlindSig;
interface RSATokenIssueBlindSig { cipher: "RSA"; // (blinded) RSA signature blinded_rsa_signature: BlindedRsaSignature; }
interface CSTokenIssueBlindSig { cipher: "CS"; // Signer chosen bit value, 0 or 1, used // in Clause Blind Schnorr to make the // ROS problem harder. b: Integer; // Blinded scalar calculated from c_b. s: Cs25519Scalar; }
interface PayWalletData { // Index of the selected choice within the choices array of // the contract terms. // @since protocol **v21** choice_index?: Integer; // Array of output tokens to be (blindly) signed by the merchant. // Output tokens specified in choice indicated by choice_index. // @since protocol **v21** tokens_evs?: TokenEnvelope[]; // Request for donation receipts to be issued. // @since protocol **v21** donau?: DonationRequestData; }
interface DonationRequestData { // Base URL of the selected Donau url: string; // Year for which the donation receipts are expected. // Also determines which keys are used to sign the // blinded donation receipts. year: Integer; // Array of blinded donation receipts to sign. // Must NOT be empty (if no donation receipts // are desired, just leave the entire donau // argument blank). budikeypairs: BlindedDonationReceiptKeyPair[]; }
interface CoinPaySig { // Signature by the coin. coin_sig: EddsaSignature; // Public key of the coin being spent. coin_pub: EddsaPublicKey; // Signature made by the denomination public key. ub_sig: UnblindedSignature; // The hash of the denomination public key associated with this coin. h_denom: HashCode; // The amount that is subtracted from this coin with this payment. contribution: Amount; // URL of the exchange this coin was withdrawn from. exchange_url: string; // Signature affirming the posession of the // respective private key proving that the payer // is old enough. Only provided if the paid contract // has an age restriction and the coin is // age-restricted. minimum_age_sig?: EddsaSignature; // Age commitment vector of the coin. // Only provided if the paid contract // has an age restriction and the coin is // age-restricted. age_commitment?: Edx25519PublicKey[]; // Hash over the agge commitment vector of the coin. // Only provided if the paid contract // does NOT have an age restriction and the coin is // age-restricted. h_age_commitment?: AgeCommitmentHash; }
interface TokenUseSig { // Signature on TALER_TokenUseRequestPS with the token use key of // the token being used in this request. token_sig: EddsaSignature; // Token use public key. token_pub: EddsaPublicKey; // Unblinded signature on TALER_TokenIssueRequestPS with the token // issue public key of the merchant. ub_sig: UnblindedSignature; // Hash of the token issue public key associated with this token. h_issue: HashCode; }
// This type depends on the cipher used to sign token families. This is // configured by the merchant and defined for each token family in the // contract terms. type TokenEnvelope = RSATokenEnvelope | CSTokenEnvelope;
interface RSATokenEnvelope { // RSA is used for the blind signature. cipher: "RSA"; // Blinded signature of the token's public EdDSA key. rsa_blinded_pub: BlindedRsaSignature; }
interface CSTokenEnvelope { // Blind Clause-Schnorr signature scheme is used for the blind signature. // See https://taler.net/papers/cs-thesis.pdf for details. cipher: "CS"; // Public nonce cs_nonce: string; // Crockford Base32 encoded // Two Curve25519 scalars, each representing a blinded challenge cs_blinded_c0: string; // Crockford Base32 encoded cs_blinded_c1: string; // Crockford Base32 encoded }
interface PaymentDeniedLegallyResponse { // Numeric error code unique to the condition. // Error code, must be // TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LEGALLY_REFUSED. code: Integer; // Base URL of the exchanges that denied the payment. // The wallet should refresh the coins from these // exchanges, but may try to pay with coins from // other exchanges. exchange_base_urls: string[]; }