diff --git a/main.go b/main.go
index 30df483..5900b65 100644
--- a/main.go
+++ b/main.go
@@ -10,10 +10,24 @@ import (
"os/signal"
"syscall"
"bytes"
+ // "time"
"github.com/joho/godotenv"
)
+type OrderData struct {
+ ID string `json:"id"`
+ Status string `json:"status"`
+ PurchaseUnits []struct {
+ Payments struct {
+ Captures []struct {
+ ID string `json:"id"`
+ Status string `json:"status"`
+ } `json:"captures"`
+ } `json:"payments"`
+ } `json:"purchase_units"`
+}
+
var (
baseURL = "https://api-m.sandbox.paypal.com"
)
@@ -27,6 +41,7 @@ func init() {
func main() {
// Handlers
http.HandleFunc("/api/orders", CreateOrder)
+ http.HandleFunc("/api/orders/", CaptureOrder)
http.Handle("/", http.FileServer(http.Dir("./public")))
// Channel to listen for signals
@@ -121,3 +136,47 @@ func CreateOrder(w http.ResponseWriter, r *http.Request) {
return
}
}
+
+func CaptureOrder(w http.ResponseWriter, r *http.Request) {
+ path := strings.TrimPrefix(r.URL.Path, "/api/orders/")
+ parts := strings.Split(path, "/")
+ orderID := parts[0]
+
+ client := &http.Client{}
+ req, err := http.NewRequest("POST", baseURL+"/v2/checkout/orders/"+orderID+"/capture", nil)
+ if err != nil {
+ http.Error(w, "Failed to create request", http.StatusInternalServerError)
+ return
+ }
+
+ token, err := Token()
+ if err != nil {
+ http.Error(w, "Failed to get access token", http.StatusInternalServerError)
+ return
+ }
+
+ req.Header.Set("Authorization", "Bearer "+token)
+ req.Header.Set("Content-Type", "application/json")
+ resp, err := client.Do(req)
+ if err != nil {
+ http.Error(w, "Failed to send request", http.StatusInternalServerError)
+ return
+ }
+ defer resp.Body.Close()
+
+ // Create an instance of AutoGenerated
+ var result OrderData
+
+ // Decode the response into the AutoGenerated struct
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ http.Error(w, "Failed to decode response", http.StatusInternalServerError)
+ return
+ }
+
+ // Now, `result` contains the entire structured response
+ // You can send the whole `result` back to the client, or you can selectively send fields.
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(result); err != nil {
+ http.Error(w, "Failed to encode response", http.StatusInternalServerError)
+ }
+}
diff --git a/public/form.js b/public/form.js
index 9249033..cc87420 100644
--- a/public/form.js
+++ b/public/form.js
@@ -46,10 +46,10 @@ function togglePaymentMethod(selectedButtonId) {
if (selectedButtonId === 'showOneTimeButton') {
document.getElementById('paypal-button-container').classList.add('active');
- document.getElementById('paypalOneTimeButton').classList.add('active');
+ document.getElementById('paypal-button-container-order').classList.add('active');
} else if (selectedButtonId === 'showSubButton') {
document.getElementById('paypal-button-container').classList.add('active');
- document.getElementById('paypalSubButton').classList.add('active');
+ document.getElementById('paypal-button-container-subscribe').classList.add('active');
}
}
@@ -57,37 +57,99 @@ function isFormValid(form) {
return form.checkValidity();
}
-paypal_onetime.Buttons({
+window.paypal_order.Buttons({
style: { shape: 'pill', color: 'black', layout: 'vertical', label: 'pay' },
- createOrder: function(data, actions) {
- return actions.order.create({
- intent: 'CAPTURE',
- purchase_units: [{
- amount: {
- currency_code: 'USD',
- value: '20.00'
- }
- }]
- });
- },
- onApprove: function(data, actions) {
- return actions.order.capture().then(function(details) {
- alert('Transaction completed by ' + details.payer.name.given_name);
- });
- }
-}).render("#paypalOneTimeButton");
+ async createOrder() {
+ try {
+ const response = await fetch("/api/orders", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ // use the "body" param to optionally pass additional order information
+ // like product ids and quantities
+ body: JSON.stringify({
+ cart: [
+ {
+ id: "YOUR_PRODUCT_ID",
+ quantity: "YOUR_PRODUCT_QUANTITY",
+ },
+ ],
+ }),
+ });
-paypal_subscribe.Buttons({
- style: { shape: 'pill', color: 'black', layout: 'vertical', label: 'subscribe' },
- createSubscription: function(data, actions) {
- return actions.subscription.create({
- plan_id: PlanID
- });
+ const orderData = await response.json();
+
+ if (orderData.id) {
+ return orderData.id;
+ } else {
+ const errorDetail = orderData?.details?.[0];
+ const errorMessage = errorDetail
+ ? `${errorDetail.issue} ${errorDetail.description} (${orderData.debug_id})`
+ : JSON.stringify(orderData);
+
+ throw new Error(errorMessage);
+ }
+ } catch (error) {
+ console.error(error);
+ resultMessage(`Could not initiate PayPal Checkout...
${error}`);
+ }
},
- onApprove: function(data, actions) {
- alert(data.subscriptionID); // You can add optional success message for the subscriber here
- }
-}).render('#paypalSubButton');
+ async onApprove(data, actions) {
+ try {
+ const response = await fetch(`/api/orders/${data.orderID}/capture`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
+
+ const orderData = await response.json();
+ // Three cases to handle:
+ // (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
+ // (2) Other non-recoverable errors -> Show a failure message
+ // (3) Successful transaction -> Show confirmation or thank you message
+
+ const errorDetail = orderData?.details?.[0];
+
+ if (errorDetail?.issue === "INSTRUMENT_DECLINED") {
+ // (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
+ // recoverable state, per https://developer.paypal.com/docs/checkout/standard/customize/handle-funding-failures/
+ return actions.restart();
+ } else if (errorDetail) {
+ // (2) Other non-recoverable errors -> Show a failure message
+ throw new Error(`${errorDetail.description} (${orderData.debug_id})`);
+ } else if (!orderData.purchase_units) {
+ throw new Error(JSON.stringify(orderData));
+ } else {
+ // (3) Successful transaction -> Show confirmation or thank you message
+ // Or go to another URL: actions.redirect('thank_you.html');
+ const transaction =
+ orderData?.purchase_units?.[0]?.payments?.captures?.[0] ||
+ orderData?.purchase_units?.[0]?.payments?.authorizations?.[0];
+ resultMessage(
+ `Transaction ${transaction.status}: ${transaction.id}
See console for all available details`,
+ );
+ console.log(
+ "Capture result",
+ orderData,
+ JSON.stringify(orderData, null, 2),
+ );
+ }
+ } catch (error) {
+ console.error(error);
+ resultMessage(
+ `Sorry, your transaction could not be processed...
${error}`,
+ );
+ }
+ },
+}).render("#paypal-button-container-order");
+
+// Example function to show a result to the user. Your site's UI library can be used instead.
+function resultMessage(message) {
+ const container = document.querySelector("#checkout");
+ container.innerHTML = message;
+}
document.getElementById('showOneTimeButton').addEventListener('click', function() {
if (isFormValid(form)) {
diff --git a/public/index.html b/public/index.html
index 24746a7..d0d48c0 100644
--- a/public/index.html
+++ b/public/index.html
@@ -26,20 +26,23 @@
-
+
+
+
+