Skip to content

How to work with the marketplace service

Technical definitions of each API, sample code, test UI and Open API definitions to download you can find in the developer portal.
-> developer.discover.swiss/apis

Overview

Detailed definitions of the endpoints you can find for each api in the developer portal of our Api Management including sample code and Open API definition download. Marketplace service is responsible for placing orders. Completed orders and e-tickets are saved in profile service.

The discover.swiss Checkout process is defined and implemented by the order-state machine defined on Marketplace service page. This is a step by step how-to for implementing the client side.

Create a profile

At the current stage you can't create an order without a profile.

see How to work with the profile for a description of how to do that.

Create, configure and place an order

On the client side the full order object is created with references to products and travelers.

POST {{marketUrl}}/orders  
PUT {{marketUrl}}/orders/{{orderNumber}}
This is the minimal data the client needs to send to create an order for a Zürich-card 24h.

minimal order

{
  "orderStatus": "Placed",
  "priceCurrency": "CHF",
  "orderedItem": [
      {
          "orderQuantity": 1,
          "orderedItem": {
              "product": {
                    "identifier": "NOVA_zurichcard24"
              },
              "validFrom": "2019-12-24T06:15:00+01:00",
              "traveler": [
                  {
                      "givenName": "Philipp",
                      "familyName": "Muster",
                      "birthDate": "1968-06-26T00:00:00+02:00"
                  }
              ]
          }
      },
      {
          "orderQuantity": 1,
          "orderedItem": {
              "product": {
                    "identifier": "NOVA_zurichcard72"
              },
              "validFrom": "2019-12-28T06:15:00+01:00",
              "traveler": [
                  {
                      "givenName": "Sohn",
                      "familyName": "Tester",
                      "birthDate": "2008-01-01T00:00:00+01:00"
                  }
              ]
          }
      }
  ],
  "term": [
      {
          "termCode": "ZHT-AGB|ZHT-DPP",
          "accepted": true
      }
  ]
}

  • The customer data is taken from the profile-person object
  • A BillingAddress is not mandatory, because we create e-tickets only
  • The product Identifier is the same in all environments (test/prod) but prices can change.
  • the order validation will add the name and the price on the orderItem
  • ValidFrom is the desired starting date and time of the ticket
  • Travelers must have a GivenName, FamilyName and BirthDate
  • orderNumber and orderItem-number is determined by the server and must not be part of the request
  • orderItem-state is determined by the server and not part of the request

accept terms and conditions

  • term list is created by the server based on the products in the order.
  • all terms must be accepted to initialize the payment (next step)
  • for this the terms must be art of the order-request
  • the property termDocument contains a link to the terms

Saving the order on the server (Post or Put) with

  • Status = Checkout validates the order but does not start anything more.
  • Status = Placed validates the order and if possible moves it to the placed state and the order is ready to get paid

The response contains the updated order object (product information, prices) and the validation errors if there are any.

sample response

{
    "order": {
        "orderNumber": "19-100164",
        "orderDate": "2019-12-13T14:47:51.6524834+00:00",
        "customer": {
            "profileId": "11661e9b-39a5-4d42-9b19-d0c2d6e14071",
            "address": {},
            "email": "sample@gmail.com",
            "familyName": "Muster",
            "faxNumber": "xxx",
            "givenName": "Philipp",
            "birthDate": "1967-12-07T01:00:00+01:00",
            "telephone": "033 100 11 11",
            "displayName": "Philipp Muster",
            "isGuest": "False",
            "createdDateTime": "2019-11-28T11:38:01.0845805+01:00",
            "lastModified": "2019-11-28T11:38:01.0845807+01:00"
        },
        "orderStatus": "Placed",
        "priceCurrency": "CHF",
        "totalAmount": 64.0,
        "totalAmountCHF": 64.0,
        "language": "en",
        "orderedItem": [
            {
                "orderItemNumber": "19-100164-1",
                "orderItemStatus": "Placed",
                "orderQuantity": 1,
                "orderedItem": {
                    "product": {
                        "@id": "http://localhost:7071/api/products/NOVA_zurichcard24",
                        "identifier": "NOVA_zurichcard24",
                        "name": "ZVV Zürich Card 24h",
                        "sku": "339102",
                        "priceList": [
                            {
                                "priceCHF": 0.0,
                                "maxAllowedAge": 5
                            },
                            {
                                "priceCHF": 19.0,
                                "maxAllowedAge": 15
                            },
                            {
                                "priceCHF": 27.0,
                                "maxAllowedAge": 2500
                            }
                        ],
                        "taxRate": 0.077
                    },
                    "validFrom": "2019-12-24T06:15:00+01:00",
                    "traveler": [
                        {
                            "priceCHF": 27.0,
                            "givenName": "Philipp",
                            "familyName": "Muster",
                            "birthDate": "1968-06-26T00:00:00+02:00"
                        }
                    ]
                },
                "orderDelivery": "e-ticket",
                "amount": 27.0,
                "amountCHF": 27.0,
                "tax": 2.079,
                "ticket": []
            },
            {
                "orderItemNumber": "19-100164-2",
                "orderItemStatus": "Placed",
                "orderQuantity": 1,
                "orderedItem": {
                    "product": {
                        "@id": "http://localhost:7071/api/products/NOVA_zurichcard72",
                        "identifier": "NOVA_zurichcard72",
                        "name": "ZVV Zürich Card 72h",
                        "sku": "387804",
                        "priceList": [
                            {
                                "priceCHF": 0.0,
                                "maxAllowedAge": 5
                            },
                            {
                                "priceCHF": 37.0,
                                "maxAllowedAge": 15
                            },
                            {
                                "priceCHF": 53.0,
                                "maxAllowedAge": 2500
                            }
                        ],
                        "taxRate": 0.077
                    },
                    "validFrom": "2019-12-28T06:15:00+01:00",
                    "traveler": [
                        {
                            "priceCHF": 37.0,
                            "givenName": "Sohn",
                            "familyName": "Muster",
                            "birthDate": "2008-01-01T00:00:00+01:00"
                        }
                    ]
                },
                "orderDelivery": "e-ticket",
                "amount": 37.0,
                "amountCHF": 37.0,
                "tax": 2.8489999999999998,
                "ticket": []
            }
        ],
        "term": [
            {             
                "termCode": "ZHT-AGB|ZHT-DPP",
                "accepted": true,
                "acceptedDate": "2020-02-06T18:43:39.203324+01:00",
                "termVersions": [
                    {
                        "@id": "http://localhost:7071/api/termversion/ZHT-AGB-2.0",
                        "code": "ZHT-AGB-2.0",
                        "name": "Allgemeine Geschäftsbedingungen von Zürich Tourismus",
                        "termDocument": "https://www.zuerich.com/gtc",
                        "validFrom": "2018-08-29T02:00:00+02:00",
                        "termCode": "ZHT-AGB"
                    },
                    {
                        "@id": "http://localhost:7071/api/termversion/ZHT-DPP-2019-10-28",
                        "code": "ZHT-DPP-2019-10-28",
                        "name": "Datenschutzerklärung von Zürich Tourismus",
                        "termDocument": "https://www.zuerich.com/datenschutz",
                        "validFrom": "2019-10-28T01:00:00+01:00",
                        "termCode": "ZHT-DPP"
                    }
                ]
            }
        ],
        "partnerAcronym": "Philipp",
        "profileId": "11661e9b-39a5-4d42-9b19-d0c2d6e14071",
        "@id": "http://localhost:7073/api/orders/19-100164",
        "createdDateTime": "2019-12-12T14:18:38.8819476+01:00",
        "lastModified": "2019-12-13T15:48:01.6872831+01:00"
    },
    "validationMessages": []
}
For possible errors and hints how to solve them see the list of the order validation messages in the reference section.

Initialize payment

There are 2 supported payment procedures: Checkout and "payment intent". On the first stripe provides the UI, on the second the client must use stripe libraries to create it's own UI.

You can call init payment endpoint only if order in state Placed or Payment. If the order is in state Payment you already have initialized a payment session. You will get the same response again, but you can't change the process - thus provided InitpaymentData is not taken into account. The URL of the endpoint (PUT):

/orders/{orderNumber}/payment

Web checkout

Using the checkout process of Stripe takes only two steps:

  1. We create a session object on the backend and send it to Stripe. The session already contains all required information for the payment.
  2. checkout: the client uses the session Id to redirect the customer to the payment page. otherwise: Client create its own form and sends the payment to stripe

You must send callbacks URL as part for the payment initialization as body of the request. Stripe will redirect to those URL after payment done:

{
    "createCheckoutSession" : true,
    "successUrl": "https://my.server/payment_ok",
    "errorUrl": "https://my.server/payment_failed"
}
Payment endpoint returns session id required to redirect to Stripe:
{
  "initPaymentData":
  {
    "sessionId": "cs_test_Px7s8kZKHpYwRfrlETarNX1MQNwEBnixCmpaTzVfPl7stpBOfIaLGm6B",
    "stripeApiKey": "...key to use...",
    "result": true
  },
  "validationMessages": []
}
You can use Stripe's API to redirect the customer. Here is a sample for JavaScript: where the api-key which is part of the response above must be used (in the sample: pk_test_BFvwJBVHlQbtxmftXMbF3gs00052KLsbTk)
var stripe = window['Stripe']('pk_test_BFvwJBVHlQbtxmftXMbF3gs00052KLsbTk');
      stripe.redirectToCheckout({
        // Make the id field from the Checkout Session creation API response
        // available to this file, so you can provide it as parameter here
        // instead of the {{CHECKOUT_SESSION_ID}} placeholder.
          sessionId: response.initPaymentData.sessionId
      }).then(function (result) {
         // do something
      }, function (result) {
        // using `result.error.message`.
        this.errorText = result.error.message;
      });
More information:

Payment intents

This is the default.

{
    "createCheckoutSession" : false
}
Payment endpoint returns the client secret which is required to call the confirm CardPayment at Stripe:
{
    "initPaymentData": {
        "clientSecret": "pi_1FeGZ2EJdYTfvwwnYjgpCyH3_secret_pudwIs3Sq3r38JE4fLY9JAfqi",
        "paymentApiKey": "...key to use...",
        "result": true
    },
    "validationMessages": []
}

The client implementations are different for each technology but stripe offers good libraries. In the sample app we used JavaScript and the Angular elements-wrapper https://github.com/AckerApple/stripe-angular

More information:

Pay

After a successful payment the following things happen

  1. the client gets redirected to the success page which must be implemented by the client → the client side ordering process has finished
  2. stripe calls our server to confirm the payment → the fulfillment process gets started

Warning

We only reserve money of the customer's card. The charging of the cards occurs only if fulfillment has completed.
On a partial fulfillment only the successfully processsed items are charged.

Stripe sends a checkout.session.completed event to the web hook we registered.
This webhook is an endpoint in a discover.swiss API which is not accessible to the public and is the same for all partners.

When the web hook is called, it does following:

  1. Validate the message and signature from Stripe
  2. Save the payment id into the order and change the status to Paid. If this phase fails the webhook will return an error to Stripe. This will cause Stripe to repeat the web hook call.
  3. Start the fulfillment and delivery processes. If something fails in this process the web hook still returns an OK response to Stripe.

Fulfillment

The fullfill process starts automatically. see 3. above

During the fulfillment process we communicate asynchronously with the partners and things can go wrong as we all know. If so - The guest will receive an e-mail in all cases and - Information about all steps in the order workflow can be found in the BusinessTrail and in the customer portal.

Tip

You can provoke an order-item fulfillment to fail by setting the GivenName of a traveler to ## failure ##

Delivery

Delivery process starts automatically.
During this state we charge the customer's card and split the amount to each supplier who delivers items in the order via its connected Stripe account.

More info: - Capture charge: https://stripe.com/docs/charges#auth-capture - Creating Separate Charges and Transfers https://stripe.com/docs/connect/charges-transfers

Get the tickets.

E-Mail to the customer/guest

After the fulfillment, when all tickets are ready the server sends an e-mail to the customer which contains the tickets and all related information.

Polling the order

The client can poll the order (get /orders/{id}) and can check its state.
When the fulfillment process has finished the order Status = Fulfilled

In this state the order holds information about all ticket including the url to directly accessing it (ticketToken) and the qr-code (qrCodeToken).

Get the ticket from the profile

After the fulfillment of the order the ticket is saved in the profile of the guest. It can be accessed and downloaded using the profile service endpoints

Get all tickets of an order as json download

After the fulfillment of the order all data needed to display the tickets in the App (the same information like on the pdf ticket) can be downloaded as json without authentication.
The url is passed to the mail (orderToken property) so a link can be placed in the mail to call the app and pass the download url.

Dataformat see Orderinfo download

download order tickets sample

{
    "orderNumber": "19-100168",
    "orderDate": "2019-12-17T14:10:37.4195685+01:00",
    "partnerAcronym": "christian",
    "@id": "http://localhost:7072/api/orders/19-100168",
    "customer": {
        "email": "kajakchristian@gmail.com",
        "familyName": "script>ss",
        "givenName": "ddd",
        "birthDate": "1967-12-07T01:00:00+01:00",
        "createdDateTime": "2019-11-28T11:38:01.0845805+01:00",
        "lastModified": "2019-11-28T11:38:01.0845807+01:00"
    },
    "orderStatus": "Fulfilled",
    "priceCurrency": "CHF",
    "totalAmount": 27.0,
    "totalAmountCHF": 27.0,
    "ticket": [
        {
            "name": "ZVV Zürich Card 24h",
            "additionalType": "e-ticket",
            "ticketNumber": "12335121",
            "ticketTokenId": "811215527162",
            "ticketToken": "https://discoverswissdevbusiness.blob.core.windows.net/tickets/76f409a9-8f5e-44b1-8637-c98e87b7d784_Eggenberger_Christian.pdf",
            "qrCodeToken": "https://discoverswissdevbusiness.blob.core.windows.net/qrcodes/49017a95-3c38-4ee6-82a5-678c01d67379_Eggenberger_Christian.png",
            "dateIssued": "2019-12-17T14:46:30.608267+01:00",
            "priceCurrency": "CHF",
            "totalPrice": 27.0,
            "underName": {
                "givenName": "Christian",
                "familyName": "Eggenberger",
                "birthDate": "1968-06-26T00:00:00+02:00"
            },
            "validFrom": "2019-12-24T06:15:00+01:00",
            "validUntil": "2019-12-25T06:15:00+01:00"
        }
    ],
    "errorTicket": [
        {
            "name": "Sample product for testing",
            "underName": {
                "givenName": "Sohn",
                "familyName": "Tester",
                "birthDate": "2008-01-01T00:00:00+01:00"
            },
            "validFrom": "2019-12-28T06:15:00+01:00",
            "orderItemNumber": "19-100168-2"
        }
    ]
}

Refund

Refund can be done only manually using the Stripe Dashboard.

Localization

default: de-CH

DateTime formatting: "2019-08-09T13:59:57.097+02:00"

Use Accept-Timezone header to specify request timezone. Supply a TimeZone id property acquired from Infocenter /timezones route in order to get data with desired time offset

Default timezone is W. Europe Standard Time (+02:00)

Number formatting: CH

Marketplace data is not translated, it is stored in the current language of the request (Accept-Language header).

This current language is stored in the order and is used to generate E-Mails and tickets.


Last update: August 13, 2020 10:07:36