-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PAYOP-1116] Implement Larky function for rendering network token (AL…
…T implementation) (#327) * Add code provided by salava * Interface * Rename module * Implement json path stuff * Add token module stuff * Add tests * More test cases * Add NT not found test * Remove unused stuff * Handle all the corner cases * Make member private * Extract json path version * Remove json path deps * New approach * Update tests * Implement the same interface with jsonpath in pure larky * Add nts helpers * Code style * Update for feedbacks * doc change * Add payment details * Fix the interface, add payment details * Fix get network token tests * Fix noop test * Improve nts helper interface * Update docs * Update doc * Fix a bug in test * Move file around to vgs folder instead
- Loading branch information
1 parent
d15249b
commit 42a83fb
Showing
11 changed files
with
493 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
larky/src/main/java/com/verygood/security/larky/modules/NetworkTokenModule.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package com.verygood.security.larky.modules; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import com.verygood.security.larky.modules.vgs.nts.LarkyNetworkToken; | ||
import com.verygood.security.larky.modules.vgs.nts.MockNetworkTokenService; | ||
import com.verygood.security.larky.modules.vgs.nts.NoopNetworkTokenService; | ||
import com.verygood.security.larky.modules.vgs.nts.spi.NetworkTokenService; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.ServiceLoader; | ||
import net.starlark.java.annot.Param; | ||
import net.starlark.java.annot.ParamType; | ||
import net.starlark.java.annot.StarlarkBuiltin; | ||
import net.starlark.java.annot.StarlarkMethod; | ||
import net.starlark.java.eval.Dict; | ||
import net.starlark.java.eval.EvalException; | ||
import net.starlark.java.eval.Starlark; | ||
import net.starlark.java.eval.StarlarkInt; | ||
import net.starlark.java.eval.StarlarkThread; | ||
|
||
@StarlarkBuiltin(name = "nts", category = "BUILTIN", doc = "Overridable Network Token API in Larky") | ||
public class NetworkTokenModule implements LarkyNetworkToken { | ||
public static final NetworkTokenModule INSTANCE = new NetworkTokenModule(); | ||
|
||
public static final String ENABLE_MOCK_PROPERTY = "larky.modules.vgs.nts.enableMockNetworkToken"; | ||
|
||
private final NetworkTokenService networkTokenService; | ||
|
||
public NetworkTokenModule() { | ||
ServiceLoader<NetworkTokenService> loader = ServiceLoader.load(NetworkTokenService.class); | ||
List<NetworkTokenService> networkTokenProviders = ImmutableList.copyOf(loader.iterator()); | ||
|
||
if (Boolean.getBoolean(ENABLE_MOCK_PROPERTY)) { | ||
networkTokenService = new MockNetworkTokenService(); | ||
} else if (networkTokenProviders.isEmpty()) { | ||
networkTokenService = new NoopNetworkTokenService(); | ||
} else { | ||
if (networkTokenProviders.size() != 1) { | ||
throw new IllegalArgumentException( | ||
String.format( | ||
"NetworkTokenModule expecting only 1 network token provider of type NetworkTokenService, found %d", | ||
networkTokenProviders.size())); | ||
} | ||
networkTokenService = networkTokenProviders.get(0); | ||
} | ||
} | ||
|
||
@StarlarkMethod( | ||
name = "get_network_token", | ||
doc = "Retrieves a network token for the given PAN alias.", | ||
useStarlarkThread = true, | ||
parameters = { | ||
@Param( | ||
name = "pan", | ||
named = true, | ||
doc = "PAN alias. Used to look up the corresponding network token to be returned", | ||
allowedTypes = {@ParamType(type = String.class)}), | ||
@Param( | ||
name = "cvv", | ||
named = true, | ||
doc = | ||
"CVV of the credit card. Used to pass to the network for retrieving the corresponding network token " | ||
+ "and cryptogram to be returned", | ||
allowedTypes = {@ParamType(type = String.class)}), | ||
@Param( | ||
name = "amount", | ||
named = true, | ||
doc = | ||
"The amount of payment for the transaction to be made with the network token. Used to pass to the " | ||
+ "network for retrieving the corresponding network token and cryptogram to be returned", | ||
allowedTypes = {@ParamType(type = String.class)}), | ||
@Param( | ||
name = "currency_code", | ||
named = true, | ||
doc = | ||
"The currency code of payment amount for the transaction to be made with the network token. Used to " | ||
+ "pass to the network for retrieving the corresponding network token and cryptogram to be " | ||
+ "returned", | ||
allowedTypes = {@ParamType(type = String.class)}), | ||
}) | ||
@Override | ||
public Dict<String, Object> getNetworkToken( | ||
String pan, String cvv, String amount, String currencyCode, StarlarkThread thread) | ||
throws EvalException { | ||
if (pan.trim().isEmpty()) { | ||
throw Starlark.errorf("pan argument cannot be blank"); | ||
} | ||
final Optional<NetworkTokenService.NetworkToken> networkTokenOptional; | ||
try { | ||
networkTokenOptional = networkTokenService.getNetworkToken(pan, cvv, amount, currencyCode); | ||
} catch (UnsupportedOperationException exception) { | ||
throw Starlark.errorf("nts.get_network_token operation must be overridden"); | ||
} | ||
if (!networkTokenOptional.isPresent()) { | ||
throw Starlark.errorf("network token is not found"); | ||
} | ||
final NetworkTokenService.NetworkToken networkToken = networkTokenOptional.get(); | ||
return Dict.<String, Object>builder() | ||
.put("token", networkToken.getToken()) | ||
.put("exp_month", StarlarkInt.of(networkToken.getExpireMonth())) | ||
.put("exp_year", StarlarkInt.of(networkToken.getExpireYear())) | ||
.put("cryptogram_value", networkToken.getCryptogramValue()) | ||
.put("cryptogram_eci", networkToken.getCryptogramEci()) | ||
.build(thread.mutability()); | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
larky/src/main/java/com/verygood/security/larky/modules/vgs/nts/LarkyNetworkToken.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.verygood.security.larky.modules.vgs.nts; | ||
|
||
import net.starlark.java.eval.Dict; | ||
import net.starlark.java.eval.EvalException; | ||
import net.starlark.java.eval.StarlarkThread; | ||
import net.starlark.java.eval.StarlarkValue; | ||
|
||
public interface LarkyNetworkToken extends StarlarkValue { | ||
/** | ||
* Get network token by pan alias. | ||
* | ||
* @param pan the pan alias value for getting network token | ||
* @param cvv cvv of card for retrieving cryptogram | ||
* @param amount amount of payment for retrieving cryptogram | ||
* @param currencyCode currency code of payment for retrieving cryptogram | ||
* @param thread Starlark thread object | ||
* @return a dict contains the network token values | ||
*/ | ||
Dict<String, Object> getNetworkToken( | ||
String pan, String cvv, String amount, String currencyCode, StarlarkThread thread) | ||
throws EvalException; | ||
} |
22 changes: 22 additions & 0 deletions
22
larky/src/main/java/com/verygood/security/larky/modules/vgs/nts/MockNetworkTokenService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.verygood.security.larky.modules.vgs.nts; | ||
|
||
import com.verygood.security.larky.modules.vgs.nts.spi.NetworkTokenService; | ||
import java.util.Optional; | ||
|
||
public class MockNetworkTokenService implements NetworkTokenService { | ||
@Override | ||
public Optional<NetworkToken> getNetworkToken( | ||
String panAlias, String cvv, String amount, String currencyCode) { | ||
if (panAlias.equals("NOT_FOUND")) { | ||
return Optional.empty(); | ||
} | ||
return Optional.of( | ||
NetworkToken.builder() | ||
.token("4242424242424242") | ||
.expireMonth(12) | ||
.expireYear(27) | ||
.cryptogramValue("MOCK_CRYPTOGRAM_VALUE") | ||
.cryptogramEci("MOCK_CRYPTOGRAM_ECI") | ||
.build()); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
larky/src/main/java/com/verygood/security/larky/modules/vgs/nts/NoopNetworkTokenService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.verygood.security.larky.modules.vgs.nts; | ||
|
||
import com.verygood.security.larky.modules.vgs.nts.spi.NetworkTokenService; | ||
import java.util.Optional; | ||
|
||
public class NoopNetworkTokenService implements NetworkTokenService { | ||
@Override | ||
public Optional<NetworkToken> getNetworkToken( | ||
String panAlias, String cvv, String amount, String currencyCode) { | ||
throw new UnsupportedOperationException("Not implemented"); | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
larky/src/main/java/com/verygood/security/larky/modules/vgs/nts/spi/NetworkTokenService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.verygood.security.larky.modules.vgs.nts.spi; | ||
|
||
import java.util.Optional; | ||
import lombok.Builder; | ||
import lombok.Data; | ||
|
||
public interface NetworkTokenService { | ||
/** | ||
* Get network token for the given PAN alias. | ||
* | ||
* @param panAlias PAN alias of the network token to get | ||
* @param cvv cvv of card for retrieving cryptogram | ||
* @param amount amount of payment for retrieving cryptogram | ||
* @param currencyCode currency code of payment for retrieving cryptogram | ||
* @return the network token value | ||
*/ | ||
Optional<NetworkToken> getNetworkToken( | ||
String panAlias, String cvv, String amount, String currencyCode); | ||
|
||
@Data | ||
@Builder | ||
class NetworkToken { | ||
private final String token; | ||
private final Integer expireMonth; | ||
private final Integer expireYear; | ||
private final String cryptogramValue; | ||
private final String cryptogramEci; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
load("@stdlib//larky", larky="larky") | ||
load("@vendor//jsonpath_ng", jsonpath_ng="jsonpath_ng") | ||
load("@vgs//nts", "nts") | ||
|
||
|
||
def render( | ||
input, | ||
pan, | ||
cvv, | ||
amount, | ||
currency_code, | ||
raw_pan=False, | ||
raw_cvv=False, | ||
raw_amount=False, | ||
raw_currency_code=False, | ||
output_pan=None, | ||
output_exp_month=None, | ||
output_exp_year=None, | ||
output_cryptogram_value=None, | ||
output_cryptogram_eci=None, | ||
): | ||
"""Retrieves a network token for the given PAN alias, renders the cryptogram, and injects the network token values | ||
into the payload. | ||
For the output JSONPaths, please note that inserting a value into a non-existing deep nested note is not currently | ||
supported. For example, for an input payload like this:: | ||
input = { | ||
"data": {} | ||
} | ||
To insert into `$.data.network_token.exp_month` JSONPath, you need to place an empty value at the exact path first | ||
like this in order to make JSONPath value insertion work:: | ||
input["data"]["network_token"] = {"exp_month": "TO_BE_REPLACED"} | ||
nts_helpers.render(input, ...) | ||
:param input: JSON payload to inject network token into | ||
:param pan: JSONPath to the PAN alias in the input payload or a raw PAN alias value if `raw_pan` is true. | ||
Used to look up the corresponding network token to be rendered and injected into the payload. | ||
:param cvv: JSONPath to the CVV of the credit card in the input payload or a raw CVV value if `raw_cvv` is true. | ||
Used to pass to the network for retrieving the corresponding network token and cryptogram to be returned. | ||
:param amount: JSONPath to the amount of payment for the transaction to be made with the network token in the input | ||
payload or a raw amount value if `raw_amount` is true. Used to pass to the network for retrieving the | ||
corresponding network token and cryptogram to be returned. | ||
:param currency_code: JSONPath to the currency code of payment amount for the transaction to be made with the | ||
network token in the input payload or a raw amount value if `raw_amount` is true. Used to pass to the | ||
network for retrieving the corresponding network token and cryptogram to be returned. | ||
:param raw_pan: treat `pan` value as a raw input value instead of a JSONPath value | ||
:param raw_cvv: treat `raw_cvv` value as a raw input value instead of a JSONPath value | ||
:param raw_amount: treat `raw_amount` value as a raw input value instead of a JSONPath value | ||
:param raw_currency_code: treat `raw_currency_code` value as a raw input value instead of a JSONPath value | ||
:param output_pan: JSONPath to insert the PAN value of the network token within the input payload. | ||
By default, the `pan` JSONPath path value will be used if no not provided. | ||
:param output_exp_month: JSONPath to insert the expiration month of the network token within the input payload | ||
:param output_exp_year: JSONPath to insert the expiration year of the network token within the input payload | ||
:param output_cryptogram_value: JSONPath to insert the cryptogram value of the network token within the input | ||
payload | ||
:param output_cryptogram_eci: JSONPath to insert the cryptogram ECI of the network token within the input payload | ||
:return: JSON payload injected with network token values | ||
""" | ||
if raw_pan: | ||
pan_value = pan | ||
else: | ||
pan_value = jsonpath_ng.parse(pan).find(input).value | ||
if raw_cvv: | ||
cvv_value = cvv | ||
else: | ||
cvv_value = jsonpath_ng.parse(cvv).find(input).value | ||
if raw_amount: | ||
amount_value = amount | ||
else: | ||
amount_value = str(jsonpath_ng.parse(amount).find(input).value) | ||
if raw_currency_code: | ||
currency_code_value = currency_code | ||
else: | ||
currency_code_value = jsonpath_ng.parse(currency_code).find(input).value | ||
|
||
network_token = nts.get_network_token( | ||
pan=pan_value, | ||
cvv=cvv_value, | ||
amount=amount_value, | ||
currency_code=currency_code_value, | ||
) | ||
output_pan_jp = output_pan | ||
if output_pan_jp == None and not raw_pan: | ||
output_pan_jp = pan | ||
placements = [ | ||
(output_pan_jp, network_token["token"]), | ||
(output_exp_month, network_token["exp_month"]), | ||
(output_exp_year, network_token["exp_year"]), | ||
(output_cryptogram_value, network_token["cryptogram_value"]), | ||
(output_cryptogram_eci, network_token["cryptogram_eci"]), | ||
] | ||
for path, value in placements: | ||
if path == None: | ||
continue | ||
input = jsonpath_ng.parse(path).update(input, value).value | ||
return input | ||
|
||
|
||
nts_helpers = larky.struct( | ||
render=render | ||
) |
1 change: 1 addition & 0 deletions
1
...ces/META-INF/services/com.verygood.security.larky.modules.vgs.nts.spi.NetworkTokenService
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
com.verygood.security.larky.modules.vgs.nts.MockNetworkTokenService |
48 changes: 48 additions & 0 deletions
48
larky/src/test/resources/vgs_tests/nts/test_default_nts.star
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
load("@vendor//asserts", "asserts") | ||
load("@stdlib//unittest", "unittest") | ||
load("@vgs//nts", "nts") | ||
|
||
|
||
def _test_get_network_token(): | ||
output = nts.get_network_token( | ||
pan="MOCK_PAN_ALIAS", | ||
cvv="MOCK_CVV", | ||
amount="123.45", | ||
currency_code="USD", | ||
) | ||
asserts.assert_that(output).is_equal_to({ | ||
"token": "4242424242424242", | ||
"exp_month": 12, | ||
"exp_year": 27, | ||
"cryptogram_value": "MOCK_CRYPTOGRAM_VALUE", | ||
"cryptogram_eci": "MOCK_CRYPTOGRAM_ECI" | ||
}) | ||
|
||
|
||
def _test_pan_empty_value(): | ||
asserts.assert_fails(lambda: nts.get_network_token("", cvv="MOCK_CVV", amount="123.45", currency_code="USD"), | ||
"pan argument cannot be blank") | ||
|
||
|
||
def _test_not_found(): | ||
input = { | ||
"pan": "NOT_FOUND", | ||
} | ||
asserts.assert_fails( | ||
lambda: nts.get_network_token("NOT_FOUND", cvv="MOCK_CVV", amount="123.45", currency_code="USD"), | ||
"network token is not found") | ||
|
||
|
||
def _suite(): | ||
_suite = unittest.TestSuite() | ||
|
||
# Redact Tests | ||
_suite.addTest(unittest.FunctionTestCase(_test_get_network_token)) | ||
_suite.addTest(unittest.FunctionTestCase(_test_pan_empty_value)) | ||
_suite.addTest(unittest.FunctionTestCase(_test_not_found)) | ||
|
||
return _suite | ||
|
||
|
||
_runner = unittest.TextTestRunner() | ||
_runner.run(_suite()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
"""Unit tests for VaultModule.java using NoopVault API""" | ||
|
||
load("@vendor//asserts", "asserts") | ||
load("@stdlib//unittest", "unittest") | ||
load("@vgs//nts", "nts") | ||
|
||
|
||
def _test_get_network_token(): | ||
asserts.assert_fails( | ||
lambda: nts.get_network_token(pan="MOCK_PAN_ALIAS", cvv="MOCK_CVV", amount="123.45", currency_code="USD"), | ||
"nts.get_network_token operation must be overridden") | ||
|
||
|
||
def _suite(): | ||
_suite = unittest.TestSuite() | ||
_suite.addTest(unittest.FunctionTestCase(_test_get_network_token)) | ||
return _suite | ||
|
||
|
||
_runner = unittest.TextTestRunner() | ||
_runner.run(_suite()) |
Oops, something went wrong.