Angular 6 PayPal VALIDATION_ERROR

106
10

Я пытаюсь использовать [код JavaScript PayPal с их веб-сайтом www.sandbox.paypal.com 1 в Angular 6 и получаю следующую ошибку:

Error: Request to post https://www.sandbox.paypal.com/v1/payments/payment failed with 400 error. Correlation id: 6c0ce47611849, 6c0ce47611849

{
"name": "VALIDATION_ERROR",
"details": [
{
"field": "transactions.amount",
"issue": "Currency amount must be non-negative number, may optionally contain exactly 2 decimal places separated by '.', optional thousands separator ',', limited to 7 digits before the decimal point and currency which is a valid ISO Currency Code"
}
],
"message": "Invalid request - see details",
"information_link": "https://developer.paypal.com/docs/api/payments/#errors",
"debug_id": "6c0ce47611849"
}

request/</<@https://www.paypalobjects.com/api/checkout.js:14216:39

Как вы можете видеть на следующем скриншоте, формат мне кажется правильным: the format looks correct to me[2]

Вот моя форма доставки HTML:

 <div class="container">
<div class="row">
<div class="col-md-8 order-md-1">
<h4 class="mb-3">Shipping address</h4>
<form name="form" #f="ngForm" novalidate>
<div class="row">
<div class="col-md-6 mb-3">
<label for="firstName">First name</label>
<input
type="text"
class="form-control"
[(ngModel)]="model.firstName"
#firstName="ngModel"
[ngClass]="{
'is-invalid': f.submitted && firstName.invalid
}"
name="firstName"
placeholder="First name"
required
/>
<div
*ngIf="f.submitted && firstName.required"
class="invalid-feedback"
>
<div *ngIf="firstName.errors.required">
First name is required
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<label for="lastName">Last name</label>
<input
type="text"
class="form-control"
[(ngModel)]="model.lastName"
#lastName="ngModel"
[ngClass]="{
'is-invalid': f.submitted && lastName.invalid
}"
name="lastName"
placeholder="Last name"
required
/>
<div
*ngIf="f.submitted && lastName.required"
class="invalid-feedback"
>
<div *ngIf="lastName.errors.required">Last name is required</div>
</div>
</div>
</div>

<div class="mb-3">
<label for="email"
>Email <span class="text-muted">(Same as username)</span></label
>
<input
type="email"
class="form-control"
[(ngModel)]="model.email"
#email="ngModel"
[ngClass]="{
'is-invalid': f.submitted && email.invalid
}"
name="email"
placeholder="you@example.com"
required
value="{{ model.email }}"
email
/>
<div *ngIf="f.submitted && email.required" class="invalid-feedback">
<div *ngIf="email.errors.email">Email is required</div>
</div>
</div>

<div class="mb-3">
<label for="address">Address</label>
<input
type="text"
class="form-control"
[(ngModel)]="model.address"
#address="ngModel"
[ngClass]="{
'is-invalid': f.submitted && address.invalid
}"
name="address"
placeholder="1234 Main St"
required
/>
<div *ngIf="f.submitted && address.required" class="invalid-feedback">
<div *ngIf="address.errors.required">
Shipping address is required
</div>
</div>
</div>

<div class="mb-3">
<label for="address2"
>Address 2 <span class="text-muted">(Optional)</span></label
>
<input
type="text"
class="form-control"
[(ngModel)]="model.address2"
name="address2"
placeholder="Apartment or suite"
#address2="ngModel"
/>
</div>

<div class="mb-3">
<label for="city">City <span class="text-muted"></span></label>
<input
type="text"
class="form-control"
[(ngModel)]="model.city"
name="city"
placeholder="Apartment or suite"
#city="ngModel"
/>
</div>

<div class="mb-3">
<label for="phone">Phone <span class="text-muted"></span></label>
<input
type="text"
class="form-control"
[(ngModel)]="model.phone"
name="phone"
placeholder="Apartment or suite"
#phone="ngModel"
/>
</div>

<div class="row">
<div class="col-md-5 mb-3">
<label for="country">Country</label>
<select
class="custom-select d-block w-100"
[(ngModel)]="model.country"
[ngClass]="{
'is-invalid': f.submitted && country.invalid
}"
name="country"
#country="ngModel"
required
>
<option value="" selected="selected"
>Please choose a country</option
>
<option
*ngFor="let country of countriesObj"
value="{{ country.code }}"
>{{ country.name }}</option
>
</select>
<div
*ngIf="f.submitted && country.required"
class="invalid-feedback"
>
<div *ngIf="country.errors.required">Country is required</div>
</div>
</div>
<div class="mb-3">
<label for="state">State/Province</label>
<input
type="text"
class="form-control"
[(ngModel)]="model.state"
[ngClass]="{
'is-invalid': f.submitted && state.invalid
}"
name="state"
placeholder="California"
#state="ngModel"
required
/>
<div *ngIf="f.submitted && state.required" class="invalid-feedback">
<div *ngIf="state.errors.required">
State/Province is required
</div>
</div>
</div>
<div class="col-md-3 mb-3">
<label for="zip">Zip/Postal Code</label>
<input
type="text"
class="form-control"
[(ngModel)]="model.zip"
[ngClass]="{
'is-invalid': f.submitted && zip.invalid
}"
name="zip"
placeholder="Zip/Postal code"
#zip="ngModel"
required
/>
<div *ngIf="f.submitted && zip.required" class="invalid-feedback">
<div *ngIf="zip.errors.required">Zip/Postal code is required</div>
</div>
</div>
</div>
<hr class="mb-4" />

<h4 class="mb-3">Payment</h4>
<div id="paypal-button-container"></div>
<input
type="text"
[(ngModel)]="model.total"
style="padding-bottom: 10px;"
name="total"
#total="ngModel"
value="{{ model.total | currency }}"
/>
<h2 *ngIf="paypalLoad">Paypal button is loading</h2>
<div id="paypal-checkout-btn"></div>
</form>
</div>
</div>
</div>
<div class="ModalBackdrop"></div>
Final amount: {{ this.finalAmount }}
<!-- The Modal -->
<div class="modal" id="myModal">
<div class="modal-dialog">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">Thank you</h4>
<button type="button" class="close" data-dismiss="modal">
×
</button>
</div>

<!-- Modal body -->
<div class="modal-body">
This is confirmation that your payment has been processed. Your order
will shipped as soon as possible.
</div>

<!-- Modal footer -->
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal" onclick="document.getElementById('ModalBackdrop').style.display = 'none';">
Close
</button>
</div>
</div>
</div>
</div>

Ниже приведен мой компонент формы доставки - как вы можете видеть, я пробовал несколько разных вещей:

import { Component, OnInit, AfterViewChecked } from "@angular/core";
import { HttpClient, HttpHeaders, HttpResponse } from "@angular/common/http";
import { ShoppingCartService } from "../shopping-cart.service";
import { ProductService } from "../product.service";
import { LoginService } from "../login.service";
import { modelGroupProvider } from "@angular/forms/src/directives/ng_model_group";

declare let paypal: any;

@Component({
selector: "app-shipping-form",
templateUrl: "./shipping-form.component.html",
styleUrls: ["./shipping-form.component.css"]
})
export class ShippingFormComponent implements OnInit, AfterViewChecked {
public countriesObj;
total;
public model: any = {};
firstName = "";
lastName = "";
email = this.loginSrvc.loginObj.uName;
address = "";
address2 = "";
city = "";
phone = "";
country = "";
state = "";
zip = "";
name = "";
shoppingCartObj: any;
user_id = "";
orders_id = "";
product = "";
ts = "";

constructor(
private http: HttpClient,
private loginSrvc: LoginService,
private prdSrvc: ProductService,
private cartSrvc: ShoppingCartService
) {
this.http.get("./assets/countries.json").subscribe(result => {
this.countriesObj = result;
});
this.total = this.prdSrvc.getPriceTotal();
this.model.total = this.total.toFixed(2);
this.model.email = this.loginSrvc.loginObj.uName;
// this.model.firstName = this.firstName;
// this.model.lastName = this.lastName;
this.name = this.model.firstName + " " + this.model.lastName;
this.email = this.model.email;
this.address = this.model.address;
this.address2 = this.model.address2;
this.city = this.model.city;
this.state = this.model.state;
this.country = this.model.country;
this.zip = this.model.zip;
this.shoppingCartObj = JSON.parse(localStorage.getItem("fh_cart"));
this.user_id = this.shoppingCartObj.cart.user_id;
this.orders_id = this.shoppingCartObj.cart.orders_id;
this.product = JSON.stringify(this.shoppingCartObj.cart.products.product);
this.ts = this.shoppingCartObj.cart.ts;
}

addScript = false;
paypalLoad = true;
finalAmount: number = this.getFinalAmount();

paypalConfig = {
env: "sandbox",
client: {
sandbox:
"<my-sandbox-key>",
production: "<your-production-key here>"
},
commit: true,
payment: function(data, actions) {
return actions.payment.create({
transactions: [
{
amount: {
total: this.finalAmount,
currency: "USD",
details: {
subtotal: "30.00",
tax: "0.07",
shipping: "0.03",
handling_fee: "1.00",
shipping_discount: "-1.00",
insurance: "0.01"
}
},
description: "The payment from for-her application.",
/* custom: "90048630024435", */
invoice_number: this.orders_id, // Insert a unique invoice number
payment_options: {
allowed_payment_method: "INSTANT_FUNDING_SOURCE"
},
soft_descriptor: this.user_id,
item_list: {
items: [this.product],
shipping_address: {
recipient_name: this.name,
line1: this.address,
line2: this.address2,
city: this.city,
country_code: this.country,
postal_code: this.zip,
phone: this.phone,
state: this.state,
email: this.email
}
}
}
],
note_to_payer: "Contact us for any questions on your order."
});
},
onAuthorize: (data, actions) => {
return actions.payment.execute().then(payment => {
// Do something when payment is successful.
// window.alert("Thank you for your purchase! You order will be processed and shipped as soon as possible");
document.getElementById("myModal").style.display = "block";
document.getElementById("ModalBackdrop").style.display = "block";
this.cartSrvc.postCart();
});
}
};

ngAfterViewChecked(): void {
if (!this.addScript) {
this.addPaypalScript().then(() => {
paypal.Button.render(this.paypalConfig, "#paypal-checkout-btn");
this.paypalLoad = false;
});
}
}

addPaypalScript() {
this.addScript = true;
return new Promise((resolve, reject) => {
const scripttagElement = document.createElement("script");
scripttagElement.src = "https://www.paypalobjects.com/api/checkout.js";
scripttagElement.onload = resolve;
document.body.appendChild(scripttagElement);
});
}
getFinalAmount() {
this.total = this.prdSrvc.getPriceTotal();
this.model.total = this.total.toFixed(2);
return this.total.toFixed(2);
}

ngOnInit() {}
}

Как видите, я следовал их документации, за исключением использования динамических значений. Если я жестко кодирую значения, как показывает их пример, у меня нет ошибок. Почему он считает finalAmount недействительным?

Благодаря @paulsm4 в комментариях вкладка сети не отображается в значениях, за исключением жестко заданных значений:

hard coded values only coming through

Как обычно, заранее спасибо

спросил(а) 2019-01-15T20:42:00+03:00 1 год, 8 месяцев назад
1
Решение
98

Я не знаю, является ли это правильным способом сделать это, но я больше не получаю исходную ошибку.

Сначала я создал несколько скрытых полей для соответствующих элементов, которые я хотел опубликовать, а также дал каждому входному идентификатору идентификатор - ниже приведены скрытые поля:

    <input
type="text"
[(ngModel)]="model.total"
style="padding-bottom: 10px;"
name="total"
id="total"
#total="ngModel"
value="{{ model.total | currency }}"
/>
<input
type="hidden"
name="user_id"
id="user_id"
value="{{ this.user_id }}"
/>
<input
type="hidden"
name="orders_id"
id="orders_id"
value="{{ this.orders_id }}"
/>
<input
type="hidden"
name="product"
id="product"
value="{{ this.product }}"
/>
<input
type="hidden"
name="subTotal"
id="subTotal"
value="{{ this.payPalSrvc.getSubTotal() }}"
/>

Во-вторых, я создал отдельный PayPalService, чтобы разделить функциональность, а также я хотел, чтобы эти сообщения и проходили через мой перехватчик, чтобы поместить jwt в заголовки:

import { Injectable } from "@angular/core";
import { HttpClient, HttpHeaders, HttpResponse } from "@angular/common/http";
import { ShoppingCartService } from "./shopping-cart.service";
import { ProductService } from "./product.service";
import { LoginService } from "./login.service";

@Injectable({
providedIn: "root"
})
export class PaypalService {
constructor(
private http: HttpClient,
private loginSrvc: LoginService,
private prdSrvc: ProductService,
private cartSrvc: ShoppingCartService
) {}

addScript = false;
paypalLoad = true;
finalAmount;
subTotal;

paypalConfig = {
env: "sandbox",
client: {
sandbox:
"<my-sandbox-id>",
production: "<your-production-key here>"
},
commit: true,
payment: function(data, actions) {
return actions.payment.create({
transactions: [
{
amount: {
total: document.getElementById("total").value,
currency: "USD",
details: {
subtotal: document.getElementById("subTotal").value,
tax: (document.getElementById("total").value * 0.07).toFixed(2),
shipping: (document.getElementById("total").value * 0.03).toFixed(2),
handling_fee: "1.00",
shipping_discount: "0.00",
insurance: (document.getElementById("total").value * 0.01).toFixed(2)
}
},
description: "The payment from for-her application.",
/* custom: "90048630024435", */
invoice_number: document.getElementById("orders_id").value, // Insert a unique invoice number
payment_options: {
allowed_payment_method: "INSTANT_FUNDING_SOURCE"
},
soft_descriptor: document.getElementById("user_id").value,
item_list: {
items: [document.getElementById("product").value],
shipping_address: {
recipient_name: (document.getElementById("firstName").value + " " + document.getElementById("lastName").value),
line1: document.getElementById("address").value,
line2: document.getElementById("address2").value,
city: document.getElementById("city").value,
country_code: document.getElementById("country").value,
postal_code: document.getElementById("zip").value
phone: document.getElementById("phone").value,
state: document.getElementById("state").value,
email: document.getElementById("email").value
}
}
}
],
note_to_payer: "Contact us for any questions on your order."
});
},
onAuthorize: (data, actions) => {
return actions.payment.execute().then(payment => {
// Do something when payment is successful.
// window.alert("Thank you for your purchase! You order will be processed and shipped as soon as possible");
document.getElementById("myModal").style.display = "block";
document.getElementById("ModalBackdrop").style.display = "block";
this.cartSrvc.postCart();
});
}
};

addPaypalScript() {
this.addScript = true;
return new Promise((resolve, reject) => {
const scripttagElement = document.createElement("script");
scripttagElement.src = "https://www.paypalobjects.com/api/checkout.js";
scripttagElement.onload = resolve;
document.body.appendChild(scripttagElement);
});
}

public getSubTotal() {
this.subTotal = (document.getElementById("total").value) -
((document.getElementById("total").value * 0.07) +
(document.getElementById("total").value * 0.03) +
(document.getElementById("total").value * 0.01) +
1.00);
return this.subTotal.toFixed(2);
}

public getToken(): string {
return localStorage.getItem("jwt");
}
}

Конечно, теперь у меня есть другая ошибка искаженного запроса, которая помещена в jsonlint и, как вы можете видеть, вернулась как действительная:

From jsonlint.com

Но главное, параметры/свойства работают:

along with subtotal calculations working

Если кто-нибудь может сказать мне, в чем суть новой ошибки, то я был бы благодарен.

Error: Request to post https://www.sandbox.paypal.com/v1/payments/payment failed with 400 error. Correlation id: f31569c675597, f31569c675597

{
"name": "MALFORMED_REQUEST",
"message": "Incoming JSON request does not map to API request",
"information_link": "https://developer.paypal.com/webapps/developer/docs/api/#MALFORMED_REQUEST",
"debug_id": "f31569c675597"
}

request/</<@https://www.paypalobjects.com/api/checkout.js:14216:39

Еще раз спасибо @paulsm4 за предложение заглянуть на вкладку Сеть, чтобы увидеть, действительно ли значение публикуется.

ответил(а) 2019-01-16T01:57:00+03:00 1 год, 8 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема