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:
-
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.
-
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 calls 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",
"program_id": 1234,
"bin": "5122"
},
"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 |
program_id | number | Program ID | Yes |
bin | string | Bank identification number | 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
}
Updated 13 days ago