Preiserhöhung Deutschlandticket - Price Change of Deutschlandticket
0. Introduction: Price Change and New Requirements
Due to the upcoming price adjustment for the Deutschlandticket starting from 01.01.2025, the price of the Deutschlandticket will change to 58,00€ per month. This has been decided by the federal and state transport ministers.
Therefor a new product variant needs to be introduced into the mobilitybox. The current Deutschlandticket product variants will no longer be supported to ensure that users use the new product.
It will be a requirement to collect the first and last name, the birth date and the postal code of the passenger.
There are several changes in the Mobilitybox API helping to support this price change and the switch from the old products to the new one. To ensure smooth transitions for the end-user consider the following adjustments.
1. What happens?
- new Deutschlandticket products will be introduced early November
can be bought from 01.11.2024 and activated from 01.01.2025 - old Deutschlandticket products can be bought and activated until 31.12.2024
- active subscriptions will automatically convert to the new product for the january cycle and can be reactivated normaly
(check out 2. to see how migrate data for subscriptions where not all necessary data was collected before)
2. Data Migration for Existing Products and Users
Existing users who are subscribed to some of the old Deutschlandticket products variants will not have all the necessary data for the new product version.
What is the issue?
From 01.01.2025 the following user data is required:
- first_name
- last_name
- birth_date
- german_postal_code
If that data is not collected for an active subscription, it is not possible to reactivate the coupon to receive the January Ticket. Therefor a data migration is needed.
Products which are affected:
- mobilitybox-product-b59e8476-6ba0-4260-91fd-9e456341065d
- birth_date
and german_postal_code
is missing
- mobilitybox-product-cbe3ec49-b9ad-42f8-b694-8a41e3b8e9f8
- german_postal_code
is missing
The product mobilitybox-product-358abaeb-81d7-4d65-9d8a-420ee739c066
will automatically transfer, without anything to do on your side.
Please checkout your active subscriptions if there are affected product variants or not.
There are different options to pass the missing data to the Mobilitybox:
Option 1 - Restore
The first option is to restore the coupon and activate the new restored coupon with necessary data.
Attention: This only works if the activation of the restored coupon happens after 01.01.2025
Option 2 - Pass Postal Code before Reactivation
To pass the missing german_postal_code
you can use the POST /ticketing/coupons/{id}/tariff_settings.json
endpoint (API Docs).
Option 3 - Pass Data on Reactivation
It is possible to pass the identification_medium
and tariff_settings
additionally to the reactivation_key
on the reactivation. If the already collected data misses something it will pick it from the passed data.
3. API Changes: Introduction of API Version v7
To support the price update, API version v7
will be introduced. This new version will offer improved functionality, including the ability to track which product is booked for each subscription cycle. Key changes include:
product_id
for each Subscription Cycle:- The new API introduces a field to differentiate between old and new product variants within a user's subscription cycle.
earliest_activation_start_datetime
for a Product:- Defines what is the earliest possible validity start time for a ticket. (earliest
activation_start_datetime
passed in coupon activation or earliest time when to activate the coupon without passingactivation_start_datetime
) - Example: The new Deutschlandticket product can already be bought in November/December, but it can only be activated from January
- Defines what is the earliest possible validity start time for a ticket. (earliest
earliest_activation_start_datetime
for a Coupon: (like product)- Defines what is the earliest possible validity start time for a ticket. (earliest
activation_start_datetime
passed in coupon activation or earliest time when to activate the coupon without passingactivation_start_datetime
) - Example: The new Deutschlandticket product can already be bought in November/December, but it can only be activated from January
- Defines what is the earliest possible validity start time for a ticket. (earliest
latest_activation_start_datetime
for a Coupon:- Defines what is the latest possible validity start time for a ticket.
- The old Deutschlandticket product can be bought until 31.12.2024 and also can only be activated until then. Attention: Tickets activated for the 31.12.2024 will only be valid for 1 day and then has to be reordered again.
product
for a Coupon:- On subscriptions: the product shown in the coupon data is set to the product of the current subscription cycle
- Example: for a already active subscription, in december the product will be the old product, from 1st January the product attribute will contain the data of the new product
Backward Compatibility:
- The existing API versions (
v6
and below) will remain active and also support the new products. But the new attributes will only be available in APIv7
, so consider to upgrade.
- The existing API versions (
4. Fastlink Changes
Fastlink will update the products automatically. There will be the new product in checkout from 01.01.2025 and in December the customers will see a notice stating out, that there will be a new product and they are able to buy the new product if they want to use it in January.
If you want to skip this notice, you can add a new flag to the URL. For more details, see Fastlink Implementation
There will be a notice on the subscription manage page which shows the price change.
5. Mobility Wallet
The Mobility Wallet will implement and use the changes from the SDK updates. Users using the Mobility Wallet will receive an alert when the reactivation of their ticket failed and given the option to enter the missing options by themself.
If you are using the Mobility Wallet for your customers make sure noticing them to update the App before 01.01.2025.
6. SDK Changes for iOS and Android
To accommodate the product changes, the native SDKs for both iOS and Android will receive updates. Below are the changes, along with implementation examples for each platform.
iOS SDK Changes:
- Update SDK Version: The iOS SDK must be updated to version
7.0.0
or higher to support the features. - Identify Reactivation Issue: there will be two new MobilityboxError types:
MobilityboxError.identification_medium_not_valid
MobilityboxError.tariff_settings_not_valid
- New Data Attributes:
- MobilityboxCoupon added:
public var earliest_activation_start_datetime: String?
- MobilityboxCoupon added:
public var latest_activation_start_datetime: String?
- MobilityboxSubscriptionCycle added:
public var product_id: String?
- MobilityboxCoupon added:
- New
MobilityboxProductCode
: to fetch aMobilityboxProduct
MobilityboxIdentificationView
used for Reactivation: you can passticket: MobilityboxTicket?
to the identification view. The identification view will then use the ticketscoupon_reactivation_key
and the entered user data to reactivate the ticket. (Like Option 3 in 2.)
Example Implementations:
- check reactivation error codes
import Mobilitybox
func reactivateTicket(ticket: MobilityboxTicket) {
ticket.reactivate(onSuccess: { reactivatedTicketCode in
// implement your normal reactivation success
}) { error in
if error == .identification_medium_not_valid || error == .tariff_settings_not_valid {
// ==> her implement function to open popup or some kind of notification for the user stating out that it was not possible to reactivate the ticket and the user has to enter more information
}
}
}
- ticket reactivation failed alert example (open identification view with passed ticket):
import SwiftUI
import Mobilitybox
struct TicketReactivationFailedAlertView: View {
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
@State var ticket: MobilityboxTicket
@State var coupon: MobilityboxCoupon? = nil
@State var showIdentificationView = false
func reactivateTicketCallback(coupon: MobilityboxCoupon, ticketCode: MobilityboxTicketCode) {
// implement whatever your app does when you receive the reactivated ticket code
// example:
// - fetch ticket from ticket code
// - add ticket to list
// - close alert
}
var body: some View {
VStack(alignment: .leading) {
// add some texts with description thats not possible to reactivate the coupon
if (coupon != nil) { // if coupon ready, show button
MobilityboxNavigationLink(linkType: .modal, showDestinationView: $showIdentificationView) {
Button("Fehlende Daten hinterlegen", systemImage: "rectangle.and.pencil.and.ellipsis") {
self.showIdentificationView = true
}.buttonStyle(.borderedProminent)
} navigationDestination: {
VStack {
MobilityboxIdentificationView(
coupon: Binding($coupon)!,
activateCouponCallback: reactivateTicketCallback,
ticket: $ticket
).navigationBarTitleDisplayMode(.inline)
}
}
} else {
ProgressView()
}
Spacer()
}
.padding(.horizontal, 20)
.onAppear {
// fetch Coupon if not already available in your app (needed for the Identification View)
MobilityboxCouponCode(couponId: self.ticket.coupon_id).fetchCoupon { coupon in
self.coupon = coupon
} onFailure: { error in
// close view
}
}
}
}
Android SDK Changes:
- Update SDK Version: The Android SDK must be updated to version
7.0.0
or higher to support the features. - Identify Reactivation Issue: there will be two new MobilityboxError types:
MobilityboxError.IDENTIFICATION_MEDIUM_NOT_VALID
MobilityboxError.TARIFF_SETTINGS_NOT_VALID
- New Data Attributes:
- MobilityboxCoupon added:
var earliest_activation_start_datetime: String?
- MobilityboxCoupon added:
var latest_activation_start_datetime: String?
- MobilityboxSubscriptionCycle added:
val product_id: String?
- MobilityboxCoupon added:
- New
MobilityboxProductCode
: to fetch aMobilityboxProduct
MobilityboxIdentificationFragment
used for Reactivation: you can pass aMobilityboxTicket
to the identification fragment. You can also useMobilityboxBottomSheetFragment
and pass a coupon and ticket to open a BottomSheet with the Identification View which reactivates the ticket. The identification view will then use the ticketscoupon_reactivation_key
and the entered user data to reactivate the ticket. (Like Option 3 in 2.)
Example Implementations:
- check reactivation error codes
import com.vesputi.mobilitybox_ticketing_android.*
fun reactivateTicket(ticket: MobilityboxTicket) {
ticket.reactivate({ reactivatedTicketCode ->
// implement your normal reactivation success
}) { error ->
if (error == MobilityboxError.IDENTIFICATION_MEDIUM_NOT_VALID || error == MobilityboxError.TARIFF_SETTINGS_NOT_VALID) {
// ==> her implement function to open popup or some kind of notification for the user stating out that it was not possible to reactivate the ticket and the user has to enter more information
}
}
}
- ticket reactivation failed alert example (open identification view with passed ticket):
import com.vesputi.mobilitybox_ticketing_android.*
class TicketReactivationFailedAlertBottomSheet : BottomSheetDialogFragment() {
var ticket: MobilityboxTicket? = null
private var coupon: MobilityboxCoupon? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
ticket = it.get("ticket") as MobilityboxTicket?
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(
R.layout.fragment_ticket_reactivation_failed_alert_bottom_sheet,
container,
false
)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
initTexts(view)
initReactivationButton(view)
val closeSheetButton = view.findViewById<ImageView>(R.id.closeSheetButton)
closeSheetButton.setOnClickListener {
activity?.supportFragmentManager?.setFragmentResult("closeReactivationFailedAlert", bundleOf())
dismiss()
}
}
override fun onDestroyView() {
super.onDestroyView()
activity?.supportFragmentManager?.setFragmentResult("closeReactivationFailedAlert", bundleOf())
}
private fun initTexts(view: View) {
// add some texts with description thats not possible to reactivate the coupon
}
private fun initReactivationButton(view: View) {
val ticketReactivationAlertContentContainer = view.findViewById<View>(R.id.ticket_reactivation_alert_content)
if (this.ticket != null) {
fetchCoupon(view)
ticketReactivationAlertContentContainer.visibility = View.VISIBLE
val reactivateTicketButton = ticketReactivationAlertContentContainer.findViewById<RelativeLayout>(R.id.reactivateTicketButton)
// edit Button
this.activity?.runOnUiThread(updateReactivateButtonViewRunnable)
reactivateTicketButton?.setOnClickListener {
openIdentificationViewBottomSheet()
}
} else {
dismiss()
}
}
private var updateReactivateButtonViewRunnable = Runnable {
val reactivateTicketButton = this.view?.findViewById<RelativeLayout>(R.id.reactivateTicketButton)
val loadingReactivateButton = this.view?.findViewById<ProgressBar>(R.id.loadingReactivateButton)
if (this.coupon == null) {
reactivateTicketButton?.visibility = View.GONE
loadingReactivateButton?.visibility = View.VISIBLE
} else {
reactivateTicketButton?.visibility = View.VISIBLE
loadingReactivateButton?.visibility = View.GONE
}
}
private fun fetchCoupon(view: View) {
if (this.ticket?.coupon_id != null && this.coupon == null) {
val couponCode = MobilityboxCouponCode(this.ticketElement!!.ticket!!.coupon_id)
couponCode.fetchCoupon({
this.coupon = it
this.activity?.runOnUiThread(updateReactivateButtonViewRunnable)
}) {
// handle error
}
}
}
private fun openIdentificationViewBottomSheet() {
var identificationViewBottomSheet = MobilityboxBottomSheetFragment.newInstance(this.coupon!!, this.ticketElement!!.ticket!!, this.ticket!!.id!!)
identificationViewBottomSheet.show(childFragmentManager, "Identification View")
}
companion object {
@JvmStatic
fun newInstance(ticket: MobilityboxTicket) =
TicketReactivationFailedAlertBottomSheet().apply {
arguments = Bundle().apply {
putParcelable("ticket", ticket)
}
}
}
}
7. Summary of Changes
- New Product: A new Deutschlandticket product introduced with the updated pricing.
- Data Migration: Users on some of the old product variants will need to have their data migrated
- API Changes: A new API version (
v7
) is introduced, with endpoints that support the new product structure. - SDK Updates: Both iOS and Android SDKs received some updates to handle API v7 changes.