Recently I come across a requirement to generate single use coupon codes (Voucher codes) for Episerver promotions and use it in an email marketing campaigns or in affiliate marketing.

I come across a very good article written by David Knipe on bulk generating coupon codes against a campaign and Episerver Foundation (Example website) also added a tool to bulk generate coupon codes. So I decided to use this tool & extend its functionality.

Those of you who are not familiar with tool, This tool is included in Episerver foundation website. Its under Extensions -> Coupons

Episerver Tool: Download single use coupon codes

Now you can see this tool can generate coupon codes based on date range and Max redemption values to get a discount.

For me, the first step is to download these generated codes. I might want to supply codes to Affiliate marketing departments or to use for email marketing.

Download coupon codes in Csv

First of all, I have added a download button in EditPromotionCoupons.cshtml file.

<div class="row">
                            <div class="col-12">
                                <h3>Bulk operations</h3>
                                @using (Html.BeginForm("Download", "SingleUseCoupon", FormMethod.Post, new { @class = "form-horizontal" }))
                                {
                                    @Html.AntiForgeryToken()
                                    @Html.HiddenFor(x => x.PromotionId)
                                    <button type="submit" class="btn btn-primary">Download</button>
                                }
                            </div>
                            </div>

The complete code of EditPromotionCoupons.cshtml will be

@using EPiServer.Shell
@inherits WebViewPage<Foundation.Commerce.Marketing.PromotionCouponsViewModel>
<head>
    <title>Edit promotion coupons</title>
</head>
<main class="dash-content">
    <div class="container-fluid">
        <div class="row">
            <div class="col-12">
                <h3>Manage Coupon Codes for Promotion @Model.Promotion.Name</h3>
                <div class="card spur-card">
                    <div class="card-header">
                        <div class="spur-card-icon">
                            <i data-feather="gift"></i>
                        </div>
                        <div class="spur-card-title">Generate coupons</div>
                    </div>
                    <div class="card-body">
                        @using (Html.BeginForm("Generate", "SingleUseCoupon", FormMethod.Post, new { @class = "form-horizontal" }))
                        {
                            @Html.AntiForgeryToken()
                            @Html.HiddenFor(x => x.PromotionId)
                            <div class="form-row">
                                <div class="form-group col-md-6 col-xl-3">
                                    @Html.LabelFor(x => x.ValidFrom)
                                    @Html.TextBoxFor(x => x.ValidFrom, new { @class = "form-control", @type = "date" })
                                </div>
                                <div class="form-group col-md-6 col-xl-3">
                                    @Html.LabelFor(x => x.Expiration)
                                    @Html.TextBoxFor(x => x.Expiration, new { @class = "form-control", @type = "date" })
                                </div>
                                <div class="form-group col-md-6 col-xl-3">
                                    @Html.LabelFor(x => x.Quantity)
                                    @Html.TextBoxFor(x => x.Quantity, new { @class = "form-control", @type = "number" })
                                </div>
                                <div class="form-group col-md-6 col-xl-3">
                                    @Html.LabelFor(x => x.MaxRedemptions)
                                    @Html.TextBoxFor(x => x.MaxRedemptions, new { @class = "form-control", @type = "number" })
                                </div>
                            </div>
                            <button type="submit" class="btn btn-primary">Generate</button>
                        }
                        
                        <br />
                        <div class="row">
                            <div class="col-12">
                                <h3>Bulk operations</h3>
                                @using (Html.BeginForm("Download", "SingleUseCoupon", FormMethod.Post, new { @class = "form-horizontal" }))
                                {
                                    @Html.AntiForgeryToken()
                                    @Html.HiddenFor(x => x.PromotionId)
                                    <button type="submit" class="btn btn-primary">Download</button>
                                }
                            </div>
                            </div>
                            <div class="mvc-grid">
                                @using (Html.BeginForm("UpdateOrDeleteCoupon", "SingleUseCoupon", FormMethod.Post, new { @class = "jsCouponUpdateForm" }))
                                {
                        @Html.AntiForgeryToken()
                                        <table class="gift-cards-table">
                                            <thead>
                                                <tr>
                                                    <th>Code</th>
                                                    <th>Created</th>
                                                    <th>Valid From</th>
                                                    <th>Expiration</th>
                                                    <th>Max Redemptions</th>
                                                    <th>Used Redemptions</th>
                                                    <th>Actions</th>
                                                </tr>
                                            </thead>
                                            <tbody class="js-users-table-body">
                                                @for (var i = 0; i < Model.Coupons.Count; i++)
                                                {
                                <tr>
                                    <td>
                                        @Html.HiddenFor(x => Model.Coupons[i].Id)
                                        @Html.HiddenFor(x => Model.Coupons[i].PromotionId)
                                        @Html.TextBoxFor(x => Model.Coupons[i].Code, new { @class = "form-control" })
                                    </td>
                                    <td>
                                        @Model.Coupons[i].Created
                                    </td>
                                    <td>
                                        @Html.TextBoxFor(x => Model.Coupons[i].ValidFrom, "{0:yyyy-MM-dd}", new { @class = "form-control", @type = "date" })
                                    </td>
                                    <td>
                                        @Html.TextBoxFor(x => Model.Coupons[i].Expiration, "{0:yyyy-MM-dd}", new { @class = "form-control", @type = "date" })
                                    </td>
                                    <td>
                                        @Html.TextBoxFor(x => Model.Coupons[i].MaxRedemptions, new { @class = "form-control", @type = "number" })
                                    </td>
                                    <td>
                                        @Html.TextBoxFor(x => Model.Coupons[i].UsedRedemptions, new { @class = "form-control", @type = "number" })
                                    </td>
                                    <td style="text-align: right">
                                        <i class="fa fa-save jsUpdateCoupon" style="cursor: pointer;" />
                                        <i class="fa fa-trash-alt jsDeleteCoupon" style="cursor: pointer; color: red"></i>
                                    </td>
                                </tr>}
                                            </tbody>
                                        </table>}
                            </div>
                        </div>
                </div>
            </div>
        </div>
    </div>
</main>
<div style="position: fixed; top: 75px; right: 75px; display: none;" class="coupon-status alert alert-info" role="alert">
    Updating, please wait ...
</div>
<div style="position: fixed; top: 75px; right: 75px; display: none;" class="coupon-alert alert" role="alert">
</div>
@section AdditionalScripts {
    <script type="text/javascript" src="@Paths.ToClientResource("Foundation.Commerce", "ClientResources/js/Coupons.js")"></script>
    <script type="text/javascript">
        $(document).ready(function () {
            Coupons.init();
        });
    </script>
}

Now, we need to add an action result that will get generated coupon codes and generate a CSV file for codes.

To achieve that I have used FileResult type action in “SingleUseCouponController”. You can also use asynchronous Task<ActionResult> depends on the amount of data you intercepting to download.

[HttpPost]
        [ValidateAntiForgeryToken]
        public FileResult Download(PromotionCouponsViewModel model)
        {
            var coupons = _couponService.GetByPromotionId(model.PromotionId);
            var sb = new StringBuilder();
            
            //Headers
            
            sb.Append($"PromotionId,Code,ValidFrom,Expiration,CustomerId,MaxRedemptions,UsedRedemptions");
            sb.Append("\r\n");
            for (int i = 0; i < coupons.Count; i++)
            {
                sb.Append($"{coupons[i].PromotionId}," +
                    $"{coupons[i].Code}," +
                    $"{coupons[i].ValidFrom}," +
                    $"{coupons[i].Expiration}," +
                    $"{coupons[i].CustomerId}," +
                    $"{coupons[i].MaxRedemptions}," +
                    $"{coupons[i].UsedRedemptions}");
                sb.Append("\r\n");
            }
            return File(Encoding.UTF8.GetBytes(sb.ToString()), "text/csv", $"{model.PromotionId}.csv");
        }

I have purposely used _couponService.GetByPromotionId method because it is utilizing optimize query and cache.

So far so good, now its time to test.

If you open modified coupon code tool under extensions, you will be able to download generated coupon codes.

episerver download coupon codes

The next step is to further extend this tool and Delete expired / unused coupons

Categorized in: