I have created a Symfony form using the UX Live Component.
<?php
// statements
#[AsLiveComponent(
name: "ChargeCardForm",
template: "admin/components/charge-card-form.html.twig"
)]
class ChargeCardForm extends AbstractController
{
use DefaultActionTrait;
use LiveCollectionTrait;
use ComponentWithFormTrait;
protected function instantiateForm(): FormInterface
{
return $this->createForm(ChargeCardType::class);
}
#[LiveAction]
public function save(): Response
{
$this->submitForm();
$form = $this->getForm();
$data = $form->getData();
dd($data);
}
}
where ChargeCardType::class
is
<?php
// statements
class ChargeCardType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('amount', Type\TextType::class, [
'label' => '<b>Charge</b> Amount',
'label_html' => true,
'constraints' => [
new Constraints\NotBlank(),
new Constraints\Regex([
'pattern' => '/^\d+(\.\d{1,2})?$/',
'message' => 'Please enter a valid amount.',
]),
new Constraints\GreaterThan(0),
]
]);
$builder->add('paymentNonce', Type\HiddenType::class);
$builder->add('internalNote', Type\TextareaType::class, [
'label' => '<b>Internal</b> Note',
'label_html' => true,
'constraints' => [
new Constraints\NotBlank(),
]
]);
$builder->add('customerNote', Type\TextareaType::class, [
'label' => '<b>Customer</b> Note',
'label_html' => true,
]);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([]);
}
}
and in the component template I have the followings
<div {{ attributes }}>
{{ form_start(form, {attr: {
'novalidate': true,
'data-action': 'live#action',
'data-action-name': 'prevent',
}}) }}
<div class="card shadow-none mb-2">
<div class="card-body credit-card">
<div id="dropin-container"></div>
</div>
</div>
{{ form_widget(form) }}
<div class="mb-3">
<button type="submit" data-loading="action(save)|addAttribute(disabled)" class="btn btn-primary">
<span data-loading="action(save)|show" style="display: none">Charging Card...</span>
<span data-loading="action(save)|hide" style="display: block">Charge Card</span>
</button>
</div>
{{ form_end(form) }}
<script src="https://js.braintreegateway.com/web/dropin/1.40.2/js/dropin.min.js"></script>
<script>
const form = document.querySelector('[name="charge_card"]');
braintree.dropin.create({
authorization: 'token',
container: '#dropin-container'
}, (error, dropinInstance) => {
if (error) console.error(error);
form.addEventListener('submit', event => {
event.preventDefault();
dropinInstance.requestPaymentMethod((error, payload) => {
if (error) console.error(error);
// Step four: when the user is ready to complete their
// transaction, use the dropinInstance to get a payment
// method nonce for the user's selected payment method, then add
// it a the hidden field before submitting the complete form to
// a server-side integration
document.getElementById('charge_card_paymentNonce').value = payload.nonce;
// form.submit();
});
});
});
</script>
<style>
.credit-card .form-control {
padding: 0;
height: 43px
}
</style>
</div>
So when I submit the form it will create two requests one for the UX Component "Save" method and another with Braintree UI to generate payment_nonce
.
But, I need to make these requests in sequence, basically, I need to generate payment_nonce
first and store it into the hidden field and then start the UX Component "Save" method.
Currently, the payment_nonce
is being generated properly but not getting into the UX Component "Save" method as the request was created before the value is set on the hidden field.