3-D Secure

Przygotuj dane potrzebne do przeprowadzenia płatności jak w przykładzie poniżej.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$card_params = array(
    'sale' => array(
        'amount'      => 100.00,
        'currency'    => 'EUR',
        'description' => 'Product #1'
    ),
    'customer' => array(
        'name'    => 'John Doe',
        'email'   => 'john@doe.com',
        'ip'      => '127.0.0.1',
        'address' => array (
            'street_house' => '1600 Pennsylvania Avenue Northwest',
            'city'         => 'Washington',
            'state'        => 'DC',
            'zip'          => '500',
            'country_code' => 'US',
        ),
    ),
    'card' => array(
        'token' => '12a34b45c67d89e00f1aa2bb3cc4dd5ee6ff12a34b45c67d89e00f1aa2bb3cc4'
     ),
     'back_url' => 'http://example.com/3dsecure', // 3d secure back redirect url
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
card_params = {
    'sale' => {
        'amount'      => 19.99,
        'currency'    => 'EUR',
        'description' => 'Product #1',
    },
    'customer' => {
        'name'    => 'John Doe',
        'email'   => 'john@doe.com',
        'ip'      => '127.0.0.1',
        'address' => {
            'street_house' => '1600 Pennsylvania Avenue Northwest',
            'city'         => 'Washington',
            'state'        => 'DC',
            'zip'          => '500',
            'country_code' => 'US'
        }
    },
    'card' => {
        'token' => '12a34b45c67d89e00f1aa2bb3cc4dd5ee6ff12a34b45c67d89e00f1aa2bb3cc4'
    },
    'back_url' => 'http://example-url.com'
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
card_params = {
  'sale' : {
    'amount'      : 19.99,
    'currency'    : 'EUR',
    'description' : 'Product #1'
  },
  'customer' : {
    'name'    : 'John Doe',
    'email'   : 'john@doe.com',
    'ip'      : '127.0.0.1',
    'address' : {
      'street_house' : '1600 Pennsylvania Avenue Northwest',
      'city'         : 'Washington',
      'state'        : 'DC',
      'zip'          : '500',
      'country_code' : 'US'
    }
  },
  'card' : {
    'token' : '12a34b45c67d89e00f1aa2bb3cc4dd5ee6ff12a34b45c67d89e00f1aa2bb3cc4'
  },
  'back_url' : 'http://example.com/3dsecure'
}
1
2
3
4
Sale sale = new Sale(19.99, "EUR", "Product #1");
Address address = new Address("1600 Pennsylvania Avenue Northwest", "Washington", "DC", "500", "US");
Customer customer = new Customer("John Doe", "john@doe.com", "127.0.0.1", address);
Card card = new Card("4111111111111111", "03", "2017", "John Doe", "123")

Sprawdź kartę

Aby sprawdzić, czy karta jest zapisana do programu 3-D Secure, po prostu wywołaj metodę checkCard3DSecureByToken. Jeśli karta jest zapisana do programu 3-D Secure, otrzymasz numer autoryzacji id_3dsecure_auth.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
try {
    $status = $client->checkCard3DSecureByToken($card_params);
}
catch (Exception $e) {
    // Handle exception here, for example show an error page, stop action
}

if ($client->isSuccess()) {
    echo "Success, id_3dsecure_auth: {$status['id_3dsecure_auth']} \n";
} else {
    echo "Error ID: {$status['error']['id_error']}, \n".
         "Error number: {$status['error']['error_number']}, \n".
         "Error description: {$status['error']['error_description']}";
}

if (true == $status['is_card_enrolled'])
{
    // redirect to 3-D Secure provider
    header('Location: ' . $status['redirect_url']);
    die;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
begin
    status = client.check_card_3d_secure_by_token(card_params)
rescue PayLane::ClientError => e
    # handle exceptions here
end

if client.success?
    puts "Success, id_3dsecure_auth: #{status["id_3dsecure_auth"]}"
else
    puts "Error ID: #{status["error"]["id_error"]}, \n"\
         "Error number: #{status["error"]["error_number"]}, \n"\
         "Error description: #{status["error"]["error_description"]}"
end

if status['is_card_enrolled']
    # redirect to url in status['redirect_url']
    exit
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try:
    status = client.check_card_3d_secure_by_token(card_params)
except Exception, e:
    # handle exceptions here

if client.is_success():
    print 'Success, id_3dsecure_auth: %s' % status['id_3dsecure_auth']
else:
    print 'Error (%s), %s - %s' % (status['error'].get('id_error'),
                                   status['error'].get('error_number'),
                                   status['error'].get('error_description'))

if status['is_card_enrolled']:
    # redirect to url in status['redirect_url']
    sys.exit()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
api.secure3DCheckCard(sale, customer, card, "http:// example.com/3dsecure", new Callback<Secure3DSaleResult>() {

@Override
public void onFinish(Secure3DSaleResult result) {

    if (result.isEnrolled()) {
        WebView webview =...;
        webview.loadUrl(result.getRedirectUrl());
    }
}

    @HandleException
    public void onProtocolError(ProtocolException e) {
        // invoke if not success
        // e.getCode() - error code
        // e.getMessage() - error message
    }

    @Override
    public void onError(Exception e) {
        // connection error etc.
    }
});
Uwaga dot. Ruby:
W Ruby nie ma natywnej funkcji służącej do przekierowania na inną stronę – musisz albo skorzystać z funkcji oferowanych przez framework, z którego korzystasz, albo napisać własną funkcję.
Uwaga dot. Pythona:
W Pythonie nie ma natywnej funkcji służącej do przekierowania na inną stronę – musisz albo skorzystać z funkcji oferowanych przez framework, z którego korzystasz, albo napisać własną funkcję.

W przypadku Django możesz użyć:
1
2
from django.http import HttpResponseRedirect
HttpResponseRedirect('http://example.com/')
W przypadku Pylons możesz użyć:
1
2
from pylons.controllers.util import redirect
redirect('http://example.com/')

Jeśli autoryzacja 3-D Secure się powiodła (karta jest zapisana do programu 3-D Secure), możesz przeprowadzić transakcję z użyciem tego mechanizmu i przekierować klienta na stronę dostawcy usługi 3-D Secure (używając adresu otrzymanego w wartości redirect_url).

Jeśli transakcja została odrzucona i został zwrócony błąd, zrealizuj po prostu zwyczajną pojedynczą płatność. Powinieneś również zapisać dane błędu w swoim systemie.

Transakcja

Po wprowadzeniu stosownych danych, klient zostanie przekierowany z powrotem na Twoją stronę(back_url). Powinieneś teraz sprawdzić zwrócone informacje, aby uniknąć ewentualnych prób oszustwa i sprawdzić status transakcji.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$salt        = 'YOUR_HASH_SALT';
$status      = $_GET['status'];
$description = $_GET['description'];
$amount      = $_GET['amount'];
$currency    = $_GET['currency'];
$hash        = $_GET['hash'];

$id = '';
if ($status !== 'ERROR') // success, get id_3dsecure_auth
    $id = $_GET['id_3dsecure_auth'];
   
$calc_hash = sha1("{$salt}|{$status}|{$description}|{$amount}|{$currency}|{$id}");

// check hash salt
if ( $calc_hash !== $hash ) {
    die ("Error, wrong hash");
}

// check transaction status
if ($status === 'ERROR') {
    die("Error, 3-D auth transaction declined");
} else {
    // 3-D Secure authorization completed, perform sale
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Simple controller action code in Rails
# it's just an example - most of the logic should be moved to model

salt        = 'YOUR_HASH_SALT'
status      = params['status']
description = params['description']
amount      = params['amount']
currency    = params['currency']
hash        = params['hash']

id = ''
unless status == 'ERROR'
    id = params['id_3dsecure_auth']
else
# redirect to an index action to correct the payment + simple notice
# for Rails: redirect_to :index, :notice => "Error, 3-D auth transaction declined"
end

calc_hash = Digest::SHA1.hexdigest("#{salt}|#{status}|#{description}|#{amount}|#{currency}|#{id}")

unless calc_hash == hash
# redirect to an index action to correct the payment
# for Rails: redirect_to :index, :notice => "Wrong hash"
end


# check transaction status

if status == 'ERROR'
# redirect to an index action to correct the payment + simple notice
# for Rails: redirect_to :index, :notice => "Error, 3-D auth transaction declined"
else
# 3-D Secure authorization completed, perform sale
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
salt             = 'YOUR_HASH_SALT'
status           = get_request_param('status')
description      = get_request_param('description')
amount           = get_request_param('amount')
currency         = get_request_param('currency')
hash             = get_request_param('hash')
id_3dsecure_auth = None

# success, get id_3dsecure_auth
if status != 'ERROR':
    id_3dsecure_auth = get_request_param('id_3dsecure_auth')

calc_hash = hashlib.sha1(
    '|'.join([salt, status, description, amount, currency, id_3dsecure_auth])).hexdigest()

# check hash salt
if calc_hash != hash:
    sys.exit('Error, wrong hash')

# check transaction status
if status == 'ERROR':
    sys.exit('Error, 3-D auth transaction declined')
else:
    # '3-D Secure authorization completed, perform sale'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
webview.setWebViewClient(new WebViewClient() {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {

        if (url.contains(redirectUrl)) {
            try {
                Map<String, String> map = getQueryMap(new URL(url).getQuery());
                String salt = "YOUR_HASH_SALT";
                String status = map.get("status");
                String description = map.get("description");
                String amount = map.get("amount");
                String currency = map.get("currency");
                String hash = map.get("hash");
                String id = map.get("id_3dsecure_auth");

                String calcHash = sha1(String.format("%1$s|%2$s|%3$s|%4$s|%5$s|%6$s", salt, status, description, amount, currency, id));

                //  check hash salt
                if (!calcHash.equals(hash)) {
                    // Error, wrong hash
                }

                if (status.equals("ERROR")) {
                    String errorDescription=map.get("error_description");
                    // Error, transaction declined

                } else {
                    String idSale=map.get("id_sale");
                    // Success, transaction completed
                }

            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        } else {
            view.loadUrl(url);
        }
        return true;
    }
});

public static Map<String, String> getQueryMap(String query) {
    String[] params = query.split("&");
    Map<String, String> map = new HashMap<String, String>();
    for (String param : params) {
        String name = param.split("=")[0];
        String value = param.split("=")[1];
        map.put(name, value);
    }
    return map;
}

private static String convertToHex(byte[] data) {
    StringBuilder buf = new StringBuilder();
    for (byte b : data) {
        int halfbyte = (b >>> 4) & 0x0F;
        int two_halfs = 0;
        do {
            buf.append((0 <= halfbyte) && (halfbyte <= 9) ? (char) ('0' + halfbyte) : (char) ('a' + (halfbyte - 10)));
            halfbyte = b & 0x0F;
        } while (two_halfs++ < 1);
    }
    return buf.toString();
}

public static String sha1(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException {
    MessageDigest md = MessageDigest.getInstance("SHA-1");
    md.update(text.getBytes("utf-8"), 0, text.length());
    byte[] sha1hash = md.digest();
    return convertToHex(sha1hash);
}
Uwaga dot. Pythona:
Funkcja get_request_param ma za zadanie pobrać wartości GET. Użyj odpowiednich funkcji oferowanych przez framework, z którego korzystasz, lub napisz własną funkcję.

W przypadku Django możesz użyć:
1
param_from_get = request.GET.get('param_name')
W przypadku Pylons możesz użyć:
1
2
from pylons import request
param_from_get = request.GET.get('param_name')

Jeśli wszystko odbyło się poprawnie, to po tej weryfikacji możesz już przeprowadzić faktyczną płatność opartą na autoryzacji 3-D Secure.

Możesz sprawdzić, czy płatność się powiodła, wywołując metodę isSuccess.
Pozyskanie numeru ID transakcji (lub informacji o błędzie, jeśli coś pójdzie nie tak), jest również bardzo proste i może wyglądać jak na poniższym przykładzie.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try {
    $status = $client->saleBy3DSecureAuthorization(array ('id_3dsecure_auth' => $id));
} catch (Exception $e) {
    // Handle exception here, for example show an error page, stop action
}

if ($client->isSuccess())
{
    echo "Success, id_sale: {$status['id_sale']} \n";
} else {
    echo "Error ID: {$status['error']['id_error']}, \n".
         "Error number: {$status['error']['error_number']}, \n".
         "Error description: {$status['error']['error_description']}");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
begin
    status = client.sale_by_3d_secure_authorization({"id_3dsecure_auth" => id})
rescue PayLane::ClientError => e
    # handle exceptions here
end

if client.success?
    puts "Success, id_sale: #{status["id_sale"]}"
else
    puts "Error ID: #{status["error"]["id_error"]}, \n"\
         "Error number: #{status["error"]["error_number"]}, \n"\
         "Error description: #{status["error"]["error_description"]}"
end
1
2
3
4
5
6
7
8
9
10
11
12
try:
    status = client.sale_by_3d_secure_authorization(
        {'id_3dsecure_auth': id_3dsecure_auth})
except Exception, e:
    # handle exceptions here

if client.is_success():
    print 'Success, id_sale: %s' % status['id_sale']
else:
    print 'Error (%s), %s - %s' % (status['error'].get('id_error'),
                                   status['error'].get('error_number'),
                                   status['error'].get('error_description'))
1
2
3
4
5
6
7
8
9
Secure3DSaleResult result =...;
long id3dSecureAuth = result.getId3dSecureAuth();
api.secure3DAuthSale(id3dSecureAuth, new Callback<CardSaleResult>() {

    @Override
    public void onFinish(CardSaleResult result) {
        // success
    }
});

Karta niezapisana do 3-D Secure

Sytuacja opisana powyżej jest przypadkiem idealnym. Może się jednak okazać, że karta nie jest zapisana do programu 3-D Secure i wynik autoryzacji 3-D Secure będzie negatywny. Oto jak należy postąpić w takim przypadku.

Po wywołaniu metody mającej zrealizować autoryzację 3-D Secure, po prostu wywołaj metodę saleBy3DSecureAuthorization zupełnie tak, jakbyś zrobił w przypadku, gdyby wynik autoryzacji był pozytywny. To ważne (ze względów bezpieczeństwa), by posłużyć się ta metodą, a nie przeprowadzać w tym momencie zwykłej transakcji kartą – dzięki temu próba przeprowadzenia płatności z użyciem 3-D Secure zostanie zarejestrowana.

Możesz sprawdzić, czy płatność się powiodła, wywołując metodę isSuccess.
Pozyskanie numeru ID transakcji (lub informacji o błędzie, jeśli coś pójdzie nie tak), jest również bardzo proste i może wyglądać jak na poniższym przykładzie.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$id_3dsecure_auth = $status['id_3dsecure_auth'];

if (true != $status['is_card_enrolled'])
{
    try {
        $status = $client->saleBy3DSecureAuthorization(array ('id_3dsecure_auth' => $id_3dsecure_auth));
    } catch (Exception $e) {
        // Handle exception here, for example show an error page, stop action
    }
}

if ($client->isSuccess()) {
    echo "Success, id_sale: {$status['id_sale']} \n";
} else {
    echo "Error ID: {$status['error']['id_error']}, \n".
         "Error number: {$status['error']['error_number']}, \n".
         "Error description: {$status['error']['error_description']}");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
id_3dsecure_auth = status['id_3dsecure_auth']

unless status['is_card_enrolled']
    begin
        status = client.sale_by_3d_secure_authorization({"id_3dsecure_auth" => id_3dsecure_auth})
    rescue PayLane::ClientError => e
        # Handle exception here, for example show an error page, stop action
    end
end

if client.success?
    puts "Success, id_sale: #{status["id_sale"]}"
else
    puts "Error ID: #{status["error"]["id_error"]}, \n"\
         "Error number: #{status["error"]["error_number"]}, \n"\
         "Error description: #{status["error"]["error_description"]}"
end
1
2
3
4
5
6
7
8
9
10
11
12
13
if not is_card_enrolled:
    try:
        status = client.sale_by_3d_secure_authorization(
            {'id_3dsecure_auth': id_3dsecure_auth})
    except Exception, e:
        # Handle exception here, for example show an error page, stop action

if client.is_success():
    print 'Success, id_sale: %s' % status['id_sale']
else:
    print 'Error (%s), %s - %s' % (status['error'].get('id_error'),
                                   status['error'].get('error_number'),
                                   status['error'].get('error_description'))
1
2
3
4
5
6
7
8
9
10
Secure3DSaleResult result =...;
if (!result.isEnrolled()) {
    api.secure3DAuthSale(result.getId3dSecureAuth(), new Callback<CardSaleResult>() {

        @Override
        public void onFinish(CardSaleResult result) {
            // success
        }
    });
}