Commit daaf829c by Nilu

fix pay as you go issues when opening jobs

parent 72ae2747
package performa.form;
import com.stripe.Stripe;
import java.util.HashMap;
import java.util.Map;
import oneit.appservices.config.ConfigMgr;
import oneit.servlets.forms.SubmissionDetails;
import oneit.servlets.process.SaveFP;
import oneit.utils.BusinessException;
import oneit.utils.StringUtils;
import oneit.utils.math.NullArith;
import oneit.utils.parsers.FieldException;
import javax.servlet.http.HttpServletRequest;
import oneit.logging.LogLevel;
import oneit.logging.LogMgr;
import oneit.objstore.StorageException;
import oneit.servlets.forms.SuccessfulResult;
import oneit.servlets.process.ORMProcessState;
import com.stripe.Stripe;
import com.stripe.exception.StripeException;
import com.stripe.model.Card;
import com.stripe.model.Charge;
import java.util.Calendar;
import java.util.Date;
import oneit.logging.LoggingArea;
import oneit.security.SecUser;
import oneit.utils.DateDiff;
import performa.intercom.utils.IntercomUtils;
import performa.orm.Company;
import performa.orm.CompanyUser;
import performa.orm.Job;
import performa.orm.types.AssessmentType;
import performa.orm.types.JobStatus;
import performa.utils.StripeUtils;
......@@ -37,79 +42,84 @@ public class MakePaymentFP extends SaveFP
public SuccessfulResult processForm(ORMProcessState process, SubmissionDetails submission, Map p) throws BusinessException, StorageException
{
HttpServletRequest request = submission.getRequest();
Boolean ppj = request.getAttribute("PPJ") != null ? (Boolean) request.getAttribute("PPJ") : Boolean.FALSE;
Boolean editCard = request.getAttribute("EditCard") != null ? (Boolean) request.getAttribute("EditCard") : Boolean.FALSE;
Boolean replaceCard = request.getAttribute("ReplaceCard") != null ? (Boolean) request.getAttribute("ReplaceCard") : Boolean.FALSE;
SecUser secUser = SecUser.getTXUser(process.getTransaction());
CompanyUser companyUser = secUser.getExtension(CompanyUser.REFERENCE_CompanyUser);
Company company = companyUser.getCompany();
Job job = (Job) process.getAttribute("Job");
LogMgr.log(LOG, LogLevel.PROCESSING1,"In MakePaymentFP : " );
Stripe.apiKey = STRIPE_KEY;
LogMgr.log(LOG, LogLevel.PROCESSING1,"In MakePaymentFP from customer to open job: " + company.getStripeReference());
String token = request.getParameter("stripe-token-id");
if(editCard)
{
UpdateCardFP.updateCardDetails(process, submission);
}
if(StringUtils.subBlanks(token) == null)
if(replaceCard)
{
throw new BusinessException("Updating card details failed, Please contact adminstrator for more info.");
ReplaceCardFP.replaceCardDetails(process, submission);
}
if(company.getCardID() != null && ppj)
{
try
{
SecUser secUser = SecUser.getTXUser(process.getTransaction());
CompanyUser companyUser = secUser.getExtension(CompanyUser.REFERENCE_CompanyUser);
Card card = StripeUtils.updateCardDetails(companyUser.getCompany(), token);
Company company = companyUser.getCompany();
company.setNameOnCard(card.getName());
company.setCardPostCode(card.getAddressZip());
company.setCardID(card.getId());
Map<String, Object> chargeParams = new HashMap<>();
chargeParams.put("amount", NullArith.intVal(NullArith.multiply(job.getAssessmentType() == AssessmentType.COMPREHENSIVE ? 499.0 : 399.0, 100, 0d)));
chargeParams.put("currency", "aud");
chargeParams.put("description", "Charges of creating job");
chargeParams.put("customer", company.getStripeReference());
// cannot subscribe to a plan without card details
StripeUtils.updatePlan(company);
Charge.create(chargeParams);
}
catch(StorageException | FieldException e)
catch (StripeException e)
{
LogMgr.log(LOG, LogLevel.PROCESSING1, e, "Error while making payment");
}
LogMgr.log(LOG, LogLevel.PROCESSING1, e, "Error while making a payment of company stripe " + company.getStripeReference());
return super.processForm(process, submission, p);
throw new BusinessException("Stripe payment was failed, Please contact adminstrator for more info.");
}
}
private void performStripePayment(SubmissionDetails submission) throws FieldException, BusinessException
if(company.getCardID() == null)
{
HttpServletRequest request = submission.getRequest();
String token = request.getParameter("stripe-token-id");
Card card = StripeUtils.retrieveCard(company);
company.setNameOnCard(card.getName());
company.setCardPostCode(card.getAddressZip());
company.setCardID(card.getId());
}
if(StringUtils.subBlanks(token) == null)
if(!ppj && company.getPaymentPlan() != null)
{
throw new BusinessException("Stripe payment was failed, Please contact adminstrator for more info.");
// cannot subscribe to a plan without card details
StripeUtils.updatePlan(company);
}
Stripe.apiKey = STRIPE_KEY;
// Charge the Customer instead of the card:
Map<String, Object> chargeParams = new HashMap<>();
chargeParams.put("amount", NullArith.intVal(NullArith.multiply(100d, 100, 0d)));
chargeParams.put("currency", "aud");
chargeParams.put("description", "Charges of creating job");
chargeParams.put("source", token);
Charge charge;
job.setApplyBy(DateDiff.add(DateDiff.getToday(), Calendar.DATE, 30));
job.setOpenDate(new Date());
job.setJobStatus(JobStatus.OPEN);
job.setLastEdited(new Date());
try
{
charge = Charge.create(chargeParams);
}
catch (StripeException e)
if(job.getShortenedURL() == null)
{
throw new BusinessException("Stripe payment failure. Reason :: " + e.getMessage());
job.createShortenedURL();
}
if(charge.getFailureCode() != null)
{
String errorMsg = "Stripe payment failure Code :: " + charge.getFailureCode() + ", Message :: " + charge.getFailureMessage();
if(charge.getFraudDetails() != null)
// restarting process as custom attributes needs to be updated to intercom
completeProcessRestartAndRestoreAttribs(process, request);
secUser = SecUser.getTXUser(process.getTransaction());
companyUser = secUser.getExtension(CompanyUser.REFERENCE_CompanyUser);
// Update company in intercom
if(companyUser.getCompany() != null)
{
errorMsg += ", Fraud Details :: " + charge.getFraudDetails();
IntercomUtils.updateCompany(companyUser.getCompany());
}
throw new BusinessException(errorMsg);
}
return super.processForm(process, submission, p);
}
}
\ No newline at end of file
package performa.form;
import com.stripe.Stripe;
import com.stripe.model.Card;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import oneit.appservices.config.ConfigMgr;
import oneit.logging.LogLevel;
import oneit.logging.LogMgr;
import oneit.logging.LoggingArea;
import oneit.objstore.StorageException;
import oneit.security.SecUser;
import oneit.servlets.forms.SubmissionDetails;
import oneit.servlets.forms.SuccessfulResult;
import oneit.servlets.process.ORMProcessState;
import oneit.servlets.process.SaveFP;
import oneit.utils.BusinessException;
import oneit.utils.StringUtils;
import oneit.utils.parsers.FieldException;
import performa.orm.Company;
import performa.orm.CompanyUser;
import performa.utils.StripeUtils;
public class ReplaceCardFP extends SaveFP
{
public static final String STRIPE_KEY = ConfigMgr.getKeyfileString("stripe.key","");
public static final String STRIPE_PUB_KEY = ConfigMgr.getKeyfileString("stripe.pubkey","");
private static final LoggingArea LOG = LoggingArea.createLoggingArea("ReplaceCardFP");
@Override
public SuccessfulResult processForm(ORMProcessState process, SubmissionDetails submission, Map p) throws BusinessException, StorageException
{
LogMgr.log(LOG, LogLevel.PROCESSING1,"In ReplaceCardFP to replace card details");
replaceCardDetails(process, submission);
return super.processForm(process, submission, p);
}
public static void replaceCardDetails(ORMProcessState process, SubmissionDetails submission) throws BusinessException
{
HttpServletRequest request = submission.getRequest();
Stripe.apiKey = STRIPE_KEY;
String token = request.getParameter("stripe-token-id");
if(StringUtils.subBlanks(token) == null)
{
throw new BusinessException("Updating card details failed, Please contact adminstrator for more info.");
}
try
{
SecUser secUser = SecUser.getTXUser(process.getTransaction());
CompanyUser companyUser = secUser.getExtension(CompanyUser.REFERENCE_CompanyUser);
Card card = StripeUtils.updateCardDetails(companyUser.getCompany(), token);
Company company = companyUser.getCompany();
LogMgr.log(LOG, LogLevel.PROCESSING1,"In ReplaceCardFP replacing card details of company: " + company );
company.setNameOnCard(card.getName());
company.setCardPostCode(card.getAddressZip());
company.setCardID(card.getId());
// cannot subscribe to a plan without card details
if(company.getPaymentPlan() != null)
{
StripeUtils.updatePlan(company);
}
}
catch(StorageException | FieldException e)
{
LogMgr.log(LOG, LogLevel.PROCESSING1, e, "Error while replacing stripe card details");
}
}
}
\ No newline at end of file
......@@ -20,6 +20,7 @@ import oneit.servlets.forms.SuccessfulResult;
import oneit.servlets.process.ORMProcessState;
import oneit.servlets.process.SaveFP;
import oneit.utils.BusinessException;
import oneit.utils.parsers.FieldException;
import performa.orm.Company;
import performa.orm.CompanyUser;
import performa.utils.StripeUtils;
......@@ -35,6 +36,14 @@ public class UpdateCardFP extends SaveFP
Stripe.apiKey = StripeUtils.STRIPE_KEY;
updateCardDetails(process, submission);
return super.processForm(process, submission, p);
}
public static void updateCardDetails(ORMProcessState process, SubmissionDetails submission) throws FieldException
{
try
{
HttpServletRequest request = submission.getRequest();
......@@ -57,13 +66,20 @@ public class UpdateCardFP extends SaveFP
updateParams.put("exp_year", expiryDate.substring(5, 7));
}
if(name != null && !name.isEmpty())
{
updateParams.put("name", name);
company.setNameOnCard(name);
}
if(addressZip != null && !addressZip.isEmpty())
{
updateParams.put("address_zip", addressZip);
company.setCardPostCode(addressZip);
}
Card updatedCard = card.update(updateParams);
company.setNameOnCard(name);
company.setCardPostCode(addressZip);
LogMgr.log(LOG, LogLevel.PROCESSING1,"Updated card details of user : ", company, " updated card : " , updatedCard );
}
......@@ -71,7 +87,5 @@ public class UpdateCardFP extends SaveFP
{
LogMgr.log(LOG, LogLevel.PROCESSING1, e, "Error while updating card details of user");
}
return super.processForm(process, submission, p);
}
}
\ No newline at end of file
package performa.orm;
import com.stripe.model.Card;
import java.util.Collection;
import java.util.Collections;
import oneit.logging.LoggingArea;
import oneit.objstore.BaseBusinessClass;
import oneit.objstore.ObjectStatus;
import oneit.objstore.ValidationContext;
import oneit.objstore.rdbms.filters.EqualsFilter;
......@@ -158,15 +155,26 @@ public class Company extends BaseCompany
}
public Card getCard() throws FieldException
public String getCardNumber() throws FieldException
{
return StripeUtils.retrieveCard(this);
}
if(getStripeLast4() == null)
{
Card card = StripeUtils.retrieveCard(this);
if(card != null)
{
setStripeLast4(card.getLast4());
setStripeBrand(card.getBrand());
public String getCardNumber(Card card) throws FieldException
return "xxxx-xxxx-xxxx-" + getStripeLast4();
}
else
{
return card != null ? "xxxx-xxxx-xxxx-" + card.getLast4() : "";
return "";
}
}
return "xxxx-xxxx-xxxx-" + getStripeLast4();
}
......
......@@ -18,6 +18,8 @@
<TRANSIENT name="IsLogoDeleted" type="Boolean" defaultValue="Boolean.FALSE"/>
<TRANSIENT name="CompletedProfile" type="Boolean" defaultValue="Boolean.FALSE"/>
<TRANSIENT name="PaymentJobCount" type="Integer"/>
<TRANSIENT name="StripeLast4" type="String" />
<TRANSIENT name="StripeBrand" type="String" />
<TABLE name="tl_company" tablePrefix="object">
......
......@@ -56,6 +56,7 @@
<FORM name="*.savePayment" factory="Participant" class="performa.form.MakePaymentFP"/>
<FORM name="*.managePlans" factory="Participant" class="performa.form.ManagePlansFP"/>
<FORM name="*.updateCard" factory="Participant" class="performa.form.UpdateCardFP"/>
<FORM name="*.replaceCard" factory="Participant" class="performa.form.ReplaceCardFP"/>
</NODE>
<NODE name="job_assessment_criteria_add_jsp" factory="Participant">
......
......@@ -88,7 +88,7 @@
</div>
</div>
</div>
<oneit:button value="Pay" name="savePayment" cssClass="hide" id="payNow"
<oneit:button value="Pay" name="replaceCard" cssClass="hide" id="payNow"
requestAttribs='<%= CollectionUtils.mapEntry("nextPage", nextPage)
.toMap() %>'/>
</oneit:form>
......
......@@ -8,10 +8,10 @@
SecUser loggedInUser = SecUser.getTXUser(transaction);
CompanyUser companyUser = loggedInUser.getExtension(CompanyUser.REFERENCE_CompanyUser);
Company company = companyUser.getCompany();
String nextPage = WebUtils.getSamePageInRenderMode(request, "CardPayment");
String nextPage = WebUtils.getSamePageInRenderMode(request, WebUtils.CREATED_JOB) + "&fromJob=true";
Job job = (Job) process.getAttribute("Job");
Boolean ppj = (Boolean) process.getAttribute("PPJ");
String jobsPage = WebUtils.getSamePageInRenderMode(request, "Page");
%>
<oneit:form name="makePayment" method="post" enctype="multipart/form-data">
......@@ -68,34 +68,15 @@
.toMap() %>" />
</div>
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6 btn-right">
<oneit:button value="Pay and Open Job" name="updateCard" cssClass="btn btn-primary large-btn btn-green"
requestAttribs="<%= CollectionUtils.mapEntry("nextPage", nextPage)
<oneit:button value="Pay and Open Job" name="savePayment" cssClass="btn btn-primary large-btn btn-green"
requestAttribs='<%= CollectionUtils.mapEntry("nextPage", nextPage)
.mapEntry("Company", company)
.toMap() %>" />
</div>
</div>
</div>
.mapEntry("PPJ", ppj)
.toMap() %>'/>
</div>
</oneit:form>
<oneit:form name="editJob" method="post" enctype="multipart/form-data">
<div class="row">
<div class="col-lg-4 col-md-4 col-sm-6">
<div class="form-group hide">
<input type="hidden" name="stripe-token-id" />
</div>
</div>
</div>
<oneit:button value="Pay" name="savePayment" cssClass="hide" id="payNow"
requestAttribs='<%= CollectionUtils.mapEntry("nextPage", nextPage)
.toMap() %>'/>
</oneit:form>
<script src="https://js.stripe.com/v3/"></script>
<script>
var stripePubKey = '<%= MakePaymentFP.STRIPE_PUB_KEY %>';
</script>
<oneit:script>
<!-- MUST be included after initializing stripePubKey -->
<oneit:script src="/scripts/performaStripe.js"/>
</oneit:script>
</oneit:dynIncluded>
\ No newline at end of file
......@@ -8,16 +8,17 @@
SecUser loggedInUser = SecUser.getTXUser(transaction);
CompanyUser companyUser = loggedInUser.getExtension(CompanyUser.REFERENCE_CompanyUser);
Company company = companyUser.getCompany();
String nextPage = WebUtils.getSamePageInRenderMode(request, "CardPayment");
String nextPage = WebUtils.getSamePageInRenderMode(request, WebUtils.CREATED_JOB) + "&fromJob=true";
Job job = (Job) process.getAttribute("Job");
String jobsPage = WebUtils.getSamePageInRenderMode(request, "Page");
Boolean ppj = (Boolean) process.getAttribute("PPJ");
%>
<oneit:form name="makePayment" method="post" enctype="multipart/form-data">
<script type="text/javascript">
$(document).ready(function()
{
recalcFunction = setupRecalc ($("form"), {'recalcOnError':true});
recalcFunction = setupRecalc ($("makePayment"), {'recalcOnError':true});
});
</script>
......@@ -69,7 +70,6 @@
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6 btn-right">
<oneit:button value="Pay and Open Job" name="updateCard" cssClass="btn btn-primary large-btn btn-green"
requestAttribs="<%= CollectionUtils.mapEntry("nextPage", nextPage)
.mapEntry("Company", company)
.toMap() %>" />
</div>
</div>
......@@ -80,20 +80,29 @@
<div class="row">
<div class="col-lg-4 col-md-4 col-sm-6">
<div class="form-group hide">
<input type="hidden" name="stripe-token-id" />
<input type="hidden" name="expiry-date" />
</div>
<div class="form-group hide">
<input type="hidden" name="holder-name" />
</div>
<div class="form-group hide">
<input type="hidden" name="address-zip" />
</div>
</div>
</div>
<oneit:button value="Pay" name="savePayment" cssClass="hide" id="payNow"
requestAttribs='<%= CollectionUtils.mapEntry("nextPage", nextPage)
.mapEntry("EditCard", Boolean.TRUE)
.mapEntry("PPJ", ppj)
.toMap() %>'/>
</oneit:form>
<script src="https://js.stripe.com/v2/"></script>
<script src="https://js.stripe.com/v3/"></script>
<script>
var stripePubKey = '<%= MakePaymentFP.STRIPE_PUB_KEY %>';
</script>
<oneit:script>
<!-- MUST be included after initializing stripePubKey -->
<oneit:script src="/scripts/performaStripe.js"/>
<oneit:script src="/scripts/updateCardStripe.js"/>
</oneit:script>
</oneit:dynIncluded>
\ No newline at end of file
......@@ -61,9 +61,8 @@
<%
if(isEdit)
{
Card card = company.getCard();
%>
<input type="text" name="cardNumber" value="<%= company.getCardNumber(card) %>" class="form-control" readonly>
<input type="text" name="cardNumber" value="<%= company.getCardNumber() %>" class="form-control" readonly>
<%
}
else
......
......@@ -10,7 +10,9 @@
boolean isEdit = (boolean) getData(request, "IsEdit");
String replaceCardPage = WebUtils.getSamePageInRenderMode(request, "ReplaceCard");
String editCardPage = WebUtils.getSamePageInRenderMode(request, "EditCard");
Card card = company.getCard();
String cardNumber = company.getCardNumber();
Job job = (Job) process.getAttribute("Job");
Boolean ppj = (Boolean) process.getAttribute("PPJ");
%>
<oneit:dynIncluded>
<div class="form-group row">
......@@ -18,12 +20,14 @@
<label>Card on file</label>
</div>
<div class="col-lg-5 col-md-5 col-sm-5" style="margin-top: -20px;">
<img src="<%= card.getBrand() == "MasterCard" ? "images/master.svg" : "images/visa.svg" %>" style="width:60px;"/>
<label><%= company.getCardNumber(card) %></label>
<img src="<%= company.getStripeBrand().equals("MasterCard") ? "images/master.svg" : "images/visa.svg" %>" style="width:60px;"/>
<label><%= cardNumber %></label>
</div>
<div class="col-lg-2 col-md-2 col-sm-2">
<oneit:button value=" " name="gotoPage" skin="link" cssClass="<%= "edit-card-link " + (isEdit ? "" : "deselected-link")%>"
requestAttribs="<%= CollectionUtils.mapEntry("nextPage", editCardPage)
.mapEntry("procParams", CollectionUtils.mapEntry("Job", job)
.mapEntry("PPJ", ppj).toMap())
.toMap() %>">
Edit Card
</oneit:button>
......@@ -31,6 +35,8 @@
<div class="col-lg-2 col-md-2 col-sm-2">
<oneit:button value=" " name="gotoPage" skin="link" cssClass="<%= "edit-card-link " + (isReplace ? "" : "deselected-link")%>"
requestAttribs="<%= CollectionUtils.mapEntry("nextPage", replaceCardPage)
.mapEntry("procParams", CollectionUtils.mapEntry("Job", job)
.mapEntry("PPJ", ppj).toMap())
.toMap() %>">
Replace Card
</oneit:button>
......
......@@ -107,7 +107,8 @@
<div class="a-label-row text-center">
<oneit:recalcClass htmlTag="span" classScript="company.getPaymentPlanAmount()!=null ? 'select-plan enabled': 'select-plan disabled'" company="<%= company %>">
<oneit:button value="Select Plan" name="saveJob" cssClass="btn btn-primary largeBtn btn-green save-btn"
requestAttribs="<%= CollectionUtils.mapEntry("nextPage", nextPage)
requestAttribs="<%= CollectionUtils.mapEntry("nextPage", paymentPage)
.mapEntry("PPJ", Boolean.TRUE)
.mapEntry ("fromPage", fromPage)
.mapEntry("JobStatus", JobStatus.OPEN)
.mapEntry ("restartProcess", Boolean.TRUE)
......@@ -135,6 +136,7 @@
</div>
</div>
<div class="form-page-area payment-optio-sep">
<div class="a-label-row">
<div class="col-md-3 col-sm-3 col-xs-3">
......@@ -142,14 +144,17 @@
<div class="col-md-3 col-sm-3 col-xs-3">
<div class="annual-plan">Pay Per Job</div>
<div class="">
<span class="pay-only-job-amt">$499.00</span>
<span class="pay-only-job-amt">
<oneit:toString value="<%= job.getAssessmentType() == AssessmentType.COMPREHENSIVE ? 499.0 : 399.0%>" mode="Currency"/>
</span>
<span class="pay-only-job-txt">&ensp;&ensp;Open for 30 Days</span>
</div>
</div>
<div class="col-md-6 col-sm-6 col-xs-6">
<oneit:button value="Pay for this job only" name="gotoPage" cssClass="btn pay-only-job-btn"
requestAttribs="<%= CollectionUtils.mapEntry("nextPage", paymentPage)
.mapEntry("procParams", CollectionUtils.mapEntry("Job", job).toMap())
.mapEntry("procParams", CollectionUtils.mapEntry("Job", job)
.mapEntry("PPJ", Boolean.TRUE).toMap())
.toMap() %>" />
</div>
</div>
......
......@@ -81,7 +81,7 @@
</div>
</div>
</div>
<oneit:button value="Pay" name="savePayment" cssClass="hide" id="payNow"
<oneit:button value="Pay" name="replaceCard" cssClass="hide" id="payNow"
requestAttribs='<%= CollectionUtils.mapEntry("nextPage", nextPage)
.toMap() %>'/>
</oneit:form>
......
......@@ -8,15 +8,16 @@
SecUser loggedInUser = SecUser.getTXUser(transaction);
CompanyUser companyUser = loggedInUser.getExtension(CompanyUser.REFERENCE_CompanyUser);
Company company = companyUser.getCompany();
String nextPage = WebUtils.getSamePageInRenderMode(request, "CardPayment");
String nextPage = WebUtils.getSamePageInRenderMode(request, WebUtils.CREATED_JOB) + "&fromJob=true";
Job job = (Job) process.getAttribute("Job");
String jobsPage = WebUtils.getSamePageInRenderMode(request, "Page");
Boolean ppj = (Boolean) process.getAttribute("PPJ");
%>
<oneit:form name="makePayment" method="post" enctype="multipart/form-data">
<script type="text/javascript">
$(document).ready(function()
{
recalcFunction = setupRecalc ($("form"), {'recalcOnError':true});
recalcFunction = setupRecalc ($("makePayment"), {'recalcOnError':true});
});
</script>
......@@ -66,7 +67,7 @@
.toMap() %>" />
</div>
<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6 btn-right">
<oneit:button value="Pay and Open Job" name="updateCard" cssClass="btn btn-primary large-btn btn-green"
<oneit:button value="Pay and Open Job" name="savePayment" cssClass="btn btn-primary large-btn btn-green"
requestAttribs="<%= CollectionUtils.mapEntry("nextPage", nextPage)
.mapEntry("Company", company)
.toMap() %>" />
......@@ -85,6 +86,9 @@
</div>
<oneit:button value="Pay" name="savePayment" cssClass="hide" id="payNow"
requestAttribs='<%= CollectionUtils.mapEntry("nextPage", nextPage)
.mapEntry("Company", company)
.mapEntry("ReplaceCard", Boolean.TRUE)
.mapEntry("PPJ", ppj)
.toMap() %>'/>
</oneit:form>
<script src="https://js.stripe.com/v3/"></script>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment