Skip to content

How to work with the marketplace service

Technical definitions of each API, endpoints, sample code, test UI and Open API definitions download. You can find in the developer portal.

Overview

Marketplace service is responsible for placing orders. Completed orders and e-tickets are saved in the profile service.

This is a step by step of how-to implement the client side.

Create a profile

At the current stage, you can't create an order without a profile. Therefore, as a first step you need to create a profile (Person/Me), which contains needed information about the consumer. All the needed information are provided on the following links.

How to work with the profile.

profile service reference.

Create, configure and place an order

On the client side the full order object is created with references to products and travelers. You can find the base URLs here.

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+02: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+02:00",
              "traveler": [
                  {
                      "givenName": "Sohn",
                      "familyName": "Tester",
                      "birthDate": "2008-01-01T00:00:00+02:00"
                  }
              ]
          }
      }
  ],
  "term": [
      {
          "termCode": "ZHT-AGB|ZHT-DPP",
          "accepted": true
      }
  ]
}
General info:

  • 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

Note

For public transportation tickets, the ValidFrom date should be less than 2 months in the future.

  • 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

After creating a profile and sending an order request. The consumers must accept the terms and conditions.

General info:

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

Example input

{
  "accepted": "true",
  "termCode": "ZHT-AGB|ZHT-DPP"
}

Saving the order on the server

  • Status = Checkout validates the order but does not start anything more.
  • Status = Placed validates the order and if possible move 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 its own UI.

You can call init payment endpoint only if the order is 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 the 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 of 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": "20-107699",
    "orderDate": "2020-12-10T20:13:51.9800292+00:00",
    "partnerAcronym": "discover.swiss",
    "@id": "https://api.discover.swiss/test/profile/orders/20-107699",
    "customer": {
        "email": "ordetest@gmail.com",
        "familyName": "Eggenberger",
        "givenName": "Christian",
        "gender": "Male",
        "birthDate": "1967-12-07T00:00:00"
    },
    "orderStatus": "Fulfilled",
    "priceCurrency": "CHF",
    "totalAmount": 27.0,
    "totalAmountCHF": 27.0,
    "mailBodyToken": "https://discoverswistestbusiness.blob.core.windows.net/mailbodies/07dcc9fc-403b-42ca-8b59-6d15e5aa8844_20-107699.html",
    "language": "de",
    "ticket": [
        {
            "name": "SBB - ZVV Zürich Card 24 Stunden",
            "product": {
                "@id": "https://api.discover.swiss/test/info/products/SBB_zurichcard24",
                "identifier": "SBB_zurichcard24"
            },  
            "additionalType": "e-ticket",
            "ticketNumber": "640374080",
            "ticketTokenId": "256754788543",
            "bookingNumber": "640374080",
            "ticketToken": "https://discoverswistestbusiness.blob.core.windows.net/tickets/7c36341a-855a-4119-95b7-4b3ecff8eaa6_nr_256754788543.pdf",
            "qrCodeToken": "https://discoverswistestbusiness.blob.core.windows.net/qrcodes/f26dd17b-ca36-454e-b65a-62adbaa9a743_nr_256754788543.png",
            "htmlToken": "https://discoverswistestbusiness.blob.core.windows.net/htmls/f41ed5b1-034e-4db2-97c7-18fef39356e3_nr_256754788543.html",
            "dateIssued": "2020-12-10T20:14:32.4031913+00:00",
            "priceCurrency": "CHF",
            "totalPrice": 27.0,
            "underName": {
                "givenName": "SBB",
                "familyName": "Tester",
                "birthDate": "1967-12-07T00:00:00"
            },  
            "validFrom": "2020-12-29T09:00:00+00:00",
            "validUntil": "2020-12-29T09:00:00+00:00"
        }
    ],
    "errorTicket": [
        {
            "name": "ZVV Zürich Card 24h",
            "underName": {
                "givenName": "## failure ##",
                "familyName": "Problem",
                "birthDate": "2001-02-18T00:00:00"
            },
            "validFrom": "2020-02-22T05:15:00+00:00",
            "orderItemNumber": "20-100145-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 or IANA code in order to get data with desired time offset

Default timezone is UTC

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: September 15, 2021 12:01:55