Third-party wallets (such as Apple Pay)

In addition to the Pismo digital wallet, cards you issue through Pismo can be used in payment systems such as Apple Pay and Google Pay that have their own digital wallets. This happens through the following process:

  1. Customer enters card information (PAN, CVV) into payment app (Apple Pay, Google Pay, and so on) for tokenization in a digital wallet (card on file). The token allows payments to be made without exposing sensitive data.

  2. The payment app verifies the card with the card network (Visa, Mastercard) and Pismo.

As the card issuer, you can, for this process:

  • Provide your own anti-fraud card verification via a webhook. See Issuer anti-fraud webhook below.

  • Assign a network profile to a card that, principally, determines the picture used to represent a card in a payment app. The payment network provides card issuers a management portal where card pictures are stored and assigned an identifier.

Note: The card tokenization process generates a number of events, for more information, see Card tokenization flow and events.

Issuer anti-fraud webhook

As a card issuer, you can provide your own anti-fraud card verification via a webhook, that Pismo will call during the tokenization process. If you don't provide one, Pismo uses a default anti-fraud method.

πŸ“˜

Pismo webhook security

For information on webhook security, see Verifying webhook requests

You can:

  • Approve a card
  • Not approve a card
  • Ask Pismo to prompt the cardholder for further identity and verification in the form of SMS, phone call, or app.

Anti-fraud webhook request

Sample webhook request payload from Pismo:

{
    "card":{
        "id": 134142,
        "type": "PLASTIC"
    },
    "combo_card": { },
    "token_id": 432,
    "network_brand": "vts",
    "custom_codes": ["FRB", "BNB"],
    "original_network_data": {

    }
}

Field

Type

Description

Required

custom_codes

string array

Card status validation codes indicating Pismo's findings. Issuer can still approve.

  • BNB - Other custom code
  • FLE - Generic refusal
  • PFT - Anti-fraud refusal
  • UBT - Card blocked
  • NPL - Card not plastic
  • FRB - Card created
  • VNM - Invalid card
  • CED - Invalid expiration date
  • EXPCRD - Expired card
  • TNF - Token not found
  • FR1 - Failed CVV2
  • FR2 - Invalid CVV2
  • CNM - Card not normal
  • 998 - Card not found
  • OP1 - Internal error
  • HSE - Hardware security module (HSM) validation error

No

token_id

number

Pismo-issued token ID. You can use this to call the Get token information endpoint.

Yes

network_brand

string

  • vts - Visa
  • mdes - Mastercard

Yes

card

object

 id

number

Pismo-assigned card ID

Yes

 type

enum string

Card type:

  • PLASTIC
  • VIRTUAL
  • RECURRING
  • TEMPORARY

Yes

 status

enum string

Card status:

  • created
  • normal
  • damaged
  • warning
  • lost
  • unknown

No

 stage

enum string

Card lifecycle status

  • unknown
  • blocked
  • unblocked

No

combo_card

object

No

 status

enum string

Card status:

  • created
  • normal
  • damaged
  • warning
  • lost
  • unknown

original_network_data

object

Refer to the VISA Token Service (VTS)
Issuer API Specifications (JSON) manual
or the Mastercard Customer Interface Specification manual for more information on the contents of this field.

No

Anti-fraud webhook response

Sample webhook response payload from issuer:

{
    "step_up_methods": [
        {
            "method": "OTPCALL",
            "value": "+551130039500"
        },
        {
            "method": "OTPSMS",
            "value": null
        },
        {
            "method": "OTPAPP",
            "value": "com.btg.pactual.banking://wallet"
        }
    ],
    "approve": true,
    "action_code": "85"
}

Field

Type

Description

Required

approve

boolean

Card approved flag

Yes

action_code

string

Decision code:

  • 00 - Approved
  • 05 - Not approved
  • 85 - Needs challenge
  • 14 - Invalid PAN
  • N7 - Invalid CVV2
  • 54 - Invalid expiration date
  • 96 - Issuer internal system error

Yes

step_up_methods

object array

Needs challenge methods. At least one is required if used.

Yes if action_code = 85.

 method

enum string

One-time passcode (OTP) challenge method:

  • OTPSMS - SMS
  • OTPAPP - App
  • OTPCALL - Call

 value

string

Additional information for challenge - phone number, app, and so on.

 platform

enum string

Only used for Visa and only when the method is OPTAPP:

  • IOS
  • ANDROID
  • WINDOWS
  • WEB

 source

string

Only used for Visa and under certain conditions. This is the Source Address field as discussed in the VISA VTS manual and you should read that document for more information.

Anti-fraud code sample (Go)

package main

// Anti-fraud code sample in Go

//curl --location --request POST 'http://antifraudhost' \
//--header 'x-pismo-sign: eyJhbGciOiJSUzI1NiIsImtpZCI6IjEzY2VlZWViMWEwNjU1Nzg4Y2UyMDg0ZmZmY2FkMDJmNTFmYzMxNDAiLCJ0eXAiOiJKV1QifQ.eyJhY2NvdW50LWlkIjo5ODY2NTM4NiwiYm9keS1oYXNoIjoiUm0yNWNpTy9RRjNMdmIwL1JZbmVWMFZRUkMzd3pNbzZjemdlR1NHUE1KWT0iLCJleHAiOjE2NTI1MzY2MzAsImlhdCI6MTY1MTY3MjYzMCwiaXNzIjoiYXBpLnBpc21vLmlvIiwia2lkIjoiMTNjZWVlZWIxYTA2NTU3ODhjZTIwODRmZmZjYWQwMmY1MWZjMzE0MCIsInN1YiI6ImFwaS1jYXJkcy10b2tlbml6YXRpb24ifQ.bE7l7KQHRj_0J7zcXtLXgefQuGVQjdUr2BtetTQzTf25_tbHf6iUXHIsGB8iCVujo2e5aUrglxTQScduo3sUuJgzLNUHEyBOgMUc9b6v9K_-reFLpJa9Co0Ct8PmU_os-AZr6jqrYgzGk343I_FYDDw48BmqSMRgwtaJPzHQesaSdd8pjAw_CdSttobstplJjQiwUetufmSvZkOCQHeU-JHTwgdcTwgmtJwVMnFFERIdWQMzoz1UYtP0oMJivw3Zt-MjutqvodkaymOIx9IjJTywp0Oq61rmPtWlKW0pIsI6HCYDR8pASgantt7sXGpNb8cdynUIyvxlixpy77kdfg' \
//--header 'Content-Type: application/json' \
//--data-raw '{
//    "card":{
//        "id": 134142,
//        "type": "PLASTIC"
//    },
//    "combo_card": null,
//    "token_id": 432,
//    "network_brand": "vts",
//    "custom_codes": ["FRB", "BNB"],
//    "original_network_data": {}
//}'

import (
    "context"
    "crypto/x509"
    "encoding/json"
    "encoding/pem"
    "fmt"
    "github.com/labstack/echo/v4"
    "github.com/square/go-jose"
    "io/ioutil"
    "net/http"
)

type clientTokenDecision struct {
    Approve       bool                 `json:"approve"`
    ActionCode    string               `json:"action_code"`
    StepUpMethods []clientStepUpMethod `json:"step_up_methods"`
}

type clientStepUpMethod struct {
    Method   string `json:"method"`
    Value    string `json:"value,omitempty"`
    Source   string `json:"source"`
    Platform string `json:"platform"`
}

func main() {
    getPismoCertificates()
    e := echo.New()
    e.POST("/", handler)
    e.Logger.Fatal(e.Start(":1323"))
}

var mapCertificates map[string]*x509.Certificate

func getPismoCertificates() {
    url := "https://sandbox.pismolabs.io/robot/v1/metadata/x509/[email protected]"
    httpReq, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
    if err != nil {
        panic(err)
    }

    client := http.Client{}
    httpRes, err := client.Do(httpReq)
    if err != nil {
        panic(err)
    }

    if httpRes.StatusCode != http.StatusOK {
        panic(err)
    }

    bRes, err := ioutil.ReadAll(httpRes.Body)
    if err != nil {
        panic(err)
    }

    tmpMap := make(map[string]string)
    err = json.Unmarshal(bRes, &tmpMap)
    if err != nil {
        panic(err)
    }

    mapCertificates = make(map[string]*x509.Certificate)

    for key, val := range tmpMap {
        block, _ := pem.Decode([]byte(val))
        if block == nil {
            panic("cannot decode block")
        }

        cer, err := x509.ParseCertificate(block.Bytes)
        if err != nil {
            panic(err)
        }
        mapCertificates[key] = cer
    }
}

func handler(c echo.Context) error {
    tokenString := c.Request().Header.Get("X-Pismo-Sign")
    valid := isTokenValid(tokenString)
    if !valid {
        return fmt.Errorf("certificate not valid")
    }

    k := clientTokenDecision{

        Approve:    true,
        ActionCode: "85",
        StepUpMethods: []clientStepUpMethod{
            {

                Method: "OTPAPP",
                Value:  "app.to.app",
            },
            {
                Method: "OTPSMS",
            },
        },
    }
    return c.JSON(http.StatusOK, k)
}

func isTokenValid(tokenString string) bool {
    jwsSignature, err := jose.ParseSigned(tokenString)
    if err != nil {
        return false

    }

    for _, sign := range jwsSignature.Signatures {
        kid := sign.Header.KeyID
        cert := mapCertificates[kid]
        _, err := jwsSignature.Verify(cert.PublicKey)

        if err != nil {
            return false
        }
        break
    }
    return true
}

Did this page help you?