Unlock the full potential of our feature and take your skills to the next level! Dive into our Training Center and discover exclusive Best Practice resources that will elevate your implementation strategy. With expert tips and insider knowledge, you'll become a master in no time. Access the links below to learn more and gain a competitive edge.
Ready to get started? Our Training Center is just a click away: here.
*For more information about our Training center, please visit the article here.
Important Angular JS Directives
Piano templates support version 1.2.22 of Angular JS. Though you are not required to use Angular JS in Offer Templates (HTML/CSS will work just fine), you'll notice that the Default Offer Template and Piano's System Templates both employ Angular JS directives. If you're new to Angular JS, its own documentation site will be your best resource. However, to quickly get you up to speed, here are definitions for a few of the common Angular JS directives you'll find within Piano's templates along with some use cases for them (each directive name links to Angular JS documentation related to it):
-
ng-if: If the expression is true, the HTML element is included. If the expression is false, the HTML element is completely removed from the DOM. Within the default Offer Template,
ng-ifis used to include elements only if a particular login provider is initialized, if a term has a free trial associated with it, and if a term is for new users only. -
ng-hide: If the expression is true, the display of the HTML element is hidden (but is still available in the DOM). If the expression is false, the HTML element is visible.
-
ng-repeat: Repeats a set of HTML once per item in a collection. The collection must be an array or an object. Within the Default Offer Template,
ng-repeatis used to dynamically present all available terms. -
ng-show: If the expression is true, the HTML element is visible. If the expression is false, the HTML element is hidden (but is still available in the DOM). You could, for example, use
ng-showto display some additional text just for registration terms (which you'd identify within the template like this:term.type=="registration"). -
ng-click: specifies a custom behavior when an element is clicked. Unlike a simple
href, which you'd use to create links to external pages, this directive can be used with Piano's custom Angular JS functions (discussed in the next section). For example,ng-clickis commonly used to to attach Piano'sstartCheckoutmethod to the buttons used for selecting terms. -
ng-model: lets you bind the value of an
inputfield to a variable created in Angular JS. This is often used when collecting data and sending it to a third-party system (to seeng-modelin action check out this guide on collecting email addresses through Piano templates). -
ng-bind-html: lets you automatically bind the value of the model inside the html element. You could, for example, use this directive to bind the term name to the
<label>tag. Like this:<label ng-bind-html="term.name"></label>. -
ng-value: used to dynamically bind the value of the model to
input. -
ng-class: allows you to dynamically set the CSS classes of an element based on some conditions. You could, for example, use this directive to make the term selection button red for payment terms. Like this:
<div ng-class="{'button-red': isPaymentTerm(term)}" ng-click="startCheckout(term.termId)"></div>.
Offer Template Methods
The methods below are available within the scope of Piano Offer Templates. Note that some of these methods are only available if you have a user management system pre-integrated with Piano (Gigya, or Identity Management) and that a few of these methods are only used if you're specifically using Identity Management.
Since Piano's Offer Templates are simply HTML, within them you are also welcome to use functions that are defined within the scope of your website provided those functions are initialized before Piano's JavaScript and you surround that non-Piano JavaScript with a custom-script directive (explained below).
How to show an Offer for checkout using JavaScript is explained here.
We do not support adding tracking or ad software to our templates. This can lead to anti-tracking and adblocker extensions completely restricting access to our scripts, which will block checkout and login altogether.
Instead, we ask that you funnel events to external analytics providers via callbacks - including click tracking using external events.
Finally, if you want to perform an action outside of the template scope in response to Piano checkout, login, or meter events, you can do so by using this list of Piano callback functions.
Offer templates do support the use of a <img> tag to add images, but please take into consideration that the image would need to be hosted on your end.
Piano Method
|
Description
|
|
startCheckout(term.termId) |
Starts the checkout flow. The |
|
close() |
Closes the template modal the user is currently viewing. Though Piano gives you the option of including an X in the upper right-hand corner of an Offer Template so users can close out of it, you might also want include a link within the body of a template that users can click on to close the template. Like this: |
|
isUserValid() |
Returns true if the user's identity is known (i.e. if the user is already logged in). |
|
hasAccess(rid) |
Returns true if the user has access to the resource. This method can be used to hide or show terms presented to your users based on their access. Read more. |
|
isGiftTerm(term) |
Returns true if the displayed term (the term or terms linked to your offer) is a "gift" term. This method, as with all the term-specific methods, can be used to customize language for different types of terms or to display them in particular parts of the template. For example, you might want to present a gift term separately from the main list of terms. In that case, you could use an |
|
isPaymentTerm(term) |
Returns true if the displayed term is a "payment" term. |
|
isDynamicTerm(term) |
Returns true if the displayed term is a "dynamic" term. |
|
isFreeDynamic(term) |
Returns true if purchased access period of the dynamic term has free billing plan. |
|
isPayableTerm(term) |
Returns true if the displayed term can be purchased (no matter whether its a payment term or gift term). |
|
isExternalTerm(term) |
Returns true if the displayed term is an "external" term. |
|
isPayWhatYouWantTerm() |
Returns true if the displayed term allows the user to input a custom price (the "allow users to pay more" option available when setting up a payment term). |
|
startRedemption() |
Triggers a screen where users who have received gift subscriptions are able input a redemption code in order to activate their subscriptions. |
|
login() |
If you're using a pre-integrated user management provider, (Identity Management or Gigya), this method can be used to start the login/registration flow. For clients using other user management solutions, you'll need to use the login function native to your user management implementation. |
|
logout() |
If you're using a pre-integrated user management provider, (Identity Management or Gigya), this method can be used to start the logout flow. For clients using other user management solutions, you'll need to use the logout function native to your user management implementation. |
|
allowLogout() |
Returns true if the user is logged in and is allowed to start logout. With this method, logout is allowed if you're using a pre-integrated user provider (Identity Management or Gigya). |
|
register() |
Triggers a login/registration screen for clients using Identity Management. The user will not proceed with checkout after login/registration. This method should be used when an experience requires registration but doesn't provide ongoing access after registration. For instance, if you were providing five extra free pageviews to users who register. |
|
allowTinypassAccountsLogin() |
Returns true if you're using Piano Accounts as your user provider and login is allowed. Login is allowed if Piano Accounts is enabled on your Piano application. This has been deprecated, for Identity Management please use the method |
|
allowTinypassAccountsLogout() |
Returns true if you're using Piano Accounts as your user provider and logout is allowed. Logout is allowed if Piano Accounts is enabled on your application. This has been deprecated, for Identity Management please use the method |
|
hasTax() |
Returns true if taxes need to be applied to a purchase during the payment flow. |
|
isTaxUsEnabled() |
Returns true if US taxes are enabled on your Piano application. |
|
isVatCanadaEnabled() |
Returns true if VAT/Canada taxes are enabled on your Piano application. |
|
isVatEuEnabled() |
Returns true if VAT/EU taxes are enabled on your Piano application. |
|
isPreviewMod() |
Renders a special template view that includes a preview of the Apple Pay button. In this mode, Apple Pay checkout is not working. |
Offer Template Models
Within the Offer Template scope there are a few important Piano models that allow you to dynamically insert information and conditionals into your Offer Templates. In order to insert information, you'll use this formulation {{model.variable}} or by using ng-bind-html. So, for example, if you wanted to dynamically insert a user's first name into a template, you'd call the user model and the firstName variable like this: Hi {{user.firstName}}, welcome back!. If you have any users that might not have a first name saved, you can use the following variable to display for these users a general greeting: Hi {{user.firstName || "Reader"}}, welcome back!
The User Model
Includes information about users. Here's the structure of the user model:
User {
uid(string): The user ID,
email(string): User email address ,
valid (boolean): Whether or not the user is logged in ,
displayName(string): User first and last name ,
firstName(string): User first name ,
lastName(string): User last name ,
}
If the user is logged in, the user model will be populated with that user's information. Here's an example of a logged in user:
{
"uid": "92837648",
"email": "johnsmith@gmail.com",
"displayName": "John Smith",
"valid": "true",
"firstName": "John",
"lastName": "Smith"
}
If the user is not logged in, the user model will be populated with the anon user:
{
"uid": "anon",
"email": null,
"displayName": null,
"valid": "false",
"firstName": null,
"lastName": null
}
If you are performing logic based on whether or not the user is logged in, Piano recommends using the isUserValid() method (described above).
The App Model
Includes information about your Piano application. Here's the structure of the app model:
{
aid(string): The application ID ,
canInitCheckoutWithAnon(boolean): Whether anonymous checkout is allowed ,
image1(string): Your business logo specified in the application's Edit Business section ,
businessUrl(string): Your business URL specified in the application's Edit Business section ,
name(string): The business name specified in the application's Edit Business section ,
useTinypassAccounts(boolean): Whether the application uses Piano accounts ,
userProvider(string): The application's user provider
}
Here's the app model with example values:
{
"aid": "bdyACaOA6M",
"canInitCheckoutWithAnon": false,
"image1": "/logo/cropped.png",
"businessUrl": "www.yourdomain.com"
"name": "The Times",
"useTinypassAccounts": false,
"userProvider": "gigya"
}
The Params Model
The params model provides contextual information about the environment in which an Offer Template is displayed. This model is used during user management integrations, when reporting conversions that occur in third-party applications, and for passing data to and from outside systems. Here's the structure of the params model:
{
aid(string): The application ID ,
debug(boolean): Whether debug mode is activated ,
displayMode(string): Display mode of the template (inline or modal) ,
iframeId(string): ID of the template's iframe element ,
offerId(string): The ID of the offer connected to the template ,
url(string): The URL of the page the user was on when the offer template first displayed ,
width(number): The width of the offer template in pixels ,
trackingId(string): ID used to track actions Piano users take in third-party applications ,
termId(string): ID of the term the user selected ,
experienceId(string): ID of the Composer experience that triggered the offer template ,
tags(string): Any page tags that have been set ,
templateId(string): The ID of the offer template
}
Note that the termId variable is only populated once a term has been selected (all other variables are available once the offer template displays). Here's the params model with example values:
{
"aid": "bdyACaOA6M",
"debug": "false",
"displayMode": "modal",
"iframeId": "offer-0-GeOvq",
"offerId": "OFZLE1NN9SNE",
"url": "http://localhost:9000/checkout/test.jsp?mode=2#",
"width": "815",
"trackingId":"{jcx}H4sIAAAAAAAAAKtWSsxMUbICkzpKBYnpqWGZqeWeICEkjo5SakVBalFmal5yqmtFanJpSWZ-HlgRdnFk9Y7JWBTDBXWUigtyMktCUotLij1TipWsohECnimGyPKeKUZKsbUAO8grYbEAAAA" ,
"termId": "TM3F10520FXR",
"experienceId": "EX41SB6XCUFI",
"tags": "premium",
"templateId": "OTABCDEF",
}
More information on the Params model can be found here.
The Custom Model
Any custom variable that has been set prior to showing the Offer Template will be available within the custom model. Note that custom variable names are case-sensitive and should only contain alphanumeric characters and underscore.
If you'd set some custom variables like this:
<script>
tp = window["tp"] || [];
// Setting custom variables
tp.push(['setCustomVariable', 'fullName', 'John Smith']);
tp.push(['setCustomVariable', 'city', 'New York']);
tp.push(['setCustomVariable', 'weather', '70']);
</script>
You could then expose those custom variables in the Offer Template like this:
<span>Hello, {{custom.fullName}}. It's nice weather in {{custom.city}}: {{custom.weather}}F</span>
The Terms Model
The terms model includes information about each term and associated resources. To insert resource variables you'll use this formulation {{term.resource.variable}}. So, if you wanted to include the name of the term you'd formulate it like this {{term.name}} but if you wanted to include the name of the resource connected to that term you'd formulate it like this {{term.resource.name}}.
Here's the structure of the terms model:
{
termId(string): The ID of the term ,
name(string): The name you gave the term when you created it ,
description(string): The description you gave the term when you created it ,
type(string): The type of term (possible values include "payment", "dynamic", "registration", "gift", "external") ,
resource: {
name(string): The name you gave the resource connected to the term ,
description(string): The description you gave the resource connected to the term ,
rid(string): The resource ID ,
url(string): The URL you gave the resource when you created it ,
imageUrl(string): The image given to the resource
},
displayLine(string): The display line allows you to show the billing description based on the term's settings. For example "one payment of $1.99" or "$50 per year" or "$2.99 per month / with trial of $0.99 for 2 months" or "$0.99/every 1 month(s)".
billingPlanTable: The billing plan table will split out values for each billing period (important for terms with trial periods or dynamic terms) [
{
date(string): Indicates when the next payment period begins. For the current active period (index 0), the value will be either "Today" or a past date, but never a future date.
For all upcoming billing periods (index 1 and beyond), the value will be either "Today" or a future date, but never in the past ,
period(string): The billing period (values for payment include "month", "3 months", "6 months" and "year", "forever"; values for dynamyc term have format "every 1 year(s)", "every 3 month(s)") ,
shortPeriod(string): The abbreviated billing period for payment term (for example, "month" would be "/mo"); for dymayc terms it equals to period ,
billingPeriod(string): How often billing occurs ,
cycles(number): The number of payment cycles for payment term ,
isFreeTrial(boolean): Whether payment term has a free trial ,
isTrial(boolean): Whether payment term has a trial period of any type ,
isPayWhatYouWant(boolean): Whether term has pay what you want enabled ,
billing(string): Description of the billing schedule ,
duration(string): Length of access after each payment period for payment term (could be "7 day trial", "yearly until cancelled", or "monthly until cancelled"); Length of access period for dynamic term (could be "2 month(s)", "1 year(s)") ,
isFree(boolean): Whether the access period is free ,
pricelessBillingPre(string): The description before the payment amount ,
pricelessBillingPost(string): The description after the payment amount (for example, "per month") ,
price(string): The price to be paid (without taxes or promotions applied) ,
priceValue(number): The price to be paid (without taxes or promotions applied) ,
priceAndTax(number): The price to be paid without taxes ,
originalPrice(string): The price before promotions applied with taxes ,
originalPriceValue(number): The price before promotions applied with taxes ,
totalBilling(string): the total billing to be paid after taxes are applied ,
billingWithoutTax(string): the total billing to be paid before taxes are applied ,
tax(string): The tax to be paid ,
taxValue(number): The tax to be paid ,
currency(string): The currency code (for example, "USD") ,
}
],
// The billingPlanTable values above are broken out by billing periods. The values below are for the term in general.
chargeDisplayAmount(number): The term's display price ,
chargeAmount(number): The amount the user is charged ,
taxAmount(number): The amount to tax the user ,
price(string): The term's price (without tax) ,
totalAmount(string): The total price to be paid with currency ,
chargeCurrency(string): The type of currency (e.g. USD) ,
chargeCurrencySymbol(string): The symbol of currency (e.g. $) ,
isSubscription(boolean): Whether term is a subscription or not ,
hasFreeTrial(boolean): Whether term has a trial period ,
isSchedule(boolean): The payment term has planned billing plan ,
firstRealPrice(string): The price after any promotions have been applied ,
firstRealPriceWithTax(string): The price after any tax added (for example: "$1.00 + $0.09 tax") ,
oneOffPaymentMethods(): [
{
id(string): The ID of the user's payment method for one-off payments ,
name(string): The name of the payment method for one-off payments ,
identifier(string): The type of payment method for one-off payments
},
],
subscriptionPaymentMethods: [
{
id(string): The ID of the user's payment method for subscription payments ,
name(string): The name of the payment method for subscription payments ,
identifier(string): The type of payment method for subscription payments
},
],
isCustomPriceAvailable (boolean): Whether term has pay what you want enabled ,
isPaymentMethodRequired(boolean): Whether term requires payment ,
forceAutoRenew(boolean): Whether term is auto-renewing ,
newCustomersOnly(boolean): Whether term is avaliable to new customers only ,
firstPeriod(string): the first access period (e.g. "1 month") ,
allowPromoCodes (boolean): Whether term allows promotions ,
originalBillingPlan: The billing plan table before promotions applied
}
Here's the terms model with example values for payment term:
{
"termId": "TM49HJUDA6SW",
"name": "Monthly Subscription",
"description": "The monthly subscription term description",
"type": "payment",
"resource": {
"name": "Premium Access",
"description": "Premium Access resource description",
"rid": "RIBO98G",
"url": "http://example.com",
"imageUrl": null
},
"displayLine": "$2.99 per month / with trial of $0.49 for 2 months",
"billingPlanTable": [
{
"date": "Today",
"pstAmount": "$0.00",
"shortPeriod": " for month",
"originalPrice": "$1.07",
"qstAmount": "$0.00",
"priceChargedStr": "$0.49 + $0.04 tax",
"hstRate": 0,
"cycles": "1",
"isFreeTrial": "false",
"isTrial": "true",
"isPayWhatYouWant": "false",
"billing": "one payment of $0.49 + $0.04 tax",
"duration": "2 months of access",
"pstRate": 0,
"isFree": "false",
"pricelessBillingPre": "one payment of ",
"price": "$0.49",
"priceAndTax": 0.53,
"pricelessBillingPost": "",
"currency": "USD",
"totalBilling": "one payment of $0.53",
"gstRate": 0,
"gstAmount": "$0.00",
"period": "2 months",
"billingWithoutTax": "one payment of $0.49",
"billingPeriod": "2 months",
"hstAmount": "$0.00",
"priceValue": 0.49,
"tax": "$0.04",
"taxRate": 8,
"billingInfo": "one payment of $0.49 + $0.04 tax",
"qstRate": 0,
"taxValue": 0.04,
"originalPriceValue": 1.07
},
{
"date": "Jun 6, 2023",
"pstAmount": "$0.00",
"shortPeriod": "/mo",
"originalPrice": "",
"qstAmount": "$0.00",
"priceChargedStr": "$2.99 + $0.24 tax",
"hstRate": 0,
"cycles": "2147483647",
"isPayWhatYouWant": "false",
"billing": "$2.99 + $0.24 tax per month",
"duration": "monthly until canceled",
"pstRate": 0,
"isFree": "false",
"pricelessBillingPre": "",
"price": "$2.99",
"priceAndTax": 3.23,
"pricelessBillingPost": " per month",
"currency": "USD",
"totalBilling": "$3.23 per month",
"gstRate": 0,
"gstAmount": "$0.00",
"period": "month",
"billingWithoutTax": "$2.99 per month",
"billingPeriod": "1 month",
"hstAmount": "$0.00",
"priceValue": 2.99,
"tax": "$0.24",
"taxRate": 8,
"billingInfo": "$2.99 + $0.24 tax per month",
"qstRate": 0,
"taxValue": 0.24,
"originalPriceValue": null
}
],
"chargeDisplayAmount": "$0.53",
"chargeAmount": 0.53,
"chargeAmountInMinorUnit": 53,
"taxAmount": "$0.04",
"hstAmount": "$0.00",
"qstAmount": "$0.00",
"pstAmount": "$0.00",
"gstAmount": "$0.00",
"taxRate": 8,
"hstRate": 0,
"qstRate": 0,
"pstRate": 0,
"gstRate": 0,
"price": "$0.49",
"totalAmount": "$0.53",
"termLevelTaxProductCategory": "",
"chargeCurrency": "USD",
"chargeCurrencySymbol": "$",
"isSubscription": true,
"hasFreeTrial": false,
"isSchedule": false,
"firstRealPrice": "$0.49",
"firstRealPriceWithTax": "$0.49 + $0.04 tax",
"oneOffPaymentMethods": [
{
"id": 4,
"name": "Credit Card",
"identifier": "credit",
"paymentTypeId": "credit_card",
"authSupported": true,
"captchaSupported": true,
"customTitle": null
}
],
"subscriptionPaymentMethods": [
{
"id": 4,
"name": "Credit Card",
"identifier": "credit",
"paymentTypeId": "credit_card",
"authSupported": true,
"captchaSupported": true,
"customTitle": null
}
],
"isCustomPriceAvailable": false,
"isZero": false,
"isPaymentMethodRequired": true,
"forceAutoRenew": false,
"newCustomersOnly": false,
"firstPeriod": "2 months",
"sellDate": null,
"hasFinishedSales": false,
"restrictCheckoutProcess": false,
"originalBillingPlan": {
"displayLine": "$2.99 per month / with trial of $0.99 for 2 months",
"billingPlanTable": [
{
"date": "Today",
"pstAmount": "$0.00",
"shortPeriod": " for month",
"originalPrice": "",
"qstAmount": "$0.00",
"priceChargedStr": "$0.99 + $0.08 tax",
"hstRate": 0,
"cycles": "1",
"isFreeTrial": "false",
"isTrial": "true",
"isPayWhatYouWant": "false",
"billing": "one payment of $0.99 + $0.08 tax",
"duration": "2 months of access",
"pstRate": 0,
"isFree": "false",
"pricelessBillingPre": "one payment of ",
"price": "$0.99",
"priceAndTax": 1.07,
"pricelessBillingPost": "",
"currency": "USD",
"totalBilling": "one payment of $1.07",
"gstRate": 0,
"gstAmount": "$0.00",
"period": "2 months",
"billingWithoutTax": "one payment of $0.99",
"billingPeriod": "2 months",
"hstAmount": "$0.00",
"priceValue": 0.99,
"tax": "$0.08",
"taxRate": 8,
"billingInfo": "one payment of $0.99 + $0.08 tax",
"qstRate": 0,
"taxValue": 0.08,
"originalPriceValue": null
},
{
"date": "Jun 6, 2023",
"pstAmount": "$0.00",
"shortPeriod": "/mo",
"originalPrice": "",
"qstAmount": "$0.00",
"priceChargedStr": "$2.99 + $0.24 tax",
"hstRate": 0,
"cycles": "2147483647",
"isPayWhatYouWant": "false",
"billing": "$2.99 + $0.24 tax per month",
"duration": "monthly until canceled",
"pstRate": 0,
"isFree": "false",
"pricelessBillingPre": "",
"price": "$2.99",
"priceAndTax": 3.23,
"pricelessBillingPost": " per month",
"currency": "USD",
"totalBilling": "$3.23 per month",
"gstRate": 0,
"gstAmount": "$0.00",
"period": "month",
"billingWithoutTax": "$2.99 per month",
"billingPeriod": "1 month",
"hstAmount": "$0.00",
"priceValue": 2.99,
"tax": "$0.24",
"taxRate": 8,
"billingInfo": "$2.99 + $0.24 tax per month",
"qstRate": 0,
"taxValue": 0.24,
"originalPriceValue": null
}
],
"chargeDisplayAmount": "$1.07",
"chargeAmount": 1.07,
"nextChargeTotalAmount": "$1.07",
"taxAmount": "$0.08",
"hstAmount": "$0.00",
"qstAmount": "$0.00",
"pstAmount": "$0.00",
"gstAmount": "$0.00",
"taxRate": 8,
"hstRate": 0,
"qstRate": 0,
"pstRate": 0,
"gstRate": 0,
"price": "$0.99",
"totalAmount": "$1.07",
"chargeCurrency": "USD",
"chargeCurrencySymbol": "$",
"isSubscription": true,
"hasFreeTrial": false,
"isZero": false,
"firstRealPrice": "$0.99",
"firstRealPriceWithTax": "$0.99 + $0.08 tax",
"isCustomPriceAvailable": false,
"firstPeriod": "2 months",
"verificationAmount": null,
"verificationAmountWithTax": null
},
"sku": null,
"giftEmailSendStartTime": null,
"giftEmailSendEndTime": null,
"sharedAccountCount": null,
"authorizationAmount": null,
"linkTermSignedData": null,
"verificationAmount": null,
"verificationAmountWithTax": null,
"withNewCustomerBillingPlan": false,
"allowPromoCodes": true
}
Here's the terms model with example values for dynamic term with 2 access periods:
{
"termId": "TMB9V27V6FU7",
"name": "Monthly Dynamic Subscription",
"description": "Monthly Dynamic Subscription description text",
"type": "dynamic",
"resource": {
"name": "Premium Access",
"description": "Premium Access resource description",
"rid": "RIBO98G",
"url": "http://example.com",
"imageUrl": "/resource/image.png"
},
"displayLine": "$0.99/every 1 month(s)",
"billingPlanTable": [
{
"date": "Today",
"duration": "2 month(s)",
"billing": "$0.99 + $0.08 tax/every 1 month(s)",
"totalBilling": "$1.07/every 1 month(s)",
"billingInfo": "$0.99 + $0.08 tax/every 1 month(s)",
"billingWithoutTax": "$0.99/every 1 month(s)",
"pricelessBillingPre": "",
"price": "$0.99",
"priceValue": 0.99,
"originalPrice": null,
"originalPriceValue": null,
"priceAndTax": 1.07,
"currency": "USD",
"period": "every 1 month(s)",
"shortPeriod": "every 1 month(s)",
"billingPeriod": "every 1 month(s)",
"isFree": "false",
"cycles": "0",
"isPayWhatYouWant": "false",
"tax": "$0.08",
"taxValue": 0.08,
"taxRate": 8,
"hstRate": 0,
"hstAmount": "$0.00",
"qstRate": 0,
"qstAmount": "$0.00",
"pstRate": 0,
"pstAmount": "$0.00",
"gstRate": 0,
"gstAmount": "$0.00"
}
],
"chargeDisplayAmount": "$1.07",
"chargeAmount": 1.07,
"chargeAmountInMinorUnit": 107,
"taxAmount": "$0.08",
"hstAmount": "$0.00",
"qstAmount": "$0.00",
"pstAmount": "$0.00",
"gstAmount": "$0.00",
"taxRate": 8,
"hstRate": 0,
"qstRate": 0,
"pstRate": 0,
"gstRate": 0,
"price": "$0.99",
"totalAmount": "$1.07",
"termLevelTaxProductCategory": null,
"chargeCurrency": "USD",
"chargeCurrencySymbol": "$",
"isSubscription": true,
"hasFreeTrial": false,
"isSchedule": false,
"firstRealPrice": "$0.99",
"firstRealPriceWithTax": "$0.99 + $0.08 tax",
"oneOffPaymentMethods": [],
"subscriptionPaymentMethods": [
{
"id": 4,
"name": "Credit Card",
"identifier": "credit",
"paymentTypeId": "credit_card",
"authSupported": true,
"captchaSupported": true,
"customTitle": null
}
],
"isCustomPriceAvailable": false,
"isZero": false,
"isPaymentMethodRequired": true,
"forceAutoRenew": false,
"newCustomersOnly": false,
"firstPeriod": "every 1 month(s)",
"sellDate": null,
"hasFinishedSales": false,
"restrictCheckoutProcess": false,
"originalBillingPlan": null,
"sku": null,
"giftEmailSendStartTime": null,
"giftEmailSendEndTime": null,
"sharedAccountCount": null,
"authorizationAmount": null,
"linkTermSignedData": null,
"verificationAmount": 2.99,
"verificationAmountWithTax": 3.23,
"withNewCustomerBillingPlan": false,
"allowPromoCodes": false
}
Within the template markup, you can leverage the terms model to dynamically display and hide terms from users. You can do this by including this model within the logic of the ng-show directive:
<tr ng-repeat="term in terms">
<td class="title" ng-show="!hasAccess(term.resource.rid)">
<h2>{{term.name | tc:'term.name'}}</h2>
<span class="period" ng-if="term.newCustomersOnly"><t>for new consumer only</t></span>
</td>
</tr>
This means that you can use the same offer and offer template displayed to users with varying accesses, and each will only see the opportunity to convert on terms with resources they do not currently have access to.
Contract Info Model
Includes information about the contract and licensee associated with the offer. The Contract Info model allows you to reference contract-related fields within offer templates using the contractInfo object.
You can access properties using dot notation. For example:
contractInfo.licensee.name
contractInfo.contract.name
Here's the structure of the contractInfo model:
{
contractInfo(object): Contract information available in the offer template,
contractInfo: {
contract(object): Contract details,
contract: {
contractId(string): Unique identifier of the contract,
appId(string): Application ID associated with the contract,
scheduleId(string): Identifier of the contract schedule,
resourceId(string): Identifier of the licensed resource,
licenseeId(string): Identifier of the licensee organization,
contractState(string): Current state of the contract (e.g., ACTIVE, INACTIVE),
seatsLimitType(string): Type of seat limitation applied to the contract (e.g., SOFT, HARD),
landingPageUrl(string|null): Optional landing page URL associated with the contract,
name(string): Name of the contract,
seatsNumber(number): Number of seats allocated in the contract,
createDate(string): Contract creation date in ISO 8601 format
},
licensee(object): Licensee organization details,
licensee: {
appId(string): Application ID associated with the licensee,
licenseeId(string): Unique identifier of the licensee,
name(string): Name of the licensee organization,
description(string): Description of the licensee or its members,
logoUrl(string|null): URL of the licensee logo
},
homepageUrl(string): Homepage URL associated with the contract or application
}
}
Here's the contract model with example values:
{
"contractInfo": {
"contract": {
"contractId": "TMH1ZBI8H3DB",
"appId": "XTSRGNiPaH",
"scheduleId": "SCX2DZqiwmov",
"resourceId": "R58S4NR",
"licenseeId": "LCS9HUSUTVC8N",
"contractState": "ACTIVE",
"seatsLimitType": "SOFT",
"landingPageUrl": null,
"name": "IP Range",
"seatsNumber": 0,
"createDate": "2024-02-01T12:34:20.000+00:00"
},
"licensee": {
"appId": "XTSRGNiPaH",
"licenseeId": "LCS9HUSUTVC8N",
"name": "Local University",
"description": "Students and faculty at the local university are included in this license",
"logoUrl": null
},
"homepageUrl": "https://demo.piano.io/"
}
}
The activeMeters Model
Includes information about any active meters that where triggered when the template fired. Since you can have more than one meter running at a time, the activeMeters model is array that contains information about each individual meter.
Here's the structure of the activeMeters model:
{
meterName(string): The name given the meter in Composer,
views(integer): The number of metered pageviews a user has consumed within the meter limit,
totalViews(integer): The number of metered pageviews a user has consumed in total,
viewsLeft(integer): The number of metered pageviews a user has remaining within the meter limit,
maxViews(integer): The meter limit(the metered expires after the user reaches this number of pageviews),
}
Here's an example of the activeMeter model with a meter that has been maxed out and where the user has triggered the resulting experience multiple times (which is how totalViews exceeds maxViews):
[{
"meterName": "MeterOne",
"views": 5,
"totalViews": 7,
"viewsLeft": 0,
"maxViews": 5
}]
Here's another example of the activeMeter model were the meter is still running:
[{
"meterName": "MeterTwo",
"views": 2,
"totalViews": 2,
"viewsLeft": 3,
"maxViews": 10
}]
Within the template markup, you can use the activeMeters model to do things like tell users how many views they've consumed and/or have left. If you've got more than one meter running, you'll need to specify the meter name, like so:
<div ng-repeat="meter in activeMeters">
<div ng-if="meter.meterName=='DefaultMeter'">
Hi {{user.firstName}} you have {{meter.viewsLeft}} out of {{meter.maxViews}} left.
</div>
</div>
Note the <div ng-repeat="meter in activeMeters"> code in the example above. That line of code runs through all the objects in the activeMeters array. If you only have only one meter running in the experience, which is the most common condition, you would not need to specify the meter name. Here is how you would present meter data with only one meter running:
<div>
Hi {{user.firstName}} you have {{activeMeters[0].viewsLeft}} out of {{activeMeters[0].maxViews}} left.
</div>
Offer Template Directives
custom-script directive
You're welcome to execute your own JavaScript inside of a Piano Offer Templates, but you should always use the custom-script directive when doing so. Here's an example of it in use:
<div custom-script>
var scriptPath = "http://path/to/your/script.js";
var script = document.createElement("script");
script.type = "text/javascript";
script.src = scriptPath;
document.getElementsByTagName("body")[0].appendChild(script);
</div>
Note that the custom-script directive replaces normal <script> tags.
How to use Angular variables in the custom script
In the offer templates, we have a set of models (Angular objects) that can be used in the markup: User, App, Terms, etc. Typically you can paste some dynamic information into your template — for instance, a term name and its price. Thus, when you change some settings in the term manager, these updates get pulled into the template automatically, with no need to manually update the code as well.
But sometimes it's useful to manipulate this data through JavaScript. And to get these variables in the custom-script section in an offer template we had to define the template's Angular scope. There's an easier way to do it — simply call whatever variable you need using #{VARIABLE_NAME} syntax. For example, you want to do something with a user's name in JavaScript. You can get it like that: #{user.firstName}. Same thing with all other models available in the template's scope.
external-event directive
When editing an Offer Template, any element with an external-event directive will trigger a checkoutCustomEvent callback when the associated element is clicked on or otherwise activated. Custom events are a neat and quick way of sending data gathered inside a Piano template to the parent window of your website for processing (and important when reporting conversions that occur in third-party systems back to Piano).
Please note, that if you'd like to open links in the same tab as the parent window, you can use the command target="_parent" in your template's code.
You are able to use various attributes in this directive to get additional information from the external-event action.
For example, external-event-term="{{term}}" will return information about the term the user clicked on in the offer, containing information about the term ID or term description, as well as the resource name and resource ID of the term.
{
"termId": "TMBEOGGBZ74O",
"name": "term_name",
"description": "term_description",
"type": "payment",
"resource": {
"name": "Resource name",
"description": "",
"rid": "ABCDE123",
"url": null,
"imageUrl": null
},
"displayLine": "10 USD per month",
"billingPlanTable": [
{
...
}],
}
In addition to sending information about the custom event itself, you can send any additional data you like by using the external-event-params formulation. For more on how the external-event directive can be used to send and receive data, see this guide on how to collect newsletter sign-ups.
external-event-params="{{params}}" will return information about the displayed template after the user clicked on a term from the offer in the below format:
{
"aid": "<AID>",
"debug": true,
"displayMode": "modal",
"iframeId": "offer-0-JBYvI",
"offerId": "OFD9CNH01234",
"templateId": "OTABCW8UQ7PL",
"url": "<website_url>",
"width": 1920
}
external-event-termslist="{{terms}}" will return information about all terms from the offer shown.
mobile directive
Any markup contained within the mobile directive will only be rendered on mobile devices. For example:
<div mobile>
I am on a mobile device.
</div>
The mobile directive displays HTML elements when the available width is less than 600 px.
desktop directive
Any markup contained within the desktop directive will only be rendered on desktop devices. For example:
<div desktop>
I am on a desktop computer.
</div>
The desktop directive displays HTML elements when the available width is greater than 600 px.
config directive
This directive is used to control width of the modal template, as well as the close button. If you wanted to set the preferred width of the modal template to 600 px, you'd add the config directive like this:
<config width="600"></config>
If you don’t set a custom width, the width of the template will be 735px if your browser is 725px or more. If it is less than that, then the width will fit to 100% of the browser screen. Note that if the available screen width is less than the width specified, the template will be resized to fit the screen.
If you need further customization, you can also adjust the width using CSS by adding the following code to the CSS tab of the template:
body {
width: 500px !important;
}
This directive also has an attribute called close-button-type. The close button can be either boilerplate, meaning the close button is within the template, or default, meaning the close button is overlaid on the template.
<config close-button-type="boilerplate"></config>
language-selector directive
If you have localization enabled on your application, the language-selector directive renders a selector so users can select their preferred language. You can see it in use in this section of the Default Offer Template:
<div class="start-screen-language-toggle">
<language-selector></language-selector>
</div>
account-header-component directive
This directive is used to display a generic header with user info. When present, this directive will render the "account-header-component" located in the Checkout Components system template. Here's how you'd put the header at the top of an Offer Template:
<div account-header-component></div>
By default the account header contains:
-
Language selector
-
If user is logged in, the user's first name, last name, email, and a button to logout (if logout is possible with the user provider)
-
If user is anonymous, a login button
You might want to use this directive to create a common header across all your Offer Templates. By adding your common header to the Checkout Components system template, you could then insert this directive into all your Offer Templates to include that header everywhere.
Default Offer Template
Overview
You can create a new Offer Template by going to Manage → Templates and clicking on the button Add Template. When doing so, you'll only have the option to start with an empty template.
If needed, under the Manage → Templates section, you are able to roll out a drop-down menu with additional options for adding a new template next to the Add Template button.
The option Boilerplate template will allow you to choose one of the predefined, ready-to-go templates available in our Boilerplate library.
Alternatively, if you've already created a template in the Sandbox, you'll be able to import it easily into Production (or vice versa) via the option Import templates.
Some templates may not appear in the import list because their code is already identical in both the source and target applications.
Keep in mind that there also is a Default Offer template which can be found under Manage → Templates in the System template category, that fires if no Offer Template is specified for an offer. Since the Default Offer template is intended as a fail-safe, it's designed to work with every user management system and Subscription Management + Billing term. That means much of the code within the Default Offer template will be unnecessary depending on your implementation and the specifics of the offer you're creating.
Default Offer Template Code
A good way to see the template directives and methods discussed above in action is to go through the Default Offer Template's code. Below we'll analyze each section of that code, explaining elements you should be familiar with after reading this guide along with elements that may be unfamiliar.
Most aspects of the Default Offer code that are not covered above are related to localization. The one part of localization that is discussed above is the language-selector directive in this first part of the Default Offer Template:
<div class="row default-offer">
<div>
<div class="indents-from-border start-screen-header">
<div class="start-screen-language-toggle">
<language-selector></language-selector>
</div>
The next section is intended to work with any user provider to prompt login/registration for logged out users, to welcome logged in users, and to start checkout. When you create your own templates, much of the code in this section will be unnecessary since you'll have a single user management provider to deal with.
Be aware that some methods seen below, like isGigyaUserProvider, are used primarily to create this universal offer template and aren't typically used by clients in the templates they create themselves. Also note how the Angular directives discussed above are being used to dynamically include or exclude certain elements and how {{user.email}} is being used to call the email variable from the user model. Finally, notice the <t> tags around text. If you have localization enabled, Piano will parse any text inside <t> tags and you will be able to add a translation for that text in the localization dashboard.
Here's the code the section:
<div ng-if="allowTinypassAccountsLogin() || isGigyaUserProvider()">
<h1 ng-show="isUserValid()"><t>Welcome, {{user.email}}</t> <a ng-show="allowLogout()" ng-click="logout()"><t>Logout</t></a></h1>
<h1 ng-show="!isUserValid()"><a ng-click="login()"><t>Login</t></a></h1>
</div>
<div ng-if="!allowTinypassAccountsLogin() && !isGigyaUserProvider()">
<h1 ng-show="isUserValid()"><t>Welcome, {{user.email}}</t></h1>
<div ng-show="!isUserValid() && !canInitCheckoutWithAnon()">
<div class="start-screen-error"><t>You must be logged in to proceed.</t></div>
</div>
</div>
<a href="javascript:void(0)" class="button big start-checkout" ng-click="startCheckout()"><t>Start Checkout</t></a>
</div>
<div class="start-screen-content">
<h1 class="choose-specific-term"><t>Choose a specific term</t></h1>
In the next section, ng-repeat is being used to create N number of <tr> tags, where N is the number of objects in the terms model. That allows for the dynamic presentation of all terms connected to the Offer Template.
Also note below how various terms methods and the terms model are being used to include/exclude elements based on the nature of the term. Additionally, if you have localization enabled, the tc:'term.name' code seen below will allow you to provide a translation of that term name.
Finally, the desktop directive is being used here so this section only appears to users on desktop:
<div class="start-screen-content">
<h1 class="choose-specific-term"><t>Choose a specific term</t></h1>
<div desktop>
<div class="account-terms-section">
<table class="start-screen-terms account-terms" cellspacing="0" cellpadding="0">
<tbody>
<tr ng-repeat="term in terms">
<td class="title">
<h2>{{term.name | tc:'term.name'}}</h2>
<span class="period" ng-if="term.newCustomersOnly"><t>for new consumer only</t></span>
</td>
<td class="detail">
<span ng-show="isPayableTerm(term)">
<span class="price">
<span ng-if="term.hasFreeTrial"><t>Free</t></span>
<span ng-if="!term.hasFreeTrial">{{term.firstRealPrice}}</span>
</span>
<span class="period"><t>for {{term.firstPeriod}}</t></span>
</span>
</td>
<td class="choose">
<a href="javascript:void(0)" ng-click="startCheckout(term.termId)" class="button start-new-checkout big">
<t>Purchase</t>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
This next section uses the mobile directive to do exactly the same kind of term analysis for users visiting from mobile devices:
<div mobile>
<div class="mobile-account-terms-section">
<table class="start-screen-terms account-terms" cellspacing="0" cellpadding="0">
<tbody>
<tr ng-repeat="term in terms">
<td class="title">
<h2>{{term.name | tc:'term.name'}}</h2>
<span class="detail" ng-show="isPayableTerm(term)">
<span class="price">
<span ng-if="term.hasFreeTrial"><t>Free</t></span>
<span ng-if="!term.hasFreeTrial">{{term.firstRealPrice}}</span>
</span>
<span class="period"><t>for {{term.firstPeriod}}</t></span>
</span>
</td>
<td class="choose">
<a href="javascript:void(0)" ng-click="startCheckout(term.termId)" class="button start-new-checkout big">
<t>Purchase</t>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
This final section is for gifts. The allowRedemption method shown below returns true if a Piano application has gifting enabled. The rest of this section is for gift receivers so they redeem gift subscriptions they've received:
<div class="offer-redemption" ng-show="allowRedemption()">
<table>
<tr>
<td class="title">
<t>Did you receive a voucher?</t>
</td>
<td class="choose">
<a ng-click="startRedemption()" class="button big"><t>Redeem</t></a>
</td>
</tr>
</table>
</div>
</div>
</div>