Configure Payment Service
With Optimizely Commerce's Payment Service, you can add payment gateway providers to your ecommerce. Payment Service uses the Spreedly Checkout SDK to offer more flexibility and stability.
- Use a payment platform without implementing new code.
- Access more payment gateway options.
- Implement multiple payment services on a single website.
See Spreedly's documentation for information on the following:
Starting with Commerce Connect 14.48.0, the example integration uses the Spreedly Checkout SDK with signed authentication parameters. Earlier versions of the example initialized the Spreedly iFrame with only an environment key, which Spreedly calls non-secure tokenization and is deprecating. If your checkout calls Spreedly.init with only an environment key, update it to match the example integration in the Integration guide with example section. For Spreedly's timeline, see iFrame payment form.
Payment Service options
Name | Data type | Description | Required |
|---|---|---|---|
PaymentServiceIsEnabled | Boolean | Indicates whether PaymentService is enabled | No |
EnvironmentName | String | Environment name | No |
EnvironmentKey | String | Environment key | Yes |
GatewayToken | String | Gateway token | Yes |
ScaProviderKey | String | Sca provider key when using 3DS | Yes, if 3DS is enabled |
ThreeDsIsEnabled | Boolean | Indicates whether 3DS is enabled | No |
ThreeDsTestMode | Boolean | Indicates whether 3DS is in test mode | No |
ThreeDsTestCallbackUrl | String | 3DS call back URL testing – The URL is accessible from the internet. Optimizely can use webhook.site to expose localhost to the internet. | No |
PaymentServiceUrl | String | Payment Service URL | Yes |
AuthenticationType | String | HMAC or OBO | Yes |
AppKey | String | App key | Yes |
SecretKey | String | Secret key | Yes |
SigningSecret | String | Signing secret when using 3DS mode | Yes, if 3DS is enabled |
InstanceIdentifier | String | Instance Identifier when AuthenticationType is OBO | Yes, if AuthenticationType is OBO |
Integration guide with example
See Configure Payment Service for the steps to integrate Payment Service with Commerce Connect.
Click to download the changed files.
Checkout SDK authentication
The Checkout SDK needs signed authentication parameters before it loads the payment fields. Commerce Connect 14.48.0 and later provide them through the GET /api/paymentservice/spreedly/auth endpoint, which SpreedlyAuthController in EPiServer.Commerce.PaymentService implements. The endpoint returns these values:
- environmentKey — The value of the EnvironmentKey option.
- nonce — A unique value for the checkout session.
- timestamp — The current UTC time in ISO 8601 format.
- certificateToken — The same value as environmentKey.
- signature — A SHA-256 hash of the nonce, the timestamp, and the SecretKey option.
If EnvironmentKey is not set, the endpoint returns 400 Bad Request.
The SpreedlyConstants class pins the SDK version and its Subresource Integrity (SRI) hash in CheckoutSdkVersion, CheckoutSdkUrl, and CheckoutSdkIntegrity. Use these constants when you load the SDK in your own views, so the browser blocks a script that does not match the hash.
List of files modified
File | Modification | Description |
|---|---|---|
wwwroot/js/paymentservice.js | Added | JavaScript file that handles all checkout form events, with or without 3DS mode, using the Spreedly Checkout SDK. |
wwwroot/js/js/Checkout.js | Changed | Improved handling for multiple payments for Checkout page. |
Views/Shared/Payment/\_PaymentService.cshtml | Added | Renders the payment form for the end user. The view loads the Checkout SDK with its SRI hash, gets authentication parameters from /api/paymentservice/spreedly/auth, and passes them to initPaymentServiceCheckout in paymentservice.js. The SDK tokenizes credit card numbers and CVVs, and hidden elements support 3DS mode. |
Views/Shared/_PaymentServiceConfirmation.cshtml | Added | Render confirmation after user successfully places order. |
Features/Checkout/Controllers/CheckoutController.cs | Changed | Logic handling for Payment Service. |
Features/Checkout/Services/CheckoutService.cs | Changed | Logic handling for Payment Service. It reads the transaction token from the transactionToken key and falls back to token for earlier integrations. |
Features/Checkout/ViewModels/PaymentServicePaymentViewModel.cs | Added | Model map Payment Service payment for better handling at client side. |
Infrastructure/SiteImport/ImportSiteContentStep.cs | Changed | Step to import Payment Service automatically besides adding Payment Service manually in Administration |
lang/Quicksilver_EN.xml | Changed | Language keys for Payment Service checkout form. |

Details about the described changes
- lang/Quicksilver_EN.xml

- Features/Checkout/Controllers/CheckoutController.cs

- Features/Checkout/Services/CheckoutService.cs
using EPiServer.Commerce.Bolt;
using EPiServer.Commerce.Order;
using EPiServer.Commerce.Order.Payments.Tokenization;
using EPiServer.Commerce.PaymentService;
using EPiServer.Core;
using EPiServer.Data;
using EPiServer.Framework.Localization;
// ...
if (additionalPaymentData.TryGetValue("paymentData", out var paymentData)) {
payment.Properties["additionalPaymentData"] = paymentData;
}
if (payment is PaymentServicePayment paymentServicePayment) {
// Support both PascalCase (new Checkout SDK) and lowercase (legacy) keys
if (additionalPaymentData.TryGetValue("transactionToken", out var transactionToken)) {
paymentServicePayment.TransactionToken = transactionToken;
} else if (additionalPaymentData.TryGetValue("token", out var token)) {
paymentServicePayment.TransactionToken = token;
}
if (additionalPaymentData.TryGetValue("browserInfo", out var browserInfo)) {
paymentServicePayment.BrowserInfo = browserInfo;
}
}
if (payment is BoltPayment boltPayment) {
if (additionalPaymentData.TryGetValue("token", out var token)) {
// ...
}
}
// ...
{
modelState.AddModelError("",
_localizationService.GetString("/Checkout/Payment/Errors/ProcessingPaymentFailure") + ex.Message);
} catch (Exception ex) {
modelState.AddModelError("",
_localizationService.GetString("/Checkout/Payment/Errors/ProcessingPaymentFailure") + ex.Message);
}
return null;- Infrastructure/SiteImport/ImportSiteContentStep.cs

- Views/Shared/Payment/_PaymentService.cshtml
@using EPiServer.Commerce.PaymentService
@* ... *@
@* Load Spreedly Checkout SDK with Subresource Integrity *@
<script src="@SpreedlyConstants.CheckoutSdkUrl"
type="text/javascript"
charset="utf-8"
crossorigin="anonymous"
integrity="@Html.Raw(SpreedlyConstants.CheckoutSdkIntegrity)"
async>
</script>
<script type="text/javascript" src="/js/paymentservice.js" defer></script>
<script type="text/javascript">
// Wait for Spreedly Checkout SDK and fetch auth parameters
(function waitForSpreedly() {
if (typeof SpreedlyHostedFields !== 'undefined' && typeof initPaymentServiceCheckout === 'function') {
fetch('/api/paymentservice/spreedly/auth')
.then(function (response) {
if (!response.ok) {
throw new Error('Failed to fetch Spreedly auth parameters');
}
return response.json();
})
.then(function (authParams) {
initPaymentServiceCheckout(authParams);
})
.catch(function (error) {
// ...
});
} else {
setTimeout(waitForSpreedly, 50);
}
})();
</script>- wwwroot/js/paymentservice.js
function initPaymentServiceCheckout(authParams) {
// Store environment key for 3DS lifecycle
spreedlyEnvironmentKey = authParams.environmentKey;
// Initialize Checkout SDK with full authentication parameters
spreedlySDK = new SpreedlyHostedFields({
environment_key: authParams.environmentKey,
nonce: authParams.nonce,
timestamp: authParams.timestamp,
certificate_token: authParams.certificateToken,
signature: authParams.signature
});
// Mount payment fields
spreedlySDK.inAppElements({
number: { containerId: 'spreedly-number' },
cvv: { containerId: 'spreedly-cvv' }
});
// Token generated event
spreedlySDK.on('tokenGenerated', function (response) {
var tokenizedPaymentMethod = response && response.tokenResponse && response.tokenResponse.payment_method;
var token = tokenizedPaymentMethod && tokenizedPaymentMethod.token;
// ...
document.getElementById("additionalPaymentData[0].Key").value = "transactionToken";
document.getElementById("additionalPaymentData[0].Value").value = token;
// ...
});
// ...
function getBrowserInfo() {
// Checkout SDK uses serializeBrowserInfo global function
return serializeBrowserInfo("04", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
}
// ...
// Checkout SDK uses SpreedlyThreeDSLifecycle constructor with callbacks
const lifecycle = new SpreedlyThreeDSLifecycle({
environmentKey: spreedlyEnvironmentKey,
hiddenIframeLocation: "spreedly-device-fingerprint",
challengeIframeLocation: "spreedly-challenge",
transactionToken: transactionToken,
challengeIframeClasses: "fitToModal",
callbacks: {
onChallenge: function () { /* show the challenge modal */ },
onSuccess: function () { /* hide the modal, then complete the order */ },
onError: function () { /* hide the modal, then show an error */ }
}
});
lifecycle.start();
}
function submitPaymentForm(e) {
// ...
// Submit to Checkout SDK
spreedlySDK.submit(fields);
}