We’ve migrated our documentation to a new site, which means some URLs have changed. If you hit a broken link, submit a support ticket.
Subscriptions

Datatrans

datatrans-logo.jpg

Datatrans Payments

More than 4,500 customers, 126 million transactions in the last year make Datatrans one of Europe’s leading payment service providers. Whether you have a big global project or are an online start-up, you can expect custom solutions and the reliability of a highly successful Swiss enterprise.

Merchant Requirements

Datatrans is available for merchants in the DACH region.

More information about the required transaction volume and pricing is available under the following link.

Limitations

Following the announcement from Datatrans about security and API changes, we’ve prepared the following instructions for your compliance. There are a few changes required in your Datatrans and Piano dashboards. For more details please see this guide.

This only impacts your Credit card and Postfinance methods - as these are using XML API and the security change requires you to enable HMAC signage. If you need more time to enable HMAC, you can reach out to your Datatrans rep to whitelist your app.

The Twint payment method requires a Twint voucher, which must be linked to a Swiss bank account and therefore supports only payments in CHF.

You cannot remove the postal code field from checkout for credit card payments as it's required by the payment provider itself.

Datatrans currently does not support Apple Pay in third-party browsers. To complete a purchase using Apple Pay, the user must be on a device with Apple Pay properly configured and must use Safari. When testing in other browsers, the Apple Pay button may not appear. For more details, see the Datatrans documentation.

Template Changes

Datatrans 3DS Billing Address Fields

Three new optional fields can be added to the Datatrans Credit Card checkout form to improve 3DS authentication data quality and increase the likelihood of frictionless authentication for your subscribers

New toggles in Datatrans Credit Card settings (Edit Business → Payment providers → Datatrans Credit Card):

  • Billing address (street)billAddrLine1

  • Billing citybillAddrCity

  • Billing countrybillAddrCountry

All default to disabled. ZIP code is a separate existing field and is not part of this update.

These fields apply to Checkout, My Account Wallet, and ATW / Subscribe User (initial transactions only; recurring renewals are not affected).

Important — template update required first

If you are using customized Payment Components templates, you must update your templates before enabling the toggles. Enabling the toggles without updating the template will cause validation errors that block purchases.

Step 1 — Update templates (required for customized templates)

  • Checkout: Add the below code for the billing address field rows inside <table class="table-form-inline"> after the existing card fields in the Payment Components template.

    HTML
    <tr ng-show="cc.collectBillingAddress && !input.billingAddressEnabled">
      <td colspan="4" class="field-card-billing-address">
        <div class="field-wrapper">
          <label class="icon road"></label>
          <div class="input">
            <input class="flat" type="text" name="billAddrLine1" required
                   placeholder="{{'Billing address' | tc:'checkout.platform'}}" ng-model="credit_card.billAddrLine1"/>
          </div>
        </div>
        <span class="error-message" ng-if="ccForm.billAddrLine1.$dirty && ccForm.billAddrLine1.$invalid">
          <t>Billing address is required.</t>
        </span>
      </td>
    </tr>
    <tr ng-show="cc.collectBillingCity && !input.billingAddressEnabled">
      <td colspan="4" class="field-card-billing-city">
        <div class="field-wrapper">
          <label class="icon city"></label>
          <div class="input">
            <input class="flat" type="text" name="billAddrCity" required
                   placeholder="{{'Billing city' | tc:'checkout.platform'}}" ng-model="credit_card.billAddrCity"/>
          </div>
        </div>
        <span class="error-message" ng-if="ccForm.billAddrCity.$dirty && ccForm.billAddrCity.$invalid">
          <t>Billing city is required.</t>
        </span>
      </td>
    </tr>
    <tr ng-if="cc.showBillingCountryDropdown">
      <td colspan="4" class="field-card-billing-country">
        <div class="field-wrapper">
          <label class="icon country"></label>
          <div class="input">
            <div class="tp-dropdown__select billing-country-drop-down"
                 ng-class="{'placeholder-shown': !credit_card.billAddrCountry}"
                 tp-country-selector
                 selector-options="datatransBillingCountrySelectorOptions"
                 default-country="datatransDefaultBillingCountry"
                 click-on-select="selectDatatransBillingCountry"
                 side="'bottom'"
                 country-type="'datatransBillingCountry'"
                 selector-label="{{'Billing country' | tc:'checkout.platform'}}"
                 dropdown-styles="{width: 230, zIndex: 1004, margin: 0}"
                 tabindex="0"
                 role="combobox"
                 aria-label="{{'Billing country' | tc:'checkout.platform'}}">
            </div>
          </div>
        </div>
      </td>
    </tr>
    
  • My Account Wallet: Update the My Account Components template as follows; locate the Datatrans card table and find the Cardholder name row (the <tr> whose input has aria-label="Cardholder name"). Immediately after, insert the following:

    HTML
    <tr class="table-row" ng-if="datatransCollectBillingAddress && !fullBillingAddressEnabled">
      <td class="table-cell" colspan="4">
        <label class="icon billing-address" aria-hidden="true"></label>
        <input type="text"
               ng-model="cardset.billAddrLine1"
               placeholder="{{'Billing address' | tc:'checkout.platform'}}"
               name="billAddrLine1"
               ng-required="datatransCollectBillingAddress && !fullBillingAddressEnabled"
               ng-readonly="readonly === true"
               aria-label="{{'Billing address' | tc:'checkout.platform'}}" />
      </td>
    </tr>
    <tr class="table-row" ng-if="datatransCollectBillingCity && !fullBillingAddressEnabled">
      <td class="table-cell" colspan="4">
        <label class="icon city" aria-hidden="true"></label>
        <input type="text"
               ng-model="cardset.billAddrCity"
               placeholder="{{'Billing city' | tc:'checkout.platform'}}"
               name="billAddrCity"
               ng-required="datatransCollectBillingCity && !fullBillingAddressEnabled"
               ng-readonly="readonly === true"
               aria-label="{{'Billing city' | tc:'checkout.platform'}}" />
      </td>
    </tr>
    <tr class="table-row" ng-if="datatransCollectBillingCountry && !fullBillingAddressEnabled && !isTaxBillingCountryEnabled">
      <td class="table-cell" colspan="4">
        <label class="icon country" aria-hidden="true"></label>
        <div
                class="tp-dropdown__select billing-country-drop-down datatrans-billing-country"
                ng-class="{'placeholder-shown': !cardset.billAddrCountry}"
                tp-country-selector
                selector-options="datatransBillingCountrySelectorOptions"
                default-country="datatransDefaultBillingCountry"
                click-on-select="selectDatatransBillingCountry"
                side="'bottom'"
                country-type="'datatransBillingCountry'"
                selector-label="{{'Billing country' | tc:'checkout.platform'}}"
                dropdown-styles="{width: 230, zIndex: 1004, margin: 0}"
                tabindex="0"
                role="combobox"
                aria-label="{{'Billing country' | tc:'checkout.platform'}}">
        </div>
      </td>
    </tr>
    

Step 2 — Enable toggles in Edit Business → Payment providers → Datatrans Credit Card settings

Country selector note: The billing country field uses Piano's standard country selector component (tp-country-selector). To pre-select a country (e.g. Switzerland or Germany), customize the component in your template. Localization is supported via standard Piano localization strings.

Postal code note: The postal code field is already shown and required by default, in case you previously disabled it in your customized template, re-enabling it alongside these new fields will provide the most complete billing address signal for 3DS.

Cardholder name

Before enabling the collection of the cardholder's name for card payments in the configuration, ensure that your system templates are updated accordingly.

There are numerous ways to update your system template to fetch the latest functionality:

  1. Reset to default (easiest, but depending on your level of customization,n you may need to decide if feasible)

  2. Use the Compare to default feature in the System template editor, and carry new lines of code over to your version

  3. Follow the instructions below to implement the new lines of code in your system template

Go to Manage → Templates → System → Payment Components, and find the component <script type="text/ng-template" id="/widget/checkout/component/partials/datatrans-form-component.html">. Inside this component find <table class="table-form-inline" cellpadding="0" cellspacing="0"> and add after the last</tr>:

HTML
<tr ng-show="cc.collectCardholderName">
  <td class="field-card-cardholder-name" colspan="4">
    <div class="field-wrapper">
      <label class="icon cardholder"></label>
      <div class="input">
        <input class="flat" type="text" name="cardCardholderName" required
                maxlength="50"
                alphanumeric
                placeholder="{{'Cardholder name' | tc:'checkout.platform'}}" ng-model="credit_card.cardholderName"/>
      </div>
    </div>
  </td>
</tr>

Full piece of code after changing:

HTML
<table class="table-form-inline" cellpadding="0" cellspacing="0">
    <tr>
        <td class="field-card-number">
            <div class="field-wrapper">
                <label class="icon card"></label>
                <div id="datatrans-card-number" class="datatrans-card-number input" placeholder="{{'Credit card' | t}}"></div>
            </div>
        </td>
        <td class="field-card-expiry-month">
            <div class="field-wrapper">
                <label class="icon expiry"></label>
                <div class="input whitespace-nowrap">
                    <input class="flat inline input-date" type="text" name="expire_month" placeholder="{{'MM' | t}}"
                            maxlength="2" ng-model="credit_card.expire_month" required/>
                    <div class="date-slash">/</div>
                    <input class="flat inline input-date" type="text" name="expire_year" placeholder="{{'YY' | t}}"
                            maxlength="2" ng-model="credit_card.expire_year" required/>
                </div>
            </div>
        </td>
        <td class="field-card-cvv">
            <div class="field-wrapper">
                <label class="icon password"></label>
                <div id="datatrans-cvv" class="datatrans-cvv input" placeholder="{{'CVV' | t}}"></div>
            </div>
        </td>
        <td class="field-card-postal-code">
            <div class="field-wrapper">
                <div class="input ml10">
                    <input class="flat" type="text" name="cardZipCode" required
                            placeholder="{{'ZIP / Postal code' | tc:'checkout.platform'}}" ng-model="credit_card.cardZipCode"/>
                </div>
            </div>
        </td>
    </tr>
    <tr ng-show="cc.collectCardholderName">
      <td class="field-card-cardholder-name" colspan="4">
        <div class="field-wrapper">
          <label class="icon cardholder"></label>
          <div class="input">
            <input class="flat" type="text" name="cardCardholderName" required                        
                    maxlength="50"
                    alphanumeric
                    placeholder="{{'Cardholder name' | tc:'checkout.platform'}}" ng-model="credit_card.cardholderName"/>
          </div>
        </div>
      </td>
    </tr>
</table>

Controlling Alias Creation for Datatrans Payments

By default, Datatrans always creates an alias when a user completes a payment. If you'd prefer that an alias is only created when the user actively chooses to save their payment details, contact Piano Support to adjust this behavior. Once that's been set up, you'll also need to update the Payment Components template — with a separate change for Datatrans PayPal and Datatrans Twint.

Datatrans PayPal

Go to ManageTemplatesPayment Components and find the component <script type="text/ng-template" id="/frontend/providers/components/datatranspaypal/checkout/component.shtml">.

Inside find:

HTML
    <div class="manage-payment-method-wrapper" ng-show="state.payWithNew">
      <div ng-if="!isSubscription()">
        <div class="custom-checkbox">
          <input type="checkbox" name="store_in_vault" ng-model="data.store_in_vault"
                 id="remember-my-account-datatrans-paypal"/>
          <label for="remember-my-account-datatrans-paypal">
            <t>Remember my account for future payments</t>
          </label>
        </div>
      </div>
      <div ng-if="isSubscription()">
        <input type="hidden" name="store_in_vault" ng-model="data.store_in_vault"/>
      </div>
      <div class="custom-checkbox" ng-if="data.store_in_vault">
        <input type="checkbox" name="defaultPaymentMethodId" ng-model="data.defaultPaymentMethodId"
               id="default-payment-method-datatrans-paypal"/>
        <label for="default-payment-method-datatrans-paypal">
          <t>Apply this payment method to all active subscriptions</t>
        </label>
      </div>
    </div>

And replace with the following:

HTML
    <div class="manage-payment-method-wrapper" ng-show="state.payWithNew">
      <div ng-if="!isSubscription()">
        <div class="custom-checkbox">
          <input type="checkbox" name="store_in_vault" ng-model="data.store_in_vault"
                 ng-disabled="state.storeInVaultLocked"
                 id="remember-my-account-datatrans-paypal"/>
          <label for="remember-my-account-datatrans-paypal">
            <t>Remember my account for future payments</t>
          </label>
        </div>
      </div>
      <div ng-if="isSubscription()">
        <input type="hidden" name="store_in_vault" ng-model="data.store_in_vault"/>
      </div>
      <div class="custom-checkbox" ng-if="data.store_in_vault">
        <input type="checkbox" name="defaultPaymentMethodId" ng-model="data.defaultPaymentMethodId"
               ng-disabled="state.storeInVaultLocked"
               id="default-payment-method-datatrans-paypal"/>
        <label for="default-payment-method-datatrans-paypal">
          <t>Apply this payment method to all active subscriptions</t>
        </label>
      </div>
    </div>

Datatrans Twint

Go to ManageTemplatesPayment Components and find the component <script type="text/ng-template" id="/frontend/providers/components/datatranstwint/checkout/component.shtml">.

Inside find:

HTML
<div class="manage-payment-method-wrapper" ng-show="isNewPaymentScreenShown()">
    <div ng-if="!isSubscription()">
        <div class="custom-checkbox">
            <input type="checkbox" name="store_in_vault" ng-model="data.store_in_vault"
                   id="remember-my-card-twint"/>
            <label for="remember-my-card-twint">
                <t>Remember my card for future payments</t>
            </label>
        </div>
    </div>
    <div ng-if="isSubscription()">
      <input type="hidden" name="store_in_vault" ng-model="data.store_in_vault"/>
    </div>
    <div class="custom-checkbox" ng-if="data.store_in_vault">
      <input type="checkbox" name="isDefaultPaymentMethodApplied" ng-model="data.isDefaultPaymentMethodApplied"
             id="default-payment-method-twint"/>
      <label for="default-payment-method-twint">
        <t>Apply this payment method to all active subscriptions</t>
      </label>
    </div>
</div>

And replace with the following:

HTML
<div class="manage-payment-method-wrapper" ng-show="isNewPaymentScreenShown()">
    <div ng-if="!isSubscription()">
        <div class="custom-checkbox">
            <input type="checkbox" name="store_in_vault" ng-model="data.store_in_vault"
                   ng-disabled="state.storeInVaultLocked"
                   id="remember-my-card-twint"/>
            <label for="remember-my-card-twint">
                <t>Remember my card for future payments</t>
            </label>
        </div>
    </div>
    <div ng-if="isSubscription()">
      <input type="hidden" name="store_in_vault" ng-model="data.store_in_vault"/>
    </div>
    <div class="custom-checkbox" ng-if="data.store_in_vault">
      <input type="checkbox" name="isDefaultPaymentMethodApplied" ng-model="data.isDefaultPaymentMethodApplied"
             ng-disabled="state.storeInVaultLocked"
             id="default-payment-method-twint"/>
      <label for="default-payment-method-twint">
        <t>Apply this payment method to all active subscriptions</t>
      </label>
    </div>
</div>

Payment Components

If you plan to offer Datatrans-supported payment methods beyond credit card, you must add the code below to the Payment Components template, found under ManageTemplates in the Piano dashboard.

The exact code differs by payment method and applies to:

Apple Pay

HTML
<script type="text/ng-template" id="/frontend/providers/components/datatransapplepay/checkout/component.shtml">
<div class="datatransapplepay-wrapper new-card-form">
    <div class="row">
        <div class="applepay-checkout-payment-form" ng-class="{'applepay-checkout-payment-form--custom-indent': isIndentRequired}">
            <div ng-show="!state.addingNewCard">
                <div class="add-credit-card-wrapper">
                    <h4>
                        <t>Your Apple Pay cards</t>
                    </h4>
                    <a href="javascript:void(0)"
                       class="add-credit-card"
                       data-e2e="gotoNewCardScreen"
                       ng-click="gotoNewCardScreen()"
                       ng-show="stored.length > 0 &amp;&amp; !data.newUpi">
                        +
                        <t>Add a card</t>
                    </a>
                </div>
                <table class="applepay-table">
                    <tr ng-if="stored.length" class="applepay-table-row" ng-repeat="upi in stored">
                        <td class="applepay-table-cell applepay-table-cell--radio">
                            <input type="radio"
                                   id="radio-{{upi.id}}"
                                   name="stored_method"
                                   ng-model="$parent.data.selectedUpi"
                                   ng-value="upi"
                                   radio-track-by="id"
                                   class="applepay-radio"/>
                        </td>
                        <td class="applepay-table-cell applepay-table-cell--logo">
                            <div class="applepay-short-logo"></div>
                        </td>
                        <td class="applepay-table-cell" >
                            <label for="radio-{{upi.id}}">
                                <t context="checkout.platform">{{upi.display_name}}</t>
                            </label>
                        </td>
                        <td class="applepay-table-cell payment-method-card-cell-expiration">
                            <t>Expires in</t>
                            {{upi.expire_month}}/{{upi.expire_year}}
                        </td>
                        <td class="payment-method-card-cell payment-method-card-cell--default-payment">
                            <div class="payment-method-card-default-payment"
                                 ng-show="upi.id === data.defaultPaymentMethodId">
                                <t>Default</t>
                            </div>
                            <button ng-hide="upi.id === data.defaultPaymentMethodId"
                                    ng-click="setAsDefault(upi)"
                                    type="button"
                                    class="set-as-default-button"
                            >
                                <t>Set as default</t>
                            </button>
                        </td>
                    </tr>
                </table>
            </div>
            <div class="applepay-add-new-account" ng-show="state.addingNewCard">
                <div desktop class="go-back-to-cards-wrapper">
                    <a href="javascript:void(0)" ng-show="canGoToStored()"
                       data-e2e="gotoStoredCardsScreen"
                       class="go-back-to-cards"
                       ng-click="gotoStoredCardsScreen()">
                        <t>Return to saved cards</t>
                    </a>
                    <h4>
                        <t>Add a new Apple Pay card</t>
                    </h4>
                </div>
                <div ng-if="!data.newUpi" class="datatrans-applepay-new-card-wrapper">
                    <table class="applepay-table">
                        <tr class="applepay-table-row">
                            <td class="applepay-table-cell" >
                                <div class="hint">
                                    <div class="appleplay-hint-container">
                                        <t>Add Apple Pay as a payment account to pay for your purchases and take the protection of Datatrans with you</t>
                                    </div>
                                </div>
                            </td>
 <td class="applepay-table-cell">
                                <div class="applepay-new-card-button-container">
                                    <button id="applepay-pay-button"
                                            ng-show="isDatatransApplePayEnabled"
                                            class="pm-login-button applepay-new-card-button"
                                            data-e2e="applepay-new-card-button"
                                            ng-click="onApplePayButtonClicked()">
                                    </button>
                                </div>
                            </td>
                        </tr>
                    </table>
                </div>
                <table ng-if="data.newUpi" class="applepay-table">
                    <tr class="applepay-table-row">
                        <td class="applepay-table-cell applepay-table-cell--radio">
                            <input type="radio"
                                   id="radio-datatrans-applepay-new"
                                   name="new_method"
                                   ng-model="data.selectedUpi"
                                   ng-value="data.newUpi"
                                   class="applepay-radio"/>
                        </td>
                        <td class="applepay-table-cell applepay-table-cell--logo">
                            <div class="applepay-short-logo"></div>
                        </td>
                        <td class="applepay-table-cell" >
                            <label for="radio-{{upi.id}}">
                                <t context="checkout.platform">{{data.newUpi.display_name}}</t>
                            </label>
                        </td>
                        <td class="applepay-table-cell">
                            <div class="applepay-new-card">
                                <div ng-click="cancelNewCard()" class="applepay-cancel-button">
                                    <t>Cancel</t>
                                </div>
                            </div>
                        </td>
                    </tr>
                </table>
                <div class="hint" ng-if="data.newUpi">
                    <div class="appleplay-hint-container">
                        <t>Your payment verified. Click on the button below to proceed.</t>
                    </div>
                </div>
            </div>
        </div>

        <div class="manage-payment-method-wrapper" ng-show="state.addingNewCard">
            <div class="payment-system-condition">
                <div ng-if="!isSubscription() &amp;&amp; isPaymentTerm()">
                    <div class="custom-checkbox">
                        <input type="checkbox" name="store_in_vault" ng-model="data.store_in_vault"
                               id="datatrans-applepay-remember-my-card"/>
                        <label for="datatrans-applepay-remember-my-card">
                            <t>Remember my card for future payments</t>
                        </label>
                    </div>
                </div>
                <div ng-if="isSubscription()">
                    <input type="hidden" name="store_in_vault" ng-model="data.store_in_vault"/>
                </div>
                <div class="custom-checkbox" ng-if="data.store_in_vault">
                    <input type="checkbox" name="defaultPaymentMethodId" ng-model="data.shouldMakeDefaultPaymentMethod"
                           id="datatrans-apple-pay-default-payment-method"/>
                    <label for="datatrans-apple-pay-default-payment-method">
                        <t>Apply this payment method to all active subscriptions</t>
                    </label>
                </div>
            </div>
        </div>

        <div consents-list></div>
        <div class="footer-modal" ng-show="canContinue()">
            <div class="auto-renew-purchase" ng-show="isSelectedTermAutoRenewChoiceAvailable()">
                <input class="auto-renew-purchase-checkbox" type="checkbox" ng-model="input.autoRenew"
                       id="auto_renew"/>
                <label class="auto-renew-purchase-label" for="auto_renew">
                    <t>Automatically renew my subscription when the time comes.</t>
                </label>
            </div>
            <div ng-show="isConfirmStepEnabled()" class="complete-purchase-button"
                 data-e2e="applepay-complete-purchase-button" goto-confirmation-button
                 title="{{'Complete Purchase' | t}}"></div>
            <div ng-show="!isConfirmStepEnabled()" class="complete-purchase-button"
                 data-e2e="applepay-complete-purchase-button" complete-purchase-button
                 title="{{'Complete Purchase' | t}}"></div>
        </div>
    </div>
</div>

</script>

Google Pay

HTML
<script type="text/ng-template" id="/frontend/providers/components/datatransgooglepay/checkout/component.shtml">
  
  <div id="datatrans-google-pay-options" class="hidden"></div>

  <div class="datatransgooglepay-wrapper new-card-form">
    <div class="row">
      <div class="googlepay-checkout-payment-form" ng-class="{'googlepay-checkout-payment-form--custom-indent': isIndentRequired}">

        
        <div ng-show="isStoredPaymentsScreenShown()">
          <div class="add-credit-card-wrapper">
            <h4><t>Your Google Pay cards</t></h4>
            <a href="javascript:void(0)" class="add-credit-card" ng-click="gotoNewPaymentScreen()" ng-show="stored.length > 0">
              + <t>Add a new card</t>
            </a>
          </div>

          <table ng-if="stored.length > 0" class="googlepay-table">
            <tr class="googlepay-table-row" ng-repeat="upi in stored">
              <td class="googlepay-table-cell googlepay-table-cell--radio">
                <input
                  type="radio"
                  id="radio-{{upi.id}}"
                  name="stored_method"
                  ng-model="data.selectedUpi"
                  ng-change="onUpiChange(upi)"
                  ng-value="upi"
                  class="googlepay-radio"
                />
              </td>
              <td class="googlepay-table-cell googlepay-table-cell--short-logo">
                <div class="datatrans-gpay-short-logo"></div>
              </td>
              <td class="googlepay-table-cell">
                <label for="radio-{{upi.id}}">
                  <t context="checkout.platform">{{upi.display_name}}</t>
                </label>
              </td>
              <td class="googlepay-table-cell payment-method-card-cell-expiration">
                <t>Expires in</t> {{upi.expire_month}}/{{upi.expire_year}}
              </td>
              <td class="payment-method-card-cell payment-method-card-cell--default-payment">
                <div class="payment-method-card-default-payment" ng-show="upi.id === data.defaultPaymentMethodId">
                  <t>Default</t>
                </div>
                <button
                  ng-hide="upi.id === data.defaultPaymentMethodId"
                  ng-click="setAsDefault(upi)"
                  type="button"
                  class="set-as-default-button"
                >
                  <t>Set as default</t>
                </button>
              </td>
            </tr>
          </table>
        </div>

        
        <div ng-show="isNewPaymentScreenShown()" class="googlepay-add-new-account">
          <div desktop class="go-back-to-cards-wrapper">
            <a href="javascript:void(0)" class="go-back-to-cards" ng-click="gotoStoredPaymentsScreen()" ng-show="isReturnToStoredPaymentsShown()">
              <t>Return to saved cards</t>
            </a>
            <h4><t>Add a new Google Pay card</t></h4>
          </div>

          <div ng-if="!data.newUpi" class="datatrans-gpay-account-wrapper">
            <div class="hint">
              <div class="googleplay-hint-container">
                <t>
                  Add Google Pay as a payment account to pay for your purchases and take the protection of Datatrans with you
                </t>
              </div>
            </div>

            <div class="gpay-container-wrapper">
              <div ng-show="isDtGpayLoading" id="dt-gpay-spinner-wrapper" class="spinner-wrapper">
                <div pn-spinner size="small"></div>
              </div>
              <div
                ng-show="!isDtGpayLoading"
                id="googlepay-pay-button"
                class="pm-login-button googlepay-login-button-wrapper"
                data-e2e="googlepay-login-button">
              </div>
            </div>
          </div>

          <table ng-if="data.newUpi" class="googlepay-table">
            <tr class="googlepay-table-row">
              <td class="googlepay-table-cell googlepay-table-cell--radio">
                <input
                  type="radio"
                  id="gpay-new-payment-method"
                  class="googlepay-radio"
                  name="stored_method"
                  ng-model="data.selectedUpi"
                  ng-value="data.newUpi"
                />
              </td>
              <td class="googlepay-table-cell googlepay-table-cell--short-logo">
                <div class="datatrans-gpay-short-logo"></div>
              </td>
              <td class="googlepay-table-cell">
                <label for="gpay-new-payment-method">
                  <t context="checkout.platform">{{data.newUpi.display_name}}</t>
                </label>
              </td>
              <td class="googlepay-table-cell">
                <div class="googlepay-logged-in">
                  <div ng-click="cancelAndInitNewAccount()" class="googlepay-cancel-button">
                    <t>Cancel</t>
                  </div>
                </div>
              </td>
            </tr>
          </table>
        </div>
      </div>

      
      <div class="manage-payment-method-wrapper" ng-show="state.payWithNew">
        <div ng-if="!isSubscription() &amp;&amp; isPaymentTerm()">
          <div class="custom-checkbox">
            <input type="checkbox" name="store_in_vault" ng-model="data.store_in_vault" id="remember-my-card-datatrans-gpay" />
            <label for="remember-my-card-datatrans-gpay">
              <t>Remember my card for future payments</t>
            </label>
          </div>
        </div>

        <div ng-if="isSubscription()">
          <input type="hidden" name="store_in_vault" ng-model="data.store_in_vault" />
        </div>

        <div class="custom-checkbox" ng-if="data.store_in_vault">
          <input type="checkbox" name="defaultPaymentMethodId" ng-model="data.defaultPaymentMethodId" id="default-payment-method-cybersource" />
          <label for="default-payment-method-cybersource">
            <t>Apply this payment method to all active subscriptions</t>
          </label>
        </div>
      </div>

      
      <div consents-list></div>

      <div class="footer-modal" ng-show="canContinue()">
        <div class="auto-renew-purchase" ng-show="isSelectedTermAutoRenewChoiceAvailable()">
          <input class="auto-renew-purchase-checkbox" type="checkbox" ng-model="input.autoRenew" id="auto_renew" />
          <label class="auto-renew-purchase-label" for="auto_renew">
            <t>Automatically renew my subscription when the time comes.</t>
          </label>
        </div>

        <div ng-show="isConfirmStepEnabled()" class="complete-purchase-button" data-e2e="googlepay-complete-purchase-button" goto-confirmation-button title="{{'Complete Purchase' | t}}"></div>

        <div ng-show="!isConfirmStepEnabled()" class="complete-purchase-button" data-e2e="googlepay-complete-purchase-button" complete-purchase-button title="{{'Complete Purchase' | t}}"></div>
      </div>
    </div>
  </div>
</script>

PayPal

HTML
<script type="text/ng-template" id="/frontend/providers/components/datatranspaypal/checkout/component.shtml">
<div class="datatranspaypal-wrapper new-card-form">
  <div class="row">

    <div ng-show="isStoredPaymentsScreenShown()" class="paypal-checkout-payment-form">
      <div class="add-credit-card-wrapper">
        <h4>
          <t context="checkout.platform">Your PayPal accounts</t>
        </h4>
        <a href="javascript:void(0)" class="add-credit-card" ng-click="gotoNewPaymentScreen()"
           ng-show="stored.length > 0">
          +
          <t>Add a new account</t>
        </a>
      </div>
      <table ng-if="stored.length" class="paypal-table">
        <tr class="paypal-table-row" ng-repeat="upi in stored">
          <td class="paypal-table-cell--radio">
            <input type="radio"
                   id="radio-{{upi.id}}"
                   name="stored_method"
                   ng-model="data.selectedUpi"
                   ng-value="upi"
                   ng-change="onUpiChange(upi)"
                   radio-track-by="id"
                   class="paypal-radio"/>
          </td>
          <td class="paypal-table-cell-logo">
            <div class="datatrans-paypal-short-logo"></div>
          </td>
          <td class="paypal-table-cell-email">
            <label for="radio-{{upi.id}}">
              {{upi.email}}
            </label>
          </td>
          <td class="payment-method-card-cell payment-method-card-cell--default-payment">
            <div class="payment-method-card-default-payment"
                 ng-show="upi.id === data.defaultPaymentMethodId">
              <t>Default</t>
            </div>
            <button ng-hide="upi.id === data.defaultPaymentMethodId"
                    ng-click="setAsDefault(upi)"
                    type="button"
                    class="set-as-default-button"
            >
              <t>Set as default</t>
            </button>
          </td>
        </tr>
      </table>
    </div>

    <div ng-show="isNewPaymentScreenShown()" class="paypal-checkout-payment-form">
      <div class="go-back-to-cards-wrapper">
        <h4>
          <t>Add a new PayPal account</t>
        </h4>
        <a href="javascript:void(0)" class="go-back-to-cards" ng-click="gotoStoredPaymentsScreen()"
           ng-show="isReturnToStoredPaymentsShown()">
          <t>Return to saved accounts</t>
        </a>
      </div>

      <div ng-if="!data.newUpi" class="datatrans-paypal-account-wrapper">
        <div class="hint">
          <t>Please, log in first.</t>
        </div>
        <div id="paypal-pay-button"
             class="pm-login-button paypal-login-button"
             ng-click="startWithNewAccount()"
             data-e2e="paypal-login-button">
        </div>
      </div>

      <table ng-if="data.newUpi" class="paypal-table">
        <tr class="paypal-table-row">
          <td class="paypal-table-cell--radio">
            <input type="radio"
                   id="paypal-new-payment-method"
                   name="stored_method"
                   ng-model="data.selectedUpi"
                   ng-value="data.newUpi"
                   radio-track-by="id"
                   class="paypal-radio"/>
          </td>
          <td class="paypal-table-cell-logo">
            <div class="datatrans-paypal-short-logo"></div>
          </td>
          <td class="paypal-table-cell-email">
            <label for="paypal-new-payment-method">
              {{data.newUpi.email}}
            </label>
          </td>
          <td class="paypal-table-cell-cancel">
            <div class="paypal-logged-in">
              <div ng-click="cancelNewAccount()" class="paypal-cancel-button">
                <t>Cancel</t>
              </div>
            </div>
          </td>
        </tr>
      </table>
    </div>

    <div class="manage-payment-method-wrapper" ng-show="state.payWithNew">
      <div ng-if="!isSubscription() &amp;&amp; isPaymentTerm()">
        <div class="custom-checkbox">
          <input type="checkbox" name="store_in_vault" ng-model="data.store_in_vault"
                 id="remember-my-account-datatrans-paypal"/>
          <label for="remember-my-account-datatrans-paypal">
            <t>Remember my account for future payments</t>
          </label>
        </div>
      </div>
      <div ng-if="isSubscription()">
        <input type="hidden" name="store_in_vault" ng-model="data.store_in_vault"/>
      </div>
      <div class="custom-checkbox" ng-if="data.store_in_vault">
        <input type="checkbox" name="defaultPaymentMethodId" ng-model="data.defaultPaymentMethodId"
               id="default-payment-method-datatrans-paypal"/>
        <label for="default-payment-method-datatrans-paypal">
          <t>Apply this payment method to all active subscriptions</t>
        </label>
      </div>
    </div>

    <div consents-list></div>
    <div class="footer-modal" ng-show="canContinue()">
      <div class="auto-renew-purchase" ng-show="isSelectedTermAutoRenewChoiceAvailable()">
        <input class="auto-renew-purchase-checkbox" type="checkbox" ng-model="input.autoRenew"
               id="auto_renew"/>
        <label class="auto-renew-purchase-label" for="auto_renew">
          <t>Automatically renew my subscription when the time comes.</t>
        </label>
      </div>
      <div ng-show="isConfirmStepEnabled()" class="complete-purchase-button"
           data-e2e="paypal-complete-purchase-button" goto-confirmation-button
           title="{{'Complete Purchase' | t}}"></div>
      <div ng-show="!isConfirmStepEnabled()" class="complete-purchase-button"
           data-e2e="paypal-complete-purchase-button" complete-purchase-button
           title="{{'Complete Purchase' | t}}"></div>
    </div>
  </div>
</div>


</script>

Twint

Go to ManageTemplatesSystemPayment Components, find <script type="text/ng-template" id="/frontend/providers/components/datatranstwint/checkout/component.shtml">, and inside this component find the string <div class="footer-modal" ng-show="stored.length > 0"> and replace its content with the following code:

HTML
<div class="footer-modal" ng-show="stored.length > 0">
  <div class="auto-renew-purchase" ng-show="isSelectedTermAutoRenewChoiceAvailable()">
    <input class="auto-renew-purchase-checkbox" type="checkbox" ng-model="input.autoRenew"
            id="auto_renew"/>
    <label class="auto-renew-purchase-label" for="auto_renew">
      <t>Automatically renew my subscription when the time comes.</t>
    </label>
  </div>
  <div>
    <div ng-show="(isConfirmAndCompletePurchaseButtonShown())" class="complete-purchase-button"
          data-e2e="twint-complete-purchase-button" goto-confirmation-button
          title="{{'Complete Purchase' | t}}"></div>
    <div ng-show="(isCompletePurchaseButtonShown())" class="complete-purchase-button"
          data-e2e="twint-complete-purchase-button" complete-purchase-button
          title="{{'Complete Purchase' | t}}"></div>
  </div>
</div>

My Account Wallet Components

Go to Manage → Templates → My account → My Account Wallet Components, and find the component <script type="text/ng-template" id="/widget/myaccount/partials/wallet/wallet_form_datatrans.shtml">. Inside this component find <table class="table-view datatrans-table"> and replace it to the following:

HTML
<table class="table-view datatrans-table">
                <tr class="table-row">
                    <td class="table-cell">
                        <input type="text"
                               ng-model="cardset.number"
                               placeholder="{{'Card number' | tc:'checkout.platform'}}"
                               name="number"
                               class="card-number"
                               ng-readonly="readonly"
                               ng-required="true"
                               ng-if="readonly === true"/>
                        <div id="datatrans-card-number" class="datatrans-card-number" placeholder="{{'Card number' | t}}" ng-show="readonly === false"></div>
                    </td>
                    <td class="table-cell">
                        <label class="icon expiry"></label>
                        <input
                                type="text"
                                ng-model="cardset.expiration"
                                name="expiration"
                                placeholder="{{'MM/YY' | tc:'checkout.platform'}}"
                                format-expiry-date
                                format-year="2"
                                ng-required="true"/>
                    </td>
                    <td class="table-cell" ng-show="readonly === false">
                        <div id="datatrans-cvv" class="datatrans-cvv" placeholder="{{'CVV' | t}}"></div>
                    </td>
                    <td class="table-cell">
                        <label class="icon postal-code"></label>
                        <input type="text"
                               ng-model="cardset.zipcode"
                               placeholder="{{'ZIP code' | tc:'checkout.platform'}}"
                               name="cardZipCode"
                               ng-required="true"/>
                    </td>
                </tr>
                <tr class="table-row" ng-show="cardset.datatransCollectCardholderName">
                  <td class="table-cell" colspan="4">
                    <label class="icon cardholder"></label>
                    <input type="text"
                           ng-model="cardset.cardholder"
                           placeholder="{{'Cardholder name' | tc:'checkout.platform'}}"
                           name="cardholderName"
                           class="cardholder-name"
                           maxlength="50"
                           alphanumeric
                           ng-readonly="readonly === true"
                           ng-required="true"/>
                  </td>
                </tr>
            </table>

Additionally, for Datatrans Google Pay, go to Manage → Templates → My account → My Account Wallet Components, find the string <script type="text/ng-template" id="/frontend/providers/components/datatransgooglepay/my_account/component.shtml"> and after this string insert the following code:

HTML

<div id="datatrans-google-pay-options" class="hidden"></div>

Next, locate:

HTML
<div class="google-pay-login-button-wrapper" id="googlepay-pay-button">
</div>

And replace with the following:

HTML
<div class="gpay-container-wrapper">
  <div ng-show="isDtGpayLoading"
       id="dt-gpay-spinner-wrapper"
       class="spinner-wrapper">
    <div pn-spinner size="small"></div>
  </div>
  <div ng-show="!isDtGpayLoading"
       id="googlepay-pay-button"
       class="pm-login-button googlepay-login-button-wrapper"
       data-e2e="googlepay-login-button">
  </div>
</div>

To apply a different Google Pay button style than the default, pass a value, as shown below:

HTML
<div id="datatrans-google-pay-options" class="hidden">
  {
    "buttonStyle": "black"
  }
</div>

Supported Countries

For this payment provider, Piano supports the following countries:

Supported Countries

 Austria

Germany

Switzerland

Please reach out to your Piano Account representative in case a country supported by this payment provider is not listed in the table above.

Supported Currency

For this payment provider, Piano supports the following currencies:

Supported Currencies

EUR

USD

CHF*

Note, that Piano may support only selected currencies available in the payment providers dashboard.

*Twint only supports CHF.

Supported Payment Methods

Piano supports the following payment methods for this payment provider:

Payment method name

Are renewals supported?

Credit card (Mastercard, Visa, American Express, Discover, JCB, Diner's Club)

Yes

Twint

Yes

Apple Pay

No*

Google Play

Yes

PayPal

Yes

*Datatrans is currently not able to support tokenized payments for this payment method.

For DataTrans credit card payments, we show these specific error messages based on the error code:

Error code

Translation context

Translatable text

Note

-50

payment.provider

Operation declined. Check card data.


1006

payment.provider

Card expired


1009

payment.provider

Currency is not supported by card acquirer

In case the error text from Datatrans contains the text "payment acquirer does not exist"

For Twint, we show the error messages received from Datatrans directly to the user.

Other checkout messages are generic (i.e. Couldn't initiate transaction, Payment could not be processed, etc.), where we don't have a specific list based on the payment provider used, but you can reach out to Piano Support at support@piano.io to get a list of all strings that are related to the service errors.

Supported Features and Functionality

Feature/Functionality

Supported?

Refunds

Full / Partial

Apply Terms Wizard

Yes*

My Account - Add payment method

Yes

My Account - Edit payment method

Yes

My Account - Delete payment method

Yes

Publisher dashboard - Add payment method

Yes*

Publisher dashboard - Edit payment method

Yes*

Publisher dashboard - Delete payment method

Yes

Tax Providers and Types

TaxJar

Transaction descriptors

None

Frictionless

No

3DS / SCA

Yes

Fraud prevention

Yes (Limited number of transactions per card)

Passive churn prevention

Yes - Advanced support (for Apple Pay, Credit Cards, Google Pay and PayPal)

Payment token import support

Yes**

*Not supported for Twint, Apple Pay, and Google Pay.

**For more information about the payment token import please visit this link.

Custom Datatrans country checkout rules: 2-of-3 country match

This logic is currently available for the Datatrans Credit Card integration only. When processing a purchase, Piano considers three values:

  1. Country according to the user’s GeoIP (where the user’s device is located).

  2. Billing country or country of residence (if different from the billing country) stated by a user; similar to the default rules above.

  3. Country of the payment method (e.g. the country of the credit card provided by the Datatrans payment provider)

Piano compares these three values and, if at least two of them match, recognizes it as the billing country. Otherwise, a user is requested to reselect the billing country.

Passive Churn Prevention

It's crucial to understand how the various error codes that may arise during a payment failure are handled by the Passive Churn Prevention feature. These error codes provide insights into why a renewal attempt may have failed, enabling you to take appropriate actions to prevent churn. This section details the specific error codes encountered during a payment failure and their mapping to a decline reason.

Decline reason

Error codes

Datatrans Credit Card

Datatrans Apple Pay, Google Pay and PayPal

Suspected fraud

3003 (CC number declined by global fraud mgmt)

3004 (CC number declined by merchant fraud mgmt.)

3006 (CC number declined by group fraud mgmt.)

 -

Insufficient funds

1403 (transaction declined without any further reason)

ISO-specific response:

51 INSUFFICIENT_FUNDS

 -

Expired card

1402 (expired card)

ISO-specific response:

33 CARD_EXPIRED

EXPIRED_CARD

Card is stolen or lost

49 (Declined - do not retry card)

ISO-specific response:

04 CARD_LOST_OR_STOLEN

43 CARD_LOST_OR_STOLEN

 -

Limit exceeded

52 (Declined)

ISO-specific response:

65 CARD_ACTIVITY_EXCEEDS_COUNT_LIMIT

 -

Network failure

1201 (Connect timed out)

SERVER_ERROR

3DS failure

1407 (soft decline (retry with 3D Secure))

ISO-specific response:

65 CARD_ACTIVITY_EXCEEDS_COUNT_LIMIT

SOFT_DECLINED

Call issuer

1403 (transaction declined without any further reason)

ISO-specific response:

02 ISSUER_DECLINED

BLOCKED_CARD

Do not honor

1403 (transaction declined without any further reason)

50 (Declined)

ISO-specific response:

05 DO_NOT_HONOR

1403 (transaction declined without any further reason)

ISO-specific response:

62 TRANSACTION_NOT_ALLOWED

DECLINED

Everything else

All error codes not listed above

All error codes not listed above

How to integrate Datatrans with Piano

Credit Cards

To integrate your Datatrans account with Piano for credit card payments, simply click Edit Business→Payment Provider→Datatrans Credit Card (Add) and enter your credentials.

image-20260629-105107.png
  • Title: Pick a title for your configuration

  • Merchant ID: Enter your Datatrans Merchant ID available in your Datatrans dashboard. This should be a 10-digit code.

  • Password: Enter the Password available in your Datatrans dashboard. In addition, there is an additional checkbox Protect server-to-server services with a password available here, which needs to be selected.

    pw-1.png
  • Sign: Contains your Datatrans signature. More information about this is available here.

    Due to the recent security updates by Datatrans, you need to provide a HMAC signature which can be entered after enabling the toggle "Use HMAC Signature" so that you comply with the new security standards. More information can be found in this section.

  • HMAC key: This field is shown when the toggle "Use HMAC Signature" is enabled. The HMAC signature key can be retrieved from your Datatrans account as described here.

  • Soft descriptor: An optional field that allows you to define a dynamic label that will appear on your customers’ statements for transactions and helps identify specific transactions, reducing the likelihood of disputes and chargebacks.

    • Character Limit: Up to 26 characters.

    • Allowed Characters: Letters and numbers only. No spaces or special characters such as <, >, \, ' " *.)(.

    • Applies to all transactions including purchases, renewals, and upgrades across various payment flows.

  • Trial/promo descriptor: An optional field specifically designed for transactions related to trials or promotional offers. It allows you to label such transactions distinctly on bank statements, ensuring customers can easily differentiate between promotional and full-price transactions.

    • Character Limit: Up to 14 characters.

    • Allowed Characters: Letters and numbers only. No spaces or special characters such as <, >, \, ' " *.)(.

  • Collect cardholder name: Allows you to optionally collect the cardholder's name during payment processing for enhanced 3DS authentication and compliance with Visa’s updated mandatory data requirements. When enabled, a text input field will appear on the payment interface, requiring users to provide their cardholder name (up to 50 alphanumeric characters). Ensure that your system templates are updated before enabling the Cardholder name option in the configuration as described here.

  • Collect Billing address for 3D Secure: Prior to enabling this setting, please review the section here.

  • Collect Billing city for 3D Secure: Prior to enabling this setting, please review the section here.

  • Collect Billing country for 3D Secure: Prior to enabling this setting, please review the section here. Once enabled, you can select a default billing country.

Enter all the currency options (for example EUR, CHF, or USD) you would like to be available to customers, and click Save.

Twint

To integrate your Datatrans account with Piano for Twint payments, simply click Edit Business→Payment Provider→Datatrans: Twint (Add) and enter your credentials as explained above.

Twint-1.png

An optional setting Complete purchase automatically after Twint authorization can be toggled on. When enabled, this setting allows the purchase to complete automatically after the user authorizes the payment in the Twint app, eliminating an additional step where the user needs to complete the purchase manually and streamlining the checkout process.

Please note, that Twint currently supports only payments in CHF.

We don’t store any certificates on Piano's side related to Twint. In case your merchant certificate is about to expire, please reach out to Datatrans Support for assistance with updating the certificate.

Apple Pay

To integrate your Datatrans account with Piano for Apple Pay payments, simply click Edit Business→Payment Provider→Datatrans: Apple Pay (Add) and enter your credentials as explained above.

Apple-1.png

More information about how to set up Apple Pay for your domain is available here.

Google Pay

To integrate your Datatrans account with Piano for Google Pay payments, simply click Edit Business→Payment Provider→Datatrans: Google Pay (Add) and enter your credentials as explained above.

Google.png

Please note, that the list of qualified domains from which you intend to call the Google Pay API (that should be provided to Google during the Google Pay setup) needs to include also the URL https://buy.tinypass.com/ respectively https://buy.piano.io/ (utilized by the Piano checkout iframe for the US dashboard). For other dashboard locales, the URL would look like this:

EU dashboard - https://buy-eu.piano.io

AP dashboard - https://buy-ap.piano.io

AU dashboard - https://buy-au.piano.io

PayPal

To integrate your Datatrans account with Piano for PayPal payments, simply click Edit Business→Payment Provider→Datatrans: PayPal (Add) and enter your credentials as explained above.

PayPal-1.png

Last updated: