Widget Integration Instructions
const crypto = require("crypto");
const url = require("url");
const KEY = "sample-api-key";
const SECRET = "sample-secret";
const recipientEmail = "sample@recipient.com";
const recipientReferenceId = "sample@recipient.com";
const widgetBaseUrl = new url.URL("https://widget.trolley.com");
const querystring = new url.URLSearchParams({
ts: Math.floor(new Date().getTime() / 1000),
key: KEY,
email: recipientEmail,
refid: recipientReferenceId,
hideEmail: "false", // optional parameter: if 'true', hides the email field
roEmail: "false", // optional parameter: if 'true', renders the email field as Read Only
// payoutMethods: "bank-transfer,paypal", // optional parameter: filters the possible payout methods shown on the widget
locale: "en", // optional parameter: ISO 639-1 language code, changes the language of the widget
/*
** Adding address fields is optional, Used to easily populate
** the widget with default values.
**
'addr.firstName': 'firstName',
'addr.lastName': 'lastName',
'addr.governmentId': 'governmentId',
'addr.street1': 'street1',
'addr.street2': 'street2',
'addr.city': 'city',
'addr.postalCode': 'postalCode',
'addr.region': 'AL',
'addr.country': 'US',
*/
/*
** Adding color fields is also optional, used to override the
** color settings set in the dashboard. Note that these overrides must
** be valid CSS compatible colors.
**
'colors.heading': '#111111',
'colors.inputText': '#222222',
'colors.inputBorder': '#333333',
'colors.text': '#555555',
'colors.subText': '#666666',
'colors.background': '#777777',
'colors.primary': 'red',
'colors.border': 'blue',
'colors.success': '#AAAAAA',
'colors.error': '#BBBBBB',
'colors.warning': '#CCCCCC',
*/
})
.toString()
.replace(/\+/g, "%20");
const hmac = crypto.createHmac("sha256", SECRET);
hmac.update(querystring);
// Signature is only valid for 30 seconds
const signature = hmac.digest("hex");
widgetBaseUrl.search = querystring + "&sign=" + signature;
// you can send the link to your view engine
const widgetLink = widgetBaseUrl.toString();
<?php
$WIDGET_BASE_URL = "https://widget.trolley.com";
$KEY = "sample-api-key";
$SECRET = "sample-secret";
$recipientEmail = "sample@recipient.com";
$recipientReferenceId = "sample@recipient.com";
$ts = time();
$querystring = http_build_query([
"refid" => $recipientReferenceId,
"ts" => $ts,
"key" => $KEY,
"email" => $recipientEmail,
"hideEmail" => "false", // optional parameter: if "true", hides the email field
"roEmail" => "false", // optional parameter: if "true", renders the email field as Read Only
// "payoutMethods" => "bank-transfer,paypal", // optional parameter: filters the possible payout methods shown on the widget
"locale" => "en", // optional parameter: ISO 639-1 language code, changes the language of the widget
/*
** Adding address fields is optional, Used to easily populate
** the widget with default values.
**
"addr.firstName" => "firstName",
"addr.lastName" => "lastName",
"addr.governmentId" => "governmentId",
"addr.street1" => "street1",
"addr.street2" => "street2",
"addr.city" => "city",
"addr.postalCode" => "postalCode",
"addr.region" => "AL",
"addr.country" => "US",
*/
/*
** Adding color fields is also optional, used to override the
** color settings set in the dashboard. Note that these overrides must
** be valid CSS compatible colors.
**
"colors.heading" => "#111111",
"colors.inputText" => "#222222",
"colors.inputBorder" => "#333333",
"colors.text" => "#555555",
"colors.subText" => "#666666",
"colors.background" => "#777777",
"colors.primary" => "red",
"colors.border" => "blue",
"colors.success" => "#AAAAAA",
"colors.error" => "#BBBBBB",
"colors.warning" => "#CCCCCC",
*/
], null, "&", PHP_QUERY_RFC3986);
# Signature is only valid for 30 seconds
$signature = hash_hmac("sha256", $querystring, $SECRET);
$widget_link = $WIDGET_BASE_URL."?". $querystring."&sign=".$signature
?>
<iframe src="<?php echo $widget_link ?>"></iframe>
# -*- coding: iso-8859-15 -*-
import hashlib
import hmac
import time
try:
import urllib.parse
urlencode = urllib.parse.urlencode
except:
import urllib
urlencode = urllib.urlencode
WIDGET_BASE_URL = 'https://widget.trolley.com'
KEY = b'sample-api-key'
SECRET = 'sample-secret'
recipient_email = 'sample@recipient.com'
recipient_reference_id = 'sample@recipient.com'
query = urlencode({
'ts': int(time.time()),
'email': recipient_email,
'refid': recipient_reference_id,
'hideEmail': 'false', # optional parameter: if 'true', hides the email field
'roEmail': 'false', # optional parameter: if 'true', renders the email field as Read Only
# 'payoutMethods': 'bank-transfer,paypal', # optional parameter: filters the possible payout methods shown on the widget
'locale': 'en', # optional parameter: ISO 639-1 language code, changes the language of the widget
# 'addr.firstName' : 'firstName', # Adding addr. fields is optional. Used to easily populate
# 'addr.lastName': 'lastName', # the widget with default values.
# 'addr.governmentId': 'governmentId',
# 'addr.street1': 'street1',
# 'addr.street2': 'street2',
# 'addr.city': 'city',
# 'addr.postalCode': 'postalCode',
# 'addr.region': 'AL',
# 'addr.country': 'US',
# 'colors.heading': '#111111', # Adding color fields is also optional, used to override the
# 'colors.inputText': '#222222', # color settings set in the dashboard. Note that these overrides must
# 'colors.inputBorder': '#333333', # be valid CSS compatible colors.
# 'colors.text': '#555555',
# 'colors.subText': '#666666',
# 'colors.background': '#777777',
# 'colors.primary': 'red',
# 'colors.border': 'blue',
# 'colors.success': '#AAAAAA',
# 'colors.error': '#BBBBBB',
# 'colors.warning': '#CCCCCC',
'key': KEY,
}).replace("+", "%20").encode('utf8')
query = '%s&sign=%s' % (
query,
hmac.new(SECRET, query, digestmod=hashlib.sha256).hexdigest()
)
require "openssl"
require "ostruct"
require "uri"
WIDGET_BASE_URL = "https://widget.trolley.com"
API = "sample-api-key"
SECRET = "sample-secret"
recipient_email = "sample@recipient.com"
recipient_reference_id = "sample@recipient.com"
ts = Time.now.to_i
query = {
'email' => recipient_email,
'refid' => recipient_reference_id,
'ts' => ts,
'key' => API,
'hideEmail' => false, # optional parameter: if 'true', hides the email field
'roEmail' => false, # optional parameter: if 'true', renders the email field as Read Only
# 'payoutMethods' => 'paypal', # optional parameter: filters the possible payout methods shown on the widget
'locale' => 'en' # optional parameter: ISO 639-1 language code, changes the language of the widget
# 'addr.firstName' => 'firstName', # Adding addr. fields is optional. Used to easily populate
# 'addr.lastName' => 'lastName', # the widget with default values.
# 'addr.governmentId' => 'governmentId',
# 'addr.street1' => 'street1',
# 'addr.street2' => 'street2',
# 'addr.city' => 'city',
# 'addr.postalCode' => 'postalCode',
# 'addr.region' => 'AL',
# 'addr.country' => 'US',
# 'colors.heading' => '#111111', # Adding color fields is also optional, used to override the
# 'colors.inputText' => '#222222', # color settings set in the dashboard. Note that these overrides must
# 'colors.inputBorder' => '#333333', # be valid CSS compatible colors.
# 'colors.text' => '#555555',
# 'colors.subText' => '#666666',
# 'colors.background' => '#777777',
# 'colors.primary' => 'red',
# 'colors.border' => 'blue',
# 'colors.success' => '#AAAAAA',
# 'colors.error' => '#BBBBBB',
# 'colors.warning' => '#CCCCCC'
}
digest = OpenSSL::Digest.new("sha256")
query_string = URI.encode_www_form(query).gsub("+", "%20")
# Signature is only valid for 30 seconds
signature = OpenSSL::HMAC.hexdigest(digest, SECRET, query_string)
# you can use the link in your templating engine
widget_link = "#{WIDGET_BASE_URL}?#{query_string}&sign=#{signature}"
using System;
using System.Text;
using System.Collections.Generic;
using System.Security.Cryptography;
class Program
{
static void Main(string[] args)
{
var API = "sample-api-key";
var SECRET = "sample-secret";
var widgetBaseUrl = "https://widget.trolley.com";
var recipientEmail = "sample@recipient.com";
var recipientReferenceId = "sample@recipient.com";
Dictionary<string, string> queryParams = new Dictionary<string, string>{
{ "ts", $"{(int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds}" },
{ "key", $"{API}" },
{ "email", $"{recipientEmail}" },
{ "refid", $"{recipientReferenceId}" },
{ "hideEmail", "false" }, // optional parameter: if true, hides the email field
{ "roEmail", "false" }, // optional parameter: if true, renders the email field as Read Only
// { "payoutMethods", "bank-transfer" }, // optional parameter: filters the possible payout methods shown on the widget
{ "locale", "en" } // optional parameter: ISO 639-1 language code, changes the language of the widget
/*
** Adding address fields is optional, Used to easily populate
** the widget with default values.
**
{ "addr.firstName", "firstName" },
{ "addr.lastName", "lastName" },
{ "addr.governmentId", "governmentId" },
{ "addr.street1", "street1" },
{ "addr.street2", "street2" },
{ "addr.city", "city" },
{ "addr.postalCode", "postalCode" },
{ "addr.region", "AL" },
{ "addr.country", "US" },
*/
/*
** Adding color fields is also optional, used to override the
** color settings set in the dashboard. Note that these overrides must
** be valid CSS compatible colors.
**
{ "colors.heading", "#111111" }
{ "colors.inputText", "#222222" }
{ "colors.inputBorder", "#333333" }
{ "colors.text", "#555555" }
{ "colors.subText", "#666666" }
{ "colors.background", "#777777" }
{ "colors.primary", "red" }
{ "colors.border", "blue" }
{ "colors.success", "#AAAAAA" }
{ "colors.error", "#BBBBBB" }
{ "colors.warning", "#CCCCCC" }
*/
};
string query = "";
foreach (var kvp in queryParams)
{
query += $"{kvp.Key}={Uri.EscapeDataString(kvp.Value)}&";
}
HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(SECRET));
// Signature is only valid for 30 seconds
var signature = (BitConverter.ToString(hmac.ComputeHash(Encoding.UTF8.GetBytes(query)))).Replace("-", "").ToLower();
string returnUrl = ($"{widgetBaseUrl}?{query}&sign={signature}");
}
}
// Created using Java 19
// Using org.apache.http.client.utils.URIBuilder;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.http.client.utils.URIBuilder;
public class Widget
{
public String getTrolleyWidgetUrl() throws URISyntaxException, NoSuchAlgorithmException, InvalidKeyException
{
// Setup values
String SCHEME = "https";
String WIDGET_HOST = "widget.trolley.com";
String ACCESS_KEY = "<YOUR_ACCESS_KEY>";
String ACCESS_SECRET = "<YOUR_ACCESS_KEY>";
int timestamp = (int)(System.currentTimeMillis() / 1000L);
String recipientEmail = "sample@recipient.com";
String recipientReferenceId = "sample@recipient.com";
// Adding query parameters
URIBuilder uriBuilder = new URIBuilder();
uriBuilder.addParameter("ts", String.format("%d",timestamp));
uriBuilder.addParameter("key", ACCESS_KEY);
uriBuilder.addParameter("refid", recipientReferenceId);
uriBuilder.addParameter("email", recipientEmail);
/*
* Adding the following fields is optional, Used to easily populate
* the widget with default values.
*/
// uriBuilder.addParameter("hideEmail", "true");
// uriBuilder.addParameter("roEmail", "false");
// uriBuilder.addParameter("locale", "en");
// uriBuilder.addParameter("payoutMethods", "bank-transfer,paypal");
// uriBuilder.addParameter("addr.firstName", "John");
// uriBuilder.addParameter("addr.lastName", "Smith");
// uriBuilder.addParameter("addr.governmentId", "ABCD123");
// uriBuilder.addParameter("addr.street1", "120 Some Street");
// uriBuilder.addParameter("addr.street2", "Block 123");
// uriBuilder.addParameter("addr.city", "Montreal");
// uriBuilder.addParameter("addr.postalCode", "A0A B0B");
// uriBuilder.addParameter("addr.region", "QC");
// uriBuilder.addParameter("addr.country", "CA");
/*
* Adding color fields is also optional, used to override the
* color settings set in the dashboard. Note that these overrides must
* be valid CSS compatible colors.
*/
// uriBuilder.addParameter("addr.country", "CA");
// uriBuilder.addParameter("colors.heading", "#111111");
// uriBuilder.addParameter("colors.inputText", "#222222");
// uriBuilder.addParameter("colors.inputBorder", "#333333");
// uriBuilder.addParameter("colors.text", "#555555");
// uriBuilder.addParameter("colors.subText", "#666666");
// uriBuilder.addParameter("colors.background", "#FFFFFF");
// uriBuilder.addParameter("colors.primary", "red");
// uriBuilder.addParameter("colors.border", "blue");
// uriBuilder.addParameter("colors.success", "#AAAAAA");
// uriBuilder.addParameter("colors.error", "#BBBBBB");
// uriBuilder.addParameter("colors.warning", "#CCCCCC");
// Preparing to create hash
String encodedParams = uriBuilder.build().toString().substring(1);
final SecretKeySpec encodedSecretKey = new SecretKeySpec(ACCESS_SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
final Mac mac = Mac.getInstance("HmacSHA256");
mac.init(encodedSecretKey);
final byte[] paramBytes = mac.doFinal(encodedParams.getBytes(StandardCharsets.UTF_8));
final StringBuffer hash = new StringBuffer();
for (int i = 0; i < paramBytes.length; ++i) {
final String hex = Integer.toHexString(0xFF & paramBytes[i]);
if (hex.length() == 1) {
hash.append('0');
}
hash.append(hex);
}
// Create signed hash
String digest = hash.toString();
// Add hashed value to query parameter
uriBuilder.addParameter("sign", digest);
// Set scheme and host
uriBuilder.setScheme(SCHEME);
uriBuilder.setHost(WIDGET_HOST);
// Create Widget URI String to load into an iframe
return uriBuilder.build().toString();
}
}
Step 1
Copy the block of code below into your application, usually under your recipient profile page where you wish to display the Recipient payout preferences screen.
Step 2
Replace the following fields in the code with the appropriate values for your account and the recipient you will create or update:
API Keys:
API Keys can be created on the dashboard in the Settings > API Keys section
SECRET:
Secrets can be created on the dashboard in the Settings > API Keys section
RECIPIENT_EMAIL:
Email address of the recipient you will create or edit. Using an email address that is not yet linked to a recipient in your Trolley account will create a new recipient with that email address in your Trolley account. All email addresses are unique.
RECIPIENT_REFERENCEID:
Your internal recipient Reference ID for the recipient you will create or edit. If no Reference ID is provided Trolley will create a default value.
RECIPIENT_ADDRESS:
A set of optional fields which will be used as default values in the recipient information section of the Widget.
Supported ISO 639-1 language codes:
Code | Language |
---|---|
bg | Bulgarian |
bn | Bengali |
cs | Czech |
da | Danish |
de | Deutsch |
el | Greek |
en | English |
es | Spanish |
fi | Finnish |
fr | French |
hr | Croatian |
hu | Hungarian |
id | Indonesian |
it | Italian |
ja | Japanese |
ko | Korean |
lt | Lithuanian |
lv | Latvian |
mk | Macedonian |
ms | Malay |
nl | Dutch |
no | Norwegian |
pl | Polish |
pt | Portuguese |
pt-BR | Brazilian Portuguese |
ro | Romanian |
ru | Russian |
sk | Slovakian |
sl | Slovenian |
sv | Swedish |
th | Thai |
tr | Turkish |
uk | Ukrainian |
vi | Vietnamese |
zh | Chinese (Traditional) |
zh-CN | Chinese (Simplified) |
Step 3
widgetLink
is the URL that needs to be returned by your application, to be either used in an iFrame or in a browser address bar. Typically, the URL would be used in a page related to a recipients account profile or their payout preferences.
Note: The hmac signature is only valid for 30 seconds.
Refid and email
This is the complete rules for what happens when you use the refid
and email
parameter as
argument to the widget url.
If we assume that we have a system which already has two registered users:
- joe@example.com with referenceId = 1234
- tom@example.com with referenceId = 7777
Calling the widget with the following parameters will result in the following behaviors:
joe@example.com | tom@example.com | bob@example.com (new email) | (blank) | |
---|---|---|---|---|
1234 | Return widget for joe | Error 403: mismatch | change email to bob | Return widget for joe |
5678 | change refid to 5678 | change refid to 5678 | create new recipient | Error 403 |
7777 | Error 403: mismatch | Return widget for tom | change email to bob | Return widget for tom |
(blank) | Return widget for joe | Return widget for tom | create new recipient | Error 403 |
As a reminder the Trolley treats email adress as a unique key across the system.
Widget events emitter (beta)
(function() {
window.addEventListener("message", function(e) {
var widgetEvent = e.data;
if (widgetEvent.event === "document.height") {
//Document's height changed
console.debug('Changed Height', widgetEvent.document.height);
//Example Action: setting iframe height as per data received
var iframeElement = document.getElementById("payment-rails-widget");
if (iframeElement) {
iframeElement.style.height = widgetEvent.document.height;
}
}else if(widgetEvent.event === 'payment.created'){
// A new payment method was added, take any action
console.debug('Account ID', widgetEvent.account.id);
}else if(widgetEvent.event === 'payment.deleted'){
// A new payment method was deleted, take any action
console.debug('Account ID', widgetEvent.account.id);
}else if(widgetEvent.event === 'taxForm.submitted'){
// A tax form was submitted, take any action
console.debug('TaxForm ID', widgetEvent.taxForm.id);
}
});
})();
Widget emits events upon successful completion of some selected activities, so that your web page can take any actions when these activies happen.
This is how the event emitter also enables cross-origin communication between the page and the embedded iframe.
To listen to these Widget events, you need to implement an event listener using window.addEventListener("message", function(e) {...})
in your webpage that hosts the widget iframe. The events object contains some additional data which you can make use of.
Switch to the JavaScript view in the code viewer to see an example.
The following events will be available:
List of events:
Event Name | Event Object | Format | Description |
---|---|---|---|
document.height | data.document.height | number | The height of the iframe content |
payment.created | data.account.id | string | The account id whose payout was created |
payment.deleted | data.account.id | string | The account id whose payout was deleted |
taxForm.submitted | data.taxForm.id | string | The id of the taxform submitted |
Responding to events on mobile devices
If you’re loading the widget inside a webview on your Android/iOS apps, you can use the native bindings that Android and iOS provide, to listen to these events.
Follow these simple steps for this:
- Prepare a webpage which contains the widget iframe, along with your JavaScript code. This code will listen to the widget events.
- Load this webpage in the webview, and setup your native code binding (see blow for Android/iOS platform documentation).
- Setup your JavaScript code to call the native callback methods, whenever it receives any widget event.
For Android, here’s the official Android documentation detailing how to Bind JavaScript to Android code.
For iOS, here’s the official iOS documentation that details how to use WKUserContentController to bind with JavaScript.
Debugging Integration Errors
Not every integration goes right the first time. If you see the message
- Something went wrong…
Contact your administrator if the problem persists.
To assist you in debugging these issues, we’ve appended error messages into the HTML. If you Inspect the page, you will see a block of code similar to the following:
-
...
<!--ERRORS: invalid_api_key Timestamp is more than 30 seconds off of server time -->
</body>
The ERROR HTML comment should help you debug the issue, common values are:
- Invalid API Key – The Public key is not found
- Timestamp is more than 30 seconds off of server time – You need to syncronize your clock’s UTC time
- Invalid token: bad hash – Your computation of the signed value is incorrect
- Invalid email – Your email is incorrectly formatted
Here are some extra tips:
-
- Make sure to generate a new URL everytime the widget is loaded.
-
- Ensure that your API key and secret aren’t disabled, and that they point to the appropriate environment for you integration (live or sandbox)
Tickets in Widget
Tickets are created by the Trolley team when we need additional information from a recipient regarding a payment. They can be automatically sent directly to your recipients for prompt response to facilitate their payment without you having to individually contact them
When a Ticket is created to request for information from a recipient, the widget automatically shows a notice and requests the recipients for the information.
No Dev Setup Required
The tickets flow doesn’t require any coding to setup. However, you may need to do configuration in the Trolley Dashboard.
To find out how to do that, please refer to our product documentation: Getting Started with Tickets