v1.4.4 August 16, 2023

Prerequisites

  1. Request application setup with your Vouchr Representative
  2. Receive a Vouchr-Hosted Web Application Url or host your own, and optionally a set of Client IDs for multi-app setups

Summary

The complete eCard integration process involves the following steps:

  1. Backend: FI to provide JWT to secure eCard experience
  2. Frontend: FI to integrate the create experience
  3. Frontend: FI to show the create widget (optional)
  4. Frontend: FI to add edit abilities (optional)
  5. Frontend: FI to add remove abilities (optional)

Backend

Authorizing Users (Backend)

A JWT, created and signed by the FI’s backend with a secured asymmetric key allows Vouchr to verify that the user is coming from a valid FI banking login. For an overview of JWTs and how they function, consult https://jwt.io/

Backend Flow Image

JWT Properties

We recommend using one of the following RSA signature methods:

PS256, PS384, PS512

We require the following claims within the JWT

  • sub - subject - should be an anonymous user-id uniquely identifies the eCard user and is constant for this customer. It must not be an id that could be used to tie the customer to information outside of the context of eCards. It should be alphanumeric, and the length must be between 14 and 64 characters. It can contain dashes. Sample value: c1ff176d-b708-4417-9968-96b0587cc439

  • exp - expiry - a timestamp which specifies the expiration of this token, we recommend 30 minutes in the future. Claim syntax is intDate. Note this must be in a numeric format, formatted as time since the Epoch in seconds. Make sure this is sent as a numeric type, without quotes. Sample Value: 1578937931

We support the following additional claims for FI’s who would like the extra validation

  • nbf - not before - a timestamp which specifies the earliest time the token may be consumed. Claim syntax is intDate. Note this must be in a numeric format, formatted as time since the Epoch in seconds. Make sure this is sent as a numeric type, without quotes. Sample Value: 1578937931.This claim is OPTIONAL.

  • iat - issued at - This claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the token. This claim is OPTIONAL. Claim syntax is intDate. Sample Value: 1578937931.

  • jti - JSON token id - The claim provides a unique identifier for the JWT. The jti claim can be used to prevent the JWT from being replayed. The jti value is case sensitive. Claim syntax is a string. This claim is OPTIONAL.

In the examples below, we will show you how to construct and sign a valid JWT.

Creating a key-pair with OpenSSL

Creating the private/public key-pair can be done with OpenSSL as shown here or with your choice of tool. This key-pair allows you to sign and Vouchr to verify the authenticity of the JWT.

Creating the private key with OpenSSL

The private key is held securely within the FI Backend, allowing it to authoritatively sign the JWT.

OpenSSL example:

openssl genrsa -out privkey.pem 2048

Creating the public key with OpenSSL

The public-key allows Vouchr Services to verify the validity of the JWT

OpenSSL example:

openssl rsa -in privkey.pem -pubout > key.pub

Share the key.pub with Vouchr to allow the verification of your JWT signatures. Store the private key securely within your backend

Selecting a JWT library.

In this guide, we have selected nimbus-jose-jwt, one of the more common JWT libraries for java. If you are using another language or would like to select a different library, we recommend consulting https://jwt.io/#libraries. As long as you can fulfill the above requirements, the JWT will work.

To include nimbuse-jose-jwt within your project, if you are using a maven-based build process, you can include it in your pom.xml with the following.

<!-- https://mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt -->
<dependency>
 <groupId>com.nimbusds</groupId>
 <artifactId>nimbus-jose-jwt</artifactId>
 <version>8.2</version>
</dependency>

Additionally, to support RSA PSS Signatures, you’ll need the bouncycastle library if you do not have it already.

add the following to your pom.xml

<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
 <groupId>org.bouncycastle</groupId>
 <artifactId>bcprov-jdk15on</artifactId>
 <version>1.64</version>
</dependency>

Then add the bouncycastle provider at app startup with:

Provider bc = BouncyCastleProviderSingleton.getInstance();
Security.addProvider(bc);

Within the FI backend, provide a method to generate the JWT for eCards

The FI’s backend should verify the customer’s session and then generate a JWT using code similar to the following

public String getVouchrJwt(RSAKey rsaKey, String customerUuid) {
    // Create RSA-signer with the private key
    JWSSigner signer = new RSASSASigner(rsaKey);

    // Prepare JWT with claims set
    JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
        .subject(customerUuid)
        .expirationTime(new Date(new Date().getTime() + 30 * 60 * 1000L))
        .build();

    SignedJWT signedJWT = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.PS256).build(),claimsSet);

     // Compute the RSA signature
    signedJWT.sign(signer);
     // serialize to a compact form
    return signedJWT.serialize();
}

This signed JWT should be returned to your application via regular communication channels.

 

 

Client Integration (Frontend)

Integrating the Create Experience (Required)

Create Modal Image

Step 1: Launch the Webview/iframe

We will be including the Vouchr content in an IFrame or native WebView that allows you to isolate your application/site from Vouchr in a secure manner, while stilll while still giving you full visual control and a rich experience for your users.

First we will create an iframe/webview and then load the URL at which the Discover / Create Web Application has been deployed, passing in the relevant parameters

Create Experience Parameters

Required

Optional

  • cid the eCard Client ID (only needed in multi-app setups)
  • amt the amount of the monetary gift (eg. 50.00)
  • cur the currency of the monetary gift (eg. USD)
  • code a code, such as a gift code that can be transmitted with the merchant detail info (eg. 1234-123-1234)
  • pin a pin that can be associated with the merchant detail info (eg. 1234)
  • locale overrides the default system locale of the ui with the passed in locale (eg. pt-BR)
  • action Needed only if you’re using the Create Widget to link to specific content. No value will open the default discover experience
  • recipient a display name for the recipient of the gift (e.g. Velma)
  • sender a display name for the sender of the gift (e.g. Fred)
  • merchantExternalId for applications with multiple configured merchants, the selected merchant can be passed in here (e.g. amazoncom)

Note that these parameters will be passed in using a Url Fragment (#) instead of Query Parameter (?) as a security measure to allow us to pass them to the iframe without transmitting over an HTTP GET request

Embed the iframe in the page (on mobile, Vouchr recommends that the iframe takes up the full width of the page):

  • Web

    We will be including the Vouchr content in an iframe. This allows you to isolate your site from Vouchr in a secure manner, while still giving you full visual control and a rich experience for your users. See the example below.

    Configure an iframe then load the URL at which the Discover / Create Web Application has been deployed and pass in the following parameters

    HTML
    <iframe
      id="vouchrContent"
      class="vouchrIframe"
      width="100%"
      height="100%"
      title="Vouchr Content"
      src=""
    ></iframe>
    
    CSS
    .vouchrIframe {
      border: 0;
    }
    

    Prepare the URL to provide as the src for the iframe using JavaScript:

    JavaScript
    const baseUrl = "baseUrl";
    
    const jwt = "jwt"; // supplied by FI
    const cid = "cid"; // Vouchr partner-supplied Client ID property
    const amt = "50.0"; // the amount of the monetary gift (optional, eg. 50.00)
    const cur = "USD"; // the currency of the monetary gift (optional, e.g. USD)
    const action = "/"; // needed only if using the create widget to direct to specfic content
    
    const url = `${baseUrl}/#tkn=${encodeURIComponent(
      jwt
    )}&cid=${encodeURIComponent(cid)}&amt=${amt}&cur=${cur}&action=${action}`;
    
    document.getElementById("vouchrContent").src = url;
    
  • Android

    In Android, you need to create a WebView using WebChromeClient and set the following options.

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val jwt = /* supplied by FI */
        val cid = /* Partner-supplied Client ID property */
        val amt = "50.0"; // the amount of the monetary gift (optional, eg. 50.00)
        val cur = "USD"; // the currency of the monetary gift (optional, e.g. USD)
        val action = "/"; // optional if you're using the create widget to direct to specific content
    
        webView.settings.javaScriptEnabled = true
        webView.settings.domStorageEnabled = true
        webView.settings.mediaPlaybackRequiresUserGesture = false
        webView.webChromeClient = WebChromeClient()
        webView.loadUrl("$baseURL/#tkn=$jwt&cid=$cid&amt=$amt&cur=$cur&action=$action")
    }
    
  • iOS

    When creating the WKWebView ensure that the following options are turned on. Note that if your WKWebView is initialized via a storyboard, you will have to check the “Inline Playback” box in that element’s attribute’s inspector instead of passing in a WKWebViewConfiguration programmatically.

    let webConfiguration = WKWebViewConfiguration()
    webConfiguration.allowsInlineMediaPlayback = true
    webConfiguration.mediaTypesRequiringUserActionForPlayback = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let jwt = /* supplied by FI */
        let cid = /* Partner-supplied Client ID property */
        let action = /* Optional  */
        val amt = /* Optional the amount of the monetary gift (optional, eg. 50.00) */
        val cur = /* the currency of the monetary gift (optional, e.g. USD) */
        var urlString = "\(baseURL)/#tkn=\(jwt)&cid=\(cid)&amt=\(amt)&cur=\(cur)"
        if let action = action {
            urlString = urlString + "&action=\(action)"
        }
        let url = URL(string: urlString)!
    
    
    
        webView.load(URLRequest(url: url))
    }
    

Troubleshooting

A. If the interface is failing to come up. Perform the below steps to troubleshoot the issue
  1. Using Chrome, visit the URL complete with base URL, client id, and token.
  2. Launch the web inspector by clicking the ‘tools’ menu, followed by ‘web developer’ and ‘inspector’.
  3. Reload the page to see relevant error details in the console, which will allow you to verify the validity of JWT and Client.

Step 2: Obtain the share URL

To obtain the share URL, we will set up an event listener to listen for the mesage returning from the Vouchr UI.

VOUCHER_CREATED event data

Some additional context is available about the returned data in the return event:

  • event.data.url - redemption url including invite code
  • event.data.inviteCode - invite code
  • event.data.printableUrl - url for a printable rendering of the selected eCard
  • event.data.voucherId - unique eCard identifier
  • event.data.paymentInfo.merchant.externalId - if merchants are selectable within the eCard interface, this will return the identifier of selected merchant
  • event.data.shareInfo - metadata that can be used to build message previews or notifications foor the recipient
  • event.data.shareInfo.image - an image for this eCard meant to be used for sharing in messaging applications, it includes brand logo, custom colors and and offset version of the packaging
  • event.data.shareInfo.packagingImage - this image contains the packaging for the image only and is useful for embedding in your own UI
  • event.data.shareInfo.title - a title for the sharing info (‘Fred send you an e-Gift!’)
  • event.data.shareInfo.description - details for the sharing info (‘Click to open your e-Gift!!’)
  • event.data.shareInfo.quick - if this eCard was created using quick-pick GIF or IMAGE, this will be true, otherwise default false
  • event.data.actions - action parameters that can be appended to the base url used to interact with selected merchant
  • event.data.actions.edit - the action parameter used to edit an eCard’s content
  • event.data.actions.delete - the action parameter used to delete an eCard
  • event.data.actions.update - the action parameter used to update an eCard to provide new payment properties
  • event.data.actions.summary - the action parameter used to show an eCard summary

Full Sample Event

{
  "event": "VOUCHER_CREATED",
  "voucherId": 6050059228545024,
  "url": "https://vcfinancial-standalone.xo.cards/reveal/?s=aaGLDiMudE5nR6f4#jBA0AIuHvquQH91o;i",
  "printableUrl": "https://vcfinancial-standalone.xo.cards/reveal/?s=aaGLDiMudE5nR6f4&printed=true#jBA0AIuHvquQH91o;i",
  "inviteCode": "jBA0AIuHvquQH91o",
  "shareInfo": {
    "title": "Jeff sent you an e-Gift!",
    "description": "Click to open your e-Gift!",
    "image": "https://assets.vouchrsdk.com/uploads/vouchrsdk-prod/vcfinancial/vcfinancial-standalone/screenshot/previews/4843999736102912/1b7e3698813d2eae69b25ef2b2ef4cd9.png",
    "favIcon": "https://assets.vouchrsdk.com/uploads/vouchrsdk-prod/vcfinancial/vcfinancial-standalone/webrevealconfig/vcfinancial-1689087385754-removebg-preview-1710093445859-1710881264014.png",
    "siteName": "VCFinancial e-Gift",
    "packagingImage": "https://assets.vouchrsdk.com/uploads/vouchrsdk-prod/store/store/screenshot/previews/4843999736102912/590cf4d1049355a08cf2068e4d8570c4.png",
    "quick": false
  },
  "paymentInfo": {
    "currency": "USD",
    "merchant": {
      "id": 5097407799885824,
      "name": "AutoDeposit",
      "iconUrl": "https://assets.vouchrsdk.com/uploads/vouchrsdk-prod/vcfinancial/vcfinancial-standalone/merchant/vcfinancial-1689087385754-removebg-preview-1710093780275.png",
      "shortDescription": "Your money has been auto-deposited",
      "logoImageUrl": "https://assets.vouchrsdk.com/uploads/vouchrsdk-prod/vcfinancial/vcfinancial-standalone/merchant/vcfinancial-1689087385754-removebg-preview-1710093780275.png",
      "messageTitle": "You received an e-Gift! ",
      "externalId": "auto-deposit",
    }
  },
  "deleteAction": "%2Fdelete%2F6050059228545024",
  "editAction": "%2Fvoucher%2F6050059228545024",
  "summaryAction": "%2Fpersonalize%2Fsummary%2F6050059228545024",
  "actions": {
    "delete": "%2Fdelete%2F6050059228545024",
    "edit": "%2Fvoucher%2F6050059228545024",
    "preview": "%2Fpreview%2F6050059228545024",
    "update": "%2Fupdate%2F6050059228545024",
    "summary": "%2Fpersonalize%2Fsummary%2F6050059228545024"
  },
  "reason": "NEW"
}

  • Web

    Setup an event listener using JavaScript, to listen for the message from the iframe that contains the Vouchr URL (see the documentation on window.postMessage() for more details). Note, it is imperative that you verify the sender’s identity using the origin property. See the section on ‘Security concerns’ in the link above for more details.

    JavaScript
    const receiveMessage = (event) => {
      if (event.origin !== "https://partner.vc.hr") {
        // Ensure that the origin of the message is from the iframe
        return;
      } else if (event.data && event.data.event && event.data.url) {
        if (event.data.event === "VOUCHER_CREATED") {
          const vouchrData = {
            url: event.data.url,
            inviteCode: event.data.inviteCode
            voucherId: event.data.voucherId,
            editAction: event.data.actions.edit,
            deleteAction: event.data.actions.delete,
            summaryAction: event.data.actions.summary,
            merchantExternalId: event.data.paymentInfo.merchant.externalId,
            updateAction: event.data.actions.update,
            shareImage: event.data.shareInfo.image,
            packagingImage: event.data.shareInfo.packagingImage,
            title: event.data.shareInfo.title,
            description: event.data.shareInfo.description,
            quickMode: event.data.shareInfo.packagingImage,
          };
    
          handleVouchrData(vouchrData);
        } else {
          return;
        }
      }
    };
    
    window.addEventListener("message", receiveMessage, false);
    
    const handleVouchrData = (vouchrData) => {
      // to be implemented by FI, may launch share sheet, send to internal FI systems, etc
    };
    
  • Android

    Attach a javascript interface to obtain the share URL

    private inner class WebAppInterface {
        @JavascriptInterface
        fun postMessage(message: String) {
            val messageJson = JSONObject(message)
            if (messageJson.getString("event") == "VOUCHER_CREATED") {
                val redemptionUrl = messageJson.getString("url")
                // store the actions incase we want to remove/update the eCard later
                val deleteAction = messageJson.getObject("actions").getString("delete")
                val updateAction = messageJson.getObject("actions").getString("update")
    
                if (redemptionUrl.startsWith(verifiedHost)) {
                    setActivityResult(redemptionUrl, deleteAction)
                } else {
                    setActivityResult(null)
                }
            }
        }
    }
    
    webView.addJavascriptInterface(WebAppInterface(), "appInterface")
    
  • iOS

    In the view controller that will display the WKWebView implement the WKScriptMessageHandler protocol to obtain the share URL. Then attach the WKScriptMessageHandler to the webView

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if let messageString = message.body as? String,
            let data = messageString.data(using: .utf8),
            let eventDictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
    
            if eventDictionary["event"] as? String == "VOUCHER_CREATED" {
              guard let urlString = eventDictionary["url"] as? String else { return }
    
              guard let shareInfo = eventDictionary["shareInfo"] as? [String: Any], let
                          envelopeUrl = shareInfo["packagingImage"] as? String, let logoUrl = shareInfo["favIcon"] as? String, let messageTitle = shareInfo["title"] as? String, let messageDescription = shareInfo["description"] as? String else {
                      return
                  }
              // store the values for iOS sharing
              self.redemptionUrl = urlString
              self.envelopeUrl = envelopeUrl
              self.logoUrl = logoUrl
              self.messageTitle = messageTitle
              self.messageDescription = messageDescription
    
              //validate url
              guard redemptionUrl.scheme == "https" && redemptionUrl.host == verifiedHost else {
                  print("invalid url")
                  return
              }
              // store the actions incase we want to remove/update the eCard later
              self.deleteAction = eventDictionary["actions"]["delete"]
              self.updateAction = eventDictionary["actions"]["update"]
    
              // handle url e.g. present the share sheet
              let items = [self]
              let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil)
    
              // IPAD sharesheet requirement
              activityVC.excludedActivityTypes = [.airDrop]
              if let popoverController = activityVC.popoverPresentationController {
                  popoverController.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height / 2, width: 0, height: 0)
                  popoverController.sourceView = self.view
                  popoverController.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
              }
    
              present(activityVC, animated: true)
          }
        }
    }
    
        func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any {
            return self.activityViewController(activityViewController, itemForActivityType: UIActivity.ActivityType.message) ?? ""
        }
    
        func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? {
            let buttonText = "Open"
            let footerMessage = "Brought to you by Vouchr!"
    
            let backgroundColor = "#0c2da1"
            let textColor = "white"
            let buttonColor = "#007aff"
            let buttonTextColor = "white"
    
            // Apple Mail HTML
            let htmlString = """
                <html>
                  <body style="font-family: sans-serif">
                    <table
                      class="main"
                      style="
                        background-color: \(backgroundColor);
                        color: \(textColor);
                        margin: auto;
                        min-width: 500px;
                        padding-top: 32px;
                        padding-bottom: 32px;
                        text-align: center;
                        width: 500px;
                      "
                    >
                      <tr>
                        <td>
                          <img
                            class="logo"
                            src="\(self.logoUrl)"
                            style="height: 48px"
                          />
                          <h1>\(self.messageTitle)</h1>
                          <p>\(self.messageDescription)</p>
                          <img
                            class="envelope"
                            src="\(self.envelopeUrl)"
                            style="padding: 20px 0px 20px 0px"
                          />
                          <div>
                            <a
                              href="\(self.redemptionUrl)"
                              target="_blank"
                            >
                              <button
                                style="
                                  background: \(buttonColor);
                                  border-color: \(buttonColor);
                                  border-radius: 999px;
                                  border-style: solid;
                                  color: \(buttonTextColor);
                                  font-size: large;
                                  height: 40px;
                                  width: 200px;
                                "
                              >
                                \(buttonText)
                              </button>
                            </a>
                          </div>
                        </td>
                      </tr>
                    </table>
    
                    <table
                      class="footer"
                      style="
                        border: 1px solid #e0e0e088;
                        margin: auto;
                        min-width: 500px;
                        padding: 24px 0px 24px 0px;
                        width: 500px;
                      "
                    >
                      <tr>
                        <td>
                          <p
                            class="footerMessage"
                            style="margin: auto; text-align: center; width: 70%;"
                          >
                            \(footerMessage)
                          </p>
                        </td>
                      </tr>
                    </table>
                  </body>
                </html>
            """
    
            if (activityType == .mail) {
                return htmlString
            }
            return redemptionUrl
        }
    
    func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String {
            // Apple Mail Subject line
            return "Check out this eCard!"
        }
    
    webView.configuration.userContentController.add(self, name: "appInterface")
    

Step 3: Share the URL

The redemption URL can be inserted in your existing email/SMS notifications that are sent to the recipient, or you can allow the sender to deliver the redemption URL directly to the recipient via the native share sheet.

  • Web

    Unfortunately native share is poorly supported with most desktop browsers, though it is well supported on Mobile. We recommend using your own share options as a fallback. Below is an example with three options Copy to clipboard, Email, and Native share.

    HTML

    <div>
      <button id="email-share" onClick="email()">Email</button>
      <button id="clip-share" onClick="copy()">Copy to Clipboard</button>
      <button id="native-share" onClick="share()">Share</button>
    </div>
    
    Javascript
    // This is the url you plan to share returned from VOUCHER_CREATED event in Step 2
    const url = vouchrData.url;
    
    const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
    
    const copy = () => {
      copyClipboard(url);
      document.getElementById("clip-share").innerHTML = "Copied";
    };
    const email = () => emailShare(url);
    const share = () => nativeShare(url);
    
    if (isMobile && navigator.share) {
      // On mobile only native share is a good idea to avoid UX duplication
      document.getElementById("email-share").style.display = "none";
      document.getElementById("clip-share").style.display = "none";
    } else if (!navigator.share) {
      // This is only supported on mobile and safari so we must hide
      document.getElementById("native-share").style.display = "none";
    }
    
    const copyClipboard = (string) => {
      const dummyNode = document.createElement("input");
      document.body.appendChild(dummyNode);
      dummyNode.setAttribute("id", "dummy_id");
      dummyNode.setAttribute("value", string);
      dummyNode.select();
      document.execCommand("copy");
      document.body.removeChild(dummyNode);
    };
    
    const emailShare = (string) => {
      const email = "";
      const subject = "Check out this eCard";
      const emailBody = `Hi,%0DI sent you an eCard!%0DCheck it out here: ${string}%0D%0DCheers!`;
      window.open("mailto:" + email + "?subject=" + subject + "&body=" + emailBody);
    };
    
    const nativeShare = (string) => {
      navigator
        .share({
          title: "Share eCard",
          text: string,
        })
        .then(() => console.log("Thanks for sharing!"))
        .catch((err) => console.log(`Couldn't share: `, err.message));
    };
    
  • Android
    ShareCompat.IntentBuilder.from(this)
    	.setType("text/plain")
    	.setChooserTitle("Share your eCard with...")
    	.setText(redemptionUrl)
    	.startChooser()
    
  • iOS
    let activityVC = UIActivityViewController(activityItems: [redemptionUrl], applicationActivities: nil)
    present(activityVC, animated: true)
    

Create Widget (Optional)

Create Widget Image

The Create Widget is the entry point from the FI’s banking app that takes the customer to the eCard flow. It improves the user experience by allowing customers to select an eCard with fewer clicks. The button also drives more usage and more repeat users since the Create Widget updates dynamically based on upcoming occasions (Christmas, Valentine’s Day, etc) and allows the FI to showcase the most popular content available.

Step 1

Configure a WebView/iframe in existing FI user interface to hold the Create Widget then load the discover URL passing in the following URL parameters:

  • tkn the JWT supplied by the backend (Alternative option see: Alternative Authentication Method)
  • cid the Vouchr Client ID
  • action=%2Fpersonalize the command to show the create widget
  • Web

    Include the Vouchr content in an iframe. This allows you to isolate your site from Vouchr in a secure manner, while still giving you full visual control and a rich experience for your users. See the example below.

    Embed the iframe in the page (on mobile, Vouchr recommends that the iframe takes up the full width of the page):

    HTML
    <iframe
      id="vouchrWidgetContent"
      class="vouchrIframe"
      width="100%"
      height="100%"
      title="Vouchr Content"
      src=""
    ></iframe>
    
    CSS
    .vouchrIframe {
      border: 0;
    }
    

    Prepare the URL to provide as the src for the iframe using JavaScript:

    JavaScript
    const baseUrl = "baseUrl";
    const jwt = "jwt"; // supplied by FI
    const cid = "cid"; // Vouchr partner-supplied Client ID property
    const action = "/personalize"; // the command to show the create widget
    const url = `${baseUrl}/#tkn=${encodeURIComponent(
      jwt
    )}&cid=${encodeURIComponent(cid)}&action=${encodeURIComponent(action)}`;
    
    document.getElementById("vouchrWidgetContent").src = url;
    

    Setup an event listener using JavaScript, to listen for the message from the iframe when the user clicks a button from the create widget . The data from this message contains a actions.personalize, that you will pass into the URL of the iframe in the next step (see the documentation on window.postMessage() for more details). Note, it is imperative that you verify the sender’s identity using the origin property. See the section on ‘Security concerns’ in the link above for more details.

    JavaScript
    const receiveMessage = (event) => {
      if (event.origin !== "https://partner.vc.hr") {
        // Ensure that the origin of the message is from the discover/widget  url
        return;
      } else if (
        event.data &&
        event.data.event &&
        event.data.actions &&
        event.data.actions.personalize
      ) {
        if (event.data.event === "PERSONALIZE") {
          return event.data.actions.personalize;
        } else {
          return;
        }
      }
    };
    
    window.addEventListener("message", receiveMessage, false);
    
  • Android

    Add a Webview to your XML. The size is up to your design, we recommend 100dp.

    <WebView
      android:id="@+id/carouselView"
      android:layout_width="match_parent"
      android:layout_height="100dp"/>
    

    Then load in the URL passing in the jwt, cid.

    override fun onCreate(savedInstanceState: Bundle?) {
    	super.onCreate(savedInstanceState)
    	val jwt = /* supplied by FI */
    	val cid = /* Partner-supplied Client ID property */
    	carouselWebView.settings.javaScriptEnabled = true
    	carouselWebView.webChromeClient = WebChromeClient()
    	carouselWebView.loadUrl("$baseURL#tkn=$jwt&cid=$cid&action=%2Fpersonalize")
    }
    

    Attach a javascript interface to obtain the actions.personalize. This encapsulates the user’s selection and will be passed to the discover screen in the next step.

    private inner class WebAppInterface {
    	@JavascriptInterface
    	fun postMessage(message: String) {
    		val messageJson = JSONObject(message)
    		if (messageJson.getString("event") == "PERSONALIZE") {
          val action = if (messageJson.has("actions")) messageJson.get("actions").getString("personalize") else null
    			launchEGreeting(action)
    		}
    	}
    }
    
    webView.addJavascriptInterface(WebAppInterface(), "appInterface")
    
  • iOS

    Add a Webview to your view controller. The size is up to your design, we recommend 100pt.

    override func viewDidLoad() {
        super.viewDidLoad()
        let jwt = /* supplied by FI */
        let cid = /* Partner-supplied Client ID property */
        let url = URL(string: "\(baseURL)/#tkn=\(jwt)&cid=\(cid)&action=%2Fpersonalize")!
        webView.load(URLRequest(url: url))
    }
    

    In the view controller that will display the WKWebView, implement the WKScriptMessageHandler protocol to obtain the actions.personalize. This encapsulates the user’s selection and will be passed to the discover screen in the next step. Then attach the WKScriptMessageHandler to the webView.

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if let messageString = message.body as? String,
            let data = messageString.data(using: .utf8),
            let eventDictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
            if eventDictionary["event"] as? String == "PERSONALIZE" {
               // get the action
               guard let action = eventDictionary["actions"]["personalize"] as? String else {
                   return
               }
               let discoverViewController = //instantiate the discover view controller
               discoverViewController.action = action
               present(discoverViewController, animated: true)
           }
        }
    }
    
    carouselWebView.configuration.userContentController.add(self, name: "appInterface")
    

Summary Widget (Optional)

Summary Widget Image

The Summary Widget replaces the contents of the Create Widget with the selected eCard and provides quick way to view the eCard selected and provide buttons to edit or remove the eCard.

Step 1

Update the existing Create Widget webview/iframe or add a new webview/iframe to host the Summary widget. The summary widget will point to a url including the summary action returned from the create experience when an eCard was selected.

  • tkn the JWT supplied by the backend (Alternative option see: Alternative Authentication Method)
  • cid the Vouchr Client ID
  • action the data.actions.summary command returned from the discover screen during the VOUCHER_CREATED or VOUCHER_PENDING event
  • Web

    Update the iframe used for the create widget or add a new iframe to host the Summary Widget.

    Prepare the URL to provide as the src for the iframe with javascript.

    JavaScript
    const baseUrl = "baseUrl";
    const jwt = "jwt"; // supplied by FI
    const cid = "cid"; // Optional Vouchr partner-supplied Client ID property
    const action = vouchrData.summaryAction; // the summary action returned from Create Experience Step 2
    const url = `${baseUrl}/#tkn=${encodeURIComponent(
      jwt
    )}&cid=${encodeURIComponent(cid)}&action=${encodeURIComponent(action)}`;
    
    document.getElementById("vouchrWidgetContent").src = url;
    

    Setup an event listener using JavaScript, or update your current event listener, to listen for the message from the iframe indicating when a user has selected ‘edit’ or ‘remove’. The data from this message contains an appropriate action , that you will pass into the main vouchr iframe to allow them to edit or remove their chosen card. Note, it is imperative that you verify the sender’s identity using the origin property. See the section on ‘Security concerns’ in the link above for more details.

    JavaScript
    const receiveMessage = (event) => {
      if (event.origin !== "https://partner.vc.hr") {
        // Ensure that the origin of the message is from the discover iframe
        return;
      } else if (event.data && event.data.event && event.data.actions.personalize) {
        if (event.data.event === "EDIT") {
          handleEditAction(event.data.actions.edit); // See 'Allow User to Edit an eCard' section
          return;
        } else if (event.data.event === "REMOVE") {
          handleRemoveAction(); // optional, if FI wants to action on the removal themselves
          return;
        } else {
          return;
        }
      }
    };
    
    window.addEventListener("message", receiveMessage, false);
    
  • Android

    Update the URL of your create widget web view using jwt, cid and action

    override fun onSelectCard(savedInstanceState: Bundle?) {
    	super.onCreate(savedInstanceState)
    	val jwt = /* supplied by FI */
      val cid = /* Partner-supplied Client ID property, optional */
      val summaryAction = /* the summary action returned from Create Widget Step 2 */
    	carouselWebView.settings.javaScriptEnabled = true
    	carouselWebView.webChromeClient = WebChromeClient()
    	carouselWebView.loadUrl("$baseURL#tkn=$jwt&cid=$cid&action=$summaryAction")
    }
    

    Attach a javascript interface to obtain any action chosen from the Summary Widget. This encapsulates the user’s selection and can be passed to the discover screen in the next step.

    private inner class WebAppInterface {
    	@JavascriptInterface
    	fun postMessage(message: String) {
    		val messageJson = JSONObject(message)
    		if (messageJson.getString("event") == "EDIT") {
          val action = if (messageJson.has("actions") && messageJson.get("actions").has("edit")) messageJson.get("actions").getString("edit") else null
    			launchEGreeting(action) // See 'Allow User to Edit an eCard'
    		}
    		if (messageJson.getString("event") == "REMOVE") {
    			removeEGreeting() // Optional, allows FI to add additional custom logic on removal
    		}
    	}
    }
    
    webView.addJavascriptInterface(WebAppInterface(), "appInterface")
    
  • iOS

    Add a Webview to your view controller. The size is up to your design, we recommend 100pt.

    override func selectCard() {
        let jwt = /* supplied by FI */
        let cid = /* Partner-supplied Client ID property */
        let action = /* the summary action returned from Create Widget Step 2 */
        let url = URL(string: "\(baseURL)/#tkn=\(jwt)&cid=\(cid)&action=%2Fpersonalize")!
        webView.load(URLRequest(url: url))
    }
    

    In the view controller that will display the WKWebView, implement the WKScriptMessageHandler protocol to obtain any actions chosen from the Summary Widget. This encapsulates the user’s selection and will be passed to the discover screen in the next step. Then attach the WKScriptMessageHandler to the webView.

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if let messageString = message.body as? String,
            let data = messageString.data(using: .utf8),
            let eventDictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
            if eventDictionary["event"] as? String == "EDIT" {
               // get the action
               guard let action = eventDictionary["actions"]["edit"] as? String else {
                   return
               }
               let discoverViewController = //instantiate the discover view controller
               discoverViewController.action = action
               present(discoverViewController, animated: true)
           }
            if eventDictionary["event"] as? String == "REMOVE" {
               // Optional, allows FI to add additional custom logic on removal
           }
        }
    }
    
    carouselWebView.configuration.userContentController.add(self, name: "appInterface")
    

VOUCHER_PENDING event

When an eCard is created, it will first be in a pending state. An eCard must be activated to trigger the VOUCHER_CREATED event, either automatically from UI flow or manually at a later time. (Manual Activation option see: *Manual Activation Method)

Below is a list of the context available about the returned data in the pending event.

  • event.data.event - event title
  • event.data.voucherId - unique eCard identifier
  • event.data.paymentInfo - eCard’s payment information
  • event.data.actions.activate - the action parameter used to activate an eCard
  • event.data.actions.delete - the action parameter used to delete an eCard
  • event.data.actions.edit - the action parameter used to edit an eCard’s content
  • event.data.actions.preview - the action parameter used to preview an eCard’s content
  • event.data.actions.update - the action parameter used to update an eCard to provide new payment properties
  • event.data.actions.summary - the action parameter used to show an eCard summary
  • event.data.reason - if eCard is an edited version or a new eCard
  • event.data.previousVoucherId - if eCard existed previously, the unique identifier of that eCard
  • Web

    Setup an event listener using JavaScript, to listen for the message from the iframe that contains the Vouchr URL (see the documentation on window.postMessage() for more details). Note, it is imperative that you verify the sender’s identity using the origin property. See the section on ‘Security concerns’ in the link above for more details.

    JavaScript
    const receiveMessage = (event) => {
      if (event.origin !== "https://partner.vc.hr") {
        // Ensure that the origin of the message is from the iframe
        return;
      } else if (event.data && event.data.event && event.data.url) {
        if (event.data.event === "VOUCHER_PENDING") {
          const vouchrData = {
            event: event.data.event,
            voucherId: event.data.voucherId,
            paymentInfo: event.data.paymentInfo,
            actions: {
              activate: event.data.actions.activate,
              delete: event.data.actions.delete,
              edit: event.data.actions.edit,
              preview: event.data.actions.preview,
              update: event.data.actions.update,
              summary: event.data.actions.summary,
            },
            reason: event.data.reason,
            previousVoucherId: event.data.previousVoucherId,
          };
    
          handleVouchrData(vouchrData);
        } else {
          return;
        }
      }
    };
    
    window.addEventListener("message", receiveMessage, false);
    
    const handleVouchrData = (vouchrData) => {
      // to be implemented by FI, may launch share sheet, send to internal FI systems, etc
    };
    

Allow a user to Edit an eCard (Optional)

You may want to allow a user to edit an existing eCard. This is achieved by passing in the edit action in the url.

In the class that will edit the eCard, instantiate a WebView, WKWebView, or iframe. Load the webview/iframe with a URL passing in the following params:

  • tkn the JWT supplied by the backend (Alternative option see: Alternative Authentication Method)
  • cid the eCard Client ID
  • editAction the edit action for the voucher that was obtained when the voucher was created
  • Web

    Prepare the url to provide as the src for the iframe using JavaScript:

    JavaScript
    const baseUrl = "baseUrl";
    
    const jwt = "jwt"; // supplied by FI
    const cid = "cid"; // Vouchr partner-supplied Client ID property
    
    const url = `${baseUrl}/#tkn=${encodeURIComponent(
      jwt
    )}&cid=${encodeURIComponent(cid)}&action=${editAction}`;
    
    document.getElementById("vouchrContent").src = url;
    
  • Android

    Coming Soon

  • iOS

    Coming Soon

Updating an eCard (Optional)

Sometimes when an eCard is created the amount, currency, and/or code are not yet known. In these cases you will need to update the item once you know.

In the class that will edit the eCard, instantiate a WebView, WKWebView, or iframe. The webview/iframe does not need to be added to a view but will need to remain in memory until the call to update the eCard is complete. Load the webview/iframe with a URL passing in the following params:

Required

  • tkn the JWT supplied by the backend (Alternative option see: Alternative Authentication Method)
  • updateAction the update action that was obtained when the voucher was first created

Optional

  • cid the eCard Client ID (only required in multi-app setups)
  • amt the amount of the monetary gift (optional, eg. 50.00)
  • cur the currency of the monetary gift (optional, e.g. USD)
  • code a code or transaction id that the receiver should see to help them claim the gift (eg. XY77)
  • Web

    Prepare the url to provide as the src for the iframe using JavaScript:

    JavaScript
    const baseUrl = "baseUrl";
    const jwt = "jwt"; // supplied by FI
    const cid = "cid"; // Vouchr partner-supplied Client ID property
    const amt = "50.0"; // the amount of the monetary gift (optional, eg. 50.00)
    const cur = "USD"; // the currency of the monetary gift (optional, e.g. USD)
    
    const url = `${baseUrl}/#$
      tkn=${encodeURIComponent(jwt)}&
      cid=${encodeURIComponent(cid)}&
      amt=${amt}&
      cur=${cur}&
      action=${updateAction}`;
    
    document.getElementById("vouchrContent").src = url;
    
    const receiveMessage = (event) => {
      if (event.origin !== "https://partner.vc.hr") {
        // Ensure that the origin of the message is from the iframe
        return;
      } else if (event.data && event.data.event) {
        if (event.data.event === "UPDATED") {
          // Success
        } else if (event.data.event === "ERROR") {
          // An Error happened, try again.
        } else {
          return;
        }
      }
    };
    window.addEventListener("message", receiveMessage, false);
    
  • Android

    Coming Soon

  • iOS

    Coming Soon

Removing an eCard (Optional)

In the class that will edit/remove the eCard, instantiate a WebView, WKWebView, or iframe. The webview/iframe does not need to be added to a view but will need to remain in memory until the call to remove an eCard is complete. Load the webview/iframe with a URL passing in the following params:

  • tkn the JWT supplied by the backend (Alternative option see: Alternative Authentication Method)
  • cid the eCard Client ID
  • deleteAction the delete action for the voucher that was obtained when the voucher was created
  • Web

    Prepare the url to provide as the src for the iframe using JavaScript:

    JavaScript
    const baseUrl = "baseUrl";
    
    const jwt = "jwt"; // supplied by FI
    
    const cid = "cid"; // Vouchr partner-supplied Client ID property
    
    const url = `${baseUrl}/#tkn=${encodeURIComponent(
      jwt
    )}&cid=${encodeURIComponent(cid)}&action=${deleteAction}`;
    
    document.getElementById("vouchrContent").src = url;
    
  • iOS
    let url = URL(string: "\(baseURL)/#tkn=\(jwt)&cid=\(cid)&action=\(deleteAction)")!
    webView.load(URLRequest(url: url))
    

    In the class that has the WKWebView, implement the WKScriptMessageHandler protocol to confirm that the delete succeeded.

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if let messageString = message.body as? String,
            let data = messageString.data(using: .utf8),
            let eventDictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
            if eventDictionary["event"] as? String == "REMOVE" {
                // delete succeeded.
                let deletedVoucherId = eventDictionary["voucherId"] as? String
            }
        }
    }
    
  • Android
    webView.settings.javaScriptEnabled = true
    webView.webChromeClient = WebChromeClient()
    webView.loadUrl("$baseURL/#tkn=$jwt&cid=$cid&action=$deleteAction")
    

    Attach a javascript interface to confirm the delete succeeded.

    private inner class WebAppInterface {
        @JavascriptInterface
        fun postMessage(message: String) {
            val messageJson = JSONObject(message)
            if (messageJson.getString("event") == "REMOVE") {
                // Delete succeeded
            } else if (messageJson.getString("event") == "ERROR") {
                // Delete error
                if (messageJson.getString("status") == null) {
                    // No http status means a timeout error
                    // If this occurs we must retry the delete
                }
            }
        }
    }
    
    webView.addJavascriptInterface(WebAppInterface(), "appInterface")
    

Activating an eCard (Optional)

When an eCard is created (during ‘VOUCHER_PENDING’ state), you can choose to activate the eCard at a later stage.

In the class that will activate the eCard, instantiate a WebView, WKWebView, or iframe. The webview/iframe does not need to be added to a view but will need to remain in memory until the call to activate the eCard is complete. Load the webview/iframe with a URL passing in the following params:

Required

  • tkn the JWT supplied by the backend (Alternative option see: Alternative Authentication Method)
  • activate the activate action that was obtained when the voucher was first created

Optional

  • cid the eCard Client ID (only required in multi-app setups)
  • Web

    Prepare the url to provide as the src for the iframe using JavaScript:

    JavaScript
    const baseUrl = "baseUrl";
    const jwt = "jwt"; // supplied by FI
    const cid = "cid"; // Vouchr partner-supplied Client ID property
    
    const url = `${baseUrl}/#$
      tkn=${encodeURIComponent(jwt)}&
      cid=${encodeURIComponent(cid)}&
      action=${activate}`;
    
    document.getElementById("vouchrContent").src = url;
    

Alternative Authentication Method

Instead of passing in JWT using the tkn parameter you can provide the JWT via postMessage to the iframe/webview.

If using this method, we recommend you delay loading until the authentication has been received by passing &waitForAuth=true as a url parameter. This will block any ui interactions until the authentication message has been received. eCards can not be created until authentication has succeeded.

  • HTML
    <iframe
      id="vouchrContent"
      class="vouchrIframe"
      width="100%"
      height="100%"
      title="Vouchr Content"
      src=""
    ></iframe>
    
    JavaScript
    window.document.addEventListener("readystatechange", () => {
      const baseUrl = "baseUrl";
      const jwt = "jwt"; //supplied by FI
      if (window.document.readyState == "complete") {
        document
          .getElementById("vouchrContent")
          .contentWindow.postMessage({ type: "auth", token: jwt }, baseUrl);
      }
    });
    
    document.getElementById("vouchrContent").src = url + "&waitForAuth=true";
    
  • Android

    Create a custom WebViewClient and set the web view client before loading the url

    webView.setWebViewClient(new WebViewClient());
    webView.loadUrl(url + "&waitForAuth=true");
    
    private static class MyWebViewClient extends WebViewClient {
      boolean loginInitialized = false;
      String baseUrl = "baseUrl"
      String jwt = "jwt" //supplied by FI
      @Override
      public void onPageFinished(WebView view, String url) {
          super.onPageFinished(view, url);
          String script = String.format("postMessage({type: 'auth', token: '%s'}, '%s');", jwt, baseUrl);
          if (!loginInitialized) {
              loginInitialized = true;
              view.evaluateJavascript(script, new ValueCallback<String>() {
                  @Override
                  public void onReceiveValue(String value) {
                  }
              });
          }
      }
    }
    
  • IOS
    override func viewDidLoad() {
      super.viewDidLoad()
      let baseUrl = /* supplied by FI */
      let jwt = /* supplied by FI */
      let cid = /* Partner-supplied Client ID property */
      let action = /* Optional  */
      val amt = /* Optional the amount of the monetary gift (optional, eg. 50.00) */
      val cur = /* the currency of the monetary gift (optional, e.g. USD) */
      var urlString = "\(baseURL)/#cid=\(cid)&amt=\(amt)&cur=\(cur)&waitForAuth=true"
      if let action = action {
          urlString = urlString + "&action=\(action)"
      }
      let url = URL(string: urlString)!
    
      let javascript = "postMessage({type: 'auth', token: '\(jwt)'}, '\(baseUrl)');"
      let userScript = WKUserScript(source: javascript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
    
      self.webview.configuration.userContentController.addUserScript(userScript)
    
      webView.load(URLRequest(url: url))
    }
    

Full Examples

Below we have linked some complete examples launching the eCard experience.

ChangeLog

v1.4.4 - August 16, 2023

  • Add information on ‘VOUCHER_PENDING’ event and ‘activate’ action

v1.4.3 - May 18, 2023

  • Updated code examples to tabbed format for increased clarity

v1.4.2 - October 13, 2022

  • Add a detailed list of properties included in the VOUCHER_CREATED event

v1.4.1 - July 27, 2022

  • Add instruction on how to send auth token from main frame via post message

v1.4.0 - May 6, 2022

  • Add instructions for using Summary widget
  • Updated terminology

v1.3.7 - December 3, 2021

  • Add iOS method that can connect to a button for browser back feature

v1.3.6 - October 18, 2021

  • Add iOS Apple mail split share option, update web event to VOUCHER_CREATED, add merchant externalId

v1.3.5 - September 2, 2021

  • Make it clear that Client ID is optional in prerequisites

v1.3.4 - January 12, 2020

  • Added section about Javascript Sharing

v1.3.3 - December 1, 2020

  • Updated documentation with prerequisites for docs.vouchrsdk.com site

v1.3.2 - October 8, 2020

  • Added inline playback information for displaying our discover in Webviews on both android and iOS.

v1.3.1 - October 8, 2020

  • added amt and currency optional parameters to urls

v1.2.1 - March 17, 2020

  • updated share section to make it clear that share sheet or custom sharing methods are both possible

v1.2.0 - February 12, 2020

  • added Web documentation

v1.1.1 - January 16, 2020

  • added “Removing an eCard” section

v1.1.0 - January 16, 2020

  • added Create Widget instructions
  • added troubleshooting section

v1.01 - November 18, 2019

  • added Client ID (cid) parameters to urls